Title screen, pause menu, settings dialog and savegames

- title screen (continue/new/load/settings/help) and Esc pause menu
- settings: language & sound, camera, graphics, game, rebindable keys;
  stored in localStorage and in every savegame (always applied on load)
- savegames as JSON (.dcsave): download, load via file picker or drag & drop
- autosave to localStorage every 2 minutes and when the tab is hidden/closed
- configurable wall detail, light count, interface size, game speed, hints
- audio behind a cargo feature so native tests run (cargo test --no-default-features)
- savegame round-trip test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-02 23:08:33 +01:00
1 parent fbf6f223fc
commit fb7dab3683
17 files changed
+1742 -345

No files matched your search

+13 -5
View File
@@ -1,6 +1,7 @@
// Game logic: units, AI, economy, hero waves.
use crate::map::*;
use serde::{Deserialize, Serialize};
pub const CORE_HP: f32 = 2000.0;
pub const GATE_HP: f32 = 3000.0;
@@ -71,7 +72,7 @@ pub struct SfxEvent {
pub const MANA_MAX: f32 = 400.0;
pub const CARRY_MAX: i32 = 300;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum EK {
Worker,
Kobold,
@@ -112,7 +113,7 @@ pub fn st(k: EK) -> St {
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub enum Job {
None,
Dig(usize),
@@ -129,7 +130,7 @@ pub enum Job {
Rally,
}
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct Ent {
pub id: u32,
pub kind: EK,
@@ -217,6 +218,7 @@ pub struct Fx {
pub col: u32,
}
#[derive(Serialize, Deserialize)]
pub struct Game {
pub map: Map,
pub ents: Vec<Ent>,
@@ -231,7 +233,9 @@ pub struct Game {
pub warned: bool,
pub portal_timer: f32,
pub payday: f32,
#[serde(skip)]
pub fx: Vec<Fx>,
#[serde(skip)]
pub msgs: Vec<Msg>,
pub state: u8, // 0 running, 1 victory, 2 defeat
pub paused: bool,
@@ -241,14 +245,17 @@ pub struct Game {
pub gate_hit_t: f32,
pub gate_stage: u32, // defender thresholds triggered (0..3)
pub flag: Option<usize>,
#[serde(skip)]
pub events: Vec<SfxEvent>,
pub last_wave_value: f32,
pub torches: Vec<Torch>,
#[serde(skip)]
pub hints: bool, // show hint messages (setting)
hint_t: [f32; 16],
}
// torch on a wall face: wall tile + direction (index into DIR4) towards the open side
#[derive(Clone, Copy, PartialEq)]
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Torch {
pub wall: usize,
pub dir: usize,
@@ -315,6 +322,7 @@ impl Game {
events: Vec::new(),
last_wave_value: 0.0,
torches: Vec::new(),
hints: true,
hint_t: [-1000.0; 16],
};
let (cx, cy) = g.map.core;
@@ -391,7 +399,7 @@ impl Game {
}
fn hint(&mut self, slot: usize, key: &'static str) {
if self.time - self.hint_t[slot] > 30.0 {
if self.hints && self.time - self.hint_t[slot] > 30.0 {
self.hint_t[slot] = self.time;
self.msg(key, vec![]);
}