speech bubbles and delays

This commit is contained in:
2026-06-08 22:15:44 -05:00
parent f5ed8f2edf
commit e5c406e42d
12 changed files with 354 additions and 190 deletions
+37 -3
View File
@@ -1,10 +1,23 @@
use crate::log::LogLine;
use crate::script::{Action, Direction, ScriptHost};
use crate::script::ScriptHost;
use std::cell::{Ref, RefCell, RefMut};
use std::rc::Rc;
use std::time::Duration;
use crate::board::Board;
use crate::utils::{ObjectId, Solid};
use crate::utils::{Action, Direction, ObjectId, Solid};
/// How long a `say()` speech bubble stays on screen, in seconds.
pub const SAY_DURATION: f64 = 3.0;
/// An active speech bubble emitted by a scripted object via `say()`.
pub struct SpeechBubble {
/// The object whose `say()` call created this bubble.
pub object_id: ObjectId,
/// The text to display.
pub text: String,
/// Seconds remaining before the bubble disappears.
pub remaining: f64,
}
/// Holds the active game world and provides game-logic operations.
///
@@ -22,6 +35,8 @@ pub struct GameState {
pub log: Vec<LogLine>,
/// The Rhai scripting runtime driving this board's scripted objects.
scripts: ScriptHost,
/// Active speech bubbles from `say()` calls, displayed by the front-end over the board.
pub speech_bubbles: Vec<SpeechBubble>,
}
impl GameState {
@@ -37,6 +52,7 @@ impl GameState {
board,
log: Vec::new(),
scripts,
speech_bubbles: Vec::new(),
};
// Surface any compile-time script errors collected during ScriptHost::new.
state.drain_errors();
@@ -73,8 +89,10 @@ impl GameState {
/// then resolves the actions that were promoted onto the board queue.
pub fn tick(&mut self, dt: Duration) {
let secs = dt.as_secs_f64();
self.scripts.advance_timers(secs);
self.scripts.run_tick(secs);
// Expire speech bubbles before resolving new actions so a fresh say()
// this frame isn't immediately culled.
self.speech_bubbles.retain_mut(|b| { b.remaining -= secs; b.remaining > 0.0 });
self.resolve();
}
@@ -98,6 +116,7 @@ impl GameState {
// below also borrows `self`.
let mut logs: Vec<LogLine> = Vec::new();
let mut bumps: Vec<(ObjectId, i64)> = Vec::new();
let mut new_bubbles: Vec<SpeechBubble> = Vec::new();
{
let mut board = self.board_mut();
for ba in actions {
@@ -118,10 +137,25 @@ impl GameState {
if present { obj.tags.insert(tag); } else { obj.tags.remove(&tag); }
}
}
// Replace any existing bubble from this object so repeated say() calls
// don't stack visually — the new text resets the timer.
Action::Say(text) => new_bubbles.push(SpeechBubble {
object_id: ba.source,
text,
remaining: SAY_DURATION,
}),
// Delays are consumed by ScriptHost::drain and never reach the board queue.
Action::Delay(_) => {}
}
}
}
self.log.extend(logs);
for bubble in new_bubbles {
// One bubble per object: replace the existing one if present.
self.speech_bubbles.retain(|b| b.object_id != bubble.object_id);
self.speech_bubbles.push(bubble);
}
for (bumped, bumper) in bumps {
self.scripts.run_bump(bumped, bumper);
}