diff --git a/src/ai.rs b/src/ai.rs index 9559be4..e2a9583 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -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); } } } diff --git a/src/game.rs b/src/game.rs index 90232f7..5e5d79b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -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); } } } diff --git a/src/map.rs b/src/map.rs index a061e7a..3c2ccea 100644 --- a/src/map.rs +++ b/src/map.rs @@ -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, 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)), diff --git a/src/render3d.rs b/src/render3d.rs index 9ab9bd6..5ea5794 100644 --- a/src/render3d.rs +++ b/src/render3d.rs @@ -501,7 +501,7 @@ fn ambient_field(map: &Map) -> Vec { 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 { .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 = 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> = 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 { diff --git a/src/rival.rs b/src/rival.rs index 111c577..0489a05 100644 --- a/src/rival.rs +++ b/src/rival.rs @@ -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);