From 05ba88359e54178f46df5529443be5cd4b264933 Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Tue, 3 Nov 2026 08:58:35 +0100 Subject: [PATCH] Planned hero entry points: walls glow and pulse red, with a pulsing red light Co-Authored-By: Claude Opus 5.5 --- DESIGN.md | 2 +- src/render3d.rs | 32 ++++++++++++++++++++++++++++++++ src/save.rs | 20 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/DESIGN.md b/DESIGN.md index bc80721..ede93ed 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -82,7 +82,7 @@ An original dungeon management game (genre: dungeon building & defence) that run - Each wave picks an **entry point** on the border of the realm facing the hero gate: few towers/creatures, no reinforced walls and a short way are attractive, entry points of recent waves are avoided, plus some randomness - Waves of **6+ heroes split into two groups** with different entry points (the paladin goes with the bigger group) - Heroes first tunnel to their entry point, then go for the core -- **Warning** 30 s before: scout message with the direction(s) and blinking markers on the minimap +- **Warning** 30 s before: scout message with the direction(s), blinking markers on the minimap and the **walls at the entry points glowing and pulsing red** (plus a pulsing red light) until the wave has broken in ## Goal - **Victory**: destroy the Hero Gate diff --git a/src/render3d.rs b/src/render3d.rs index 200c6f4..4d617c3 100644 --- a/src/render3d.rs +++ b/src/render3d.rs @@ -807,6 +807,10 @@ impl R3d { let (gx, gz) = g.gate_center(); push(vec3(gx, 1.2, gz), 7.0, vec3(0.75, 0.85, 1.0), 1.0, 50.0); } + let pulse = 0.5 + 0.5 * (t * 4.0).sin(); + for &(ex, ey) in &g.planned_entries { + push(vec3(ex as f32 + 0.5, 1.2, ey as f32 + 0.5), 5.0, vec3(1.0, 0.12, 0.08), 0.6 + 1.0 * pulse, 30.0); + } for tw in &g.towers { if tw.kind == TowerKind::Magic { let (x, z) = tw.pos(); @@ -935,6 +939,7 @@ impl R3d { draw_flag(f, t); } draw_overlay(&g.map, ov, self.wall_sub); + draw_entry_warnings(g, t, self.wall_sub); // translucent glows and effects last (they write depth, see glow_material) draw_landmarks_glow(g, t); draw_towers_glow(g, t); @@ -1359,6 +1364,33 @@ fn outline(x0: f32, z0: f32, x1: f32, z1: f32, y: f32, c: Color) { } } +// Expected hero entry points: the walls around them glow red and pulse +fn draw_entry_warnings(g: &Game, t: f32, n: usize) { + if g.planned_entries.is_empty() { + return; + } + let p = 0.5 + 0.5 * (t * 4.0).sin(); + let fill = [255, 40, 30, (50.0 + 90.0 * p) as u8]; + let line = [255, 90, 60, (120.0 + 120.0 * p) as u8]; + let mut mb = MB::new(); + for &(ex, ey) in &g.planned_entries { + for dy in -2..=2 { + for dx in -2..=2 { + let (x, y) = (ex + dx, ey + dy); + let Some(tile) = g.map.get(x, y) else { continue }; + // walls of the realm's border near the entry point (not rock) + let border = DIR4.iter().any(|&(ax, ay)| g.map.get(x + ax, y + ay).map_or(false, |o| o.owner == Owner::Player && !o.solid())); + if tile.diggable() && border && dx.abs() + dy.abs() <= 3 && mb.v.len() < 30000 { + block_shell(&mut mb, &g.map, x, y, n, fill, line, 1.0, 0.04); + } + } + } + } + if let Some(m) = mb.into_plain_mesh() { + draw_mesh(&m); + } +} + // Hover feedback: wall blocks get a faint version of the dig-mark box (in the tool colour), // floor tiles an outline. While dragging a dig rectangle, every block inside is tinted. fn draw_overlay(map: &Map, ov: &Overlay, n: usize) { diff --git a/src/save.rs b/src/save.rs index 83e6988..9254514 100644 --- a/src/save.rs +++ b/src/save.rs @@ -111,4 +111,24 @@ mod tests { let l = from_bytes(json.as_bytes()).expect("load"); assert_eq!(l.game.map.t.len(), 256 * 256); } + + // writes a savegame shortly before a wave (with planned entry points) when DC_SAVE_OUT is set + #[test] + fn dump_entry_warning_save() { + let Ok(path) = std::env::var("DC_SAVE_OUT") else { return }; + let mut g = Game::new(11, 64, 64); + for _ in 0..(60 * 20) { + g.update(0.05); + } + g.wave_timer = 31.0; + for _ in 0..30 { + g.update(0.05); // the scouts warn 30 s before the wave + } + assert!(!g.planned_entries.is_empty()); + let (ex, ey) = g.planned_entries[0]; + let mut cam = Cam::new(ex as f32 + 0.5, ey as f32 + 0.5); + cam.zoom = 12.0; + std::fs::write(path, to_json(&g, &cam, &Settings { language: "en".into(), ..Settings::default() })).unwrap(); + } } +