Raise walls: plan them on own floor, imps build reinforced earth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
398bb95681
commit
ed3bc437e5
26 files changed
+347
-11
No files matched your search
+3
-1
@@ -34,6 +34,7 @@ pub enum Act {
|
||||
Sell(Rect),
|
||||
Bridge(Rect),
|
||||
Steel(Rect),
|
||||
Wall(Rect), // plan walls on own floor (or cancel planned ones)
|
||||
Drawbridge(Rect), // build drawbridges, or (one tile on an own drawbridge) switch it: down, up, automatic
|
||||
Tower {
|
||||
kind: TowerKind,
|
||||
@@ -142,7 +143,7 @@ impl Game {
|
||||
Act::Dig(r) => (rect_center(r), MARK_DIG, 4.0),
|
||||
Act::Room { r, .. } => (rect_center(r), MARK_ROOM, 5.0),
|
||||
Act::Sell(r) => (rect_center(r), MARK_SELL, 4.0),
|
||||
Act::Bridge(r) | Act::Drawbridge(r) => (rect_center(r), MARK_BRIDGE, 4.0),
|
||||
Act::Bridge(r) | Act::Drawbridge(r) | Act::Wall(r) => (rect_center(r), MARK_BRIDGE, 4.0),
|
||||
Act::Steel(r) => (rect_center(r), MARK_STEEL, 4.0),
|
||||
Act::Tower { x, y, .. } => (point(x as f32, y as f32), MARK_TOWER, 6.0),
|
||||
Act::Trap { x, y, .. } => (point(x as f32, y as f32), MARK_TRAP, 6.0),
|
||||
@@ -187,6 +188,7 @@ impl Game {
|
||||
Act::Sell(r) => self.sell_rect(r.x0, r.y0, r.x1, r.y1),
|
||||
Act::Bridge(r) => self.build_bridges(r.x0, r.y0, r.x1, r.y1),
|
||||
Act::Drawbridge(r) => self.drawbridge_order(r.x0, r.y0, r.x1, r.y1),
|
||||
Act::Wall(r) => self.plan_walls(r.x0, r.y0, r.x1, r.y1),
|
||||
Act::Steel(r) => self.build_steel(r.x0, r.y0, r.x1, r.y1),
|
||||
Act::Tower { kind, x, y } => self.build_tower(kind, x, y),
|
||||
Act::Trap { kind, x, y } => self.place_trap(kind, x, y),
|
||||
|
||||
+26
-4
@@ -208,6 +208,7 @@ pub enum Job {
|
||||
Guard(usize), // holds a guard post
|
||||
Spar(usize), // sparring in the arena
|
||||
MendBridge(usize), // imp mends a worn bridge tile
|
||||
RaiseWall(usize), // imp raises a planned wall on own floor
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
@@ -587,6 +588,8 @@ pub struct Game {
|
||||
#[serde(default)]
|
||||
pub bridgework: crate::bridges::BridgeWork, // burning and worn bridges
|
||||
#[serde(default)]
|
||||
pub wall_plans: Vec<(usize, u8)>, // walls to raise: tile, master side (see walls.rs)
|
||||
#[serde(default)]
|
||||
pub wood: f32, // the second resource (see wood.rs)
|
||||
#[serde(default)]
|
||||
pub woodwork: crate::wood::WoodWork, // felled mushrooms growing back
|
||||
@@ -765,7 +768,7 @@ pub fn room_index(k: Kind) -> Option<usize> {
|
||||
ROOM_KINDS.iter().position(|&r| r == k)
|
||||
}
|
||||
|
||||
fn adj4(a: usize, b: usize) -> bool {
|
||||
pub(crate) fn adj4(a: usize, b: usize) -> bool {
|
||||
let (ax, ay) = xy(a);
|
||||
let (bx, by) = xy(b);
|
||||
(ax - bx).abs() + (ay - by).abs() == 1
|
||||
@@ -780,7 +783,7 @@ fn norm(x: f32, y: f32) -> (f32, f32) {
|
||||
}
|
||||
}
|
||||
|
||||
fn sgn(v: f32) -> f32 {
|
||||
pub(crate) fn sgn(v: f32) -> f32 {
|
||||
if v < 0.0 {
|
||||
-1.0
|
||||
} else {
|
||||
@@ -909,6 +912,7 @@ impl Game {
|
||||
vrng: crate::particles::vrng(),
|
||||
dwarf_caves: Vec::new(),
|
||||
bridgework: Default::default(),
|
||||
wall_plans: Vec::new(),
|
||||
wood: crate::wood::WOOD_START,
|
||||
woodwork: Default::default(),
|
||||
dwarf_t: 30.0,
|
||||
@@ -1764,7 +1768,7 @@ impl Game {
|
||||
}
|
||||
|
||||
// working speed at a tile: +25% per helper, at most 200% (five imps)
|
||||
fn help_speed(&self, t: usize) -> f32 {
|
||||
pub(crate) fn help_speed(&self, t: usize) -> f32 {
|
||||
1.0 + 0.25 * self.helpers(t).min(HELPERS_MAX) as f32
|
||||
}
|
||||
|
||||
@@ -2356,7 +2360,7 @@ impl Game {
|
||||
Some((x as f32 + 0.5, y as f32 + 0.5))
|
||||
};
|
||||
let target = match e.job {
|
||||
Job::Dig(t) | Job::Reinforce(t) | Job::Help(t) | Job::TearBridge(t) | Job::MendBridge(t) => tile(t),
|
||||
Job::Dig(t) | Job::Reinforce(t) | Job::Help(t) | Job::TearBridge(t) | Job::MendBridge(t) | Job::RaiseWall(t) => tile(t),
|
||||
Job::Attack(id) | Job::Torment(id) | Job::Carry(id) | Job::Brawl(id) => self.find(id).map(|j| (self.ents[j].x, self.ents[j].y)),
|
||||
Job::Repair(id) | Job::AttackTower(id) => self.towers.iter().find(|t| t.id == id).map(|t| t.pos()),
|
||||
_ => None,
|
||||
@@ -3360,6 +3364,12 @@ impl Game {
|
||||
self.sfx(Sfx::Reinforce, tx, ty);
|
||||
}
|
||||
}
|
||||
Job::RaiseWall(w) => {
|
||||
if !self.raise_wall_job(e, w, dt) {
|
||||
self.unreserve(me);
|
||||
e.job = Job::None;
|
||||
}
|
||||
}
|
||||
Job::MendBridge(b) => {
|
||||
let (bx, by) = xy(b);
|
||||
let (tx, ty) = (bx as f32 + 0.5, by as f32 + 0.5);
|
||||
@@ -3542,6 +3552,17 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// planned walls are raised
|
||||
if let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| self.planned_wall_near(i, me).is_some(), true) {
|
||||
let end = p.last().copied().unwrap_or(cur);
|
||||
if let Some(w) = self.planned_wall_near(end, me) {
|
||||
self.map.t[w].reserved = me;
|
||||
e.path = p;
|
||||
e.job = Job::RaiseWall(w);
|
||||
e.timer = 0.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ... and so are bridges the knights worked on
|
||||
if let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| self.worn_bridge_near(i).is_some(), true) {
|
||||
let end = p.last().copied().unwrap_or(cur);
|
||||
@@ -5618,6 +5639,7 @@ pub fn job_name(e: &Ent) -> &'static str {
|
||||
Job::Reinforce(_) => "job.reinforce",
|
||||
Job::Repair(_) => "job.repair",
|
||||
Job::MendBridge(_) => "job.mend_bridge",
|
||||
Job::RaiseWall(_) => "job.raise_wall",
|
||||
Job::Carry(_) => "job.carry",
|
||||
Job::Pray(_) => "job.pray",
|
||||
Job::HealCore => "job.heal_core",
|
||||
|
||||
+15
@@ -88,6 +88,7 @@ pub const T_CAP: u32 = 200; // giant mushroom cap, glowing spots
|
||||
pub const T_BARK: u32 = 201; // tree trunk
|
||||
pub const T_LEAVES: u32 = 202; // 2 variants: tree crown
|
||||
pub const T_GRASS: u32 = 204; // 4 variants: grass above ground
|
||||
pub const I_WALL: u32 = 208; // wall tool: bricks rising from the floor
|
||||
|
||||
pub struct Gfx {
|
||||
pub atlas: Texture2D,
|
||||
@@ -827,6 +828,20 @@ fn gen_atlas() -> Image {
|
||||
None
|
||||
}
|
||||
});
|
||||
// wall tool icon: a block of bricks with an arrow up
|
||||
cv.cell(I_WALL, |x, y| {
|
||||
if (3..=12).contains(&x) && (6..=14).contains(&y) {
|
||||
let row = (y - 6) / 3;
|
||||
let joint = (y - 6) % 3 == 2 || (x + row * 3) % 6 == 0;
|
||||
Some(if joint { c(60, 40, 30) } else { c(150, 100, 70) })
|
||||
} else if y <= 4 && (x - 8).abs() <= 4 - y.max(1) + 1 && y >= 1 {
|
||||
Some(c(200, 140, 255))
|
||||
} else if y == 5 && (7..=9).contains(&x) {
|
||||
Some(c(200, 140, 255))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
// frost spell icon: a snowflake
|
||||
cv.cell(I_FREEZE, |x, y| {
|
||||
let (dx, dy) = (x - 8, y - 8);
|
||||
|
||||
+23
-2
@@ -41,6 +41,7 @@ mod spells;
|
||||
mod touch;
|
||||
mod ui;
|
||||
mod voice;
|
||||
mod walls;
|
||||
mod wilds;
|
||||
mod wood;
|
||||
mod xm;
|
||||
@@ -108,7 +109,7 @@ fn tool_tile(cam: &Cam, map: &map::Map, tool: Tool, mx: f32, my: f32) -> Option<
|
||||
}
|
||||
|
||||
fn area_tool(t: Tool) -> bool {
|
||||
matches!(t, Tool::Dig | Tool::Room(_) | Tool::Sell | Tool::Bridge | Tool::Steel | Tool::Drawbridge)
|
||||
matches!(t, Tool::Dig | Tool::Room(_) | Tool::Sell | Tool::Bridge | Tool::Steel | Tool::Drawbridge | Tool::Wall)
|
||||
}
|
||||
|
||||
// Use the active tool on the world at (mx, my) (CSS pixels): a click, or a tap on touch screens
|
||||
@@ -121,7 +122,7 @@ fn world_action(app: &mut App, ui: &mut Ui, r3d: &R3d, inp: &mut Input, mx: f32,
|
||||
let ground = app.cam.ground_point(mx, my);
|
||||
let (wx, wy) = (ground.x, ground.y);
|
||||
match (ui.tool, tile) {
|
||||
(Tool::Dig | Tool::Room(_) | Tool::Sell | Tool::Bridge | Tool::Steel | Tool::Drawbridge, Some(t)) => inp.ldown = Some(t),
|
||||
(Tool::Dig | Tool::Room(_) | Tool::Sell | Tool::Bridge | Tool::Steel | Tool::Drawbridge | Tool::Wall, Some(t)) => inp.ldown = Some(t),
|
||||
(Tool::Hand, _) => {
|
||||
let j = r3d.pick_ent(&app.game, &app.cam, mx, my, false);
|
||||
let Some(j) = j else { return };
|
||||
@@ -165,6 +166,7 @@ fn finish_rect(app: &mut App, tool: Tool, start: (i32, i32), mx: f32, my: f32) {
|
||||
Tool::Sell => app.game.queue(Act::Sell(r)),
|
||||
Tool::Bridge => app.game.queue(Act::Bridge(r)),
|
||||
Tool::Drawbridge => app.game.queue(Act::Drawbridge(r)),
|
||||
Tool::Wall => app.game.queue(Act::Wall(r)),
|
||||
Tool::Steel => app.game.queue(Act::Steel(r)),
|
||||
_ => {}
|
||||
}
|
||||
@@ -1949,6 +1951,7 @@ fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut M
|
||||
Tool::Heal => (Color::new(0.4, 1.0, 0.5, 1.0), Some((ground.x, ground.y, 2.55))),
|
||||
Tool::Summon => (Color::new(0.8, 0.5, 1.0, 1.0), None),
|
||||
Tool::Bridge | Tool::Drawbridge => (Color::new(0.9, 0.7, 0.4, 1.0), None),
|
||||
Tool::Wall => (Color::new(0.8, 0.4, 1.0, 1.0), None),
|
||||
Tool::Steel => (Color::new(0.75, 0.8, 0.9, 1.0), None),
|
||||
Tool::Tower(_) => (Color::new(1.0, 0.6, 0.25, 1.0), None),
|
||||
Tool::Trap(_) => (Color::new(1.0, 0.4, 0.3, 1.0), None),
|
||||
@@ -1966,6 +1969,24 @@ fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut M
|
||||
if app.settings.debug_trigger_zones || app.settings.debug_path_lines || app.editor_testing {
|
||||
draw_debug_3d(app, now);
|
||||
}
|
||||
// planned walls of this master: a pulsing frame where the wall will rise
|
||||
let pulse = 0.55 + 0.25 * ((now * 3.0).sin() as f32);
|
||||
let me = app.game.local_side;
|
||||
for &(i, side) in app.game.wall_plans.iter().filter(|p| p.1 == me).take(200) {
|
||||
let (x, z) = map::xy(i);
|
||||
let (x0, z0, x1, z1) = (x as f32 + 0.08, z as f32 + 0.08, x as f32 + 0.92, z as f32 + 0.92);
|
||||
let c = Color::new(0.75, 0.45, 1.0, pulse);
|
||||
let _ = side;
|
||||
for h in [0.02, 0.9] {
|
||||
line3(vec3(x0, h, z0), vec3(x1, h, z0), 0.04, c);
|
||||
line3(vec3(x1, h, z0), vec3(x1, h, z1), 0.04, c);
|
||||
line3(vec3(x1, h, z1), vec3(x0, h, z1), 0.04, c);
|
||||
line3(vec3(x0, h, z1), vec3(x0, h, z0), 0.04, c);
|
||||
}
|
||||
for (a, b) in [(x0, z0), (x1, z0), (x1, z1), (x0, z1)] {
|
||||
line3(vec3(a, 0.02, b), vec3(a, 0.9, b), 0.04, c);
|
||||
}
|
||||
}
|
||||
let title_screen = matches!(
|
||||
menu.page,
|
||||
Some(Page::Title)
|
||||
|
||||
@@ -10,7 +10,7 @@ pub const TABS: [&str; 4] = ["best.tab.creatures", "best.tab.humans", "best.tab.
|
||||
const CREATURES: [EK; 14] = [EK::Worker, EK::Kobold, EK::Spider, EK::Mage, EK::Ogre, EK::Jailer, EK::Priestess, EK::Chainbreaker, EK::Cogling, EK::Revenant, EK::Bat, EK::Gargoyle, EK::Owl, EK::Wyrm];
|
||||
const HUMANS: [EK; 8] = [EK::Knight, EK::Archer, EK::Priest, EK::Dwarf, EK::Fairy, EK::BattleMage, EK::Giant, EK::Paladin];
|
||||
// buildings: indices into ui::TOOLS
|
||||
const BUILDINGS: [usize; 31] = [2, 3, 4, 5, 6, 12, 40, 13, 18, 21, 41, 42, 43, 44, 17, 47, 19, 14, 15, 16, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
|
||||
const BUILDINGS: [usize; 32] = [2, 3, 4, 5, 6, 12, 40, 13, 18, 21, 41, 42, 43, 44, 17, 47, 49, 19, 14, 15, 16, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Entry {
|
||||
@@ -267,6 +267,7 @@ impl Menu {
|
||||
crate::ui::Tool::Tower(k) => crate::game::tower_name(k).trim_start_matches("tower.").to_string(),
|
||||
crate::ui::Tool::Bridge => "bridge".into(),
|
||||
crate::ui::Tool::Drawbridge => "drawbridge".into(),
|
||||
crate::ui::Tool::Wall => "wall".into(),
|
||||
crate::ui::Tool::Trap(k) => k.id().into(),
|
||||
_ => "steel".into(),
|
||||
};
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ pub const RESEARCH: &[(usize, f32)] = &[
|
||||
];
|
||||
|
||||
// known from the start in free play
|
||||
pub const BASE_TOOLS: &[usize] = &[0, 1, 2, 3, 4, 6, 7, 8, 9, 14, 17, 20, 45, 46];
|
||||
pub const BASE_TOOLS: &[usize] = &[0, 1, 2, 3, 4, 6, 7, 8, 9, 14, 17, 20, 45, 46, 49];
|
||||
|
||||
// campaign: what the next missions bring may be researched early
|
||||
const LOOKAHEAD: usize = 4;
|
||||
|
||||
@@ -175,13 +175,14 @@ pub enum Tool {
|
||||
Bridge,
|
||||
Steel,
|
||||
Drawbridge,
|
||||
Wall,
|
||||
Trap(crate::traps::TrapKind),
|
||||
Spell(crate::spells::Spell),
|
||||
Order(u8), // 0 attack, 1 guard
|
||||
}
|
||||
|
||||
// New tools are appended so that existing key bindings ("tool<i>") keep their meaning
|
||||
pub const TOOLS: [Tool; 49] = [
|
||||
pub const TOOLS: [Tool; 50] = [
|
||||
Tool::Dig,
|
||||
Tool::Hand,
|
||||
Tool::Room(0),
|
||||
@@ -231,6 +232,7 @@ pub const TOOLS: [Tool; 49] = [
|
||||
Tool::Order(1),
|
||||
Tool::Drawbridge,
|
||||
Tool::Spell(crate::spells::Spell::Freeze),
|
||||
Tool::Wall,
|
||||
];
|
||||
|
||||
|
||||
@@ -274,6 +276,7 @@ pub fn tool_info(t: Tool) -> ToolInfo {
|
||||
Tool::Summon => ("summon", I_SUMMON, None),
|
||||
Tool::Bridge => ("bridge", I_BRIDGE, None),
|
||||
Tool::Drawbridge => ("drawbridge", I_DRAWBRIDGE, None),
|
||||
Tool::Wall => ("wall", I_WALL, None),
|
||||
Tool::Steel => ("steel", I_STEEL, None),
|
||||
};
|
||||
let name = match room {
|
||||
@@ -289,6 +292,7 @@ pub fn tool_cost_static(t: Tool) -> String {
|
||||
Tool::Spell(sp) => trf("cost.mana", &[(sp.cost() as i32).to_string()]),
|
||||
Tool::Trap(k) => trf("cost.work", &[(k.work() as i32).to_string()]),
|
||||
Tool::Bridge => trf("cost.bridge", &[BRIDGE_COST[0].to_string(), (crate::wood::BRIDGE_WOOD as i32).to_string(), BRIDGE_COST[1].to_string()]),
|
||||
Tool::Wall => trf("cost.gold_tile", &[(crate::walls::WALL_COST as i32).to_string()]),
|
||||
Tool::Drawbridge => trf("cost.drawbridge", &[DRAWBRIDGE_COST[0].to_string(), (crate::wood::DRAWBRIDGE_WOOD as i32).to_string(), DRAWBRIDGE_COST[1].to_string()]),
|
||||
Tool::Steel => trf("cost.steel", &[STEEL_COST[0].to_string(), STEEL_COST[1].to_string()]),
|
||||
Tool::Tower(TowerKind::Rune) => {
|
||||
@@ -315,6 +319,7 @@ pub fn tool_cost(t: Tool, g: &Game) -> String {
|
||||
Tool::Heal => trf("cost.mana", &["60".into()]),
|
||||
Tool::Summon => trf("cost.mana", &[(g.summon_cost() as i32).to_string()]),
|
||||
Tool::Bridge => trf("cost.bridge", &[BRIDGE_COST[0].to_string(), (crate::wood::BRIDGE_WOOD as i32).to_string(), BRIDGE_COST[1].to_string()]),
|
||||
Tool::Wall => trf("cost.gold_tile", &[(crate::walls::WALL_COST as i32).to_string()]),
|
||||
Tool::Drawbridge => trf("cost.drawbridge", &[DRAWBRIDGE_COST[0].to_string(), (crate::wood::DRAWBRIDGE_WOOD as i32).to_string(), DRAWBRIDGE_COST[1].to_string()]),
|
||||
Tool::Steel => trf("cost.steel", &[STEEL_COST[0].to_string(), STEEL_COST[1].to_string()]),
|
||||
Tool::Tower(TowerKind::Rune) => {
|
||||
@@ -337,7 +342,7 @@ pub fn tool_cost(t: Tool, g: &Game) -> String {
|
||||
pub const CATS: [(&str, &[usize]); 7] = [
|
||||
("cat.dig", &[0]),
|
||||
("cat.hand", &[1]),
|
||||
("cat.rooms", &[2, 3, 4, 5, 6, 12, 40, 13, 18, 21, 41, 42, 43, 44, 17, 47]),
|
||||
("cat.rooms", &[2, 3, 4, 5, 6, 12, 40, 13, 18, 21, 41, 42, 43, 44, 17, 47, 49]),
|
||||
("cat.towers", &[14, 15, 16, 20]),
|
||||
("cat.traps", &[22, 23, 24, 25, 26, 27, 28, 29, 30, 31]),
|
||||
("cat.spells", &[9, 10, 11, 32, 33, 34, 35, 36, 37, 38, 39, 48]),
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user