Idle imps help: up to five imps at one task, +25% speed per helper (max 200%)

Imps with nothing else to do (no digging, delivering, claiming, repairing or reinforcing of
their own) walk to a task of another imp and help; every 1.5 s they check for own work again.
Claiming floor keeps its priority before helping. Test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 12:01:33 +01:00
1 parent a0f2660eae
commit 05d9cf8824
3 files changed
+103 -4

No files matched your search

+101 -4
View File
@@ -9,7 +9,8 @@ pub const PAYDAY: f32 = 150.0;
pub const PAYDAY_WARN: f32 = 20.0; // the payday is announced this many seconds before
pub const TREASURY_TILE: f32 = 500.0; // value one treasury tile holds
pub const CORE_STORE: f32 = 2000.0; // value the soul core holds without any treasury
pub const TRAIN_COST: f32 = 1.5; // gold per second of training (billed on payday)
pub const TRAIN_COST: f32 = 1.5;
pub const HELPERS_MAX: usize = 4; // up to five imps at one task (one worker + four helpers) // gold per second of training (billed on payday)
pub const FIRST_WAVE: f32 = 300.0;
pub const WAVE_GAP: f32 = 180.0;
pub const MAX_SQUAD: usize = 18;
@@ -154,6 +155,7 @@ pub enum Job {
Pray(usize), // temple tile
HealCore, // flame priestess heals the soul core
TearBridge(usize), // knight tears down a bridge of the player
Help(usize), // imp with nothing else to do helps another imp at this tile
}
#[derive(Clone, Serialize, Deserialize)]
@@ -942,6 +944,51 @@ impl Game {
}
}
// the imp working on tile t (dig, claim or reinforce)
fn task_worker(&self, t: usize) -> Option<usize> {
self.ents.iter().position(|o| o.alive && !o.hero && o.kind == EK::Worker && matches!(o.job, Job::Dig(x) | Job::Claim(x) | Job::Reinforce(x) if x == t))
}
// imps helping at tile t (standing next to it)
fn helpers(&self, t: usize) -> usize {
let (tx, ty) = xy(t);
self.ents
.iter()
.filter(|o| o.alive && o.kind == EK::Worker && o.job == Job::Help(t) && o.path.is_empty() && (o.x - tx as f32 - 0.5).abs() < 1.6 && (o.y - ty as f32 - 0.5).abs() < 1.6)
.count()
}
// working speed at a tile: +25% per helper, at most 200% (five imps)
fn help_speed(&self, t: usize) -> f32 {
1.0 + 0.25 * self.helpers(t).min(HELPERS_MAX) as f32
}
// an idle imp walks to the nearest task with room for another helper
fn start_help(&mut self, e: &mut Ent) -> bool {
let tasks: Vec<usize> = self
.ents
.iter()
.filter(|o| o.alive && !o.hero && o.kind == EK::Worker)
.filter_map(|o| match o.job {
Job::Dig(t) | Job::Claim(t) | Job::Reinforce(t) => Some(t),
_ => None,
})
.filter(|&t| self.ents.iter().filter(|o| o.alive && o.job == Job::Help(t)).count() < HELPERS_MAX)
.collect();
if tasks.is_empty() {
return false;
}
let cur = e.tile();
let near = |i: usize| tasks.iter().copied().find(|&t| i != t && adj4(i, t) || (i == t && self.map.t[t].walkable()));
let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| near(i).is_some(), true) else { return false };
let end = p.last().copied().unwrap_or(cur);
let Some(t) = near(end) else { return false };
e.path = p;
e.job = Job::Help(t);
e.think = 1.5;
true
}
// free beds in the lairs: much room lets creatures arrive faster, little room slower
pub fn free_beds(&self) -> usize {
self.counts[1].min(30).saturating_sub(self.creature_count())
@@ -1948,7 +1995,7 @@ impl Game {
let (tx, ty) = xy(t);
e.face = sgn(tx as f32 + 0.5 - e.x);
e.working = true;
e.timer += dt;
e.timer += dt * self.help_speed(t);
if e.timer >= 0.45 {
e.timer = 0.0;
let fxx = (e.x + tx as f32 + 0.5) * 0.5;
@@ -2004,6 +2051,21 @@ impl Game {
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 {
e.job = Job::None;
e.think = 0.2;
return;
}
let (tx, _) = xy(t);
e.face = sgn(tx as f32 + 0.5 - e.x);
e.working = true;
if e.think <= 0.0 {
// every 1.5 s: is there own work again? (if not, the imp comes back to help)
e.job = Job::None;
}
}
Job::Reinforce(w) => {
let t = self.map.t[w];
if !adj4(cur, w) || t.kind != Kind::Earth || t.reinforced || t.marked {
@@ -2014,7 +2076,7 @@ impl Game {
let (wx, wy) = xy(w);
e.face = sgn(wx as f32 + 0.5 - e.x);
e.working = true;
e.timer += dt;
e.timer += dt * self.help_speed(w);
if e.timer >= 2.5 {
self.unreserve(me);
e.job = Job::None;
@@ -2032,7 +2094,7 @@ impl Game {
return;
}
e.working = true;
e.timer += dt;
e.timer += dt * self.help_speed(t);
// knight ground takes longer
let knight = self.map.t[t].owner == Owner::Hero;
if e.timer >= if knight { 3.0 } else { 1.0 } {
@@ -2159,6 +2221,10 @@ impl Game {
return;
}
}
// nothing else to do: help another imp (up to five at one task)
if self.start_help(e) {
return;
}
e.think = 1.0;
self.wander(e, 3);
}
@@ -3813,6 +3879,7 @@ pub fn job_name(e: &Ent) -> &'static str {
Job::Study(_) => "job.study",
Job::Attack(_) => "job.fight",
Job::TearBridge(_) => "job.tear_bridge",
Job::Help(_) => "job.help",
Job::AttackCore => {
if e.working {
"job.core_attack"
@@ -4231,6 +4298,36 @@ mod tests {
assert!(!g.map.t[idx(x0 - 1, y0)].reinforced && !g.map.t[idx(x0, y0 - 1)].reinforced && !g.map.t[idx(x0 - 1, y0 - 1)].reinforced);
}
#[test]
fn idle_imps_help_and_speed_up_work() {
let mut g = Game::new(3, 64, 64);
let (cx, cy) = g.map.core;
for _ in 0..6 {
g.spawn(EK::Worker, false, cx as f32 + 4.5, cy as f32 + 1.5, 1);
}
// a single earth tile to dig, next to the start area: one digs, the others help
let (dx, dy) = (cx + 6, (cy - 3..=cy + 5).find(|&y| g.map.t[idx(cx + 6, y)].kind == Kind::Earth).expect("earth next to the start area"));
// no other work: everything around is claimed already, no walls to reinforce
for t in g.map.t.iter_mut() {
if t.kind == Kind::Earth {
t.reinforced = true;
}
}
g.map.t[idx(dx, dy)].reinforced = false;
g.map.t[idx(dx, dy)].marked = true;
let mut best = 0.0f32;
for _ in 0..200 {
g.update(0.05);
best = best.max(g.help_speed(idx(dx, dy)));
if g.map.t[idx(dx, dy)].kind != Kind::Earth {
break;
}
}
println!("best working speed {}", best);
assert!(best >= 1.75, "helpers join: {}", best);
assert!(best <= 2.0);
}
#[test]
fn imps_reinforce_walls() {
let mut g = Game::new(3, 64, 64);