no need for scriptstate any more

This commit is contained in:
2026-07-07 23:55:27 -05:00
parent 78823fcf96
commit 23b5bf2afb
18 changed files with 146 additions and 174 deletions
+33 -32
View File
@@ -13,8 +13,7 @@ pub const SAY_DURATION: f64 = 3.0;
// Re-export ScrollLine so kiln-tui can pattern-match scroll content without
// accessing the private `action` module directly.
pub use crate::action::ScrollLine;
use crate::api::state::ScriptState;
use crate::player::Player;
use crate::player::{Player, PlayerRef};
/// An active scroll overlay opened by a scripted object via `scroll()`.
///
@@ -72,7 +71,7 @@ pub struct GameState {
/// 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,
pub player: PlayerRef,
}
impl GameState {
@@ -84,11 +83,13 @@ impl GameState {
/// Compile errors are surfaced into the log immediately.
pub fn from_world(world: World) -> Self {
let name = world.start.clone();
let player = Player::new_ref();
let host = ScriptHost::new(
world
.boards
.get(&name)
.expect("world::load guarantees start board exists"),
.expect("world::load guarantees start board exists").clone(),
player.clone(),
&world.scripts,
);
let mut state = Self {
@@ -99,7 +100,7 @@ impl GameState {
speech_bubbles: Vec::new(),
active_scroll: None,
board_transition: None,
player: Player::default()
player
};
state.drain_errors();
state
@@ -163,7 +164,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(ScriptState::from_game_state(self));
self.scripts.run_init();
self.resolve();
}
@@ -177,7 +178,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(ScriptState::from_game_state(self), secs);
self.scripts.run_tick(secs);
// Expire speech bubbles before resolving new actions so a fresh say()
// this frame isn't immediately culled.
self.speech_bubbles.retain_mut(|b| {
@@ -338,23 +339,22 @@ impl GameState {
}
// Apply the net gem change (clamped at 0, since the count is unsigned).
if gem_delta != 0 {
self.player.alter_gems(gem_delta);
self.player.borrow_mut().alter_gems(gem_delta);
}
// Apply the net health change (clamped to [0, max_health]).
if health_delta != 0 {
self.player.alter_health(health_delta);
self.player.borrow_mut().alter_health(health_delta);
}
for (color, present) in key_changes {
if !self.player.keys.set_by_name(&color, present) {
if !self.player.borrow_mut().keys.set_by_name(&color, present) {
self.log.push(LogLine::error(format!("set_key: unknown color {color:?}")));
}
}
let state = ScriptState::from_game_state(self);
for (bumped, bumper) in bumps {
self.scripts.run_bump(state.clone(), bumped, bumper);
self.scripts.run_bump(bumped, bumper);
}
for (target, fn_name, arg) in sends {
self.scripts.run_send(state.clone(), target, &fn_name, arg);
self.scripts.run_send(target, &fn_name, arg);
}
self.drain_errors();
}
@@ -370,7 +370,7 @@ impl GameState {
if let Some(scroll) = self.active_scroll.take()
&& let Some(choice) = scroll.choice
{
self.scripts.run_send(ScriptState::from_game_state(self), scroll.source, &choice, SendArg::None);
self.scripts.run_send(scroll.source, &choice, SendArg::None);
self.drain_errors();
}
}
@@ -416,7 +416,8 @@ impl GameState {
self.board_mut().clear_all_queues();
// Rebuild the script host for the new board's objects.
self.scripts = ScriptHost::new(
&self.world.boards[&self.current_board_name],
self.world.boards[&self.current_board_name].clone(),
self.player.clone(),
&self.world.scripts,
);
// Stub hook for the front-end transition animation (1 second).
@@ -477,13 +478,12 @@ 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.
let state = ScriptState::from_game_state(self);
if let Some(id) = grabbed {
self.scripts.run_grab(state.clone(), id);
self.scripts.run_grab(id);
self.resolve();
}
if let Some(idx) = bumped {
self.scripts.run_bump(state, idx, -1);
self.scripts.run_bump(idx, -1);
self.drain_errors();
}
}
@@ -539,7 +539,7 @@ mod tests {
game.try_move(Direction::East);
// The gem was grabbed: gem count up, gem object gone, player on its cell.
assert_eq!(game.player.gems, 1);
assert_eq!(game.player.borrow().gems, 1);
assert!(game.board().objects.is_empty());
assert_eq!((game.board().player.x, game.board().player.y), (1, 0));
}
@@ -567,7 +567,7 @@ mod tests {
game.tick(Duration::from_millis(16));
// No grab: the gem is untouched and the player never moved.
assert_eq!(game.player.gems, 0);
assert_eq!(game.player.borrow().gems, 0);
assert!(game.board().objects.values().any(|o| o.grab));
assert_eq!((game.board().player.x, game.board().player.y), (2, 0));
}
@@ -591,7 +591,7 @@ mod tests {
// crate moves to (3,0), empty moves back to (2,0).
let mut game = game_with_object_script(
5,
"fn tick(m,s,dt) { if m.queue.length == 0 { shift([[2, 0], [3, 0]]); } }",
"fn tick(m,dt) { if m.queue.length == 0 { shift([[2, 0], [3, 0]]); } }",
);
game.tick(Duration::from_millis(16));
let b = game.board();
@@ -609,10 +609,10 @@ mod tests {
obj.script_name = Some("s".to_string());
let mut board = open_board(4, 1, (3, 0), vec![obj]);
crate_at(&mut board, 2, 0);
let src = "fn init(m,s) { \
log(if s.board.passable(1, 0) { \"empty:yes\" } else { \"empty:no\" }); \
log(if s.board.passable(2, 0) { \"crate:yes\" } else { \"crate:no\" }); \
log(if s.board.passable(9, 0) { \"off:yes\" } else { \"off:no\" }); }";
let src = "fn init(m) { \
log(if Board.passable(1, 0) { \"empty:yes\" } else { \"empty:no\" }); \
log(if Board.passable(2, 0) { \"crate:yes\" } else { \"crate:no\" }); \
log(if Board.passable(9, 0) { \"off:yes\" } else { \"off:no\" }); }";
let scripts = HashMap::from([("s".to_string(), src.to_string())]);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
@@ -727,14 +727,15 @@ mod tests {
let board = open_board(2, 1, (1, 0), vec![sobj]);
let scripts = HashMap::from([(
"s".to_string(),
"fn init(m, s) { set_key(\"blue\", true); set_key(\"red\", true); }".to_string(),
"fn init(m) { set_key(\"blue\", true); set_key(\"red\", true); }".to_string(),
)]);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();
assert!(game.player.keys.blue);
assert!(game.player.keys.red);
assert!(!game.player.keys.cyan); // cyan was not set by the script
let keys = game.player.borrow().keys;
assert!(keys.blue);
assert!(keys.red);
assert!(!keys.cyan); // cyan was not set by the script
// A second script can take a key.
let mut sobj2 = ObjectDef::new(0, 0);
@@ -743,12 +744,12 @@ mod tests {
let board2 = open_board(2, 1, (1, 0), vec![sobj2]);
let scripts2 = HashMap::from([(
"t".to_string(),
"fn init(m, s) { set_key(\"blue\", true); set_key(\"blue\", false); }".to_string(),
"fn init(m) { set_key(\"blue\", true); set_key(\"blue\", false); }".to_string(),
)]);
let mut game2 = GameState::with_scripts(board2, scripts2);
game2.run_init();
assert!(!game2.player.keys.blue);
assert!(!game2.player.borrow().keys.blue);
}
#[test]
@@ -759,7 +760,7 @@ mod tests {
let board = open_board(2, 1, (1, 0), vec![sobj]);
let scripts = HashMap::from([(
"s".to_string(),
r#"fn init(m,s) { set_key("chartreuse", true); }"#.to_string(),
r#"fn init(m) { set_key("chartreuse", true); }"#.to_string(),
)]);
let mut game = GameState::with_scripts(board, scripts);
game.run_init();