Silver, ruby and diamond veins; treasury shows its treasures; announced payday

- New veins (appended kinds): silver (common), ruby (rare), diamond (very rare, far from the core);
  treasure caves contain gems; each treasure is worth a different amount per strike and per vein
- Imps carry one kind of treasure at a time
- Treasury tiles hold up to 500 each and show gold coins, silver bars, rubies or diamonds;
  spending empties random treasury tiles, the soul core keeps what does not fit
- Training no longer drains gold continuously: it is billed on the next payday
- Payday is announced 20 s before (message with the cost); the HUD shows a payday countdown and
  the cost of the next payday in red next to the gold counter
- Tests for the treasury and the payday bill

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 10:43:33 +01:00
1 parent 75dc445d3b
commit 7e84cae910
7 files changed
+362 -31

No files matched your search

+63 -8
View File
@@ -54,10 +54,13 @@ pub enum Kind {
Bridge, // wooden bridge over water (walkable)
LavaBridge, // stone bridge over lava (walkable)
Shard, // floor with a shard of the soul core (campaign)
Silver, // silver vein: common, little value
Ruby, // ruby vein: rare, valuable
Diamond, // diamond vein: very rare, very valuable
}
// order matters for the packed savegame codec: only append new kinds
const KINDS: [Kind; 20] = [
const KINDS: [Kind; 23] = [
Kind::Rock,
Kind::Earth,
Kind::Gold,
@@ -78,8 +81,16 @@ const KINDS: [Kind; 20] = [
Kind::Bridge,
Kind::LavaBridge,
Kind::Shard,
Kind::Silver,
Kind::Ruby,
Kind::Diamond,
];
// treasures (veins, treasury content, imp loads): 0 gold, 1 silver, 2 ruby, 3 diamond
pub const RES_GOLD: u8 = 0;
// value taken per pick strike for each treasure
pub const RES_STRIKE: [i32; 4] = [25, 20, 40, 60];
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum Owner {
None,
@@ -112,17 +123,29 @@ pub struct Tile {
pub var: u8, // texture variant
#[serde(rename = "rf", default)]
pub reinforced: bool, // wall reinforced by imps
#[serde(rename = "rs", default)]
pub res: u8, // treasury: kind of treasure stored here (see RES_GOLD)
}
impl Tile {
pub fn new(kind: Kind) -> Tile {
Tile { kind, owner: Owner::None, hp: 0.0, gold: 0, marked: false, reserved: 0, food: 0.0, bed: 0, var: 0, reinforced: false }
Tile { kind, owner: Owner::None, hp: 0.0, gold: 0, marked: false, reserved: 0, food: 0.0, bed: 0, var: 0, reinforced: false, res: 0 }
}
pub fn solid(&self) -> bool {
matches!(self.kind, Kind::Rock | Kind::Earth | Kind::Gold)
matches!(self.kind, Kind::Rock | Kind::Earth | Kind::Gold | Kind::Silver | Kind::Ruby | Kind::Diamond)
}
pub fn diggable(&self) -> bool {
matches!(self.kind, Kind::Earth | Kind::Gold)
matches!(self.kind, Kind::Earth | Kind::Gold | Kind::Silver | Kind::Ruby | Kind::Diamond)
}
// the treasure of a vein (None for everything else)
pub fn vein(&self) -> Option<u8> {
match self.kind {
Kind::Gold => Some(0),
Kind::Silver => Some(1),
Kind::Ruby => Some(2),
Kind::Diamond => Some(3),
_ => None,
}
}
pub fn walkable(&self) -> bool {
matches!(
@@ -329,7 +352,7 @@ impl Map {
if d <= (r as f32 + 0.5).powi(2) - rng.f() * r as f32 {
let tl = &mut t[y as usize * w + x as usize];
tl.kind = k;
if k == Kind::Gold {
if tl.vein().is_some() {
tl.gold = rng.range(gold.0, gold.1);
}
}
@@ -346,6 +369,21 @@ impl Map {
let r = rng.range(1, 2);
blob(&mut t, &mut rng, cx, cy, r, Kind::Gold, (250, 550));
}
// more treasures: silver is common, rubies are rare, diamonds very rare and far away
for _ in 0..scale(7.0) {
let (cx, cy) = (rng.range(4, wi - 5), rng.range(4, hi - 5));
let r = rng.range(1, 2);
blob(&mut t, &mut rng, cx, cy, r, Kind::Silver, (200, 400));
}
for _ in 0..scale(3.0) {
let (cx, cy) = (rng.range(wi / 4, wi - 5), rng.range(4, hi - 5));
blob(&mut t, &mut rng, cx, cy, 1, Kind::Ruby, (500, 800));
}
for _ in 0..scale(1.2).max(1) {
let (cx, cy) = (rng.range(wi / 3, wi - 5), rng.range(4, hi - 5));
let r = rng.range(0, 1);
blob(&mut t, &mut rng, cx, cy, r, Kind::Diamond, (1000, 1500));
}
// guaranteed gold near the start
for k in 0..3 {
let cx = core.0 + rng.range(-3, 9);
@@ -374,6 +412,22 @@ impl Map {
// treasure caves: a cave surrounded by very rich gold veins (guarded by heroes, see Game::new)
for &(cx, cy) in &treasures {
blob(&mut t, &mut rng, cx, cy, 4, Kind::Gold, (1000, 1600));
// some of the riches are gems
for y in cy - 4..=cy + 4 {
for x in cx - 4..=cx + 4 {
if inner(x, y) && t[y as usize * w + x as usize].kind == Kind::Gold {
let r = rng.f();
let tl = &mut t[y as usize * w + x as usize];
if r < 0.08 {
tl.kind = Kind::Diamond;
tl.gold = rng.range(1200, 1800);
} else if r < 0.3 {
tl.kind = Kind::Ruby;
tl.gold = rng.range(800, 1200);
}
}
}
}
room(&mut t, cx - 2, cy - 2, cx + 2, cy + 2, Owner::None);
}
// starting area and core
@@ -404,7 +458,7 @@ impl Map {
}
}
for tl in t.iter_mut() {
if tl.kind == Kind::Gold {
if tl.vein().is_some() {
tl.hp = 6.0;
}
}
@@ -477,7 +531,7 @@ impl Map {
match tl.kind {
Kind::Earth if tl.reinforced => Some(70),
Kind::Earth => Some(7),
Kind::Gold => Some(12),
Kind::Gold | Kind::Silver | Kind::Ruby | Kind::Diamond => Some(12),
Kind::Rock | Kind::Core | Kind::Tower | Kind::Water | Kind::Lava => None,
_ => Some(1),
}
@@ -552,7 +606,7 @@ fn tile_record(t: &Tile) -> [u8; REC] {
let mut r = [0u8; REC];
r[0] = KINDS.iter().position(|&k| k == t.kind).unwrap_or(0) as u8;
r[1] = OWNERS.iter().position(|&o| o == t.owner).unwrap_or(0) as u8;
r[2] = t.marked as u8 | (t.reinforced as u8) << 1;
r[2] = t.marked as u8 | (t.reinforced as u8) << 1 | (t.res & 3) << 2;
r[3..7].copy_from_slice(&t.hp.to_le_bytes());
r[7..11].copy_from_slice(&t.gold.to_le_bytes());
r[11..15].copy_from_slice(&t.food.to_le_bytes());
@@ -616,6 +670,7 @@ fn tiles_de<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Tile>, D::Error> {
owner: *OWNERS.get(r[1] as usize).unwrap_or(&Owner::None),
marked: r[2] & 1 != 0,
reinforced: r[2] & 2 != 0,
res: (r[2] >> 2) & 3,
hp: f32::from_le_bytes(f(3)),
gold: i32::from_le_bytes(f(7)),
food: f32::from_le_bytes(f(11)),