portals
This commit is contained in:
+60
-1
@@ -5,7 +5,7 @@ use crate::script::ScriptHost;
|
||||
use crate::world::World;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::time::Duration;
|
||||
use crate::utils::{Direction, ObjectId, ScriptArg, Solid};
|
||||
use crate::utils::{Direction, ObjectId, Player, ScriptArg, Solid};
|
||||
|
||||
/// How long a `say()` speech bubble stays on screen, in seconds.
|
||||
pub const SAY_DURATION: f64 = 3.0;
|
||||
@@ -57,6 +57,10 @@ pub struct GameState {
|
||||
/// 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>,
|
||||
/// Remaining seconds for the board-entry transition animation, set to `1.0`
|
||||
/// by [`enter_board`](GameState::enter_board). Front-ends tick this down and
|
||||
/// may block input or show a visual effect while it is `Some(t)` where `t > 0`.
|
||||
pub board_transition: Option<f64>,
|
||||
}
|
||||
|
||||
impl GameState {
|
||||
@@ -79,6 +83,7 @@ impl GameState {
|
||||
scripts: host,
|
||||
speech_bubbles: Vec::new(),
|
||||
active_scroll: None,
|
||||
board_transition: None,
|
||||
};
|
||||
state.drain_errors();
|
||||
state
|
||||
@@ -253,6 +258,48 @@ impl GameState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Switches the active board, placing the player at the named arrival portal,
|
||||
/// rebuilding the script host, and running `init()` hooks on the new board's objects.
|
||||
///
|
||||
/// Called automatically by [`try_move`](GameState::try_move) when the player
|
||||
/// steps onto a portal. Front-ends may check [`board_transition`](GameState::board_transition)
|
||||
/// to play a visual effect during the switch.
|
||||
pub fn enter_board(&mut self, target_map: &str, target_entry: &str) {
|
||||
if !self.world.boards.contains_key(target_map) {
|
||||
self.log.push(LogLine::error(format!("portal target board {target_map:?} not found")));
|
||||
return;
|
||||
}
|
||||
// Find the named arrival portal on the target board (borrow then release).
|
||||
let arrival = self.world.boards[target_map].borrow()
|
||||
.portals.iter()
|
||||
.find(|p| p.name == target_entry)
|
||||
.map(|p| (p.x, p.y));
|
||||
let (ax, ay) = match arrival {
|
||||
Some(pos) => pos,
|
||||
None => {
|
||||
self.log.push(LogLine::error(format!(
|
||||
"portal entry {target_entry:?} not found on board {target_map:?}"
|
||||
)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Clear per-board transient state.
|
||||
self.speech_bubbles.clear();
|
||||
self.active_scroll = None;
|
||||
// Switch to the new board and place the player at the arrival portal.
|
||||
self.current_board_name = target_map.to_string();
|
||||
self.board_mut().player = Player { x: ax as i32, y: ay as i32 };
|
||||
// Rebuild the script host for the new board's objects.
|
||||
self.scripts = ScriptHost::new(
|
||||
&self.world.boards[&self.current_board_name],
|
||||
&self.world.scripts,
|
||||
);
|
||||
// Stub hook for the front-end transition animation (1 second).
|
||||
self.board_transition = Some(1.0);
|
||||
// Run init hooks and resolve their queued actions.
|
||||
self.run_init();
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -261,6 +308,7 @@ impl GameState {
|
||||
/// object's `bump(-1)` hook fires (whether or not the player ends up moving).
|
||||
pub fn try_move(&mut self, dir: Direction) {
|
||||
let bumped;
|
||||
let portal_target;
|
||||
{
|
||||
let (dx, dy): (i32, i32) = dir.into();
|
||||
let mut board = self.board_mut();
|
||||
@@ -278,8 +326,19 @@ impl GameState {
|
||||
board.push(nx, ny, dir); // shoves a crate/object out of the way; no-op otherwise
|
||||
board.player.x = nx as i32;
|
||||
board.player.y = ny as i32;
|
||||
// Check for a portal at the new position; clone strings to release the borrow.
|
||||
portal_target = board.portals.iter()
|
||||
.find(|p| p.x == nx && p.y == ny)
|
||||
.map(|p| (p.target_map.clone(), p.target_entry.clone()));
|
||||
} else {
|
||||
portal_target = None;
|
||||
}
|
||||
}
|
||||
// A portal takes priority: board transitions skip the bump hook.
|
||||
if let Some((target_map, target_entry)) = portal_target {
|
||||
self.enter_board(&target_map, &target_entry);
|
||||
return;
|
||||
}
|
||||
if let Some(idx) = bumped {
|
||||
self.scripts.run_bump(idx, -1);
|
||||
self.drain_errors();
|
||||
|
||||
Reference in New Issue
Block a user