Files
dungeoncraft/src/walls.rs
T

163 lines
5.8 KiB
Rust

// Raising walls: the master plans walls on his own empty floor (the wall tool, paid at once);
// pit imps raise them as reinforced earth. Tunnels can be closed again and doors put where they
// matter. Dragging over a planned tile again cancels the plan (the gold comes back).
use crate::game::*;
use crate::map::*;
// gold a raised wall costs
pub const WALL_COST: f32 = 50.0;
// seconds of imp work for one wall
pub const WALL_WORK: f32 = 3.0;
impl Game {
// a tile where a wall may be planned: own plain floor without a trap, door or torch stand
fn wall_spot(&self, i: usize) -> bool {
let t = &self.map.t[i];
t.kind == Kind::Floor && t.owner == self.me && !self.traps.iter().any(|tr| tr.tile == i)
}
// the wall tool over a rectangle: plans walls (or cancels planned ones)
pub fn plan_walls(&mut self, x0: i32, y0: i32, x1: i32, y1: i32) {
let (mut planned, mut cancelled, mut broke) = (0, 0, false);
for y in y0.min(y1)..=y0.max(y1) {
for x in x0.min(x1)..=x0.max(x1) {
if !inb(x, y) {
continue;
}
let i = idx(x, y);
if let Some(k) = self.wall_plans.iter().position(|p| p.0 == i && p.1 == self.me_side) {
self.wall_plans.swap_remove(k);
self.gold += WALL_COST;
cancelled += 1;
continue;
}
if !self.wall_spot(i) {
continue;
}
if self.gold < WALL_COST {
broke = true;
continue;
}
self.gold -= WALL_COST;
self.wall_plans.push((i, self.me_side));
planned += 1;
}
}
if broke {
self.msg("msg.no_gold", vec![]);
self.sfx_ui(Sfx::Error);
} else if planned + cancelled == 0 {
self.hint(33, "hint.wall_where");
} else {
self.sfx_ui(Sfx::FlagSet);
}
}
// a planned wall of the master being processed next to tile i (for his imps)
pub(crate) fn planned_wall_near(&self, i: usize, me: u32) -> Option<usize> {
if self.wall_plans.is_empty() {
return None;
}
let (x, y) = xy(i);
DIR4.iter().find_map(|&(dx, dy)| {
if !inb(x + dx, y + dy) {
return None;
}
let n = idx(x + dx, y + dy);
let t = &self.map.t[n];
let mine = self.wall_plans.iter().any(|p| p.0 == n && p.1 == self.me_side);
(mine && (t.reserved == 0 || t.reserved == me)).then_some(n)
})
}
// an imp works on a planned wall: true while there is more to do
pub(crate) fn raise_wall_job(&mut self, e: &mut Ent, w: usize, dt: f32) -> bool {
let me = e.id + 1;
let planned = self.wall_plans.iter().any(|p| p.0 == w && p.1 == self.me_side);
if !planned || !adj4(e.tile(), w) {
return false;
}
// the ground was lost or built over: the plan is dropped, the gold comes back
if !self.wall_spot(w) {
self.wall_plans.retain(|p| p.0 != w);
self.gold += WALL_COST;
return false;
}
self.map.t[w].reserved = me;
let (wx, wy) = xy(w);
e.face = sgn(wx as f32 + 0.5 - e.x);
e.working = true;
e.timer += dt * self.help_speed(w);
if e.timer < WALL_WORK {
return true;
}
e.timer = 0.0;
// whoever stands there steps aside
let bank = DIR8.iter().map(|&(dx, dy)| (wx + dx, wy + dy)).find(|&(bx, by)| self.map.walk(bx, by) && idx(bx, by) != w);
for o in self.ents.iter_mut() {
if o.alive && !o.held && o.tile() == w {
if let Some((bx, by)) = bank {
o.x = bx as f32 + 0.5;
o.y = by as f32 + 0.5;
o.path.clear();
}
}
}
// torch stands on the tile and wall torches facing it go
self.torches.retain(|t| !(t.stand && t.wall == w) && !(!t.stand && {
let (tx, ty) = xy(t.wall);
let (dx, dy) = DIR4[t.dir % 4];
inb(tx + dx, ty + dy) && idx(tx + dx, ty + dy) == w
}));
let tl = &mut self.map.t[w];
tl.kind = Kind::Earth;
tl.reinforced = true;
tl.hp = 12.0;
tl.marked = false;
tl.reserved = 0;
tl.bed = 0;
tl.food = 0.0;
self.wall_plans.retain(|p| p.0 != w);
self.fx(8, wx as f32 + 0.5, wy as f32 + 0.5, 0.0, 0.0, 0.5, 0xff4040e0);
self.sfx(Sfx::Reinforce, wx as f32 + 0.5, wy as f32 + 0.5);
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn imps_raise_planned_walls() {
let mut g = Game::new(4, 64, 64);
let (cx, cy) = g.map.core;
// own floor next to the core
let (x, y) = (cx + 4, cy + 1);
for (xx, yy) in [(x, y), (x - 1, y), (x + 1, y)] {
let t = &mut g.map.t[idx(xx, yy)];
t.kind = Kind::Floor;
t.owner = Owner::Player;
}
g.gold = 1000.0;
g.plan_walls(x, y, x, y);
assert_eq!(g.wall_plans.len(), 1);
assert_eq!(g.gold, 1000.0 - WALL_COST);
// planning it again cancels (and pays back)
g.plan_walls(x, y, x, y);
assert!(g.wall_plans.is_empty() && g.gold == 1000.0);
g.plan_walls(x, y, x, y);
let mut raised = false;
for _ in 0..(60 * 20) {
g.update(0.05);
if g.map.t[idx(x, y)].kind == Kind::Earth {
raised = true;
break;
}
}
assert!(raised, "an imp raised the wall");
assert!(g.map.t[idx(x, y)].reinforced && g.wall_plans.is_empty());
}
}