spinners and gems
This commit is contained in:
+72
-8
@@ -8,6 +8,8 @@
|
||||
//! - `tick(dt)` — run every frame with the elapsed seconds (see [`ScriptHost::run_tick`]).
|
||||
//! - `bump(id)` — run when another mover steps into this object's cell, with the
|
||||
//! bumper's object index (`-1` for the player); see [`ScriptHost::run_bump`].
|
||||
//! - `grab()` — run when the player walks onto (or pushes into themselves) a
|
||||
//! `grab` object; see [`ScriptHost::run_grab`]. Typically adds a stat + `die()`s.
|
||||
//!
|
||||
//! ## Script constants
|
||||
//!
|
||||
@@ -55,7 +57,7 @@
|
||||
//! `move(dir)`, `delay(secs)`, `now()`, `set_tile(n)`, `log(msg)`, `say(msg)`,
|
||||
//! `set_fg(fg)`, `set_bg(bg)`, `set_color(fg, bg)`, `set_tag(target, tag, present)`,
|
||||
//! `send(target_id, fn_name [, arg])`, `teleport(x, y)`, `push(x, y, dir)`,
|
||||
//! `swap([[src_x, src_y, dst_x, dst_y], …])`
|
||||
//! `swap([[src_x, src_y, dst_x, dst_y], …])`, `add_gems(n)`, `die()`
|
||||
//!
|
||||
//! ## Queue API
|
||||
//!
|
||||
@@ -106,6 +108,7 @@ struct CompiledScript {
|
||||
has_init: bool,
|
||||
has_tick: bool,
|
||||
has_bump: bool,
|
||||
has_grab: bool,
|
||||
}
|
||||
|
||||
/// The runtime state of one scripted object.
|
||||
@@ -122,11 +125,7 @@ struct ObjectRuntime {
|
||||
/// built-in (so identical built-ins share one compiled AST), or the world-pool
|
||||
/// name for a named script. `None` if the object has no script.
|
||||
fn script_key(obj: &ObjectDef) -> Option<String> {
|
||||
if let Some(src) = obj.builtin_script {
|
||||
Some(src.to_string())
|
||||
} else {
|
||||
obj.script_name.clone()
|
||||
}
|
||||
obj.script_name.clone()
|
||||
}
|
||||
|
||||
// ── Rhai types exposed to scripts ────────────────────────────────────────────
|
||||
@@ -266,6 +265,7 @@ impl ScriptHost {
|
||||
has_init: defines("init", 0),
|
||||
has_tick: defines("tick", 1),
|
||||
has_bump: defines("bump", 1),
|
||||
has_grab: defines("grab", 0),
|
||||
ast,
|
||||
},
|
||||
);
|
||||
@@ -356,6 +356,48 @@ impl ScriptHost {
|
||||
self.drain(i, 0.0);
|
||||
}
|
||||
|
||||
/// Calls `grab()` on the object with [`ObjectId`] `object_id`, if it defines the
|
||||
/// hook, then drains its queue with `dt = 0`.
|
||||
///
|
||||
/// 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, object_id: ObjectId) {
|
||||
let Some(i) = self.objects.iter().position(|o| o.object_id == object_id) else {
|
||||
return;
|
||||
};
|
||||
{
|
||||
let Self {
|
||||
engine,
|
||||
scripts,
|
||||
objects,
|
||||
errors,
|
||||
..
|
||||
} = self;
|
||||
let obj = &mut objects[i];
|
||||
let Some(compiled) = scripts.get(&obj.script_key) else {
|
||||
return;
|
||||
};
|
||||
if !compiled.has_grab {
|
||||
return;
|
||||
}
|
||||
let options = CallFnOptions::default().with_tag(object_id as i64);
|
||||
if let Err(err) = engine.call_fn_with_options::<()>(
|
||||
options,
|
||||
&mut obj.scope,
|
||||
&compiled.ast,
|
||||
"grab",
|
||||
(),
|
||||
) {
|
||||
errors.borrow_mut().push(LogLine::error(format!(
|
||||
"script '{}' grab error: {err}",
|
||||
obj.script_key
|
||||
)));
|
||||
}
|
||||
}
|
||||
self.drain(i, 0.0);
|
||||
}
|
||||
|
||||
/// Calls the named function on the object with [`ObjectId`] `target_id`.
|
||||
///
|
||||
/// If `arg` is `Some` and the function accepts one parameter, the arg is passed.
|
||||
@@ -689,8 +731,9 @@ fn register_read_api(
|
||||
});
|
||||
|
||||
// can_shift(x, y, dir) — like can_push but inspects only the cell ahead: (x, y)
|
||||
// holds a pushable and the next cell is empty or another pushable. False
|
||||
// off-board. The right gate for a simultaneous shift/rotation via swap().
|
||||
// holds a pushable and the next cell is empty or another pushable. Shifting
|
||||
// onto the player counts only when the source is a grab thing (it gets
|
||||
// grabbed). False off-board. The right gate for a shift/rotation via swap().
|
||||
let b = board.clone();
|
||||
engine.register_fn("can_shift", move |x: i64, y: i64, dir: Direction| -> bool {
|
||||
let board = b.borrow();
|
||||
@@ -706,6 +749,15 @@ fn register_read_api(
|
||||
board.in_bounds((x as i32, y as i32)) && board.is_passable(x as usize, y as usize)
|
||||
});
|
||||
|
||||
// combinable(x1, y1, x2, y2) — true if the solids at (x1, y1) and (x2, y2) can coexist in a cell
|
||||
// (meaning, either one contains no solid, or one contains a grab solid and the other contains the
|
||||
// player). Off-board is not combinable with anything.
|
||||
let b = board.clone();
|
||||
engine.register_fn("combinable", move |x1: i64, y1: i64, x2: i64, y2: i64| -> bool {
|
||||
let board = b.borrow();
|
||||
board.is_combinable(x1 as usize, y1 as usize, x2 as usize, y2 as usize)
|
||||
});
|
||||
|
||||
// Board.tagged(tag) -> Array[ObjectInfo]
|
||||
let b = board.clone();
|
||||
engine.register_fn(
|
||||
@@ -841,6 +893,18 @@ fn register_write_api(engine: &mut Engine, queues: &QueueMap) {
|
||||
emit(&q, source_of(&ctx), Action::SetTile(tile as u32));
|
||||
});
|
||||
|
||||
// add_gems(n): change the player's gem count (negative subtracts; clamped at 0).
|
||||
let q = queues.clone();
|
||||
engine.register_fn("add_gems", move |ctx: NativeCallContext, n: i64| {
|
||||
emit(&q, source_of(&ctx), Action::AddGems(n));
|
||||
});
|
||||
|
||||
// die(): remove the calling object from the board.
|
||||
let q = queues.clone();
|
||||
engine.register_fn("die", move |ctx: NativeCallContext| {
|
||||
emit(&q, source_of(&ctx), Action::Die);
|
||||
});
|
||||
|
||||
let q = queues.clone();
|
||||
engine.register_fn(
|
||||
"log",
|
||||
|
||||
Reference in New Issue
Block a user