enter hook
This commit is contained in:
+52
-8
@@ -7,6 +7,19 @@ use crate::utils::Direction;
|
||||
use crate::utils::{Behavior, ObjectId, PlayerPos, PortalDef, Pushable, RegistryValue, Solid};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
/// The result of [`Board::apply_shift`]: any error lines to log, plus the
|
||||
/// `(from, to)` cell relocations the shift actually performed.
|
||||
///
|
||||
/// The `moves` let the caller fire an `enter` hook on any non-solid object each
|
||||
/// shifted solid landed on; the direction is derived best-effort from `to - from`
|
||||
/// (a shift can rotate cells that aren't cardinally adjacent).
|
||||
pub struct ShiftOutcome {
|
||||
/// Error lines (e.g. an out-of-bounds cell) for the caller to log.
|
||||
pub errors: Vec<LogLine>,
|
||||
/// Each `(from, to)` relocation a non-blocked solid underwent.
|
||||
pub moves: Vec<((i64, i64), (i64, i64))>,
|
||||
}
|
||||
|
||||
/// A non-solid `(glyph, archetype)` placed at a board coordinate, **outside** the
|
||||
/// main grid, drawn only when the grid cell at `(x, y)` is empty.
|
||||
///
|
||||
@@ -373,9 +386,12 @@ impl Board {
|
||||
///
|
||||
/// No-op when the chain can't move (it self-checks via [`can_push`](Board::can_push)),
|
||||
/// so it is safe to call unconditionally.
|
||||
pub fn push(&mut self, x: usize, y: usize, dir: Direction) {
|
||||
/// Returns the cells the shoved solids moved **into** (each chain cell stepped
|
||||
/// one cell in `dir`), so the caller can fire `enter` on any non-solid object a
|
||||
/// pushed solid landed on. Empty when nothing moved.
|
||||
pub fn push(&mut self, x: usize, y: usize, dir: Direction) -> Vec<(usize, usize)> {
|
||||
if !self.can_push(x, y, dir) {
|
||||
return;
|
||||
return Vec::new();
|
||||
}
|
||||
let (dx, dy): (i64, i64) = dir.into();
|
||||
// can_push guaranteed the chain ends at an in-bounds passable cell, so
|
||||
@@ -391,6 +407,12 @@ impl Board {
|
||||
for &(px, py) in chain.iter().rev() {
|
||||
self.shift_solid(px, py, dx, dy);
|
||||
}
|
||||
// Each solid ended up one step along `dir`; those destination cells are
|
||||
// where an `enter` may need to fire.
|
||||
chain
|
||||
.iter()
|
||||
.map(|&(px, py)| ((px as i64 + dx) as usize, (py as i64 + dy) as usize))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Moves the single solid occupant of `(x, y)` one step by `(dx, dy)`.
|
||||
@@ -421,6 +443,19 @@ impl Board {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the [`ObjectId`]s of the **non-solid** objects at `(x, y)`.
|
||||
///
|
||||
/// These are the targets of an `enter` hook when a solid relocates onto the
|
||||
/// cell (terrain is always solid, so only objects can be non-solid). Mirrors
|
||||
/// [`object_ids_at`](Board::object_ids_at) / [`solid_object_id_at`](Board::solid_object_id_at).
|
||||
pub fn non_solid_object_ids_at(&self, x: usize, y: usize) -> Vec<ObjectId> {
|
||||
self.objects
|
||||
.iter()
|
||||
.filter(|(_, o)| o.x == x && o.y == y && !o.solid)
|
||||
.map(|(&id, _)| id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a borrow of the actual object at `(x, y)` if any
|
||||
pub fn solid_object_id_at(&self, x: usize, y: usize) -> Option<ObjectId> {
|
||||
self.objects.iter().find_map(|(&id, o)| {
|
||||
@@ -552,11 +587,16 @@ impl Board {
|
||||
}
|
||||
|
||||
/// Shifts a set of cells, given as `(x, y)` coordinates. Backs the script
|
||||
/// `shift()` fn. Returns any errors as [`LogLine`]s for the caller to log.
|
||||
pub fn apply_shift(&mut self, cells: &[(i64, i64)]) -> Vec<LogLine> {
|
||||
/// `shift()` fn. Returns a [`ShiftOutcome`] carrying any error [`LogLine`]s for
|
||||
/// the caller to log plus the `(from, to)` relocations it performed (so the
|
||||
/// caller can fire `enter` on non-solids each moved solid landed on).
|
||||
pub fn apply_shift(&mut self, cells: &[(i64, i64)]) -> ShiftOutcome {
|
||||
// Validate all the cells are in bounds, error if not:
|
||||
if cells.iter().any(|&c| !self.in_bounds(c)) {
|
||||
return vec![LogLine::error("Called shift() with a cell out of bounds")]
|
||||
return ShiftOutcome {
|
||||
errors: vec![LogLine::error("Called shift() with a cell out of bounds")],
|
||||
moves: Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
// Get all the Solids at these cells:
|
||||
@@ -609,15 +649,19 @@ impl Board {
|
||||
}
|
||||
}
|
||||
|
||||
// Now, move anything that we've decided is not blocked:
|
||||
// Now, move anything that we've decided is not blocked, recording each
|
||||
// relocation so the caller can fire `enter` at every destination.
|
||||
let mut moves = Vec::new();
|
||||
for (curr_idx, curr) in solids.iter().enumerate() {
|
||||
if let Some(solid) = curr && !blocked.contains(&curr_idx) {
|
||||
let origin = cells[curr_idx];
|
||||
let target = cells[(curr_idx + 1) % cells.len()];
|
||||
solid.place(self, target.0 as usize, target.1 as usize);
|
||||
moves.push((origin, target));
|
||||
}
|
||||
}
|
||||
|
||||
vec![]
|
||||
ShiftOutcome { errors: Vec::new(), moves }
|
||||
}
|
||||
|
||||
/// Clear the queues of all objects on this board: called when entering a board, objects
|
||||
@@ -975,7 +1019,7 @@ pub(crate) mod tests {
|
||||
let mut board = open_board(3, 1, (2, 0), vec![]);
|
||||
crate_at(&mut board, 0, 0);
|
||||
let errs = board.apply_shift(&[(0, 0), (9, 0)]);
|
||||
assert_eq!(errs.len(), 1);
|
||||
assert_eq!(errs.errors.len(), 1);
|
||||
assert_eq!(board.get(0, 0).1, Archetype::Crate); // unchanged
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user