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:
1 parent
a0f2660eae
commit
05d9cf8824
3 files changed
+103
-4
No files matched your search
@@ -503,3 +503,4 @@ camp.m20.title = Der Großmeister
|
||||
camp.m20.story = Großmeister Valerian Hellschild erwartet dich in seiner Zitadelle jenseits des großen Flusses, mit dem letzten Splitter und dem Tor des Ordens. Hundert Jahre lang hat er gebetet, poliert und sich vorbereitet. | Zerstöre sein Tor, hol dir den Splitter und mach deinen Kern wieder ganz. Ich bin direkt hinter dir. Ein gutes Stück hinter dir.
|
||||
camp.epilogue = Der letzte Splitter versinkt im Kern, und die Dunkelheit atmet wieder. Irgendwo dort oben schreibt der Orden der Lichtwacht einen sehr langen Bericht. | Krummfinger hat Tee gekocht. Du bist wieder ganz, Meister. Das Unterreich gehört dir.
|
||||
lair.arrivals = {0} freie Plätze: Neue Kreaturen kommen mit {1}% Tempo
|
||||
job.help = Hilft mit
|
||||
@@ -504,3 +504,4 @@ camp.m20.title = The Grand Master
|
||||
camp.m20.story = Grand Master Valerian Brightshield waits in his citadel beyond the great river, with the last shard and the gate of the order. He has prayed, polished and prepared for a hundred years. | Destroy his gate, take back the shard and make your core whole again. I will be right behind you. Well behind you.
|
||||
camp.epilogue = The last shard sinks into the core, and the darkness breathes again. Somewhere above, the Order of the Lightwatch is writing a very long report. | Crookfinger has made tea. You are whole, master. The underworld is yours.
|
||||
lair.arrivals = {0} free beds: new creatures arrive at {1}% speed
|
||||
job.help = Helping
|
||||
+101
-4
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user