refactor
This commit is contained in:
@@ -77,16 +77,16 @@ pub(crate) enum Action {
|
||||
/// Zero time cost.
|
||||
Shift(Vec<(i32, i32)>),
|
||||
/// Add `n` to the player's gem count (negative subtracts; the count is
|
||||
/// clamped at 0). Zero time cost. Applied to `GameState::player_gems`.
|
||||
/// clamped at 0). Zero time cost. Applied to `GameState::player.gems`.
|
||||
AddGems(i64),
|
||||
/// Add `dh` to the player's health (clamped to `[0, max_health]`). Zero time
|
||||
/// cost. Applied to `GameState::player_health`.
|
||||
/// cost. Applied to `GameState::player.health`.
|
||||
AlterHealth(i64),
|
||||
/// Give (`true`) or take (`false`) the named key color from the player.
|
||||
///
|
||||
/// Color must be one of `"blue"`, `"green"`, `"cyan"`, `"red"`, `"purple"`,
|
||||
/// `"orange"`, `"yellow"`, `"white"`. An unrecognized name is logged and
|
||||
/// ignored. Zero time cost. Applied to `GameState::player_keys`.
|
||||
/// ignored. Zero time cost. Applied to `GameState::player.keys`.
|
||||
SetKey(String, bool),
|
||||
/// Remove the source object from the board. Zero time cost. Used by grab
|
||||
/// things (e.g. gems) to despawn themselves from their `grab()` hook.
|
||||
|
||||
@@ -43,7 +43,8 @@ pub struct Board {
|
||||
/// top-down; solidity ([`Board::solid_at`]) scans every layer. Access a single
|
||||
/// cell with [`Board::get`]/[`Board::get_mut`] by `(z, x, y)`.
|
||||
pub(crate) layers: Vec<Layer>,
|
||||
/// Current player position. See [`Player`] for caveats about its future.
|
||||
/// Current player position on this board. See [`PlayerPos`] for caveats
|
||||
/// about its future. Game-global player *stats* live in [`crate::player::Player`].
|
||||
pub player: PlayerPos,
|
||||
/// Scripted objects on this board, keyed by stable [`ObjectId`]. A `BTreeMap`
|
||||
/// (not a `Vec`) so an object can be removed without invalidating other
|
||||
|
||||
+15
-12
@@ -1,7 +1,7 @@
|
||||
use crate::action::Action;
|
||||
use crate::board::Board;
|
||||
use crate::log::LogLine;
|
||||
use crate::script::ScriptHost;
|
||||
use crate::script::{ScriptHost, ScriptState};
|
||||
use crate::utils::{Direction, ObjectId, PlayerPos, ScriptArg};
|
||||
use crate::world::World;
|
||||
use std::cell::{Ref, RefMut};
|
||||
@@ -67,7 +67,10 @@ pub struct GameState {
|
||||
/// by [`enter_board`](GameState::enter_board). Front-ends tick this down and
|
||||
/// may block input or show a visual effect while it is `Some(t)` where `t > 0`.
|
||||
pub board_transition: Option<f64>,
|
||||
/// The player's state
|
||||
/// The game-global player state (health, gems, keys) — see [`Player`]. Not
|
||||
/// per-board: it persists across board transitions, unlike the per-board
|
||||
/// position in [`Board::player`](crate::board::Board::player). Scripts mutate
|
||||
/// it via `add_gems`/`alter_health`/`set_key` and read a snapshot of it.
|
||||
pub player: Player,
|
||||
}
|
||||
|
||||
@@ -159,7 +162,7 @@ impl GameState {
|
||||
/// the game is about to start — never during map deserialization, since a script
|
||||
/// may inspect the board.
|
||||
pub fn run_init(&mut self) {
|
||||
self.scripts.run_init(self.player);
|
||||
self.scripts.run_init(ScriptState(self.player));
|
||||
self.resolve();
|
||||
}
|
||||
|
||||
@@ -173,7 +176,7 @@ impl GameState {
|
||||
// this runs exactly once per player interaction with a scroll.
|
||||
self.handle_scroll();
|
||||
let secs = dt.as_secs_f64();
|
||||
self.scripts.run_tick(self.player, secs);
|
||||
self.scripts.run_tick(ScriptState(self.player), secs);
|
||||
// Expire speech bubbles before resolving new actions so a fresh say()
|
||||
// this frame isn't immediately culled.
|
||||
self.speech_bubbles.retain_mut(|b| {
|
||||
@@ -308,11 +311,11 @@ impl GameState {
|
||||
Action::Shift(cells) => {
|
||||
logs.extend(board.apply_shift(&cells));
|
||||
}
|
||||
// Accumulated and applied to `self.player_gems` after the borrow drops.
|
||||
// Accumulated and applied to `self.player.gems` after the borrow drops.
|
||||
Action::AddGems(n) => gem_delta += n,
|
||||
// Accumulated and applied to `self.player_health` after the borrow drops.
|
||||
// Accumulated and applied to `self.player.health` after the borrow drops.
|
||||
Action::AlterHealth(dh) => health_delta += dh,
|
||||
// Collected and applied to `self.player_keys` after the borrow drops.
|
||||
// Collected and applied to `self.player.keys` after the borrow drops.
|
||||
Action::SetKey(color, present) => key_changes.push((color, present)),
|
||||
// A grab thing despawns itself from its grab() hook.
|
||||
Action::Die => {
|
||||
@@ -345,10 +348,10 @@ impl GameState {
|
||||
}
|
||||
}
|
||||
for (bumped, bumper) in bumps {
|
||||
self.scripts.run_bump(self.player, bumped, bumper);
|
||||
self.scripts.run_bump(ScriptState(self.player), bumped, bumper);
|
||||
}
|
||||
for (target, fn_name, arg) in sends {
|
||||
self.scripts.run_send(self.player, target, &fn_name, arg);
|
||||
self.scripts.run_send(ScriptState(self.player), target, &fn_name, arg);
|
||||
}
|
||||
self.drain_errors();
|
||||
}
|
||||
@@ -364,7 +367,7 @@ impl GameState {
|
||||
if let Some(scroll) = self.active_scroll.take()
|
||||
&& let Some(choice) = scroll.choice
|
||||
{
|
||||
self.scripts.run_send(self.player, scroll.source, &choice, None);
|
||||
self.scripts.run_send(ScriptState(self.player), scroll.source, &choice, None);
|
||||
self.drain_errors();
|
||||
}
|
||||
}
|
||||
@@ -471,11 +474,11 @@ impl GameState {
|
||||
// Fire the grab hook and resolve it immediately so the grabbed thing's
|
||||
// die()/add_gems() apply now — no player+object overlap survives this call.
|
||||
if let Some(id) = grabbed {
|
||||
self.scripts.run_grab(self.player, id);
|
||||
self.scripts.run_grab(ScriptState(self.player), id);
|
||||
self.resolve();
|
||||
}
|
||||
if let Some(idx) = bumped {
|
||||
self.scripts.run_bump(self.player, idx, -1);
|
||||
self.scripts.run_bump(ScriptState(self.player), idx, -1);
|
||||
self.drain_errors();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
use crate::keys::Keyring;
|
||||
|
||||
/// The game-global player state: stats that follow the player across boards.
|
||||
///
|
||||
/// Owned by [`GameState::player`](crate::game::GameState) (a single value, not
|
||||
/// per-board), so health, gems, and keys persist through board transitions.
|
||||
/// Distinct from [`PlayerPos`](crate::utils::PlayerPos), which is the player's
|
||||
/// position *on a particular board*. A `Copy` snapshot is handed to each script
|
||||
/// hook and exposed to Rhai (read-only) as the `Player` constant.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Player {
|
||||
/// The player's current health. Game-global (not per-board), so it persists
|
||||
@@ -15,7 +22,7 @@ pub struct Player {
|
||||
pub keys: Keyring,
|
||||
|
||||
/// The number of gems the player has collected. Game-global like
|
||||
/// [`player_health`](GameState::player_health); starts at `0`.
|
||||
/// [`health`](Player::health); starts at `0`.
|
||||
pub gems: i64,
|
||||
}
|
||||
|
||||
|
||||
+18
-12
@@ -81,6 +81,12 @@ use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::rc::Rc;
|
||||
use crate::player::Player;
|
||||
|
||||
/// The host-provided context handed to every script hook for the duration of one
|
||||
/// call. Currently just the player snapshot, but it exists so more host state can
|
||||
/// be threaded through the `run_*` methods without changing each signature again.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ScriptState(pub Player);
|
||||
|
||||
/// An action promoted from an object's output queue onto the board queue, tagged
|
||||
/// with the object that issued it.
|
||||
pub(crate) struct BoardAction {
|
||||
@@ -318,18 +324,18 @@ impl ScriptHost {
|
||||
}
|
||||
|
||||
/// Calls `init()` on every scripted object that defines it and drains each queue.
|
||||
pub fn run_init(&mut self, player: Player) {
|
||||
self.run("init", |c| c.has_init, (), 0.0, player);
|
||||
pub fn run_init(&mut self, state: ScriptState) {
|
||||
self.run("init", |c| c.has_init, (), 0.0, state);
|
||||
}
|
||||
|
||||
/// Calls `tick(dt)` on every scripted object that defines it, then drains queues.
|
||||
pub fn run_tick(&mut self, player: Player, dt: f64) {
|
||||
self.run("tick", |c| c.has_tick, (dt,), dt, player);
|
||||
pub fn run_tick(&mut self, state: ScriptState, dt: f64) {
|
||||
self.run("tick", |c| c.has_tick, (dt,), dt, state);
|
||||
}
|
||||
|
||||
/// Calls `bump(id)` on the object with [`ObjectId`] `object_id`, if it defines
|
||||
/// the hook. After the hook, drains the object's queue with `dt = 0`.
|
||||
pub fn run_bump(&mut self, player: Player, object_id: ObjectId, bumper: i64) {
|
||||
pub fn run_bump(&mut self, state: ScriptState, object_id: ObjectId, bumper: i64) {
|
||||
let Some(i) = self.objects.iter().position(|o| o.object_id == object_id) else {
|
||||
return;
|
||||
};
|
||||
@@ -349,7 +355,7 @@ impl ScriptHost {
|
||||
return;
|
||||
}
|
||||
let options = CallFnOptions::default().with_tag(object_id as i64);
|
||||
obj.scope.set_or_push("Player", player);
|
||||
obj.scope.set_or_push("Player", state.0);
|
||||
if let Err(err) = engine.call_fn_with_options::<()>(
|
||||
options,
|
||||
&mut obj.scope,
|
||||
@@ -372,7 +378,7 @@ impl ScriptHost {
|
||||
/// Fired when the player walks onto a grab object or a grab object is pushed
|
||||
/// into the player (see [`GameState`](crate::game::GameState)). The hook
|
||||
/// typically increments a player stat and removes the object via `die()`.
|
||||
pub fn run_grab(&mut self, player: Player, object_id: ObjectId) {
|
||||
pub fn run_grab(&mut self, state: ScriptState, object_id: ObjectId) {
|
||||
let Some(i) = self.objects.iter().position(|o| o.object_id == object_id) else {
|
||||
return;
|
||||
};
|
||||
@@ -392,7 +398,7 @@ impl ScriptHost {
|
||||
return;
|
||||
}
|
||||
let options = CallFnOptions::default().with_tag(object_id as i64);
|
||||
obj.scope.set_or_push("Player", player);
|
||||
obj.scope.set_or_push("Player", state.0);
|
||||
if let Err(err) = engine.call_fn_with_options::<()>(
|
||||
options,
|
||||
&mut obj.scope,
|
||||
@@ -415,7 +421,7 @@ impl ScriptHost {
|
||||
/// If the function accepts zero parameters (or arg is `None`), it is called with
|
||||
/// no args. If neither arity exists, the call is silently skipped.
|
||||
/// After the call, drains the object's queue with `dt = 0`.
|
||||
pub(crate) fn run_send(&mut self, player: Player, target_id: ObjectId, fn_name: &str, arg: Option<ScriptArg>) {
|
||||
pub(crate) fn run_send(&mut self, state: ScriptState, target_id: ObjectId, fn_name: &str, arg: Option<ScriptArg>) {
|
||||
let Some(i) = self.objects.iter().position(|o| o.object_id == target_id) else {
|
||||
return;
|
||||
};
|
||||
@@ -442,7 +448,7 @@ impl ScriptHost {
|
||||
.any(|f| f.name == fn_name && f.params.is_empty());
|
||||
|
||||
let options = CallFnOptions::default().with_tag(obj.object_id as i64);
|
||||
obj.scope.set_or_push("Player", player);
|
||||
obj.scope.set_or_push("Player", state.0);
|
||||
let result = if has_1 {
|
||||
// Call with arg (or unit if arg is absent).
|
||||
let dyn_arg: Dynamic = match &arg {
|
||||
@@ -530,7 +536,7 @@ impl ScriptHost {
|
||||
defined: fn(&CompiledScript) -> bool,
|
||||
args: A,
|
||||
drain_dt: f64,
|
||||
player: Player
|
||||
state: ScriptState,
|
||||
) {
|
||||
for i in 0..self.objects.len() {
|
||||
{
|
||||
@@ -546,7 +552,7 @@ impl ScriptHost {
|
||||
&& defined(compiled)
|
||||
{
|
||||
let options = CallFnOptions::default().with_tag(obj.object_id as i64);
|
||||
obj.scope.set_or_push("Player", player);
|
||||
obj.scope.set_or_push("Player", state.0);
|
||||
if let Err(err) = engine.call_fn_with_options::<()>(
|
||||
options,
|
||||
&mut obj.scope,
|
||||
|
||||
Reference in New Issue
Block a user