Dig marks in the colour of the master who set them; every master's dungeon is lit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 08:56:36 +01:00
1 parent 5121a2109b
commit 450e55d693
5 files changed
+65 -12

No files matched your search

+3
View File
@@ -99,6 +99,7 @@ impl Game {
continue;
}
self.map.t[i].marked = true;
self.map.t[i].mark_by = self.me.side().min(7);
added += 1;
}
}
@@ -150,6 +151,7 @@ impl Game {
let t = self.map.t[i];
if t.diggable() && !t.plant() && t.kind != Kind::Steel && (t.owner == Owner::None || t.owner == me || t.reinforced) {
self.map.t[i].marked = true;
self.map.t[i].mark_by = self.me.side().min(7);
if t.vein().is_some() {
cluster.push(i);
}
@@ -162,6 +164,7 @@ impl Game {
if let Some(t) = self.map.get(x + dx, y + dy) {
if t.vein().is_some() && t.owner == Owner::None {
self.map.t[idx(x + dx, y + dy)].marked = true;
self.map.t[idx(x + dx, y + dy)].mark_by = self.me.side().min(7);
}
}
}
+1
View File
@@ -4853,6 +4853,7 @@ impl Game {
let t = &mut self.map.t[idx(x, y)];
if t.diggable() {
t.marked = on;
t.mark_by = self.me.side().min(7);
}
}
}
+5 -2
View File
@@ -217,11 +217,13 @@ pub struct Tile {
pub reinforced: bool, // wall reinforced by imps
#[serde(rename = "rs", default)]
pub res: u8, // treasury: kind of treasure stored here (see RES_GOLD)
#[serde(rename = "mb", default)]
pub mark_by: u8, // side of the master who marked it for digging (its colour)
}
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, res: 0 }
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, mark_by: 0 }
}
pub fn solid(&self) -> bool {
matches!(self.kind, Kind::Rock | Kind::Earth | Kind::Gold | Kind::Silver | Kind::Ruby | Kind::Diamond | Kind::Steel)
@@ -865,7 +867,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 | (t.res & 3) << 2;
r[2] = t.marked as u8 | (t.reinforced as u8) << 1 | (t.res & 3) << 2 | (t.mark_by & 7) << 4;
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());
@@ -930,6 +932,7 @@ fn tiles_de<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Tile>, D::Error> {
marked: r[2] & 1 != 0,
reinforced: r[2] & 2 != 0,
res: (r[2] >> 2) & 3,
mark_by: (r[2] >> 4) & 7,
hp: f32::from_le_bytes(f(3)),
gold: i32::from_le_bytes(f(7)),
food: f32::from_le_bytes(f(11)),
+40 -10
View File
@@ -501,7 +501,7 @@ fn ambient_field(map: &Map) -> Vec<f32> {
let mut f = vec![0.0f32; n()];
for (i, t) in map.t.iter().enumerate() {
f[i] = match (t.solid(), t.owner) {
(false, Owner::Player) => 1.0,
(false, Owner::Player | Owner::Rival1 | Owner::Rival2 | Owner::Rival3) => 1.0,
(false, Owner::Hero) => 0.6,
_ => 0.0,
};
@@ -666,12 +666,34 @@ fn edge_pts(a: Vec3, b: Vec3, n: usize) -> Vec<Vec3> {
.collect()
}
const MARK_FILL: [u8; 4] = [255, 205, 50, 70];
const MARK_LINE: [u8; 4] = [255, 220, 70, 235];
thread_local! {
// the side of the master looking at the map (own dig marks are yellow)
static VIEW_SIDE: std::cell::Cell<u8> = const { std::cell::Cell::new(0) };
}
// Glowing yellow box around a block that is marked for digging
fn mark_block(mk: &mut MB, map: &Map, x: i32, z: i32, n: usize) {
block_shell(mk, map, x, z, n, MARK_FILL, MARK_LINE, 1.0, 0.03);
fn view_side() -> u8 {
VIEW_SIDE.with(|v| v.get())
}
// colour of a dig mark: own marks yellow, other masters' marks in their colour
fn mark_rgb(by: u8) -> [u8; 3] {
if by == view_side() {
return [255, 205, 50];
}
match by {
0 => [235, 90, 125], // the first master, seen by another one
2 => [80, 240, 110], // green rival
3 => [100, 150, 255], // blue rival
4 => [255, 150, 50], // the fourth master
_ => [255, 205, 50],
}
}
// Glowing box in the marker's colour around a block that is marked for digging
fn mark_block(mk: &mut MB, map: &Map, x: i32, z: i32, n: usize, by: u8) {
let [r, g, b] = mark_rgb(by);
let line = [r, (g as u16 + 15).min(255) as u8, (b as u16 + 20).min(255) as u8, 235];
block_shell(mk, map, x, z, n, [r, g, b, 70], line, 1.0, 0.03);
}
// Colored, slightly lifted copy of a displaced face (same grid and displacement as the wall)
@@ -748,7 +770,7 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
// fog of war: a black block, with black sides towards known open ground;
// tiles marked for digging glow dark yellow so the plan stays visible
let black = col(Vec3::splat(0.0), 1.0);
let top = if t.marked { col(vec3(0.42, 0.33, 0.06), 1.0) } else { black };
let top = if t.marked && t.mark_by == view_side() { col(vec3(0.42, 0.33, 0.06), 1.0) } else { black };
mb.quad([vec3(xf, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf + 1.0), vec3(xf, WALL_H, zf + 1.0)], [ua, ua, ua, ua], [top; 4], [Vec3::Y; 4]);
for (d, &(dx, dz)) in DIR4.iter().enumerate() {
let open = map.get(x + dx, z + dz).map_or(false, |o| !o.solid()) && map.shown(idx((x + dx).max(0), (z + dz).max(0)));
@@ -1045,7 +1067,7 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
}
// vertex budget: one draw call holds at most 40000 vertices
if t.marked && mk.v.len() < 34000 {
mark_block(&mut mk, map, x, z, n);
mark_block(&mut mk, map, x, z, n, t.mark_by);
}
for (d, &(dx, dz)) in DIR4.iter().enumerate() {
if solid_at(map, x + dx, z + dz) {
@@ -1182,7 +1204,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
_ => 0,
};
let shown = map.shown(idx(x, z)) as u64 | (map.sky(idx(x, z)) as u64) << 1;
(t.kind as u64) | (t.owner as u64) << 5 | (t.marked as u64) << 8 | (t.reinforced as u64) << 9 | shown << 10 | extra << 11
(t.kind as u64) | (t.owner as u64) << 5 | (t.marked as u64 | (t.mark_by as u64) << 50) << 8 | (t.reinforced as u64) << 9 | shown << 10 | extra << 11
}
None => 0xff,
};
@@ -1235,6 +1257,14 @@ impl R3d {
// Rebuilds visible chunks whose content changed (off-screen chunks are updated when they appear)
fn update_chunks(&mut self, g: &Game, gfx: &Gfx, m: &Mat4) {
self.ensure_chunks();
let side = g.me.side().min(7);
if view_side() != side {
// another master looks: the marks change colour
VIEW_SIDE.with(|v| v.set(side));
for ch in self.chunks.iter_mut() {
ch.sig = 0;
}
}
let mut amb: Option<Vec<f32>> = None;
let mut lava_fresh = false;
for cz in 0..self.ncz {
@@ -1573,7 +1603,7 @@ impl R3d {
let toward = cam.eye_dir();
let amb = |x: f32, z: f32| {
let (ix, iz) = (x as i32, z as i32);
let own = g.map.get(ix, iz).map_or(false, |t| t.owner == Owner::Player);
let own = g.map.get(ix, iz).map_or(false, |t| matches!(t.owner, Owner::Player | Owner::Rival1 | Owner::Rival2 | Owner::Rival3));
if inb(ix, iz) && g.map.sky(idx(ix, iz)) {
0.95
} else if own {
+16
View File
@@ -447,6 +447,22 @@ mod tests {
assert!(g.torches.iter().any(|t| g.map.t[t.wall].owner == owner || g.map.t[t.wall].solid()), "torches");
}
#[test]
fn ai_dig_marks_carry_the_masters_side() {
let mut g = rival_game(1);
let side = g.rivals[0].side;
for _ in 0..(40 * 20) {
g.update(0.05);
}
assert!(g.map.t.iter().any(|t| t.marked && t.mark_by == side), "the AI marks in its own colour");
let back = crate::save::from_bytes(crate::save::to_json(&g).as_bytes()).ok().expect("loads").game;
assert_eq!(
back.map.t.iter().filter(|t| t.marked && t.mark_by == side).count(),
g.map.t.iter().filter(|t| t.marked && t.mark_by == side).count(),
"kept in the save"
);
}
#[test]
fn beating_the_rival_wins_the_free_game() {
let mut g = rival_game(1);