This commit is contained in:
2026-06-21 18:44:42 -05:00
parent d4b9278838
commit cbdaca4a90
8 changed files with 48 additions and 27 deletions
+6 -4
View File
@@ -235,11 +235,13 @@ impl Board {
self.solid_at(x, y).is_none()
}
/// Returns `true` if the mover as `(x1, y1)` can enter `(x2, y2)` — i.e. it would not
/// break the rules of "one solid per cell, except for player + grab".
/// Returns `true` if the solids at `(x1, y1)` and `(x2, y2)` may share a cell —
/// i.e. moving one onto the other wouldn't break the "one solid per cell"
/// invariant. That holds when at least one cell is empty, or one holds the
/// player and the other a **grab** thing (the player collects it).
///
/// Convenience inverse of [`solid_at`](Board::solid_at).
/// Panics if `x` or `y` are out of bounds.
/// Off-board coordinates are never combinable (returns `false` rather than
/// panicking). Backs the script-facing `combinable(x1, y1, x2, y2)` fn.
pub fn is_combinable(&self, x1: usize, y1: usize, x2: usize, y2: usize) -> bool {
// Are either out of bounds?
if !self.in_bounds((x1 as i32, y1 as i32)) || !self.in_bounds((x2 as i32, y2 as i32)) {
+5 -3
View File
@@ -55,9 +55,11 @@ pub(crate) fn archetype_script(arch: Archetype) -> Option<&'static str> {
}
}
/// Returns the embedded script implementing `arch` as an object, or `None` if the
/// archetype is a plain terrain cell. The one place script-backed archetypes are
/// declared.
/// Returns the synthetic `BUILTIN_*` compile-key for `arch`'s embedded script, or
/// `None` if the archetype is a plain terrain cell. [`Board::expand_builtin_archetypes`](crate::board::Board::expand_builtin_archetypes)
/// stores this as the expanded object's `script_name` so every instance of a
/// script-backed archetype shares one compiled AST (the source comes from
/// [`archetype_script`]).
pub(crate) fn archetype_script_key(arch: Archetype) -> Option<&'static str> {
match arch {
Archetype::Pusher(_) => Some("BUILTIN_pusher"),
+10 -5
View File
@@ -54,13 +54,18 @@ pub struct ObjectDef {
/// remove itself via `die()`). Set when a grab archetype (e.g. a gem) is
/// expanded into an object. Defaults to `false`.
pub grab: bool,
/// Name of the Rhai script in [`Board::scripts`] that drives this object.
/// `None` means this object has no script yet.
/// Compile-key of the Rhai script that drives this object: a name in
/// [`World::scripts`](crate::world::World::scripts) for a hand-authored object,
/// or a synthetic `BUILTIN_*` name set when a script-backed archetype is
/// expanded (see [`builtin_script`](ObjectDef::builtin_script)). `None` means
/// this object has no script yet.
pub script_name: Option<String>,
/// Embedded built-in script source, set when a script-backed archetype (e.g. a
/// `pusher_*`) is expanded into an object at load time (see
/// [`crate::builtin_scripts`]). Takes precedence over [`script_name`](ObjectDef::script_name)
/// and is not part of the map file (it is regenerated from the archetype on load).
/// `pusher_*` or `gem`) is expanded into an object at load time (see
/// [`crate::builtin_scripts`]). When set, this is the object's script *source*
/// (its compile-key is the synthetic `BUILTIN_*` [`script_name`](ObjectDef::script_name)
/// the same expansion assigns). Not part of the map file — it is regenerated
/// from the archetype on load.
pub builtin_script: Option<&'static str>,
/// Open-ended string labels for this object. Serialized as a TOML array;
/// not subject to any rate limit — mutations take effect immediately after
+12 -7
View File
@@ -115,15 +115,19 @@ struct CompiledScript {
struct ObjectRuntime {
object_id: ObjectId,
/// Key into [`ScriptHost::scripts`] for this object's compiled script — the
/// pool name for a named script, or the source text for an embedded built-in.
/// world-pool name for a named script, or the synthetic `BUILTIN_*` name for an
/// expanded built-in.
script_key: String,
scope: Scope<'static>,
output_queue: ObjQueue,
}
/// The compile-key for an object's script: the embedded source itself for a
/// 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.
/// The compile-key for an object's script: the object's `script_name` — a
/// world-pool name for a named script, or a synthetic `BUILTIN_*` name set by
/// [`Board::expand_builtin_archetypes`](crate::board::Board::expand_builtin_archetypes)
/// for an expanded built-in (so identical built-ins share one compiled AST,
/// while the source still comes from `builtin_script`). `None` if the object has
/// no script.
fn script_key(obj: &ObjectDef) -> Option<String> {
obj.script_name.clone()
}
@@ -226,9 +230,10 @@ impl ScriptHost {
let board = board_cell.borrow();
// Compile each referenced script once, keyed by `script_key` (pool name for
// named scripts, source text for embedded built-ins so identical ones share
// one AST).
// Compile each referenced script once, keyed by `script_key` (the object's
// `script_name`: a world-pool name for named scripts, or a synthetic
// `BUILTIN_*` name for built-ins so identical ones share one AST). The
// source for a built-in still comes from its embedded `builtin_script`.
let mut scripts: HashMap<String, CompiledScript> = HashMap::new();
let mut failed: HashSet<String> = HashSet::new();
for obj in board.objects.values() {
+3 -1
View File
@@ -36,7 +36,9 @@ fn pusher_loads_as_a_tagged_scripted_solid_object() {
p.builtin_script.is_some(),
"carries the embedded pusher script"
);
assert!(p.script_name.is_none());
// Expansion keys all pushers under one synthetic script name so they share a
// compiled AST; the source still comes from `builtin_script`.
assert_eq!(p.script_name.as_deref(), Some("BUILTIN_pusher"));
assert!(!board.is_passable(0, 0), "pusher is solid");
}
+3 -1
View File
@@ -25,7 +25,9 @@ fn spinner_loads_as_a_tagged_scripted_solid_object() {
obj.builtin_script.is_some(),
"carries the embedded spinner script"
);
assert!(obj.script_name.is_none());
// Expansion keys all spinners under one synthetic script name so they share a
// compiled AST; the source still comes from `builtin_script`.
assert_eq!(obj.script_name.as_deref(), Some("BUILTIN_spinner"));
assert!(!board.is_passable(0, 0), "spinner is solid");
}
+6 -4
View File
@@ -84,17 +84,19 @@ pub enum Solid {
}
impl Solid {
/// Whether this occupant is the player.
pub fn player(&self) -> bool {
matches!(self, Solid::Player)
}
/// Whether this occupant is a **grab** thing (a gem-like solid the player
/// collects on contact). The player itself is never grabbable; a terrain cell
/// reports its archetype's `grab` flag, and an object reports its own.
pub fn grab(&self, board: &Board) -> bool {
match self {
Solid::Player => false,
Solid::Cell(arch) => arch.behavior().grab,
Solid::Object(id) => {
board.objects.get(id).map_or(false, |obj| obj.grab)
}
Solid::Object(id) => board.objects.get(id).is_some_and(|obj| obj.grab),
}
}
}