Wooden bridges burn; knights wear bridges down, imps mend them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 21:34:33 +01:00
1 parent c11d281281
commit 29e4970ab1
23 files changed
+356 -17

No files matched your search

+46 -17
View File
@@ -207,6 +207,7 @@ pub enum Job {
Brawl(u32), // two creatures that cannot stand each other scuffle (never to the death)
Guard(usize), // holds a guard post
Spar(usize), // sparring in the arena
MendBridge(usize), // imp mends a worn bridge tile
}
#[derive(Clone, Serialize, Deserialize)]
@@ -583,6 +584,8 @@ pub struct Game {
#[serde(default)]
pub dwarf_caves: Vec<crate::dwarves::DwarfCave>,
#[serde(default)]
pub bridgework: crate::bridges::BridgeWork, // burning and worn bridges
#[serde(default)]
pub dwarf_t: f32,
#[serde(default)]
pub rune_free: u32, // rune forge towers that can be built for free (from chests)
@@ -627,7 +630,7 @@ pub struct Game {
#[serde(skip)]
pub debug_markers: bool, // show debug-only command markers
#[serde(skip, default = "hint_times")]
hint_t: [f32; 32], // last time each hint slot was shown
hint_t: [f32; 48], // last time each hint slot was shown
}
pub const MARK_DIG: u8 = 0;
@@ -660,8 +663,8 @@ pub struct Mark {
pub fixed: bool, // guard/attack/gather order flags are shown without the debug setting
}
fn hint_times() -> [f32; 32] {
[-1000.0; 32]
fn hint_times() -> [f32; 48] {
[-1000.0; 48]
}
// every simulation step has the same length: on every client of a match the same commands are
@@ -898,6 +901,7 @@ impl Game {
stuck: Vec::new(),
vrng: crate::particles::vrng(),
dwarf_caves: Vec::new(),
bridgework: Default::default(),
dwarf_t: 30.0,
rune_free: 0,
rune_final: 0,
@@ -1650,6 +1654,7 @@ impl Game {
self.update_rivals(dt);
}
self.update_dwarves(dt);
self.update_bridges(dt);
// fallen hearts of a match are shared state: every client marks them in the same step
self.update_fallen_masters();
// the end of the game and the fog are about the master at this computer
@@ -2339,7 +2344,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) => tile(t),
Job::Dig(t) | Job::Reinforce(t) | Job::Help(t) | Job::TearBridge(t) | Job::MendBridge(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,
@@ -2625,6 +2630,10 @@ impl Game {
}
}
if e.kind == EK::Priestess || e.kind == EK::Wyrm || e.kind == EK::BattleMage {
// fire on a wooden bridge sets it alight now and then
if self.rng.f() < 0.25 {
self.ignite_at(tx, ty);
}
// fireball / fire breath: half damage to enemies around the target
let me = e.side();
let splash: Vec<usize> = self
@@ -3074,21 +3083,14 @@ impl Game {
e.path.clear();
e.working = true;
e.face = sgn(bx as f32 + 0.5 - e.x);
// the bridge wears down step by step; imps can mend it while it stands
e.timer += dt;
if e.timer >= 4.0 {
e.timer = 0.0;
let lava = t.kind == Kind::LavaBridge;
let tl = &mut self.map.t[i];
tl.kind = if lava { Kind::Lava } else { Kind::Water };
tl.owner = Owner::None;
tl.reserved = 0;
self.ground_lost(i);
self.fx(6, bx as f32 + 0.5, by as f32 + 0.5, 0.0, 0.0, 0.6, 0xff3a5a7a);
self.sfx(Sfx::DigDone, bx as f32 + 0.5, by as f32 + 0.5);
self.msg("msg.bridge_torn", vec![]);
return false;
if e.timer >= 1.0 {
e.timer -= 1.0;
self.sfx(Sfx::MeleeHit, bx as f32 + 0.5, by as f32 + 0.5);
self.fx(2, bx as f32 + 0.5, by as f32 + 0.5, 0.0, 0.0, 0.2, 0xff3a5a7a);
}
return true;
return self.wear_bridge(i, dt);
}
if e.path.is_empty() && e.repath <= 0.0 {
e.repath = 1.5;
@@ -3322,6 +3324,22 @@ impl Game {
self.sfx(Sfx::Reinforce, tx, ty);
}
}
Job::MendBridge(b) => {
let (bx, by) = xy(b);
let (tx, ty) = (bx as f32 + 0.5, by as f32 + 0.5);
if (tx - e.x).abs() > 1.6 || (ty - e.y).abs() > 1.6 || self.burning(b) {
e.job = Job::None;
return;
}
e.face = sgn(tx - e.x);
e.working = true;
if !self.mend_bridge(b, dt) {
e.job = Job::None;
}
if (e.anim * 3.0).fract() < dt * 3.0 {
self.sfx(Sfx::Reinforce, tx, ty);
}
}
Job::Help(t) => {
// the task is done or nobody works on it any more: back to thinking
if self.task_worker(t).is_none() || self.helpers(t) > HELPERS_MAX {
@@ -3488,6 +3506,15 @@ impl Game {
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);
if let Some(b) = self.worn_bridge_near(end) {
e.path = p;
e.job = Job::MendBridge(b);
return;
}
}
if let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| self.claimable(i, me), true) {
let end = p.last().copied().unwrap_or(cur);
self.map.t[end].reserved = me;
@@ -5368,6 +5395,7 @@ impl Game {
self.mana -= COST;
self.sfx(Sfx::Lightning, wx, wy);
self.fx(1, wx, wy, 0.0, 0.0, 0.5, 0xffffeeaa);
self.ignite_at(wx, wy);
let mut kills = Vec::new();
for (j, e) in self.ents.iter_mut().enumerate() {
if e.alive && !e.held && e.active_hero() && (e.x - wx).powi(2) + (e.y - wy).powi(2) < 2.6 {
@@ -5551,6 +5579,7 @@ pub fn job_name(e: &Ent) -> &'static str {
Job::Leave => "job.leave",
Job::Reinforce(_) => "job.reinforce",
Job::Repair(_) => "job.repair",
Job::MendBridge(_) => "job.mend_bridge",
Job::Carry(_) => "job.carry",
Job::Pray(_) => "job.pray",
Job::HealCore => "job.heal_core",