Creatures report at the portal and set up their bed; payday wages are fetched from the treasury
- New creatures report at the portal (2 s), walk to the lair (flyers: aerie), set up their bed (4 s) and only then do as they like; without a reachable lair they start right away - Payday: the training bill is paid at once; every creature goes for its wage at a random moment within about a minute, to the nearest treasury tile holding treasure (or the core), and takes it out of that tile; no gold = an angry creature; unreachable = paid anyway - Tests for the arrival and the walking payday Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f13ab1a629
commit
52c4d1caf8
3 files changed
+211
-26
No files matched your search
@@ -571,3 +571,8 @@ account.users_changed = Benutzer aktualisiert.
|
|||||||
standalone.running = Die Standalone-Version wird vorbereitet ...
|
standalone.running = Die Standalone-Version wird vorbereitet ...
|
||||||
standalone.done = DungeonCraft.html heruntergeladen: In jedem Browser öffnen, kein Server nötig. Deine heruntergeladenen Spielstände lassen sich dort auch laden.
|
standalone.done = DungeonCraft.html heruntergeladen: In jedem Browser öffnen, kein Server nötig. Deine heruntergeladenen Spielstände lassen sich dort auch laden.
|
||||||
standalone.failed = Die Standalone-Version konnte nicht heruntergeladen werden.
|
standalone.failed = Die Standalone-Version konnte nicht heruntergeladen werden.
|
||||||
|
job.register = Meldet sich am Portal
|
||||||
|
job.make_bed = Richtet sein Bett ein
|
||||||
|
job.fetch_wage = Holt sich seinen Lohn
|
||||||
|
msg.payday_walk = Zahltag! {0} Kreaturen holen sich ihren Lohn aus der Schatzkammer ({1} Gold inkl. Training).
|
||||||
|
msg.wage_unpaid = Dein {0} hat kein Gold für seinen Lohn gefunden und ist wütend!
|
||||||
@@ -572,3 +572,8 @@ account.users_changed = Users updated.
|
|||||||
standalone.running = Preparing the standalone version ...
|
standalone.running = Preparing the standalone version ...
|
||||||
standalone.done = DungeonCraft.html downloaded: open it in any browser, no server needed. Your downloaded savegames load there too.
|
standalone.done = DungeonCraft.html downloaded: open it in any browser, no server needed. Your downloaded savegames load there too.
|
||||||
standalone.failed = The standalone version could not be downloaded.
|
standalone.failed = The standalone version could not be downloaded.
|
||||||
|
job.register = Reporting at the portal
|
||||||
|
job.make_bed = Setting up its bed
|
||||||
|
job.fetch_wage = Fetching its wage
|
||||||
|
msg.payday_walk = Payday! {0} creatures fetch their wages from the treasury ({1} gold incl. training).
|
||||||
|
msg.wage_unpaid = Your {0} found no gold for its wage and is furious!
|
||||||
+201
-26
@@ -171,6 +171,9 @@ pub enum Job {
|
|||||||
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
|
Torment(u32), // the jailer works on a prisoner in the torture chamber
|
||||||
|
Register, // new creature: reports at the portal
|
||||||
|
MakeBed(usize), // new creature: sets up its bed
|
||||||
|
FetchWage(usize), // payday: walks to the treasury (or the core) for the wage
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +231,16 @@ pub struct Ent {
|
|||||||
pub patrol: Vec<(i32, i32)>, // fortress patrol: route of tiles, walked in a loop
|
pub patrol: Vec<(i32, i32)>, // fortress patrol: route of tiles, walked in a loop
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub patrol_k: usize, // next point of the route
|
pub patrol_k: usize, // next point of the route
|
||||||
|
#[serde(default = "yes")]
|
||||||
|
pub settled: bool, // creatures: reported at the portal and set up their bed
|
||||||
|
#[serde(default)]
|
||||||
|
pub pay_due: f32, // wage of the last payday not fetched yet
|
||||||
|
#[serde(default)]
|
||||||
|
pub pay_t: f32, // seconds until the creature goes for its wage
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yes() -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ent {
|
impl Ent {
|
||||||
@@ -273,6 +286,9 @@ impl Ent {
|
|||||||
home: None,
|
home: None,
|
||||||
patrol: Vec::new(),
|
patrol: Vec::new(),
|
||||||
patrol_k: 0,
|
patrol_k: 0,
|
||||||
|
settled: true,
|
||||||
|
pay_due: 0.0,
|
||||||
|
pay_t: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn tile(&self) -> usize {
|
pub fn tile(&self) -> usize {
|
||||||
@@ -1147,44 +1163,140 @@ impl Game {
|
|||||||
let owned: Vec<usize> = (0..self.map.portals.len()).filter(|&k| self.map.portal_owned(k)).collect();
|
let owned: Vec<usize> = (0..self.map.portals.len()).filter(|&k| self.map.portal_owned(k)).collect();
|
||||||
let pk = owned[(self.rng.next() as usize) % owned.len()];
|
let pk = owned[(self.rng.next() as usize) % owned.len()];
|
||||||
let (px, py) = self.map.portals[pk];
|
let (px, py) = self.map.portals[pk];
|
||||||
self.spawn(kind, false, px as f32 + 1.5, py as f32 + 1.5, 1);
|
let id = self.spawn(kind, false, px as f32 + 1.5, py as f32 + 1.5, 1);
|
||||||
|
// new creatures report at the portal and set up their bed first
|
||||||
|
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||||
|
e.settled = false;
|
||||||
|
e.job = Job::Register;
|
||||||
|
e.timer = 2.0;
|
||||||
|
}
|
||||||
self.fx(3, px as f32 + 1.5, py as f32 + 1.5, 0.0, 0.0, 0.8, 0xffcc44ff);
|
self.fx(3, px as f32 + 1.5, py as f32 + 1.5, 0.0, 0.0, 0.8, 0xffcc44ff);
|
||||||
self.sfx(Sfx::PortalArrive, px as f32 + 1.5, py as f32 + 1.5);
|
self.sfx(Sfx::PortalArrive, px as f32 + 1.5, py as f32 + 1.5);
|
||||||
self.msg("msg.creature_arrived", vec![format!("@{}", st(kind).name)]);
|
self.msg("msg.creature_arrived", vec![format!("@{}", st(kind).name)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Payday: the training bill is paid at once; every creature fetches its wage from a
|
||||||
|
// treasury itself, at a random moment within about a minute
|
||||||
fn do_payday(&mut self) {
|
fn do_payday(&mut self) {
|
||||||
let mut paid = 0;
|
let bill = self.training_bill.min(self.gold).max(0.0);
|
||||||
let mut unpaid = 0;
|
self.gold -= bill;
|
||||||
let mut total = 0.0;
|
self.training_bill = 0.0;
|
||||||
|
let mut n = 0;
|
||||||
|
let mut total = bill;
|
||||||
for i in 0..self.ents.len() {
|
for i in 0..self.ents.len() {
|
||||||
if !self.ents[i].alive || !self.ents[i].creature() {
|
if !self.ents[i].alive || !self.ents[i].creature() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let e = &self.ents[i];
|
let r = self.rng.f();
|
||||||
let w = st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32);
|
|
||||||
if self.gold >= w {
|
|
||||||
self.gold -= w;
|
|
||||||
total += w;
|
|
||||||
paid += 1;
|
|
||||||
let e = &mut self.ents[i];
|
let e = &mut self.ents[i];
|
||||||
e.happy = (e.happy + 0.2).min(1.0);
|
let w = st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32);
|
||||||
} else {
|
e.pay_due += w;
|
||||||
unpaid += 1;
|
e.pay_t = r * 55.0;
|
||||||
self.ents[i].happy -= 0.45;
|
n += 1;
|
||||||
|
total += w;
|
||||||
}
|
}
|
||||||
}
|
if n > 0 || bill > 0.0 {
|
||||||
// training costs collected since the last payday
|
|
||||||
let bill = self.training_bill.min(self.gold).max(0.0);
|
|
||||||
self.gold -= bill;
|
|
||||||
total += bill;
|
|
||||||
self.training_bill = 0.0;
|
|
||||||
if paid + unpaid > 0 {
|
|
||||||
self.sfx_ui(Sfx::Payday);
|
self.sfx_ui(Sfx::Payday);
|
||||||
if unpaid > 0 {
|
self.msg("msg.payday_walk", vec![n.to_string(), (total as i32).to_string()]);
|
||||||
self.msg("msg.payday_partial", vec![paid.to_string(), (total as i32).to_string(), unpaid.to_string()]);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a creature goes for its wage: the nearest treasury tile holding treasure, else the core
|
||||||
|
fn start_fetch_wage(&mut self, e: &mut Ent) {
|
||||||
|
let cur = e.tile();
|
||||||
|
let goal = |i: usize| {
|
||||||
|
let t = &self.map.t[i];
|
||||||
|
(t.kind == Kind::Treasury && t.owner == Owner::Player && t.gold > 0) || (self.counts[0] == 0 && self.map.adj_core(i))
|
||||||
|
};
|
||||||
|
let found = if flies(e.kind) { self.map.search(cur, self.map.fly_cost(), goal, true) } else { self.map.search(cur, self.map.walk_cost(), goal, true) };
|
||||||
|
match found {
|
||||||
|
Some(p) => {
|
||||||
|
let t = p.last().copied().unwrap_or(cur);
|
||||||
|
e.path = p;
|
||||||
|
e.job = Job::FetchWage(t);
|
||||||
|
}
|
||||||
|
// no way to a treasury: the wage is paid out anyway
|
||||||
|
None => self.take_wage(e, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// takes the wage out of the treasury tile (the rest from elsewhere); unpaid = angry
|
||||||
|
fn take_wage(&mut self, e: &mut Ent, at: Option<usize>) {
|
||||||
|
let mut need = e.pay_due;
|
||||||
|
if let Some(t) = at {
|
||||||
|
let tile = &mut self.map.t[t];
|
||||||
|
if tile.kind == Kind::Treasury {
|
||||||
|
let take = (tile.gold as f32).min(need).floor();
|
||||||
|
tile.gold -= take as i32;
|
||||||
|
self.gold -= take;
|
||||||
|
need -= take;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rest = need.min(self.gold.max(0.0));
|
||||||
|
self.gold -= rest;
|
||||||
|
need -= rest;
|
||||||
|
e.pay_due = 0.0;
|
||||||
|
if need > 0.5 {
|
||||||
|
e.happy -= 0.45;
|
||||||
|
self.msg("msg.wage_unpaid", vec![format!("@{}", st(e.kind).name)]);
|
||||||
|
self.sfx(Sfx::Error, e.x, e.y);
|
||||||
} else {
|
} else {
|
||||||
self.msg("msg.payday", vec![paid.to_string(), (total as i32).to_string()]);
|
e.happy = (e.happy + 0.2).min(1.0);
|
||||||
|
self.fx(5, e.x, e.y, 0.0, 0.0, 0.8, 0xff30d0ff);
|
||||||
|
self.sfx(Sfx::Deposit, e.x, e.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a new creature: report at the portal, go to the lair (aerie), set up the bed
|
||||||
|
fn settle(&mut self, e: &mut Ent, dt: f32) {
|
||||||
|
let me = e.id + 1;
|
||||||
|
match e.job {
|
||||||
|
Job::Register => {
|
||||||
|
e.working = true;
|
||||||
|
e.timer -= dt;
|
||||||
|
if e.timer <= 0.0 {
|
||||||
|
let cur = e.tile();
|
||||||
|
let bed_kind = if flies(e.kind) { Kind::Aerie } else { Kind::Lair };
|
||||||
|
let free = |i: usize| self.map.t[i].kind == bed_kind && self.map.t[i].bed == 0;
|
||||||
|
let found = if flies(e.kind) { self.map.search(cur, self.map.fly_cost(), free, true) } else { self.map.search(cur, self.map.walk_cost(), free, true) };
|
||||||
|
match found {
|
||||||
|
Some(p) => {
|
||||||
|
let b = p.last().copied().unwrap_or(cur);
|
||||||
|
self.map.t[b].bed = me;
|
||||||
|
e.bed = Some(b);
|
||||||
|
e.path = p;
|
||||||
|
e.job = Job::MakeBed(b);
|
||||||
|
e.timer = 4.0;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
e.settled = true;
|
||||||
|
e.job = Job::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Job::MakeBed(b) => {
|
||||||
|
if self.step_path(e, dt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.map.t[b].bed != me {
|
||||||
|
e.settled = true;
|
||||||
|
e.job = Job::None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.working = true;
|
||||||
|
e.timer -= dt;
|
||||||
|
if e.timer <= 0.0 {
|
||||||
|
e.settled = true;
|
||||||
|
e.job = Job::None;
|
||||||
|
let (bx, by) = xy(b);
|
||||||
|
self.fx(8, bx as f32 + 0.5, by as f32 + 0.5, 0.0, 0.0, 0.6, 0xff40d0ff);
|
||||||
|
self.sfx(Sfx::Build, bx as f32 + 0.5, by as f32 + 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
e.job = Job::Register;
|
||||||
|
e.timer = 2.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2519,6 +2631,28 @@ impl Game {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// arrival: report at the portal and set up the bed before anything else
|
||||||
|
if !e.settled {
|
||||||
|
self.settle(e, dt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// payday: the wage is fetched from a treasury
|
||||||
|
if e.pay_due > 0.0 {
|
||||||
|
e.pay_t -= dt;
|
||||||
|
if e.pay_t <= 0.0 && !matches!(e.job, Job::FetchWage(_) | Job::Leave | Job::Rest(_)) {
|
||||||
|
self.unreserve(me);
|
||||||
|
self.start_fetch_wage(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Job::FetchWage(t) = e.job {
|
||||||
|
if self.step_path(e, dt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.take_wage(e, Some(t));
|
||||||
|
e.job = Job::None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Job::Rest(b) = e.job {
|
if let Job::Rest(b) = e.job {
|
||||||
if self.step_path(e, dt) {
|
if self.step_path(e, dt) {
|
||||||
return;
|
return;
|
||||||
@@ -4159,6 +4293,9 @@ pub fn job_name(e: &Ent) -> &'static str {
|
|||||||
Job::TearBridge(_) => "job.tear_bridge",
|
Job::TearBridge(_) => "job.tear_bridge",
|
||||||
Job::Rest(_) => "job.rest",
|
Job::Rest(_) => "job.rest",
|
||||||
Job::Torment(_) => "job.torment",
|
Job::Torment(_) => "job.torment",
|
||||||
|
Job::Register => "job.register",
|
||||||
|
Job::MakeBed(_) => "job.make_bed",
|
||||||
|
Job::FetchWage(_) => "job.fetch_wage",
|
||||||
Job::Help(_) => "job.help",
|
Job::Help(_) => "job.help",
|
||||||
Job::AttackCore => {
|
Job::AttackCore => {
|
||||||
if e.working {
|
if e.working {
|
||||||
@@ -4382,7 +4519,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn training_is_billed_on_payday() {
|
fn training_is_billed_on_payday() {
|
||||||
let mut g = Game::new(3, 64, 64);
|
let mut g = Game::new(3, 64, 64);
|
||||||
g.spawn(EK::Kobold, false, 10.0, 10.0, 1);
|
let (cx, cy) = g.map.core;
|
||||||
|
g.spawn(EK::Kobold, false, (cx + 4) as f32 + 0.5, (cy + 4) as f32 + 0.5, 1);
|
||||||
g.training_bill = 50.0;
|
g.training_bill = 50.0;
|
||||||
let cost = g.next_payday_cost();
|
let cost = g.next_payday_cost();
|
||||||
assert_eq!(cost, 50.0 + st(EK::Kobold).wage as f32);
|
assert_eq!(cost, 50.0 + st(EK::Kobold).wage as f32);
|
||||||
@@ -4392,8 +4530,45 @@ mod tests {
|
|||||||
assert!(g.payday_warned);
|
assert!(g.payday_warned);
|
||||||
g.payday = 0.01;
|
g.payday = 0.01;
|
||||||
g.update(0.1);
|
g.update(0.1);
|
||||||
|
// the training bill is paid at once, the wage when the kobold fetches it
|
||||||
assert_eq!(g.training_bill, 0.0);
|
assert_eq!(g.training_bill, 0.0);
|
||||||
assert!((gold - g.gold - cost).abs() < 1.0);
|
assert!((gold - g.gold - 50.0).abs() < 1.0);
|
||||||
|
let mut fetched = false;
|
||||||
|
for _ in 0..(70 * 20) {
|
||||||
|
g.update(0.05);
|
||||||
|
fetched |= g.ents.iter().any(|e| matches!(e.job, Job::FetchWage(_)));
|
||||||
|
}
|
||||||
|
assert!(fetched, "the kobold walks to the treasury");
|
||||||
|
assert!((gold - g.gold - cost).abs() < 1.0, "paid {}", gold - g.gold);
|
||||||
|
assert!(g.ents.iter().all(|e| e.pay_due == 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_creatures_report_and_set_up_their_bed() {
|
||||||
|
let mut g = Game::new(3, 64, 64);
|
||||||
|
let (cx, cy) = g.map.core;
|
||||||
|
g.gold = 3000.0;
|
||||||
|
g.build_rect(1, cx - 3, cy - 3, cx - 2, cy + 1);
|
||||||
|
g.build_rect(2, cx + 4, cy - 3, cx + 5, cy + 1);
|
||||||
|
// arrives like from a portal (the test map's portal is not connected yet)
|
||||||
|
let id = g.spawn(EK::Kobold, false, (cx + 4) as f32 + 0.5, (cy + 4) as f32 + 0.5, 1);
|
||||||
|
if let Some(e) = g.ents.iter_mut().find(|e| e.id == id) {
|
||||||
|
e.settled = false;
|
||||||
|
e.job = Job::Register;
|
||||||
|
e.timer = 2.0;
|
||||||
|
}
|
||||||
|
let mut made_bed = false;
|
||||||
|
for _ in 0..(90 * 20) {
|
||||||
|
g.update(0.05);
|
||||||
|
let e = g.ents.iter().find(|e| e.id == id).unwrap();
|
||||||
|
made_bed |= matches!(e.job, Job::MakeBed(_)) && e.path.is_empty();
|
||||||
|
if e.settled {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let e = g.ents.iter().find(|e| e.id == id).unwrap();
|
||||||
|
assert!(made_bed && e.settled);
|
||||||
|
assert_eq!(g.map.t[e.bed.unwrap()].kind, Kind::Lair);
|
||||||
}
|
}
|
||||||
|
|
||||||
// a fortress east of the start area, joined to it by a claimed corridor
|
// a fortress east of the start area, joined to it by a claimed corridor
|
||||||
|
|||||||
Reference in new issue
Block a user