Big API refactor
This commit is contained in:
+32
-41
@@ -1,8 +1,8 @@
|
||||
use crate::action::Action;
|
||||
use crate::action::{Action, SendArg};
|
||||
use crate::board::Board;
|
||||
use crate::log::LogLine;
|
||||
use crate::script::{ScriptHost, ScriptState};
|
||||
use crate::utils::{Direction, ObjectId, PlayerPos, ScriptArg};
|
||||
use crate::script::ScriptHost;
|
||||
use crate::utils::{Direction, ObjectId, PlayerPos};
|
||||
use crate::world::World;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::time::Duration;
|
||||
@@ -13,6 +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;
|
||||
|
||||
/// An active scroll overlay opened by a scripted object via `scroll()`.
|
||||
@@ -162,7 +163,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(self.player));
|
||||
self.scripts.run_init(ScriptState::from_game_state(&self));
|
||||
self.resolve();
|
||||
}
|
||||
|
||||
@@ -176,7 +177,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(self.player), secs);
|
||||
self.scripts.run_tick(ScriptState::from_game_state(&self), secs);
|
||||
// Expire speech bubbles before resolving new actions so a fresh say()
|
||||
// this frame isn't immediately culled.
|
||||
self.speech_bubbles.retain_mut(|b| {
|
||||
@@ -212,10 +213,11 @@ impl GameState {
|
||||
let mut gem_delta: i64 = 0;
|
||||
let mut health_delta: i64 = 0;
|
||||
let mut key_changes: Vec<(String, bool)> = Vec::new();
|
||||
let mut sends: Vec<(ObjectId, String, Option<ScriptArg>)> = Vec::new();
|
||||
let mut sends: Vec<(ObjectId, String, SendArg)> = Vec::new();
|
||||
let mut new_bubbles: Vec<SpeechBubble> = Vec::new();
|
||||
// Collected outside the board borrow so we can assign to self.active_scroll.
|
||||
let mut new_scroll: Option<Scroll> = None;
|
||||
|
||||
{
|
||||
let mut board = self.board_mut();
|
||||
for ba in actions {
|
||||
@@ -347,11 +349,12 @@ impl GameState {
|
||||
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(ScriptState(self.player), bumped, bumper);
|
||||
self.scripts.run_bump(state.clone(), bumped, bumper);
|
||||
}
|
||||
for (target, fn_name, arg) in sends {
|
||||
self.scripts.run_send(ScriptState(self.player), target, &fn_name, arg);
|
||||
self.scripts.run_send(state.clone(), target, &fn_name, arg);
|
||||
}
|
||||
self.drain_errors();
|
||||
}
|
||||
@@ -367,7 +370,7 @@ impl GameState {
|
||||
if let Some(scroll) = self.active_scroll.take()
|
||||
&& let Some(choice) = scroll.choice
|
||||
{
|
||||
self.scripts.run_send(ScriptState(self.player), scroll.source, &choice, None);
|
||||
self.scripts.run_send(ScriptState::from_game_state(&self), scroll.source, &choice, SendArg::None);
|
||||
self.drain_errors();
|
||||
}
|
||||
}
|
||||
@@ -407,8 +410,8 @@ impl GameState {
|
||||
// Switch to the new board and place the player at the arrival portal.
|
||||
self.current_board_name = target_map.to_string();
|
||||
self.board_mut().player = PlayerPos {
|
||||
x: ax as i32,
|
||||
y: ay as i32,
|
||||
x: ax as i64,
|
||||
y: ay as i64,
|
||||
};
|
||||
// Rebuild the script host for the new board's objects.
|
||||
self.scripts = ScriptHost::new(
|
||||
@@ -432,9 +435,9 @@ impl GameState {
|
||||
let grabbed;
|
||||
let portal_target;
|
||||
{
|
||||
let (dx, dy): (i32, i32) = dir.into();
|
||||
let (dx, dy): (i64, i64) = dir.into();
|
||||
let mut board = self.board_mut();
|
||||
let target = (board.player.x + dx, board.player.y + dy);
|
||||
let target = (board.player.x as i64 + dx, board.player.y as i64 + dy);
|
||||
if !board.in_bounds(target) {
|
||||
return;
|
||||
}
|
||||
@@ -454,8 +457,8 @@ impl GameState {
|
||||
if grabbed.is_none() {
|
||||
board.push(nx, ny, dir);
|
||||
}
|
||||
board.player.x = nx as i32;
|
||||
board.player.y = ny as i32;
|
||||
board.player.x = nx as i64;
|
||||
board.player.y = ny as i64;
|
||||
// Check for a portal at the new position; clone strings to release the borrow.
|
||||
portal_target = board
|
||||
.portals
|
||||
@@ -473,12 +476,13 @@ 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(ScriptState(self.player), id);
|
||||
self.scripts.run_grab(state.clone(), id);
|
||||
self.resolve();
|
||||
}
|
||||
if let Some(idx) = bumped {
|
||||
self.scripts.run_bump(ScriptState(self.player), idx, -1);
|
||||
self.scripts.run_bump(state, idx, -1);
|
||||
self.drain_errors();
|
||||
}
|
||||
}
|
||||
@@ -493,9 +497,9 @@ impl GameState {
|
||||
/// pushed aside or blocks the move — since something tried to move into it. Walls and
|
||||
/// crates carry no script, so only solid objects yield a bump.
|
||||
fn step_object(board: &mut Board, id: ObjectId, dir: Direction) -> Option<ObjectId> {
|
||||
let (dx, dy): (i32, i32) = dir.into();
|
||||
let (dx, dy): (i64, i64) = dir.into();
|
||||
let (ox, oy) = board.objects.get(&id).map(|o| (o.x, o.y))?;
|
||||
let target = (ox as i32 + dx, oy as i32 + dy);
|
||||
let target = (ox as i64 + dx, oy as i64 + dy);
|
||||
if !board.in_bounds(target) {
|
||||
return None;
|
||||
}
|
||||
@@ -572,7 +576,7 @@ mod tests {
|
||||
let mut obj = ObjectDef::new(0, 0);
|
||||
obj.solid = false;
|
||||
obj.script_name = Some("s".to_string());
|
||||
let mut board = open_board(board_w, 1, (board_w as i32 - 1, 0), vec![obj]);
|
||||
let mut board = open_board(board_w, 1, (board_w as i64 - 1, 0), vec![obj]);
|
||||
crate_at(&mut board, 2, 0);
|
||||
let scripts = HashMap::from([("s".to_string(), src.to_string())]);
|
||||
let mut game = GameState::with_scripts(board, scripts);
|
||||
@@ -580,26 +584,13 @@ mod tests {
|
||||
game
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_push_shoves_a_crate() {
|
||||
// The object pushes the crate at (2,0) one step east on its first tick.
|
||||
let mut game = game_with_object_script(
|
||||
5,
|
||||
"fn tick(dt) { if Queue.length() == 0 { push(2, 0, East); } }",
|
||||
);
|
||||
game.tick(Duration::from_millis(16));
|
||||
let b = game.board();
|
||||
assert_eq!(b.get(0, 2, 0).1, Archetype::Empty);
|
||||
assert_eq!(b.get(0, 3, 0).1, Archetype::Crate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_shift_rotates_crates() {
|
||||
// Rotate the crate at (2,0) with the empty cell at (3,0) in a two-cell cycle:
|
||||
// crate moves to (3,0), empty moves back to (2,0).
|
||||
let mut game = game_with_object_script(
|
||||
5,
|
||||
"fn tick(dt) { if Queue.length() == 0 { shift([[2, 0], [3, 0]]); } }",
|
||||
"fn tick(m,s,dt) { if m.queue.length == 0 { shift([[2, 0], [3, 0]]); } }",
|
||||
);
|
||||
game.tick(Duration::from_millis(16));
|
||||
let b = game.board();
|
||||
@@ -617,10 +608,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() { \
|
||||
log(if passable(1, 0) { \"empty:yes\" } else { \"empty:no\" }); \
|
||||
log(if passable(2, 0) { \"crate:yes\" } else { \"crate:no\" }); \
|
||||
log(if passable(9, 0) { \"off:yes\" } else { \"off:no\" }); }";
|
||||
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 scripts = HashMap::from([("s".to_string(), src.to_string())]);
|
||||
let mut game = GameState::with_scripts(board, scripts);
|
||||
game.run_init();
|
||||
@@ -735,7 +726,7 @@ mod tests {
|
||||
let board = open_board(2, 1, (1, 0), vec![sobj]);
|
||||
let scripts = HashMap::from([(
|
||||
"s".to_string(),
|
||||
"fn init() { set_key(\"blue\", true); set_key(\"red\", true); }".to_string(),
|
||||
"fn init(m, s) { set_key(\"blue\", true); set_key(\"red\", true); }".to_string(),
|
||||
)]);
|
||||
let mut game = GameState::with_scripts(board, scripts);
|
||||
game.run_init();
|
||||
@@ -751,7 +742,7 @@ mod tests {
|
||||
let board2 = open_board(2, 1, (1, 0), vec![sobj2]);
|
||||
let scripts2 = HashMap::from([(
|
||||
"t".to_string(),
|
||||
"fn init() { set_key(\"blue\", true); set_key(\"blue\", false); }".to_string(),
|
||||
"fn init(m, s) { set_key(\"blue\", true); set_key(\"blue\", false); }".to_string(),
|
||||
)]);
|
||||
let mut game2 = GameState::with_scripts(board2, scripts2);
|
||||
game2.run_init();
|
||||
@@ -767,7 +758,7 @@ mod tests {
|
||||
let board = open_board(2, 1, (1, 0), vec![sobj]);
|
||||
let scripts = HashMap::from([(
|
||||
"s".to_string(),
|
||||
r#"fn init() { set_key("chartreuse", true); }"#.to_string(),
|
||||
r#"fn init(m,s) { set_key("chartreuse", true); }"#.to_string(),
|
||||
)]);
|
||||
let mut game = GameState::with_scripts(board, scripts);
|
||||
game.run_init();
|
||||
|
||||
Reference in New Issue
Block a user