portals 1

This commit is contained in:
2026-06-13 17:58:04 -05:00
parent 49d32eedf5
commit 6818f0545a
9 changed files with 151 additions and 7 deletions
+59 -2
View File
@@ -19,6 +19,7 @@
//! | `Board` | `Board` | Read-only world handle. |
//! | `Queue` | `Queue` | This object's pending-action queue. |
//! | `Me` | `Me` | Self-reference (id, name, x, y, tags, glyph). |
//! | `Registry` | `Registry` | Board-scoped key→value store persisting across board transitions. |
//!
//! Direction and color constants are registered as a global Rhai module and
//! are visible to all functions at any call depth, including Rhai-to-Rhai calls:
@@ -46,7 +47,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])`
//! `send(target_id, fn_name [, arg])`, `teleport(x, y)`
//!
//! ## Queue API
//!
@@ -57,7 +58,7 @@ use crate::game::SAY_DURATION;
use crate::board::Board;
use crate::log::LogLine;
use crate::map_file::{color_to_hex, parse_color};
use crate::utils::{Direction, ObjectId, ScriptArg};
use crate::utils::{Direction, ObjectId, RegistryValue, ScriptArg};
use rhai::{Array, CallFnOptions, Dynamic, Engine, FuncArgs, ImmutableString, Module, NativeCallContext, Scope, AST};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet, VecDeque};
@@ -133,6 +134,15 @@ struct Me {
board: BoardRef,
}
/// The board's script registry, pushed into scope as the constant `Registry`.
/// `get`/`set`/`get_or` methods let scripts read and write per-board key→value pairs
/// that persist across board transitions. Mutation goes through the inner
/// `Rc<RefCell<Board>>`, so the struct itself can be shared as a scope constant.
#[derive(Clone)]
struct Registry {
board: BoardRef,
}
// ── ObjectInfo helper ─────────────────────────────────────────────────────────
fn object_info_from(id: ObjectId, board: &Board) -> Option<ObjectInfo> {
@@ -187,6 +197,7 @@ impl ScriptHost {
register_me_type(&mut engine);
register_object_info_type(&mut engine);
register_glyph_type(&mut engine);
register_registry_type(&mut engine);
register_global_constants(&mut engine);
let board = board_cell.borrow();
@@ -449,6 +460,43 @@ fn register_me_type(engine: &mut Engine) {
});
}
// ── Register Registry type ────────────────────────────────────────────────────
fn register_registry_type(engine: &mut Engine) {
engine.register_type_with_name::<Registry>("Registry");
// Registry.get(key) -> Dynamic — returns () if the key is absent.
engine.register_fn("get", |r: &mut Registry, key: ImmutableString| -> Dynamic {
r.board.borrow()
.registry
.get(key.as_str())
.map(|v| v.clone().into())
.unwrap_or(Dynamic::UNIT)
});
// Registry.set(key, value) — stores a primitive value; () removes the key;
// unsupported types (closures, custom objects) are silently ignored.
engine.register_fn("set", |r: &mut Registry, key: ImmutableString, value: Dynamic| {
match RegistryValue::try_from(value.clone()) {
Ok(rv) => { r.board.borrow_mut().registry.insert(key.to_string(), rv); }
Err(()) if value.is_unit() => { r.board.borrow_mut().registry.remove(key.as_str()); }
Err(()) => {} // unsupported type — silently ignore
}
});
// Registry.get_or(key, default) — returns the stored value when present and the
// same Rhai type as `default`; falls back to `default` otherwise.
engine.register_fn("get_or", |r: &mut Registry, key: ImmutableString, default: Dynamic| -> Dynamic {
if let Some(stored) = r.board.borrow().registry.get(key.as_str()).cloned() {
let candidate: Dynamic = stored.into();
// Only return the stored value if it matches the type of the default.
if candidate.type_id() == default.type_id() { candidate } else { default }
} else {
default
}
});
}
// ── Register ObjectInfo type ──────────────────────────────────────────────────
fn register_object_info_type(engine: &mut Engine) {
@@ -725,6 +773,14 @@ fn register_write_api(engine: &mut Engine, queues: &QueueMap) {
});
},
);
// teleport(x, y): move the calling object to an arbitrary cell. Zero time cost.
// Blocked (with an error logged at resolve time) if the object is solid and the
// destination already has a solid occupant.
let q = queues.clone();
engine.register_fn("teleport", move |ctx: NativeCallContext, x: i64, y: i64| {
emit(&q, source_of(&ctx), Action::Teleport { x: x as i32, y: y as i32 });
});
}
// ── Queue API ─────────────────────────────────────────────────────────────────
@@ -790,6 +846,7 @@ fn new_object_scope(board: &BoardRef, queue: &ObjQueue, object_id: ObjectId) ->
scope.push_constant("Board", board.clone());
scope.push_constant("Queue", queue.clone());
scope.push_constant("Me", Me { object_id, board: board.clone() });
scope.push_constant("Registry", Registry { board: board.clone() });
scope
}