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
@@ -527,3 +527,4 @@ unit.gargoyle = Kettengargoyle
|
|||||||
unit.owl = Arkane Eule
|
unit.owl = Arkane Eule
|
||||||
unit.wyrm = Glutwyrm
|
unit.wyrm = Glutwyrm
|
||||||
job.rest = Erholt sich im Horst
|
job.rest = Erholt sich im Horst
|
||||||
|
job.torment = Bearbeitet einen Gefangenen
|
||||||
@@ -528,3 +528,4 @@ unit.gargoyle = Chain Gargoyle
|
|||||||
unit.owl = Arcane Owl
|
unit.owl = Arcane Owl
|
||||||
unit.wyrm = Ember Wyrm
|
unit.wyrm = Ember Wyrm
|
||||||
job.rest = Recovering in the aerie
|
job.rest = Recovering in the aerie
|
||||||
|
job.torment = Working on a prisoner
|
||||||
+77
-2
@@ -170,6 +170,7 @@ pub enum Job {
|
|||||||
HealCore, // flame priestess heals the soul core
|
HealCore, // flame priestess heals the soul core
|
||||||
TearBridge(usize), // knight tears down a bridge of the player
|
TearBridge(usize), // knight tears down a bridge of the player
|
||||||
Rest(usize), // flying creature recovers in the aerie (flees when badly hurt)
|
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
|
Help(usize), // imp with nothing else to do helps another imp at this tile
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2541,6 +2542,21 @@ impl Game {
|
|||||||
e.job = Job::None;
|
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) => {
|
Job::Train(t) => {
|
||||||
// training is billed on payday; it stops when the bill would exceed the gold
|
// 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 {
|
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();
|
let r = self.rng.f();
|
||||||
if (e.kind == EK::Mage && r < 0.8) || r < 0.2 {
|
if (e.kind == EK::Mage && r < 0.8) || r < 0.2 {
|
||||||
if let Some(p) = self.find_room_tile(cur, Kind::Library, me, false) {
|
if let Some(p) = self.find_room_tile(cur, Kind::Library, me, false) {
|
||||||
@@ -2891,8 +2920,13 @@ impl Game {
|
|||||||
e.torture_t += dt;
|
e.torture_t += dt;
|
||||||
if e.torture_t >= 5.0 {
|
if e.torture_t >= 5.0 {
|
||||||
e.torture_t -= 5.0;
|
e.torture_t -= 5.0;
|
||||||
// ~40% defect over the whole torture (paladins ~25%)
|
// ~40% defect over the whole torture (paladins ~25%); a jailer working on
|
||||||
let p = if e.kind == EK::Paladin { 0.0143 } else { 0.0252 };
|
// 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 {
|
if self.rng.f() < p {
|
||||||
self.defect(e);
|
self.defect(e);
|
||||||
return;
|
return;
|
||||||
@@ -4066,6 +4100,7 @@ pub fn job_name(e: &Ent) -> &'static str {
|
|||||||
Job::Attack(_) => "job.fight",
|
Job::Attack(_) => "job.fight",
|
||||||
Job::TearBridge(_) => "job.tear_bridge",
|
Job::TearBridge(_) => "job.tear_bridge",
|
||||||
Job::Rest(_) => "job.rest",
|
Job::Rest(_) => "job.rest",
|
||||||
|
Job::Torment(_) => "job.torment",
|
||||||
Job::Help(_) => "job.help",
|
Job::Help(_) => "job.help",
|
||||||
Job::AttackCore => {
|
Job::AttackCore => {
|
||||||
if e.working {
|
if e.working {
|
||||||
@@ -4705,6 +4740,46 @@ mod tests {
|
|||||||
assert!((8..=26).contains(&converted), "defect rate off: {}/40", converted);
|
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]
|
#[test]
|
||||||
fn temple_prayer_and_priestess_heals_core() {
|
fn temple_prayer_and_priestess_heals_core() {
|
||||||
let mut g = Game::new(9, 64, 64);
|
let mut g = Game::new(9, 64, 64);
|
||||||
|
|||||||
Reference in new issue
Block a user