Added scroll()

This commit is contained in:
2026-06-10 01:42:33 -05:00
parent c2168b992d
commit f73c02437d
7 changed files with 375 additions and 43 deletions
+42
View File
@@ -10,6 +10,21 @@ use crate::utils::{Direction, ObjectId, ScriptArg, Solid};
/// How long a `say()` speech bubble stays on screen, in seconds.
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;
/// An active scroll overlay opened by a scripted object via `scroll()`.
///
/// While a `Scroll` is present on [`GameState`], the front-end should pause ticks
/// and display the overlay. Closed via [`GameState::close_scroll`].
pub struct Scroll {
/// The object whose `scroll()` call opened this overlay.
pub source: ObjectId,
/// The lines of content to display.
pub lines: Vec<ScrollLine>,
}
/// An active speech bubble emitted by a scripted object via `say()`.
pub struct SpeechBubble {
/// The object whose `say()` call created this bubble.
@@ -38,6 +53,9 @@ pub struct GameState {
scripts: ScriptHost,
/// Active speech bubbles from `say()` calls, displayed by the front-end over the board.
pub speech_bubbles: Vec<SpeechBubble>,
/// An active scroll overlay opened by `scroll()`. `Some` while the overlay is
/// visible; the front-end pauses ticks and closes it via [`close_scroll`](GameState::close_scroll).
pub active_scroll: Option<Scroll>,
}
impl GameState {
@@ -54,6 +72,7 @@ impl GameState {
log: Vec::new(),
scripts,
speech_bubbles: Vec::new(),
active_scroll: None,
};
// Surface any compile-time script errors collected during ScriptHost::new.
state.drain_errors();
@@ -120,6 +139,8 @@ impl GameState {
let mut bumps: Vec<(ObjectId, i64)> = Vec::new();
let mut sends: Vec<(ObjectId, String, Option<ScriptArg>)> = 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 {
@@ -159,6 +180,10 @@ impl GameState {
Action::Send { target, fn_name, arg } => {
sends.push((target, fn_name, arg));
}
// Later scrolls overwrite earlier ones from the same tick.
Action::Scroll(lines) => {
new_scroll = Some(Scroll { source: ba.source, lines });
}
}
}
}
@@ -168,6 +193,9 @@ impl GameState {
self.speech_bubbles.retain(|b| b.object_id != bubble.object_id);
self.speech_bubbles.push(bubble);
}
if let Some(scroll) = new_scroll {
self.active_scroll = Some(scroll);
}
for (bumped, bumper) in bumps {
self.scripts.run_bump(bumped, bumper);
}
@@ -177,6 +205,20 @@ impl GameState {
self.drain_errors();
}
/// Closes the active scroll overlay, optionally dispatching a named choice
/// back to the object that opened it via `send`.
///
/// Call from the front-end when the player dismisses the scroll or selects a
/// choice. If `choice` is `Some`, the source object receives a `send` call with
/// that string as the function name (e.g. `"eat"` triggers `fn eat()`).
pub fn close_scroll(&mut self, choice: Option<&str>) {
let source = self.active_scroll.take().map(|s| s.source);
if let (Some(src), Some(ch)) = (source, choice) {
self.scripts.run_send(src, ch, None);
self.drain_errors();
}
}
/// Attempts to move the player one cell in `dir`.
///
/// The move is ignored if the target cell is out of bounds, or it is neither