163 lines
5.8 KiB
Rust
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());
|
|
}
|
|
}
|