wip 2 pointifying
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
//! object that issued it).
|
//! object that issued it).
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use crate::utils::{Direction, ObjectId};
|
use crate::utils::{Direction, ObjectId, Point};
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
use rhai::Dynamic;
|
use rhai::Dynamic;
|
||||||
use crate::Board;
|
use crate::Board;
|
||||||
@@ -171,15 +171,15 @@ pub fn apply_teleport(board: &mut Board, target: i64, x: i64, y: i64) -> Result<
|
|||||||
let from = if target == -1 {
|
let from = if target == -1 {
|
||||||
Some(board.player_pos())
|
Some(board.player_pos())
|
||||||
} else if let Some(obj) = board.get_hookable(target as ObjectId) {
|
} else if let Some(obj) = board.get_hookable(target as ObjectId) {
|
||||||
Some(obj.location())
|
Some(obj.location().into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if let Some(from) = from {
|
if let Some(from) = from {
|
||||||
// Check if we're blocked:
|
// Check if we're blocked:
|
||||||
if (from.0 != x || from.1 != y) && board.get((x, y)).is_none() {
|
if (from.ux() != x || from.uy() != y) && board.get((x, y)).is_none() {
|
||||||
// Not blocked, move it
|
// Not blocked, move it
|
||||||
board.move_cell(from.into(), (x, y).into());
|
board.move_cell(from, (x, y));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -188,7 +188,7 @@ pub fn apply_teleport(board: &mut Board, target: i64, x: i64, y: i64) -> Result<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_shift(board: &mut Board, cells: &[(i64, i64)]) -> Result<(), String> {
|
pub fn apply_shift(board: &mut Board, cells: &[Point]) -> Result<(), String> {
|
||||||
board.apply_shift(cells)?;
|
board.apply_shift(cells)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Cell queries
|
//! ## Cell queries
|
||||||
//!
|
//!
|
||||||
//! - `board.can_push(x, y, dir) -> bool` — is the pushable chain at `(x, y)` shovable in `dir`
|
|
||||||
//! - `board.passable(x, y) -> bool` — is `(x, y)` on-board and free of any solid (a hole)
|
//! - `board.passable(x, y) -> bool` — is `(x, y)` on-board and free of any solid (a hole)
|
||||||
//! cell (one empty, or a grab thing and the player)
|
//! cell (one empty, or a grab thing and the player)
|
||||||
|
|
||||||
@@ -30,19 +29,12 @@ impl Registerable for BoardRef {
|
|||||||
engine.register_get("width", |b: &mut BoardRef| b.borrow().width as i64);
|
engine.register_get("width", |b: &mut BoardRef| b.borrow().width as i64);
|
||||||
engine.register_get("height", |b: &mut BoardRef| b.borrow().height as i64);
|
engine.register_get("height", |b: &mut BoardRef| b.borrow().height as i64);
|
||||||
|
|
||||||
// can_push(x, y, dir) — true if (x, y) holds a pushable whose chain can be
|
|
||||||
// shoved one step in dir (the read-only half of push()). False off-board.
|
|
||||||
engine.register_fn("can_push", move |board: BoardRef, x: i64, y: i64, dir: Direction| -> bool {
|
|
||||||
let board = board.borrow();
|
|
||||||
board.in_bounds((x, y)) && board.can_push(x as usize, y as usize, dir)
|
|
||||||
});
|
|
||||||
|
|
||||||
// passable(x, y) — true if (x, y) is on-board and holds no solid (an empty cell
|
// passable(x, y) — true if (x, y) is on-board and holds no solid (an empty cell
|
||||||
// a mover could enter). Off-board is not passable. Lets a script distinguish a
|
// a mover could enter). Off-board is not passable. Lets a script distinguish a
|
||||||
// hole from a blocked solid (which can_shift/can_push alone cannot).
|
// hole from a blocked solid (which can_shift/can_push alone cannot).
|
||||||
engine.register_fn("passable", move |board: BoardRef, x: i64, y: i64| -> bool {
|
engine.register_fn("passable", move |board: BoardRef, x: i64, y: i64| -> bool {
|
||||||
let board = board.borrow();
|
let board = board.borrow();
|
||||||
board.in_bounds((x, y)) && board.is_passable(x as usize, y as usize)
|
board.in_bounds((x, y)) && board.is_empty((x, y))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Board.get(id) -> ObjectInfo | () (unknown id logs error)
|
// Board.get(id) -> ObjectInfo | () (unknown id logs error)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use crate::action::BoardAction;
|
|||||||
use crate::object_def::ObjectDef;
|
use crate::object_def::ObjectDef;
|
||||||
use crate::script::Registerable;
|
use crate::script::Registerable;
|
||||||
use crate::tile::{Hookable, Optics, ScriptAttributes, ScriptKey, Tile};
|
use crate::tile::{Hookable, Optics, ScriptAttributes, ScriptKey, Tile};
|
||||||
use crate::utils::{LogSink, ObjectId};
|
use crate::utils::{LogSink, ObjectId, Point};
|
||||||
|
|
||||||
/// A snapshot of one board object, returned by `Board.tagged`, `Board.named`,
|
/// A snapshot of one board object, returned by `Board.tagged`, `Board.named`,
|
||||||
/// and `Board.get`. Passed by value — scripts read fields, not a live reference.
|
/// and `Board.get`. Passed by value — scripts read fields, not a live reference.
|
||||||
@@ -39,8 +39,7 @@ use crate::utils::{LogSink, ObjectId};
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ObjectInfo {
|
pub struct ObjectInfo {
|
||||||
pub id: ObjectId,
|
pub id: ObjectId,
|
||||||
pub x: i64,
|
pub location: Point,
|
||||||
pub y: i64,
|
|
||||||
pub board: BoardRef,
|
pub board: BoardRef,
|
||||||
pub script_key: ScriptKey,
|
pub script_key: ScriptKey,
|
||||||
pub queue: ObjQueue
|
pub queue: ObjQueue
|
||||||
@@ -54,11 +53,9 @@ impl ObjectInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_hookable(hookable: Box<dyn Hookable + '_>, board: BoardRef) -> ObjectInfo {
|
pub fn from_hookable(hookable: Box<dyn Hookable + '_>, board: BoardRef) -> ObjectInfo {
|
||||||
let (x, y) = hookable.location();
|
|
||||||
Self {
|
Self {
|
||||||
id: hookable.id(),
|
id: hookable.id(),
|
||||||
x: x as i64,
|
location: hookable.location(),
|
||||||
y: y as i64,
|
|
||||||
board,
|
board,
|
||||||
script_key: hookable.scriptable().script_name.clone(),
|
script_key: hookable.scriptable().script_name.clone(),
|
||||||
queue: hookable.scriptable().queue.clone(),
|
queue: hookable.scriptable().queue.clone(),
|
||||||
@@ -75,8 +72,8 @@ impl ObjectInfo {
|
|||||||
impl Registerable for ObjectInfo {
|
impl Registerable for ObjectInfo {
|
||||||
fn register(engine: &mut Engine, _log_sink: LogSink) {
|
fn register(engine: &mut Engine, _log_sink: LogSink) {
|
||||||
engine.register_type_with_name::<ObjectInfo>("ObjectInfo")
|
engine.register_type_with_name::<ObjectInfo>("ObjectInfo")
|
||||||
.register_get("x", |obj: &mut ObjectInfo| obj.x)
|
.register_get("x", |obj: &mut ObjectInfo| obj.location.x)
|
||||||
.register_get("y", |obj: &mut ObjectInfo| obj.y)
|
.register_get("y", |obj: &mut ObjectInfo| obj.location.y)
|
||||||
.register_get("id", |obj: &mut ObjectInfo| obj.id as i64)
|
.register_get("id", |obj: &mut ObjectInfo| obj.id as i64)
|
||||||
.register_get("waiting", |obj: &mut ObjectInfo| obj.queue.waiting())
|
.register_get("waiting", |obj: &mut ObjectInfo| obj.queue.waiting())
|
||||||
.register_get("queue", |obj: &mut ObjectInfo| obj.queue.clone());
|
.register_get("queue", |obj: &mut ObjectInfo| obj.queue.clone());
|
||||||
@@ -122,15 +119,10 @@ impl Registerable for ObjectInfo {
|
|||||||
|
|
||||||
engine.register_fn("delay", |o: &mut ObjectInfo, dt: f64| o.queue.delay(dt));
|
engine.register_fn("delay", |o: &mut ObjectInfo, dt: f64| o.queue.delay(dt));
|
||||||
|
|
||||||
engine.register_fn("can_push", move |o: &mut ObjectInfo, dir: Direction| {
|
|
||||||
let board = o.board.borrow();
|
|
||||||
board.in_bounds((o.x, o.y)) && board.can_push(o.x as usize, o.y as usize, dir)
|
|
||||||
});
|
|
||||||
|
|
||||||
engine.register_fn("blocked", move |o: &mut ObjectInfo, dir: Direction| -> bool {
|
engine.register_fn("blocked", move |o: &mut ObjectInfo, dir: Direction| -> bool {
|
||||||
let board = o.board.borrow();
|
let board = o.board.borrow();
|
||||||
let (tx, ty) = dir.from_point(o.x, o.y);
|
let t = dir.from_point(o.location);
|
||||||
!board.in_bounds((tx, ty)) || !board.is_passable(tx as usize, ty as usize)
|
!board.in_bounds(t) || !board.is_empty(t)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ impl Registerable for PlayerWithPos {
|
|||||||
.register_get("health", |player: &mut PlayerWithPos| player.0.borrow().health)
|
.register_get("health", |player: &mut PlayerWithPos| player.0.borrow().health)
|
||||||
.register_get("max_health", |player: &mut PlayerWithPos| player.0.borrow().max_health)
|
.register_get("max_health", |player: &mut PlayerWithPos| player.0.borrow().max_health)
|
||||||
.register_get("keys", |player: &mut PlayerWithPos| player.0.borrow().keys)
|
.register_get("keys", |player: &mut PlayerWithPos| player.0.borrow().keys)
|
||||||
.register_get("x", |player: &mut PlayerWithPos| player.1.borrow().player_pos().0 as i64)
|
.register_get("x", |player: &mut PlayerWithPos| player.1.borrow().player_pos().x)
|
||||||
.register_get("y", |player: &mut PlayerWithPos| player.1.borrow().player_pos().1 as i64);
|
.register_get("y", |player: &mut PlayerWithPos| player.1.borrow().player_pos().y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-142
@@ -129,7 +129,7 @@ impl Board {
|
|||||||
let p = p.into();
|
let p = p.into();
|
||||||
let grid_glyph = self.get(p).as_ref().map(Tile::glyph);
|
let grid_glyph = self.get(p).as_ref().map(Tile::glyph);
|
||||||
|
|
||||||
let sensors = self.sensors.iter().filter(|&s| s.x == p.ux() && s.y == p.uy());
|
let sensors = self.sensors.iter().filter(|&s| s.location == p);
|
||||||
|
|
||||||
// Is there a sensor above the grid?
|
// Is there a sensor above the grid?
|
||||||
if let Some(above) = sensors.clone().find(|s| s.draw_layer == DrawLayer::Above && s.scripting.glyph.is_visible()) {
|
if let Some(above) = sensors.clone().find(|s| s.draw_layer == DrawLayer::Above && s.scripting.glyph.is_visible()) {
|
||||||
@@ -148,7 +148,7 @@ impl Board {
|
|||||||
|
|
||||||
// Otherwise the floor, or the canonical black empty cell.
|
// Otherwise the floor, or the canonical black empty cell.
|
||||||
self.floor
|
self.floor
|
||||||
.glyph_at(p.ux(), p.uy(), self.width)
|
.glyph_at(p, self.width)
|
||||||
.unwrap_or_else(|| Glyph::transparent())
|
.unwrap_or_else(|| Glyph::transparent())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ impl Board {
|
|||||||
/// Panics if `x` or `y` are out of bounds.
|
/// Panics if `x` or `y` are out of bounds.
|
||||||
pub fn is_opaque_at<P: Into<Point>>(&self, p: P) -> bool {
|
pub fn is_opaque_at<P: Into<Point>>(&self, p: P) -> bool {
|
||||||
let p = p.into();
|
let p = p.into();
|
||||||
if self.sensors.iter().any(|s| s.x == p.ux() && s.y == p.uy() && s.scripting.optics.opaque) {
|
if self.sensors.iter().any(|s| s.location == p && s.scripting.optics.opaque) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
match self.get(p) {
|
match self.get(p) {
|
||||||
@@ -240,110 +240,13 @@ impl Board {
|
|||||||
// Glowing sensors
|
// Glowing sensors
|
||||||
for s in self.sensors.iter() {
|
for s in self.sensors.iter() {
|
||||||
if s.optics().glow > 0 {
|
if s.optics().glow > 0 {
|
||||||
add_source(&mut lighting, s.x, s.y, s.optics().glow, color_to_rgb(s.scripting.glyph.fg));
|
add_source(&mut lighting, s.location.ux(), s.location.uy(), s.optics().glow, color_to_rgb(s.scripting.glyph.fg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(lighting)
|
Some(lighting)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Whether the solid at `(x, y)` can be **shifted** one step in `dir`: it is
|
|
||||||
// /// itself a pushable solid *and* the next cell is either empty or holds another
|
|
||||||
// /// pushable solid.
|
|
||||||
// ///
|
|
||||||
// /// Unlike [`can_push`](Board::can_push) this inspects only the single cell
|
|
||||||
// /// ahead — it does **not** verify the whole chain ends in open space. It is the
|
|
||||||
// /// right test for a simultaneous rotation/shift (applied via
|
|
||||||
// /// [`apply_swap`](Board::apply_swap)), where a destination is occupied by
|
|
||||||
// /// another pushable that is itself moving the same frame. Returns `false` if
|
|
||||||
// /// `(x, y)` holds no pushable, or the cell ahead runs off the board.
|
|
||||||
// ///
|
|
||||||
// /// The **player** is always a blocker: a shift can't relocate the player, and
|
|
||||||
// /// `apply_swap` refuses to overwrite it, so a cell holding the player is never
|
|
||||||
// /// an acceptable shift destination.
|
|
||||||
// pub fn can_shift(&self, x: usize, y: usize, dir: Direction) -> bool {
|
|
||||||
// // The source must hold a solid pushable in `dir`.
|
|
||||||
// if !self.is_pushable(x, y, dir) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// let (dx, dy): (i64, i64) = dir.into();
|
|
||||||
// let next = (x as i64 + dx, y as i64 + dy);
|
|
||||||
// if !self.in_bounds(next) {
|
|
||||||
// return false; // nothing to shift into off the board
|
|
||||||
// }
|
|
||||||
// let (nx, ny) = (next.0 as usize, next.1 as usize);
|
|
||||||
// // The player always blocks a shift: a shift can't relocate it and
|
|
||||||
// // `apply_swap` refuses to overwrite it. (The player reads as pushable, so it
|
|
||||||
// // must be excluded explicitly before the cell-ahead test below.)
|
|
||||||
// if self.get(nx, ny).as_ref().is_some_and(Tile::player) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// // The cell ahead is acceptable if it is empty or another pushable solid.
|
|
||||||
// self.is_passable(nx, ny) || self.is_pushable(nx, ny, dir)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Shoves the chain of pushable solids starting at `(x, y)` one step in `dir`,
|
|
||||||
// /// leaving `Empty` floor behind each moved cell.
|
|
||||||
// ///
|
|
||||||
// /// No-op when the chain can't move (it self-checks via [`can_push`](Board::can_push)),
|
|
||||||
// /// so it is safe to call unconditionally.
|
|
||||||
// /// 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 Vec::new();
|
|
||||||
// }
|
|
||||||
// let (dx, dy): (i64, i64) = dir.into();
|
|
||||||
// // can_push guaranteed the chain ends at an in-bounds passable cell, so
|
|
||||||
// // re-walk it (no bounds checks needed) and shift the far end first, which
|
|
||||||
// // keeps each destination cell vacated before its occupant arrives.
|
|
||||||
// let mut chain: Vec<(usize, usize)> = Vec::new();
|
|
||||||
// let (mut cx, mut cy) = (x, y);
|
|
||||||
// while !self.is_passable(cx, cy) {
|
|
||||||
// chain.push((cx, cy));
|
|
||||||
// cx = (cx as i64 + dx) as usize;
|
|
||||||
// cy = (cy as i64 + dy) as usize;
|
|
||||||
// }
|
|
||||||
// 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()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Tries to walk the object in x,y in the given direction. Pushes the cell in that direction
|
|
||||||
// /// first. This won't move anything if:
|
|
||||||
// /// - the cell x,y is empty; no-op
|
|
||||||
// /// - x,y is out of bounds, no-op
|
|
||||||
// /// - the target cell can't be pushed in that direction, either because it's `Block` or
|
|
||||||
// /// something in its chain is
|
|
||||||
// pub fn move_object<P: Into<Point>>(&mut self, p: P, dir: Direction) {
|
|
||||||
// let p = p.into();
|
|
||||||
// // Is this even a cell?
|
|
||||||
// if self.in_bounds(p) {
|
|
||||||
// // Is there anything in this cell?
|
|
||||||
// if !self.is_empty(p) {
|
|
||||||
// let (tx, ty) = dir.from_point(p.x, p.y);
|
|
||||||
// // Is the _target_ in bounds?
|
|
||||||
// if self.in_bounds((tx, ty)) {
|
|
||||||
// // Attempt to push the target out of the way
|
|
||||||
// self.push(tx as usize, ty as usize, dir);
|
|
||||||
// // Is the cell now empty?
|
|
||||||
// if self.get(tx as usize, ty as usize).is_none() {
|
|
||||||
// // Then finally, move the thing:
|
|
||||||
// let thing = self.grid[x + y * self.width].take();
|
|
||||||
// self.grid[tx as usize + ty as usize * self.width] = thing;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Returns the [`ObjectId`]s of the **non-solid** objects at `(x, y)`.
|
/// 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
|
/// These are the targets of an `enter` hook when a solid relocates onto the
|
||||||
@@ -353,7 +256,7 @@ impl Board {
|
|||||||
let p = p.into();
|
let p = p.into();
|
||||||
self.sensors
|
self.sensors
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|s| s.x == p.ux() && s.y == p.uy())
|
.filter(|s| s.location == p)
|
||||||
.map(|s| s.scripting.id)
|
.map(|s| s.scripting.id)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -361,7 +264,7 @@ impl Board {
|
|||||||
/// Find and return the portal at the given location
|
/// Find and return the portal at the given location
|
||||||
pub fn portal_at<P: Into<Point>>(&self, p: P) -> Option<&Portal> {
|
pub fn portal_at<P: Into<Point>>(&self, p: P) -> Option<&Portal> {
|
||||||
let p = p.into();
|
let p = p.into();
|
||||||
self.portals.iter().find(|&portal| portal.location() == (p.ux(), p.uy()))
|
self.portals.iter().find(|&portal| portal.location == p)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Editor primitive: stamps `arch` (with visual `glyph`) into the cell at
|
/// Editor primitive: stamps `arch` (with visual `glyph`) into the cell at
|
||||||
@@ -391,7 +294,7 @@ impl Board {
|
|||||||
/// `shift()` fn. Returns a [`ShiftOutcome`] carrying any error [`LogLine`]s for
|
/// `shift()` fn. Returns a [`ShiftOutcome`] carrying any error [`LogLine`]s for
|
||||||
/// the caller to log plus the `(from, to)` relocations it performed (so the
|
/// 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).
|
/// caller can fire `enter` on non-solids each moved solid landed on).
|
||||||
pub fn apply_shift(&mut self, cells: &[(i64, i64)]) -> Result<Vec<((i64, i64), (i64, i64))>, String> {
|
pub fn apply_shift(&mut self, cells: &[Point]) -> Result<Vec<(Point, Point)>, String> {
|
||||||
// Validate all the cells are in bounds, error if not:
|
// Validate all the cells are in bounds, error if not:
|
||||||
if cells.iter().any(|&c| !self.in_bounds(c)) {
|
if cells.iter().any(|&c| !self.in_bounds(c)) {
|
||||||
return Err("Called shift() with a cell out of bounds".to_string())
|
return Err("Called shift() with a cell out of bounds".to_string())
|
||||||
@@ -474,7 +377,7 @@ impl Board {
|
|||||||
|
|
||||||
for (i, tile) in self.grid.iter().enumerate() {
|
for (i, tile) in self.grid.iter().enumerate() {
|
||||||
if let Some(Tile::Object(obj)) = tile && obj.scripting.id == id {
|
if let Some(Tile::Object(obj)) = tile && obj.scripting.id == id {
|
||||||
return Some(Box::new(LocatedObject(obj, (i % self.width, i / self.width))))
|
return Some(Box::new(LocatedObject(obj, (i % self.width, i / self.width).into())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +397,7 @@ impl Board {
|
|||||||
if let Some(Tile::Object(obj)) = tile &&
|
if let Some(Tile::Object(obj)) = tile &&
|
||||||
let Some(n) = obj.scripting.name.as_ref() &&
|
let Some(n) = obj.scripting.name.as_ref() &&
|
||||||
n.as_str() == name {
|
n.as_str() == name {
|
||||||
return Some(Box::new(LocatedObject(obj, (i % self.width, i / self.width))))
|
return Some(Box::new(LocatedObject(obj, (i % self.width, i / self.width).into())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +433,7 @@ impl Board {
|
|||||||
// Add the objects into it
|
// Add the objects into it
|
||||||
for (i, tile) in self.grid.iter().enumerate() {
|
for (i, tile) in self.grid.iter().enumerate() {
|
||||||
if let Some(Tile::Object(obj)) = tile {
|
if let Some(Tile::Object(obj)) = tile {
|
||||||
hookables.push(Box::new(LocatedObject(obj, (i % self.width, i / self.width))))
|
hookables.push(Box::new(LocatedObject(obj, (i % self.width, i / self.width).into())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,10 +456,9 @@ impl Board {
|
|||||||
|
|
||||||
pub fn move_sensor(&mut self, id: ObjectId, dir: Direction) {
|
pub fn move_sensor(&mut self, id: ObjectId, dir: Direction) {
|
||||||
if let Some((sensor_idx, _)) = self.sensors.iter().enumerate().find(|(idx, s)| s.scripting.id == id) {
|
if let Some((sensor_idx, _)) = self.sensors.iter().enumerate().find(|(idx, s)| s.scripting.id == id) {
|
||||||
let new_loc = (self.sensors[sensor_idx].x as i64 + dir.dx(), self.sensors[sensor_idx].y as i64 + dir.dy());
|
let new_loc = self.sensors[sensor_idx].location.in_dir(dir);
|
||||||
if self.in_bounds(new_loc) {
|
if self.in_bounds(new_loc) {
|
||||||
self.sensors[sensor_idx].x = new_loc.0 as usize;
|
self.sensors[sensor_idx].location = new_loc
|
||||||
self.sensors[sensor_idx].y = new_loc.1 as usize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,7 +497,7 @@ pub(crate) mod tests {
|
|||||||
use crate::builtin::Builtin;
|
use crate::builtin::Builtin;
|
||||||
use crate::floor::Floor;
|
use crate::floor::Floor;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::utils::{Direction, ObjectId};
|
use crate::utils::{Direction, ObjectId, Point};
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::object_def::ObjectDef;
|
use crate::object_def::ObjectDef;
|
||||||
@@ -638,17 +540,17 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
/// Stamps a crate cell onto the grid.
|
/// Stamps a crate cell onto the grid.
|
||||||
pub(crate) fn crate_at(board: &mut Board, x: usize, y: usize) {
|
pub(crate) fn crate_at(board: &mut Board, x: usize, y: usize) {
|
||||||
*board.get_mut(x, y) = Some(TileSpec::krate().into_tile(&mut board.next_object_id).unwrap());
|
*board.get_mut((x, y)) = Some(TileSpec::krate().into_tile(&mut board.next_object_id).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stamps a wall cell onto the grid.
|
/// Stamps a wall cell onto the grid.
|
||||||
pub(crate) fn wall_at(board: &mut Board, x: usize, y: usize) {
|
pub(crate) fn wall_at(board: &mut Board, x: usize, y: usize) {
|
||||||
*board.get_mut(x, y) = Some(TileSpec::wall().into_tile(&mut board.next_object_id).unwrap());
|
*board.get_mut((x, y)) = Some(TileSpec::wall().into_tile(&mut board.next_object_id).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stamps a gem cell onto the grid.
|
/// Stamps a gem cell onto the grid.
|
||||||
pub(crate) fn gem_at(board: &mut Board, x: usize, y: usize) {
|
pub(crate) fn gem_at(board: &mut Board, x: usize, y: usize) {
|
||||||
*board.get_mut(x, y) = Some(TileSpec::gem().into_tile(&mut board.next_object_id).unwrap());
|
*board.get_mut((x, y)) = Some(TileSpec::gem().into_tile(&mut board.next_object_id).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stamps the builtin named `kind` (any alias accepted by [`Builtin::from_name`],
|
/// Stamps the builtin named `kind` (any alias accepted by [`Builtin::from_name`],
|
||||||
@@ -666,7 +568,7 @@ pub(crate) mod tests {
|
|||||||
Tile::Object(obj) => obj.scripting.id,
|
Tile::Object(obj) => obj.scripting.id,
|
||||||
Tile::Player => unreachable!("a builtin never resolves to the player"),
|
Tile::Player => unreachable!("a builtin never resolves to the player"),
|
||||||
};
|
};
|
||||||
*board.get_mut(x, y) = Some(tile);
|
*board.get_mut((x, y)) = Some(tile);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,7 +599,7 @@ pub(crate) mod tests {
|
|||||||
Tile::Object(obj) => obj.scripting.id,
|
Tile::Object(obj) => obj.scripting.id,
|
||||||
Tile::Player => unreachable!("an object spec never resolves to the player"),
|
Tile::Player => unreachable!("an object spec never resolves to the player"),
|
||||||
};
|
};
|
||||||
*board.get_mut(x, y) = Some(tile);
|
*board.get_mut((x, y)) = Some(tile);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +628,7 @@ pub(crate) mod tests {
|
|||||||
Tile::Object(obj) => obj.scripting.id,
|
Tile::Object(obj) => obj.scripting.id,
|
||||||
Tile::Player => unreachable!("an object spec never resolves to the player"),
|
Tile::Player => unreachable!("an object spec never resolves to the player"),
|
||||||
};
|
};
|
||||||
*board.get_mut(x, y) = Some(tile);
|
*board.get_mut((x, y)) = Some(tile);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,7 +657,7 @@ pub(crate) mod tests {
|
|||||||
|
|
||||||
/// Stamps a player cell onto the grid.
|
/// Stamps a player cell onto the grid.
|
||||||
pub(crate) fn player_at(board: &mut Board, x: usize, y: usize) {
|
pub(crate) fn player_at(board: &mut Board, x: usize, y: usize) {
|
||||||
*board.get_mut(x, y) = Some(TileSpec::player().into_tile(&mut board.next_object_id).unwrap());
|
*board.get_mut((x, y)) = Some(TileSpec::player().into_tile(&mut board.next_object_id).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lamp_at(board: &mut Board, x: usize, y: usize) {
|
pub(crate) fn lamp_at(board: &mut Board, x: usize, y: usize) {
|
||||||
@@ -778,7 +680,7 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_builtin(board: &Board, x: usize, y: usize, tag: &str) -> bool {
|
pub(crate) fn is_builtin(board: &Board, x: usize, y: usize, tag: &str) -> bool {
|
||||||
if let Some(Tile::Object(obj)) = board.get(x, y) {
|
if let Some(Tile::Object(obj)) = board.get((x, y)) {
|
||||||
obj.scripting.tags.contains(&format!("BUILTIN_{tag}"))
|
obj.scripting.tags.contains(&format!("BUILTIN_{tag}"))
|
||||||
} else { false }
|
} else { false }
|
||||||
}
|
}
|
||||||
@@ -794,20 +696,6 @@ pub(crate) mod tests {
|
|||||||
assert!(!board.in_bounds((0, 2)));
|
assert!(!board.in_bounds((0, 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_push_is_read_only_and_correct() {
|
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
|
||||||
crate_at(&mut board, 1, 0);
|
|
||||||
assert!(board.can_push(1, 0, Direction::East));
|
|
||||||
assert_matches!(board.get(1, 0), Some(Tile::Object(_)));
|
|
||||||
assert_matches!(board.get(2, 0), None);
|
|
||||||
|
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
|
||||||
crate_at(&mut board, 1, 0);
|
|
||||||
wall_at(&mut board, 2, 0);
|
|
||||||
assert!(!board.can_push(1, 0, Direction::East));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_shift_only_checks_the_cell_ahead() {
|
fn can_shift_only_checks_the_cell_ahead() {
|
||||||
// Source must be pushable.
|
// Source must be pushable.
|
||||||
@@ -817,7 +705,7 @@ pub(crate) mod tests {
|
|||||||
// Crate with open space ahead: shiftable.
|
// Crate with open space ahead: shiftable.
|
||||||
crate_at(&mut board, 0, 0);
|
crate_at(&mut board, 0, 0);
|
||||||
assert!(board.can_shift(0, 0, Direction::East));
|
assert!(board.can_shift(0, 0, Direction::East));
|
||||||
assert_matches!(board.get(0, 0), Some(Tile::Object(_)));
|
assert_matches!(board.get((0, 0)), Some(Tile::Object(_)));
|
||||||
|
|
||||||
// Crate with another pushable crate ahead: still shiftable (unlike can_push,
|
// Crate with another pushable crate ahead: still shiftable (unlike can_push,
|
||||||
// which would follow the chain to the wall and fail).
|
// which would follow the chain to the wall and fail).
|
||||||
@@ -861,9 +749,9 @@ pub(crate) mod tests {
|
|||||||
crate_at(&mut board, 1, 0);
|
crate_at(&mut board, 1, 0);
|
||||||
assert!(board.can_push(1, 0, Direction::East));
|
assert!(board.can_push(1, 0, Direction::East));
|
||||||
board.push(1, 0, Direction::East);
|
board.push(1, 0, Direction::East);
|
||||||
assert!(board.get(1, 0).is_none());
|
assert!(board.get((1, 0)).is_none());
|
||||||
assert!(is_builtin(&board, 2, 0,"crate"));
|
assert!(is_builtin(&board, 2, 0,"crate"));
|
||||||
assert_eq!(board.player_pos(), (3, 0));
|
assert_eq!(board.player_pos(), Point { x: 3, y: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -875,7 +763,7 @@ pub(crate) mod tests {
|
|||||||
assert!(!board.can_push(1, 0, Direction::East));
|
assert!(!board.can_push(1, 0, Direction::East));
|
||||||
board.push(1, 0, Direction::East); // no-op
|
board.push(1, 0, Direction::East); // no-op
|
||||||
assert!(is_builtin(&board, 1, 0, "crate"));
|
assert!(is_builtin(&board, 1, 0, "crate"));
|
||||||
assert_eq!(board.player_pos(), (2, 0));
|
assert_eq!(board.player_pos(), Point { x: 2, y: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -901,8 +789,8 @@ pub(crate) mod tests {
|
|||||||
add_floor(&mut board, floor_glyph);
|
add_floor(&mut board, floor_glyph);
|
||||||
wall_at(&mut board, 0, 0);
|
wall_at(&mut board, 0, 0);
|
||||||
// The wall (solid) draws over the floor; the empty cell reveals the floor.
|
// The wall (solid) draws over the floor; the empty cell reveals the floor.
|
||||||
assert_eq!(board.glyph_at(0, 0), Builtin::Wall.default_glyph_for("wall"));
|
assert_eq!(board.glyph_at((0, 0)), Builtin::Wall.default_glyph_for("wall"));
|
||||||
assert_eq!(board.glyph_at(1, 0), floor_glyph);
|
assert_eq!(board.glyph_at((1, 0)), floor_glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -939,7 +827,7 @@ pub(crate) mod tests {
|
|||||||
assert!(is_builtin(&board, 0, 0, "crate")); // wrapped from (4,0)
|
assert!(is_builtin(&board, 0, 0, "crate")); // wrapped from (4,0)
|
||||||
assert!(is_builtin(&board, 1, 0, "crate")); // moved from (0,0)
|
assert!(is_builtin(&board, 1, 0, "crate")); // moved from (0,0)
|
||||||
assert!(is_builtin(&board, 2, 0, "wall")); // blocked, immobile
|
assert!(is_builtin(&board, 2, 0, "wall")); // blocked, immobile
|
||||||
assert!(board.get(3, 0).is_none()); // cleared, crate moved
|
assert!(board.get((3, 0)).is_none()); // cleared, crate moved
|
||||||
assert!(is_builtin(&board, 4, 0, "crate")); // moved from (3,0)
|
assert!(is_builtin(&board, 4, 0, "crate")); // moved from (3,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,8 +843,8 @@ pub(crate) mod tests {
|
|||||||
fn wall_is_opaque_empty_is_not() {
|
fn wall_is_opaque_empty_is_not() {
|
||||||
let mut board = open_board(3, 1, (0, 0));
|
let mut board = open_board(3, 1, (0, 0));
|
||||||
wall_at(&mut board, 1, 0);
|
wall_at(&mut board, 1, 0);
|
||||||
assert!(board.is_opaque_at(1, 0)); // wall blocks sight
|
assert!(board.is_opaque_at((1, 0))); // wall blocks sight
|
||||||
assert!(!board.is_opaque_at(2, 0)); // empty cell is transparent
|
assert!(!board.is_opaque_at((2, 0))); // empty cell is transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -154,9 +154,9 @@ impl BoardSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for Portal { name, x, y, .. } in self.portals.iter() {
|
for Portal { name, location, .. } in self.portals.iter() {
|
||||||
count(&mut portal_names, name);
|
count(&mut portal_names, name);
|
||||||
count(&mut portal_locations, &(x + y * self.width));
|
count(&mut portal_locations, &(location.ux() + location.uy() * self.width));
|
||||||
}
|
}
|
||||||
|
|
||||||
let obj_dupes = obj_names.into_iter().filter_map(|(name, count)| if count > 1 { Some(name) } else { None }).collect::<Vec<_>>();
|
let obj_dupes = obj_names.into_iter().filter_map(|(name, count)| if count > 1 { Some(name) } else { None }).collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::glyph::Glyph;
|
|||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tinyrand::{Probability, Rand, Seeded, StdRand};
|
use tinyrand::{Probability, Rand, Seeded, StdRand};
|
||||||
|
use crate::utils::Point;
|
||||||
|
|
||||||
/// A board's floor: the cosmetic backdrop drawn beneath everything, replacing the
|
/// A board's floor: the cosmetic backdrop drawn beneath everything, replacing the
|
||||||
/// old dedicated floor *layer*. A board has exactly one [`Floor`] (see
|
/// old dedicated floor *layer*. A board has exactly one [`Floor`] (see
|
||||||
@@ -62,11 +63,12 @@ impl Floor {
|
|||||||
/// The floor glyph to draw at `(x, y)`, or `None` for [`Floor::Blank`].
|
/// The floor glyph to draw at `(x, y)`, or `None` for [`Floor::Blank`].
|
||||||
///
|
///
|
||||||
/// `width` is the board width, needed to index a biome's row-major buffer.
|
/// `width` is the board width, needed to index a biome's row-major buffer.
|
||||||
pub(crate) fn glyph_at(&self, x: usize, y: usize, width: usize) -> Option<Glyph> {
|
pub(crate) fn glyph_at<P: Into<Point>>(&self, p: P, width: usize) -> Option<Glyph> {
|
||||||
|
let p = p.into();
|
||||||
match self {
|
match self {
|
||||||
Floor::Blank => None,
|
Floor::Blank => None,
|
||||||
Floor::Fixed(g) => Some(*g),
|
Floor::Fixed(g) => Some(*g),
|
||||||
Floor::Biome { glyphs, .. } => glyphs.get(y * width + x).copied(),
|
Floor::Biome { glyphs, .. } => glyphs.get(p.uy() * width + p.ux()).copied(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-58
@@ -231,7 +231,8 @@ impl GameState {
|
|||||||
for ba in actions {
|
for ba in actions {
|
||||||
match ba.action {
|
match ba.action {
|
||||||
Action::Move(dir) => {
|
Action::Move(dir) => {
|
||||||
step_object(&mut self.board_mut(), ba.source, dir);
|
let loc = self.board().get_hookable(ba.source).map(|o| o.location());
|
||||||
|
loc.map(|loc| self.resolve_move(loc, dir));
|
||||||
}
|
}
|
||||||
Action::SetTile(tile) => {
|
Action::SetTile(tile) => {
|
||||||
if let Some(scr) = self.board_mut().scripting_mut(ba.source) {
|
if let Some(scr) = self.board_mut().scripting_mut(ba.source) {
|
||||||
@@ -296,10 +297,11 @@ impl GameState {
|
|||||||
apply_teleport(&mut self.board_mut(), target, x, y).unwrap_or_else(|e| log_sink.error(e))
|
apply_teleport(&mut self.board_mut(), target, x, y).unwrap_or_else(|e| log_sink.error(e))
|
||||||
}
|
}
|
||||||
Action::Push { x, y, dir } => {
|
Action::Push { x, y, dir } => {
|
||||||
self.resolve_move((x as usize, y as usize), dir);
|
self.resolve_move((x, y), dir);
|
||||||
//apply_push(&mut self.board_mut(), x, y, dir).unwrap_or_else(|e| log_sink.error(e))
|
//apply_push(&mut self.board_mut(), x, y, dir).unwrap_or_else(|e| log_sink.error(e))
|
||||||
}
|
}
|
||||||
Action::Shift(cells) => {
|
Action::Shift(cells) => {
|
||||||
|
let cells = cells.into_iter().map(|c| c.into()).collect::<Vec<Point>>();
|
||||||
apply_shift(&mut self.board_mut(), &cells).unwrap_or_else(|e| log_sink.error(e))
|
apply_shift(&mut self.board_mut(), &cells).unwrap_or_else(|e| log_sink.error(e))
|
||||||
}
|
}
|
||||||
Action::AddGems(n) => {
|
Action::AddGems(n) => {
|
||||||
@@ -354,8 +356,8 @@ impl GameState {
|
|||||||
// Find the named arrival portal on the target board (borrow then release).
|
// Find the named arrival portal on the target board (borrow then release).
|
||||||
let arrival = self.world.boards[target_map]
|
let arrival = self.world.boards[target_map]
|
||||||
.borrow()
|
.borrow()
|
||||||
.named_portal(target_entry).map(Portal::location);
|
.named_portal(target_entry).map(|p| p.location);
|
||||||
let (ax, ay) = match arrival {
|
let new_board_pos = match arrival {
|
||||||
Some(pos) => pos,
|
Some(pos) => pos,
|
||||||
None => {
|
None => {
|
||||||
self.log.push(LogLine::error(format!(
|
self.log.push(LogLine::error(format!(
|
||||||
@@ -371,9 +373,9 @@ impl GameState {
|
|||||||
self.current_board_name = target_map.to_string();
|
self.current_board_name = target_map.to_string();
|
||||||
// Clear any player instance that's already on the new board
|
// Clear any player instance that's already on the new board
|
||||||
let new_board_player_pos = self.board().player_pos();
|
let new_board_player_pos = self.board().player_pos();
|
||||||
self.board_mut().get_mut(new_board_player_pos.0, new_board_player_pos.1).take();
|
self.board_mut().get_mut(new_board_player_pos).take();
|
||||||
// place the player at the arrival portal.
|
// place the player at the arrival portal.
|
||||||
self.board_mut().get_mut(ax, ay).replace(Tile::Player);
|
self.board_mut().get_mut(new_board_pos).replace(Tile::Player);
|
||||||
self.board_mut().clear_all_queues();
|
self.board_mut().clear_all_queues();
|
||||||
// Rebuild the script host for the new board's objects.
|
// Rebuild the script host for the new board's objects.
|
||||||
self.scripts = ScriptHost::new(
|
self.scripts = ScriptHost::new(
|
||||||
@@ -387,24 +389,25 @@ impl GameState {
|
|||||||
self.run_init();
|
self.run_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_move(&mut self, from: (usize, usize), dir: Direction) -> bool {
|
fn resolve_move<P: Into<Point>>(&mut self, from: P, dir: Direction) -> bool {
|
||||||
|
let from = from.into();
|
||||||
// Get the target coords, if they're out of bounds then the move fails.
|
// Get the target coords, if they're out of bounds then the move fails.
|
||||||
let target: Point = dir.from_point(from.0 as i64, from.1 as i64).into();
|
let target: Point = dir.from_point(from).into();
|
||||||
if !self.board().in_bounds((target.x, target.y)) { return false; }
|
if !self.board().in_bounds(target) { return false; }
|
||||||
|
|
||||||
if self.board().is_empty(target) {
|
if self.board().is_empty(target) {
|
||||||
// If it's empty, we can move in there; do so and return true:
|
// If it's empty, we can move in there; do so and return true:
|
||||||
self.board_mut().move_cell(from.into(), target);
|
self.board_mut().move_cell(from, target);
|
||||||
} else if self.board().is_player(target) {
|
} else if self.board().is_player(target) {
|
||||||
// Target cell contains player, who's pushable, so we'll recurse and see what happens:
|
// Target cell contains player, who's pushable, so we'll recurse and see what happens:
|
||||||
if self.resolve_move((target.x as usize, target.y as usize), dir) {
|
if self.resolve_move(target, dir) {
|
||||||
self.board_mut().move_cell(from.into(), target);
|
self.board_mut().move_cell(from, target);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, what enter response does the thing there have? We need to be able to call
|
// Otherwise, what enter response does the thing there have? We need to be able to call
|
||||||
// hooks on objects, so we can't hold a mut reference to the board going into this match
|
// hooks on objects, so we can't hold a mut reference to the board going into this match
|
||||||
let id = {
|
let id = {
|
||||||
if let Some(Tile::Object(b)) = self.board_mut().get(target.x as usize, target.y as usize) && let def = b.as_ref() {
|
if let Some(Tile::Object(b)) = self.board_mut().get(target) && let def = b.as_ref() {
|
||||||
def.scripting.id
|
def.scripting.id
|
||||||
} else {
|
} else {
|
||||||
unreachable!("moving into the player was handled above")
|
unreachable!("moving into the player was handled above")
|
||||||
@@ -412,7 +415,7 @@ impl GameState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Whether the player is what initiated the move directly. Matters for grab / swap
|
// Whether the player is what initiated the move directly. Matters for grab / swap
|
||||||
// let from_player = matches!(self.board().get(from.0, from.1), Some(Tile::Player));
|
// let from_player = matches!(self.board().get((from.0, from.1)), Some(Tile::Player));
|
||||||
|
|
||||||
// Call the target's bump hook and resolve whatever it did
|
// Call the target's bump hook and resolve whatever it did
|
||||||
let actions = self.scripts.run_bump(id, dir.opposite());
|
let actions = self.scripts.run_bump(id, dir.opposite());
|
||||||
@@ -426,7 +429,7 @@ impl GameState {
|
|||||||
// // The player can grab things directly, so let's do that
|
// // The player can grab things directly, so let's do that
|
||||||
// EnterResponse::Grab if from_player => {
|
// EnterResponse::Grab if from_player => {
|
||||||
// let a = self.scripts.run_grab(id);
|
// let a = self.scripts.run_grab(id);
|
||||||
// self.board_mut().get_mut(target.0, target.1).take();
|
// self.board_mut().get_mut((target.0, target.1)).take();
|
||||||
// a
|
// a
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -445,10 +448,10 @@ impl GameState {
|
|||||||
// // player, but still.
|
// // player, but still.
|
||||||
// EnterResponse::Swap if from_player => {
|
// EnterResponse::Swap if from_player => {
|
||||||
// let mut board = self.board_mut();
|
// let mut board = self.board_mut();
|
||||||
// let mover = board.get_mut(from.0, from.1).take();
|
// let mover = board.get_mut((from.0, from.1)).take();
|
||||||
// let swapper = board.get_mut(target.0, target.1).take();
|
// let swapper = board.get_mut((target.0, target.1)).take();
|
||||||
// board.get_mut(target.0, target.1).replace(mover.unwrap());
|
// board.get_mut((target.0, target.1)).replace(mover.unwrap());
|
||||||
// board.get_mut(from.0, from.1).replace(swapper.unwrap());
|
// board.get_mut((from.0, from.1)).replace(swapper.unwrap());
|
||||||
// vec![]
|
// vec![]
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -456,7 +459,7 @@ impl GameState {
|
|||||||
// EnterResponse::Swap => { self.resolve_move(target, dir); vec![] }
|
// EnterResponse::Swap => { self.resolve_move(target, dir); vec![] }
|
||||||
//
|
//
|
||||||
// // Squish just lets the mover overwrite, and always leaves the source cell empty:
|
// // Squish just lets the mover overwrite, and always leaves the source cell empty:
|
||||||
// EnterResponse::Squish => { self.board_mut().get_mut(from.0, from.1).take(); vec![] }
|
// EnterResponse::Squish => { self.board_mut().get_mut((from.0, from.1)).take(); vec![] }
|
||||||
//
|
//
|
||||||
// // Finally, hook, we need to call a hook and let it do its thing:
|
// // Finally, hook, we need to call a hook and let it do its thing:
|
||||||
// EnterResponse::Hook => { self.scripts.run_bump(id, dir.opposite()) }
|
// EnterResponse::Hook => { self.scripts.run_bump(id, dir.opposite()) }
|
||||||
@@ -467,14 +470,14 @@ impl GameState {
|
|||||||
|
|
||||||
// Now, check again if the target cell is empty. If it is, put the mover there. Also
|
// Now, check again if the target cell is empty. If it is, put the mover there. Also
|
||||||
// check that the source cell still contains something! It may have been teleported away.
|
// check that the source cell still contains something! It may have been teleported away.
|
||||||
if !self.board().is_empty(from.into()) && self.board().is_empty(target) {
|
if !self.board().is_empty(from) && self.board().is_empty(target) {
|
||||||
self.board_mut().move_cell(from.into(), target);
|
self.board_mut().move_cell(from, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either way, return whether the source cell is now empty, so calls up the chain
|
// Either way, return whether the source cell is now empty, so calls up the chain
|
||||||
// can move into it (push chains)
|
// can move into it (push chains)
|
||||||
self.board().is_empty(from.into())
|
self.board().is_empty(from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to move the player one cell in `dir`.
|
/// Attempts to move the player one cell in `dir`.
|
||||||
@@ -487,7 +490,7 @@ impl GameState {
|
|||||||
pub fn try_move(&mut self, dir: Direction) {
|
pub fn try_move(&mut self, dir: Direction) {
|
||||||
let (dx, dy): (i64, i64) = dir.into();
|
let (dx, dy): (i64, i64) = dir.into();
|
||||||
let player_loc = self.board().player_pos();
|
let player_loc = self.board().player_pos();
|
||||||
let target = (player_loc.0 as i64 + dx, player_loc.1 as i64 + dy);
|
let target = (player_loc.x + dx, player_loc.y + dy);
|
||||||
if !self.board().in_bounds(target) {
|
if !self.board().in_bounds(target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -499,7 +502,7 @@ impl GameState {
|
|||||||
if new_loc != player_loc {
|
if new_loc != player_loc {
|
||||||
// Portals take priority: if we're on a portal it doesn't matter what else we entered:
|
// Portals take priority: if we're on a portal it doesn't matter what else we entered:
|
||||||
let portal_info = {
|
let portal_info = {
|
||||||
if let Some(Portal { target_board, target_name, ..}) = self.board().portal_at(new_loc.0, new_loc.1) {
|
if let Some(Portal { target_board, target_name, ..}) = self.board().portal_at(new_loc) {
|
||||||
Some((target_board.clone(), target_name.clone()))
|
Some((target_board.clone(), target_name.clone()))
|
||||||
} else { None }
|
} else { None }
|
||||||
};
|
};
|
||||||
@@ -508,7 +511,7 @@ impl GameState {
|
|||||||
self.enter_board(&target_board, &target_name)
|
self.enter_board(&target_board, &target_name)
|
||||||
} else {
|
} else {
|
||||||
// We're still on the board, so see if we stepped on any sensors:
|
// We're still on the board, so see if we stepped on any sensors:
|
||||||
let sensor_ids = self.board().sensor_ids_at(new_loc.0, new_loc.1);
|
let sensor_ids = self.board().sensor_ids_at(new_loc);
|
||||||
for id in sensor_ids {
|
for id in sensor_ids {
|
||||||
let actions = self.scripts.run_enter(id, dir.opposite());
|
let actions = self.scripts.run_enter(id, dir.opposite());
|
||||||
self.apply_actions(actions)
|
self.apply_actions(actions)
|
||||||
@@ -520,37 +523,11 @@ impl GameState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves object `id` one cell in `dir` on `board`, reporting the `bump`/`enter`
|
|
||||||
/// reactions for the caller to resolve after the board borrow drops.
|
|
||||||
///
|
|
||||||
/// The move itself proceeds only if the target is in bounds and either passable or a
|
|
||||||
/// pushable solid the object can shove out of the way (see [`Board::can_push`]). The
|
|
||||||
/// bump is recorded for the solid object the move presses into — directly, or at the
|
|
||||||
/// end of a chain of crates being shoved (see [`Board::bump_target`]) — whether it
|
|
||||||
/// gets pushed aside or blocks the move. Walls and crates carry no script, so only
|
|
||||||
/// solid objects yield a bump. An `enter` is recorded for every non-solid object a
|
|
||||||
/// solid lands on: the mover's destination cell (only when the mover is itself solid)
|
|
||||||
/// and each cell a pushed crate moved into (crates are always solid entrants).
|
|
||||||
fn step_object(board: &mut Board, id: ObjectId, dir: Direction) {
|
|
||||||
// TODO when an object pushes the player, it should still trigger actions on
|
|
||||||
// what the player is pushed into. But, for right now, just call board::push
|
|
||||||
let (loc, solid) = if let Some(obj) = board.get_hookable(id) {
|
|
||||||
(obj.location(), obj.solid())
|
|
||||||
} else { return };
|
|
||||||
|
|
||||||
if solid {
|
|
||||||
// This is a real object on the board, try and push it
|
|
||||||
board.move_object((loc.0, loc.1), dir);
|
|
||||||
} else {
|
|
||||||
// This is a sensor, we can just teleport it
|
|
||||||
board.move_sensor(id, dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::GameState;
|
use super::GameState;
|
||||||
use crate::Direction;
|
use crate::Direction;
|
||||||
|
use crate::utils::Point;
|
||||||
use crate::board::tests::{
|
use crate::board::tests::{
|
||||||
builtin_at, crate_at, gem_at, is_builtin, open_board, sensor_at, wall_at,
|
builtin_at, crate_at, gem_at, is_builtin, open_board, sensor_at, wall_at,
|
||||||
};
|
};
|
||||||
@@ -572,7 +549,7 @@ mod tests {
|
|||||||
// The gem was grabbed: gem count up, gem object gone, player on its cell.
|
// The gem was grabbed: gem count up, gem object gone, player on its cell.
|
||||||
assert_eq!(game.player.borrow().gems, 1);
|
assert_eq!(game.player.borrow().gems, 1);
|
||||||
assert!(game.board().all_ids().is_empty());
|
assert!(game.board().all_ids().is_empty());
|
||||||
assert_eq!(game.board().player_pos(), (1, 0));
|
assert_eq!(game.board().player_pos(), Point { x: 1, y: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -600,7 +577,7 @@ mod tests {
|
|||||||
let mut game = game_with_object_script(5, "fn tick(me, dt) { shift([[2, 0], [3, 0]]); }");
|
let mut game = game_with_object_script(5, "fn tick(me, dt) { shift([[2, 0], [3, 0]]); }");
|
||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert!(b.get(2, 0).is_none());
|
assert!(b.get((2, 0)).is_none());
|
||||||
assert!(is_builtin(&b, 3, 0, "crate"));
|
assert!(is_builtin(&b, 3, 0, "crate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,7 +686,7 @@ mod tests {
|
|||||||
assert!(is_builtin(&b, 2, 0, "crate")); // NE: filled by N
|
assert!(is_builtin(&b, 2, 0, "crate")); // NE: filled by N
|
||||||
assert!(is_builtin(&b, 1, 0, "crate")); // N: filled by NW
|
assert!(is_builtin(&b, 1, 0, "crate")); // N: filled by NW
|
||||||
assert!(is_builtin(&b, 0, 0, "crate")); // NW: filled by W
|
assert!(is_builtin(&b, 0, 0, "crate")); // NW: filled by W
|
||||||
assert!(b.get(0, 1).is_none()); // W: vacated (hole moved here)
|
assert!(b.get((0, 1)).is_none()); // W: vacated (hole moved here)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -720,8 +697,8 @@ mod tests {
|
|||||||
game.tick(Duration::from_millis(16));
|
game.tick(Duration::from_millis(16));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert!(is_builtin(&b, 0, 0, "crate")); // NW: crate rotated counter-clockwise
|
assert!(is_builtin(&b, 0, 0, "crate")); // NW: crate rotated counter-clockwise
|
||||||
assert!(b.get(2, 0).is_none()); // NE: untouched
|
assert!(b.get((2, 0)).is_none()); // NE: untouched
|
||||||
assert!(b.get(1, 0).is_none()); // N: vacated
|
assert!(b.get((1, 0)).is_none()); // N: vacated
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
|
use crate::utils::Point;
|
||||||
|
|
||||||
/// A portal that teleports the player to a named entry point on another board.
|
/// A portal that teleports the player to a named entry point on another board.
|
||||||
///
|
///
|
||||||
@@ -16,8 +17,8 @@ use crate::glyph::Glyph;
|
|||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub struct Portal {
|
pub struct Portal {
|
||||||
pub x: usize,
|
#[serde(flatten)]
|
||||||
pub y: usize,
|
pub location: Point,
|
||||||
/// Board-unique name for this portal, also used as `target_entry` by portals
|
/// Board-unique name for this portal, also used as `target_entry` by portals
|
||||||
/// on other boards that want to arrive here.
|
/// on other boards that want to arrive here.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -28,9 +29,3 @@ pub struct Portal {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub glyph: Option<Glyph>
|
pub glyph: Option<Glyph>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Portal {
|
|
||||||
pub fn location(&self) -> (usize, usize) {
|
|
||||||
(self.x, self.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::board::tests::{crate_at, is_builtin, object_at, open_board, wall_at};
|
|||||||
use crate::game::GameState;
|
use crate::game::GameState;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::tile::EnterResponse;
|
use crate::tile::EnterResponse;
|
||||||
use crate::utils::ObjectId;
|
use crate::utils::{ObjectId, Point};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// The cell object `id` currently occupies.
|
/// The cell object `id` currently occupies.
|
||||||
@@ -85,7 +85,7 @@ fn object_pushes_crate_on_init() {
|
|||||||
assert_eq!(loc(&game, id), (2, 0));
|
assert_eq!(loc(&game, id), (2, 0));
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert!(is_builtin(&b, 3, 0, "crate")); // crate shoved one east
|
assert!(is_builtin(&b, 3, 0, "crate")); // crate shoved one east
|
||||||
assert!(b.get(1, 0).is_none()); // the object's old cell is vacated
|
assert!(b.get((1, 0)).is_none()); // the object's old cell is vacated
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -93,7 +93,7 @@ fn object_push_into_player() {
|
|||||||
// A scripted object moving into the player pushes the player when there's room.
|
// A scripted object moving into the player pushes the player when there's room.
|
||||||
let (game, id) = game_with_mover(5, 1, (2, 0), (1, 0), "fn init(me) { move(East); }");
|
let (game, id) = game_with_mover(5, 1, (2, 0), (1, 0), "fn init(me) { move(East); }");
|
||||||
assert_eq!(loc(&game, id), (2, 0));
|
assert_eq!(loc(&game, id), (2, 0));
|
||||||
assert_eq!(game.board().player_pos(), (3, 0));
|
assert_eq!(game.board().player_pos(), Point { x: 3, y: 0 });
|
||||||
|
|
||||||
// With a wall behind the player, the object is blocked and nothing moves.
|
// With a wall behind the player, the object is blocked and nothing moves.
|
||||||
let mut board = open_board(4, 1, (2, 0));
|
let mut board = open_board(4, 1, (2, 0));
|
||||||
@@ -104,7 +104,7 @@ fn object_push_into_player() {
|
|||||||
game.run_init();
|
game.run_init();
|
||||||
|
|
||||||
assert_eq!(loc(&game, id), (1, 0));
|
assert_eq!(loc(&game, id), (1, 0));
|
||||||
assert_eq!(game.board().player_pos(), (2, 0));
|
assert_eq!(game.board().player_pos(), Point { x: 2, y: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::board::Board;
|
|||||||
use crate::board::tests::open_board;
|
use crate::board::tests::open_board;
|
||||||
use crate::game::GameState;
|
use crate::game::GameState;
|
||||||
use crate::portal::Portal;
|
use crate::portal::Portal;
|
||||||
use crate::utils::Direction;
|
use crate::utils::{Direction, Point};
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -75,7 +75,7 @@ fn enter_board_places_player_at_arrival_portal() {
|
|||||||
let mut game = GameState::from_world(two_board_world());
|
let mut game = GameState::from_world(two_board_world());
|
||||||
game.enter_board("b2", "from_b1");
|
game.enter_board("b2", "from_b1");
|
||||||
// Arrival portal "from_b1" is at (1, 1) on b2.
|
// Arrival portal "from_b1" is at (1, 1) on b2.
|
||||||
assert_eq!(game.board().player_pos(), (1, 1));
|
assert_eq!(game.board().player_pos(), Point { x: 1, y: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -90,7 +90,7 @@ fn entering_a_board_leaves_exactly_one_player_on_it() {
|
|||||||
let b = game.board();
|
let b = game.board();
|
||||||
let players = (0..b.height)
|
let players = (0..b.height)
|
||||||
.flat_map(|y| (0..b.width).map(move |x| (x, y)))
|
.flat_map(|y| (0..b.width).map(move |x| (x, y)))
|
||||||
.filter(|&(x, y)| b.get(x, y).as_ref().is_some_and(|t| t.player()))
|
.filter(|&(x, y)| b.get((x, y)).as_ref().is_some_and(|t| t.player()))
|
||||||
.count();
|
.count();
|
||||||
assert_eq!(players, 1, "arriving on a board must not duplicate the player");
|
assert_eq!(players, 1, "arriving on a board must not duplicate the player");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ palette = { " " = { kind = "empty" }, "@" = { kind = "player" }, "O" = { kind =
|
|||||||
assert_eq!(board.objects.len(), 1);
|
assert_eq!(board.objects.len(), 1);
|
||||||
assert_eq!((board.objects[&1].x, board.objects[&1].y), (2, 0));
|
assert_eq!((board.objects[&1].x, board.objects[&1].y), (2, 0));
|
||||||
// An unlisted sparse cell is a transparent empty.
|
// An unlisted sparse cell is a transparent empty.
|
||||||
assert_eq!(board.get(1, 0).1, Archetype::Empty);
|
assert_eq!(board.get((1, 0)).1, Archetype::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -96,5 +96,5 @@ palette = { " " = { kind = "empty" } }
|
|||||||
!board.is_valid(),
|
!board.is_valid(),
|
||||||
"a non-single-char fill is a nonfatal error"
|
"a non-single-char fill is a nonfatal error"
|
||||||
);
|
);
|
||||||
assert_eq!(board.get(1, 0).1, Archetype::Empty);
|
assert_eq!(board.get((1, 0)).1, Archetype::Empty);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ fn unknown_kind_produces_error_block() {
|
|||||||
);
|
);
|
||||||
let board = load_board(&toml);
|
let board = load_board(&toml);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*board.get(0, 0),
|
*board.get((0, 0)),
|
||||||
(Archetype::ErrorBlock.default_glyph(), Archetype::ErrorBlock)
|
(Archetype::ErrorBlock.default_glyph(), Archetype::ErrorBlock)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ fn palette_placement_puts_object_on_empty_floor() {
|
|||||||
let board = load_board(&map_3x1_object("G.@", "G", ""));
|
let board = load_board(&map_3x1_object("G.@", "G", ""));
|
||||||
assert_eq!(board.objects.len(), 1);
|
assert_eq!(board.objects.len(), 1);
|
||||||
assert_eq!((board.objects[&1].x, board.objects[&1].y), (0, 0));
|
assert_eq!((board.objects[&1].x, board.objects[&1].y), (0, 0));
|
||||||
assert_eq!(board.get(0, 0).1, Archetype::Empty);
|
assert_eq!(board.get((0, 0)).1, Archetype::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const WALL: (&str, &str) = (
|
|||||||
fn player_char_places_on_empty_floor() {
|
fn player_char_places_on_empty_floor() {
|
||||||
let b = load_board(&map(3, 1, &grid(".@.", &[EMPTY, PLAYER])));
|
let b = load_board(&map(3, 1, &grid(".@.", &[EMPTY, PLAYER])));
|
||||||
assert_eq!((b.player.x, b.player.y), (1, 0));
|
assert_eq!((b.player.x, b.player.y), (1, 0));
|
||||||
assert_eq!(b.get(1, 0).1, Archetype::Empty);
|
assert_eq!(b.get((1, 0)).1, Archetype::Empty);
|
||||||
assert!(b.is_valid());
|
assert!(b.is_valid());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ fn player_fallback_to_origin_clears_solid_terrain() {
|
|||||||
// player wins its cell: the wall is cleared. The fallback is still reported.
|
// player wins its cell: the wall is cleared. The fallback is still reported.
|
||||||
let b = load_board(&map(3, 1, &grid("#..", &[WALL, EMPTY])));
|
let b = load_board(&map(3, 1, &grid("#..", &[WALL, EMPTY])));
|
||||||
assert_eq!((b.player.x, b.player.y), (0, 0));
|
assert_eq!((b.player.x, b.player.y), (0, 0));
|
||||||
assert_eq!(b.get(0, 0).1, Archetype::Empty);
|
assert_eq!(b.get((0, 0)).1, Archetype::Empty);
|
||||||
assert!(!b.is_valid());
|
assert!(!b.is_valid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ fn pusher_advances_and_shoves_a_crate() {
|
|||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert!(b.objects[&pid].x > 0, "pusher advanced east");
|
assert!(b.objects[&pid].x > 0, "pusher advanced east");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
b.get(1, 0).1,
|
b.get((1, 0)).1,
|
||||||
Archetype::Empty,
|
Archetype::Empty,
|
||||||
"crate left its start cell"
|
"crate left its start cell"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ fn fixed_floor_attribute_round_trips_through_toml() {
|
|||||||
};
|
};
|
||||||
let board = load_board(toml);
|
let board = load_board(toml);
|
||||||
// An empty grid cell reveals the fixed floor.
|
// An empty grid cell reveals the fixed floor.
|
||||||
assert_eq!(board.glyph_at(1, 0), fixed);
|
assert_eq!(board.glyph_at((1, 0)), fixed);
|
||||||
|
|
||||||
let board2 = round_trip(toml);
|
let board2 = round_trip(toml);
|
||||||
assert_eq!(board2.glyph_at(1, 0), fixed);
|
assert_eq!(board2.glyph_at((1, 0)), fixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -153,5 +153,5 @@ fn biome_floor_round_trips_generator_name() {
|
|||||||
);
|
);
|
||||||
// Reloaded floor glyphs match (deterministic seed).
|
// Reloaded floor glyphs match (deterministic seed).
|
||||||
let board2 = load_board(&toml_out);
|
let board2 = load_board(&toml_out);
|
||||||
assert_eq!(board2.glyph_at(1, 0), board.glyph_at(1, 0));
|
assert_eq!(board2.glyph_at((1, 0)), board.glyph_at((1, 0)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::board::tests::{add_floor, builtin_at, crate_at, is_builtin, open_boar
|
|||||||
use crate::game::GameState;
|
use crate::game::GameState;
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::tile::EnterResponse;
|
use crate::tile::EnterResponse;
|
||||||
use crate::utils::{Direction, Pushable};
|
use crate::utils::{Direction, Point, Pushable};
|
||||||
use color::Rgba8;
|
use color::Rgba8;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -30,10 +30,10 @@ fn pushing_a_crate_reveals_the_floor_underneath() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (1, 0));
|
assert_eq!(b.player_pos(), Point { x: 1, y: 0 });
|
||||||
assert!(is_builtin(&b, 2, 0, "crate"));
|
assert!(is_builtin(&b, 2, 0, "crate"));
|
||||||
assert!(b.get(0, 0).is_none()); // vacated
|
assert!(b.get((0, 0)).is_none()); // vacated
|
||||||
assert_eq!(b.glyph_at(0, 0), floor_glyph);
|
assert_eq!(b.glyph_at((0, 0)), floor_glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -44,9 +44,9 @@ fn player_pushes_single_crate() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (1, 0));
|
assert_eq!(b.player_pos(), Point { x: 1, y: 0 });
|
||||||
assert!(is_builtin(&b, 2, 0, "crate"));
|
assert!(is_builtin(&b, 2, 0, "crate"));
|
||||||
assert!(b.get(0, 0).is_none());
|
assert!(b.get((0, 0)).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -58,7 +58,7 @@ fn push_blocked_by_wall_moves_nothing() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (0, 0));
|
assert_eq!(b.player_pos(), Point { x: 0, y: 0 });
|
||||||
assert!(is_builtin(&b, 1, 0, "crate"));
|
assert!(is_builtin(&b, 1, 0, "crate"));
|
||||||
assert!(is_builtin(&b, 2, 0, "wall"));
|
assert!(is_builtin(&b, 2, 0, "wall"));
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ fn push_blocked_by_edge_moves_nothing() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (0, 0));
|
assert_eq!(b.player_pos(), Point { x: 0, y: 0 });
|
||||||
assert!(is_builtin(&b, 1, 0, "crate"));
|
assert!(is_builtin(&b, 1, 0, "crate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ fn cascade_pushes_two_crates() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (1, 0));
|
assert_eq!(b.player_pos(), Point { x: 1, y: 0 });
|
||||||
assert!(is_builtin(&b, 2, 0, "crate"));
|
assert!(is_builtin(&b, 2, 0, "crate"));
|
||||||
assert!(is_builtin(&b, 3, 0, "crate"));
|
assert!(is_builtin(&b, 3, 0, "crate"));
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ fn cascade_blocked_by_wall_moves_nothing() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (0, 0));
|
assert_eq!(b.player_pos(), Point { x: 0, y: 0 });
|
||||||
assert!(is_builtin(&b, 1, 0, "crate"));
|
assert!(is_builtin(&b, 1, 0, "crate"));
|
||||||
assert!(is_builtin(&b, 2, 0, "crate"));
|
assert!(is_builtin(&b, 2, 0, "crate"));
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ fn player_pushes_pushable_solid_object() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (1, 0));
|
assert_eq!(b.player_pos(), Point { x: 1, y: 0 });
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
b.get_hookable(id).expect("object still on the board").location(),
|
b.get_hookable(id).expect("object still on the board").location(),
|
||||||
(2, 0)
|
(2, 0)
|
||||||
@@ -132,7 +132,7 @@ fn hcrate_pushes_east_but_not_north() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
{
|
{
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (1, 0));
|
assert_eq!(b.player_pos(), Point { x: 1, y: 0 });
|
||||||
assert!(is_builtin(&b, 2, 0, "hcrate"));
|
assert!(is_builtin(&b, 2, 0, "hcrate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ fn hcrate_pushes_east_but_not_north() {
|
|||||||
game.try_move(Direction::North);
|
game.try_move(Direction::North);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (0, 3));
|
assert_eq!(b.player_pos(), Point { x: 0, y: 3 });
|
||||||
assert!(is_builtin(&b, 0, 2, "hcrate"));
|
assert!(is_builtin(&b, 0, 2, "hcrate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ fn vcrate_pushes_north_but_not_east() {
|
|||||||
game.try_move(Direction::North);
|
game.try_move(Direction::North);
|
||||||
{
|
{
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (0, 2));
|
assert_eq!(b.player_pos(), Point { x: 0, y: 2 });
|
||||||
assert!(is_builtin(&b, 0, 1, "vcrate"));
|
assert!(is_builtin(&b, 0, 1, "vcrate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +168,6 @@ fn vcrate_pushes_north_but_not_east() {
|
|||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
|
|
||||||
let b = game.board();
|
let b = game.board();
|
||||||
assert_eq!(b.player_pos(), (0, 0));
|
assert_eq!(b.player_pos(), Point { x: 0, y: 0 });
|
||||||
assert!(is_builtin(&b, 1, 0, "vcrate"));
|
assert!(is_builtin(&b, 1, 0, "vcrate"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::board::Board;
|
|||||||
use crate::board::tests::{object_at, open_board, plain_object_at, sensor_at};
|
use crate::board::tests::{object_at, open_board, plain_object_at, sensor_at};
|
||||||
use crate::game::{GameState, ScrollLine};
|
use crate::game::{GameState, ScrollLine};
|
||||||
use crate::tile::EnterResponse;
|
use crate::tile::EnterResponse;
|
||||||
use crate::utils::{Direction, ObjectId};
|
use crate::utils::{Direction, ObjectId, Point};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ fn player_bump_reports_the_direction_it_came_from() {
|
|||||||
);
|
);
|
||||||
game.run_init();
|
game.run_init();
|
||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
assert_eq!(game.board().player_pos(), (0, 0)); // blocked
|
assert_eq!(game.board().player_pos(), Point { x: 0, y: 0 }); // blocked
|
||||||
// log() bypasses the queue, so no tick is needed to flush it.
|
// log() bypasses the queue, so no tick is needed to flush it.
|
||||||
assert!(log_texts(&game).iter().any(|t| t == "bumped from West"));
|
assert!(log_texts(&game).iter().any(|t| t == "bumped from West"));
|
||||||
}
|
}
|
||||||
@@ -538,6 +538,6 @@ fn player_walking_onto_a_sensor_fires_enter_from_the_travel_side() {
|
|||||||
game.run_init();
|
game.run_init();
|
||||||
game.try_move(Direction::East);
|
game.try_move(Direction::East);
|
||||||
// A sensor is off-grid, so it never blocks: the player moves onto the cell.
|
// A sensor is off-grid, so it never blocks: the player moves onto the cell.
|
||||||
assert_eq!(game.board().player_pos(), (1, 0));
|
assert_eq!(game.board().player_pos(), Point { x: 1, y: 0 });
|
||||||
assert!(log_texts(&game).iter().any(|t| t == "entered from West"));
|
assert!(log_texts(&game).iter().any(|t| t == "entered from West"));
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-14
@@ -6,7 +6,7 @@ use crate::builtin::BUILTIN_SOURCES;
|
|||||||
use crate::floor::{Floor, FloorBiome};
|
use crate::floor::{Floor, FloorBiome};
|
||||||
use crate::glyph::Glyph;
|
use crate::glyph::Glyph;
|
||||||
use crate::object_def::ObjectDef;
|
use crate::object_def::ObjectDef;
|
||||||
use crate::utils::{ObjectId, Pushable};
|
use crate::utils::{ObjectId, Point, Pushable};
|
||||||
use crate::utils::Pushable::No;
|
use crate::utils::Pushable::No;
|
||||||
|
|
||||||
/// The various ways that a tile might respond to another tile trying to move on top of it
|
/// The various ways that a tile might respond to another tile trying to move on top of it
|
||||||
@@ -198,10 +198,8 @@ pub struct ScriptAttributes {
|
|||||||
/// affect board movement, as it doesn't live in the grid.
|
/// affect board movement, as it doesn't live in the grid.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Sensor {
|
pub struct Sensor {
|
||||||
/// Where it is on the board: x coord
|
/// Where it is on the board:
|
||||||
pub x: usize,
|
pub location: Point,
|
||||||
/// Where it is on the board: y coord
|
|
||||||
pub y: usize,
|
|
||||||
/// Whether this is drawn above or below the grid
|
/// Whether this is drawn above or below the grid
|
||||||
pub draw_layer: DrawLayer,
|
pub draw_layer: DrawLayer,
|
||||||
/// Sensors have scripting ability
|
/// Sensors have scripting ability
|
||||||
@@ -211,8 +209,8 @@ pub struct Sensor {
|
|||||||
/// The serialized representation of a Sensor. Can be turned into a Sensor, or vice versa
|
/// The serialized representation of a Sensor. Can be turned into a Sensor, or vice versa
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct SensorSpec {
|
pub struct SensorSpec {
|
||||||
pub x: usize,
|
#[serde(flatten)]
|
||||||
pub y: usize,
|
pub location: Point,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub script: Option<String>,
|
pub script: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Glyph::is_transparent")]
|
#[serde(default, skip_serializing_if = "Glyph::is_transparent")]
|
||||||
@@ -232,8 +230,7 @@ impl SensorSpec {
|
|||||||
let id = *next_object_id;
|
let id = *next_object_id;
|
||||||
*next_object_id += 1;
|
*next_object_id += 1;
|
||||||
Sensor {
|
Sensor {
|
||||||
x: self.x,
|
location: self.location,
|
||||||
y: self.y,
|
|
||||||
draw_layer: self.draw_layer,
|
draw_layer: self.draw_layer,
|
||||||
scripting: ScriptAttributes {
|
scripting: ScriptAttributes {
|
||||||
id,
|
id,
|
||||||
@@ -250,7 +247,7 @@ impl SensorSpec {
|
|||||||
|
|
||||||
pub trait Hookable {
|
pub trait Hookable {
|
||||||
fn scriptable(&self) -> &ScriptAttributes;
|
fn scriptable(&self) -> &ScriptAttributes;
|
||||||
fn location(&self) -> (usize, usize);
|
fn location(&self) -> Point;
|
||||||
fn id(&self) -> ObjectId {
|
fn id(&self) -> ObjectId {
|
||||||
self.scriptable().id
|
self.scriptable().id
|
||||||
}
|
}
|
||||||
@@ -282,21 +279,21 @@ impl Hookable for &Sensor {
|
|||||||
fn scriptable(&self) -> &ScriptAttributes {
|
fn scriptable(&self) -> &ScriptAttributes {
|
||||||
&self.scripting
|
&self.scripting
|
||||||
}
|
}
|
||||||
fn location(&self) -> (usize, usize) {
|
fn location(&self) -> Point {
|
||||||
(self.x, self.y)
|
self.location
|
||||||
}
|
}
|
||||||
fn solid(&self) -> bool {
|
fn solid(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LocatedObject<'a>(pub &'a ObjectDef, pub (usize, usize));
|
pub struct LocatedObject<'a>(pub &'a ObjectDef, pub Point);
|
||||||
|
|
||||||
impl Hookable for LocatedObject<'_> {
|
impl Hookable for LocatedObject<'_> {
|
||||||
fn scriptable(&self) -> &ScriptAttributes {
|
fn scriptable(&self) -> &ScriptAttributes {
|
||||||
&self.0.scripting
|
&self.0.scripting
|
||||||
}
|
}
|
||||||
fn location(&self) -> (usize, usize) {
|
fn location(&self) -> Point {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
fn solid(&self) -> bool {
|
fn solid(&self) -> bool {
|
||||||
|
|||||||
+16
-5
@@ -3,7 +3,6 @@ use std::fmt::Display;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use rhai::{Dynamic, Engine};
|
use rhai::{Dynamic, Engine};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::keys::Keyring;
|
|
||||||
use crate::log::LogLine;
|
use crate::log::LogLine;
|
||||||
use crate::script::Registerable;
|
use crate::script::Registerable;
|
||||||
|
|
||||||
@@ -122,8 +121,9 @@ impl Direction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Translate the given point in this direction
|
/// Translate the given point in this direction
|
||||||
pub fn from_point(self, x: i64, y: i64) -> (i64, i64) {
|
pub fn from_point<P: Into<Point>>(self, p: P) -> Point {
|
||||||
(x + self.dx(), y + self.dy())
|
let p = p.into();
|
||||||
|
(p.x + self.dx(), p.y + self.dy()).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,11 +131,11 @@ impl Registerable for Direction {
|
|||||||
fn register(engine: &mut Engine, _log_sink: LogSink) {
|
fn register(engine: &mut Engine, _log_sink: LogSink) {
|
||||||
engine.register_type_with_name::<Direction>("Direction")
|
engine.register_type_with_name::<Direction>("Direction")
|
||||||
.register_get("opposite", |dir: &mut Direction| dir.opposite())
|
.register_get("opposite", |dir: &mut Direction| dir.opposite())
|
||||||
.register_fn("from_point", |dir: &mut Direction, x: i64, y: i64| Point::from(dir.from_point(x, y)));
|
.register_fn("from_point", |dir: &mut Direction, x: i64, y: i64| Point::from(dir.from_point((x, y))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct Point {
|
pub struct Point {
|
||||||
pub x: i64,
|
pub x: i64,
|
||||||
pub y: i64,
|
pub y: i64,
|
||||||
@@ -149,10 +149,18 @@ impl From<(usize, usize)> for Point {
|
|||||||
fn from((x, y): (usize, usize)) -> Self { Self { x: x as i64, y: y as i64 } }
|
fn from((x, y): (usize, usize)) -> Self { Self { x: x as i64, y: y as i64 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(i32, i32)> for Point {
|
||||||
|
fn from((x, y): (i32, i32)) -> Self { Self { x: x as i64, y: y as i64 } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Into<(i64, i64)> for Point {
|
impl Into<(i64, i64)> for Point {
|
||||||
fn into(self) -> (i64, i64) { (self.x, self.y) }
|
fn into(self) -> (i64, i64) { (self.x, self.y) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<(usize, usize)> for Point {
|
||||||
|
fn into(self) -> (usize, usize) { (self.x as usize, self.y as usize) }
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Point {
|
impl Display for Point {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "({}, {})", self.x, self.y)
|
write!(f, "({}, {})", self.x, self.y)
|
||||||
@@ -162,6 +170,9 @@ impl Display for Point {
|
|||||||
impl Point {
|
impl Point {
|
||||||
pub fn ux(&self) -> usize { self.x as usize }
|
pub fn ux(&self) -> usize { self.x as usize }
|
||||||
pub fn uy(&self) -> usize { self.y as usize }
|
pub fn uy(&self) -> usize { self.y as usize }
|
||||||
|
pub fn in_dir(self, dir: Direction) -> Self {
|
||||||
|
dir.from_point(self).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registerable for Point {
|
impl Registerable for Point {
|
||||||
|
|||||||
@@ -157,11 +157,11 @@ mod tests {
|
|||||||
|
|
||||||
let copy = world.deep_clone();
|
let copy = world.deep_clone();
|
||||||
// Stamp a wall into the copy's board.
|
// Stamp a wall into the copy's board.
|
||||||
copy.boards["start"].borrow_mut().place(1, 0, Some(TileSpec::wall())).expect("could not place wall");
|
copy.boards["start"].borrow_mut().place((1, 0), Some(TileSpec::wall())).expect("could not place wall");
|
||||||
|
|
||||||
// The copy changed; the original is still empty at that cell.
|
// The copy changed; the original is still empty at that cell.
|
||||||
assert_matches!(copy.boards["start"].borrow().get(1, 0), Some(Tile::Object(_)));
|
assert_matches!(copy.boards["start"].borrow().get((1, 0)), Some(Tile::Object(_)));
|
||||||
assert!(world.boards["start"].borrow().get(1, 0).is_none());
|
assert!(world.boards["start"].borrow().get((1, 0)).is_none());
|
||||||
// And they are genuinely different allocations.
|
// And they are genuinely different allocations.
|
||||||
assert!(!Rc::ptr_eq(&world.boards["start"], ©.boards["start"]));
|
assert!(!Rc::ptr_eq(&world.boards["start"], ©.boards["start"]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ impl EditorState {
|
|||||||
/// cursor, applying the placement rules (see [`Board::place_archetype`]).
|
/// cursor, applying the placement rules (see [`Board::place_archetype`]).
|
||||||
pub(crate) fn place_current(&mut self) {
|
pub(crate) fn place_current(&mut self) {
|
||||||
let (x, y) = (self.cursor.0 as usize, self.cursor.1 as usize);
|
let (x, y) = (self.cursor.0 as usize, self.cursor.1 as usize);
|
||||||
let res = self.board_mut().place(x, y, Some(TileSpec::Builtin { kind: self.current_archetype.to_string(), glyph: Some(self.current_glyph) }));
|
let res = self.board_mut().place((x, y), Some(TileSpec::Builtin { kind: self.current_archetype.to_string(), glyph: Some(self.current_glyph) }));
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
self.log.push(LogLine::error(e))
|
self.log.push(LogLine::error(e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ pub struct BoardWidget<'a> {
|
|||||||
impl<'a> BoardWidget<'a> {
|
impl<'a> BoardWidget<'a> {
|
||||||
/// Creates a widget that renders `board`, scrolling to follow the player.
|
/// Creates a widget that renders `board`, scrolling to follow the player.
|
||||||
pub fn new(board: &'a Board) -> Self {
|
pub fn new(board: &'a Board) -> Self {
|
||||||
let (x, y) = board.player_pos();
|
let p = board.player_pos();
|
||||||
let focus = (x as i32, y as i32);
|
let focus = (p.x as i32, p.y as i32);
|
||||||
Self { board, focus, fov: None }
|
Self { board, focus, fov: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ impl Widget for BoardWidget<'_> {
|
|||||||
let visible = self.fov.is_none_or(|l| l.is_visible(bx, by));
|
let visible = self.fov.is_none_or(|l| l.is_visible(bx, by));
|
||||||
if let Some(cell) = buf.cell_mut((sx, sy)) {
|
if let Some(cell) = buf.cell_mut((sx, sy)) {
|
||||||
if visible {
|
if visible {
|
||||||
let glyph = board.glyph_at(bx, by);
|
let glyph = board.glyph_at((bx, by));
|
||||||
// Modulate by lighting when the board is dark; pass through otherwise.
|
// Modulate by lighting when the board is dark; pass through otherwise.
|
||||||
let (fg, bg) = match self.fov {
|
let (fg, bg) = match self.fov {
|
||||||
Some(l) => (l.tint(bx, by, glyph.fg), l.tint(bx, by, glyph.bg)),
|
Some(l) => (l.tint(bx, by, glyph.fg), l.tint(bx, by, glyph.bg)),
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ impl Widget for SpeechBubblesWidget<'_> {
|
|||||||
let (px, py, player_vis) = board_screen_pos(
|
let (px, py, player_vis) = board_screen_pos(
|
||||||
area,
|
area,
|
||||||
self.board,
|
self.board,
|
||||||
player.0,
|
player.ux(),
|
||||||
player.1,
|
player.uy(),
|
||||||
);
|
);
|
||||||
let player = player_vis.then_some((px, py));
|
let player = player_vis.then_some((px, py));
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ impl Widget for SpeechBubblesWidget<'_> {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|b| {
|
.filter_map(|b| {
|
||||||
let obj = self.board.get_hookable(b.object_id)?;
|
let obj = self.board.get_hookable(b.object_id)?;
|
||||||
let (x, y) = obj.location();
|
let (x, y) = obj.location().into();
|
||||||
let (sx, sy, on_screen) = board_screen_pos(area, self.board, x, y);
|
let (sx, sy, on_screen) = board_screen_pos(area, self.board, x, y);
|
||||||
// On a dark board, a speaker the player can't see is treated like an
|
// On a dark board, a speaker the player can't see is treated like an
|
||||||
// off-screen speaker: the box still draws, but its tail is suppressed.
|
// off-screen speaker: the box still draws, but its tail is suppressed.
|
||||||
@@ -426,8 +426,8 @@ fn center_top(obj_sy: u16, box_h: u16, area: Rect) -> u16 {
|
|||||||
/// `on_screen` is `false` when clamping occurred (tail should be suppressed for that bubble).
|
/// `on_screen` is `false` when clamping occurred (tail should be suppressed for that bubble).
|
||||||
fn board_screen_pos(area: Rect, board: &Board, bx: usize, by: usize) -> (u16, u16, bool) {
|
fn board_screen_pos(area: Rect, board: &Board, bx: usize, by: usize) -> (u16, u16, bool) {
|
||||||
let player = board.player_pos();
|
let player = board.player_pos();
|
||||||
let (off_x, pad_x, _) = BoardWidget::axis(board.width, area.width as usize, player.0 as i32);
|
let (off_x, pad_x, _) = BoardWidget::axis(board.width, area.width as usize, player.x as i32);
|
||||||
let (off_y, pad_y, _) = BoardWidget::axis(board.height, area.height as usize, player.1 as i32);
|
let (off_y, pad_y, _) = BoardWidget::axis(board.height, area.height as usize, player.y as i32);
|
||||||
let raw_sx = area.x as i32 + pad_x as i32 + (bx as i32 - off_x as i32);
|
let raw_sx = area.x as i32 + pad_x as i32 + (bx as i32 - off_x as i32);
|
||||||
let raw_sy = area.y as i32 + pad_y as i32 + (by as i32 - off_y as i32);
|
let raw_sy = area.y as i32 + pad_y as i32 + (by as i32 - off_y as i32);
|
||||||
let sx = raw_sx.clamp(area.left() as i32, area.right() as i32 - 1) as u16;
|
let sx = raw_sx.clamp(area.left() as i32, area.right() as i32 - 1) as u16;
|
||||||
|
|||||||
Reference in New Issue
Block a user