The jailer works on prisoners in the torture chamber: 2.5 times the chance to defect
Jailers pick a prisoner nobody works on yet (their favourite work), stand next to it and torment it; with a jailer about 28 of 40 prisoners defect instead of about 18. Test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b7d7fc2b52
commit
6e257e4034
3 files changed
+79
-2
No files matched your search
+77
-2
@@ -170,6 +170,7 @@ pub enum Job {
|
||||
HealCore, // flame priestess heals the soul core
|
||||
TearBridge(usize), // knight tears down a bridge of the player
|
||||
Rest(usize), // flying creature recovers in the aerie (flees when badly hurt)
|
||||
Torment(u32), // the jailer works on a prisoner in the torture chamber
|
||||
Help(usize), // imp with nothing else to do helps another imp at this tile
|
||||
}
|
||||
|
||||
@@ -2541,6 +2542,21 @@ impl Game {
|
||||
e.job = Job::None;
|
||||
}
|
||||
}
|
||||
Job::Torment(id) => {
|
||||
// working on a prisoner: makes it more likely to change sides (see captive_update)
|
||||
let Some(k) = self.ents.iter().position(|o| o.id == id && o.alive && o.captive == 3) else {
|
||||
e.job = Job::None;
|
||||
return;
|
||||
};
|
||||
let px = self.ents[k].x;
|
||||
e.face = sgn(px - e.x);
|
||||
e.working = true;
|
||||
e.xp += 2.0 * dt;
|
||||
e.timer -= dt;
|
||||
if e.timer <= 0.0 {
|
||||
e.job = Job::None;
|
||||
}
|
||||
}
|
||||
Job::Train(t) => {
|
||||
// training is billed on payday; it stops when the bill would exceed the gold
|
||||
if cur != t || self.map.t[t].kind != Kind::Training || self.training_bill + 1.0 > self.gold {
|
||||
@@ -2822,6 +2838,19 @@ impl Game {
|
||||
}
|
||||
}
|
||||
}
|
||||
// the jailer's favourite work: prisoners nobody is working on yet
|
||||
if e.kind == EK::Jailer && self.rng.f() < 0.85 {
|
||||
let busy: Vec<u32> = self.ents.iter().filter_map(|o| if let Job::Torment(p) = o.job { Some(p) } else { None }).collect();
|
||||
let prisoner = self.ents.iter().filter(|o| o.alive && o.captive == 3 && !busy.contains(&o.id)).map(|o| (o.id, o.tile())).next();
|
||||
if let Some((id, pt)) = prisoner {
|
||||
if let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| i != pt && adj4(i, pt) || i == pt, true) {
|
||||
e.path = p;
|
||||
e.job = Job::Torment(id);
|
||||
e.timer = 12.0 + self.rng.f() * 8.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
let r = self.rng.f();
|
||||
if (e.kind == EK::Mage && r < 0.8) || r < 0.2 {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Library, me, false) {
|
||||
@@ -2891,8 +2920,13 @@ impl Game {
|
||||
e.torture_t += dt;
|
||||
if e.torture_t >= 5.0 {
|
||||
e.torture_t -= 5.0;
|
||||
// ~40% defect over the whole torture (paladins ~25%)
|
||||
let p = if e.kind == EK::Paladin { 0.0143 } else { 0.0252 };
|
||||
// ~40% defect over the whole torture (paladins ~25%); a jailer working on
|
||||
// the prisoner makes it 2.5 times as likely
|
||||
let tormented = self.ents.iter().any(|o| o.alive && o.kind == EK::Jailer && o.job == Job::Torment(e.id) && o.path.is_empty() && (o.x - e.x).abs() < 1.6 && (o.y - e.y).abs() < 1.6);
|
||||
let p = if e.kind == EK::Paladin { 0.0143 } else { 0.0252 } * if tormented { 2.5 } else { 1.0 };
|
||||
if tormented {
|
||||
self.fx(2, e.x, e.y, 0.0, 0.0, 0.3, 0xff3040ff);
|
||||
}
|
||||
if self.rng.f() < p {
|
||||
self.defect(e);
|
||||
return;
|
||||
@@ -4066,6 +4100,7 @@ pub fn job_name(e: &Ent) -> &'static str {
|
||||
Job::Attack(_) => "job.fight",
|
||||
Job::TearBridge(_) => "job.tear_bridge",
|
||||
Job::Rest(_) => "job.rest",
|
||||
Job::Torment(_) => "job.torment",
|
||||
Job::Help(_) => "job.help",
|
||||
Job::AttackCore => {
|
||||
if e.working {
|
||||
@@ -4705,6 +4740,46 @@ mod tests {
|
||||
assert!((8..=26).contains(&converted), "defect rate off: {}/40", converted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jailer_makes_prisoners_defect_more_often() {
|
||||
let mut converted = 0;
|
||||
let mut worked = 0;
|
||||
for seed in 1..=40u32 {
|
||||
let mut g = Game::new(seed, 64, 64);
|
||||
g.gold = 5000.0;
|
||||
let (cx, cy) = g.map.core;
|
||||
g.build_rect(5, cx - 3, cy - 3, cx - 2, cy + 1);
|
||||
g.build_rect(1, cx + 4, cy - 3, cx + 5, cy - 2); // a lair for the jailer
|
||||
let jailer = g.spawn(EK::Jailer, false, (cx - 1) as f32 + 0.5, (cy - 3) as f32 + 0.5, 1);
|
||||
let id = g.spawn(EK::Knight, true, (cx + 4) as f32 + 0.5, (cy + 4) as f32 + 0.5, 1);
|
||||
let j = g.ents.iter().position(|e| e.id == id).unwrap();
|
||||
g.kill(j);
|
||||
for _ in 0..(200 * 20) {
|
||||
g.update(0.05);
|
||||
if let Some(jl) = g.ents.iter_mut().find(|e| e.id == jailer) {
|
||||
// keep the jailer fresh so that it works
|
||||
jl.hunger = 0.0;
|
||||
jl.energy = 1.0;
|
||||
jl.happy = 1.0;
|
||||
if jl.job == Job::Torment(id) {
|
||||
worked += 1;
|
||||
}
|
||||
}
|
||||
match g.ents.iter().find(|e| e.id == id) {
|
||||
Some(e) if !e.hero => {
|
||||
converted += 1;
|
||||
break;
|
||||
}
|
||||
None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("with a jailer: {} of 40 defected (jailer worked {} ticks)", converted, worked);
|
||||
assert!(worked > 0, "the jailer works on the prisoners");
|
||||
assert!(converted >= 20, "defections with a jailer: {}/40", converted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temple_prayer_and_priestess_heals_core() {
|
||||
let mut g = Game::new(9, 64, 64);
|
||||
|
||||
Reference in new issue
Block a user