Configurable map size (64-256, non-square), one portal per 64x64 tiles, guarded treasure caves, glowing dig-mark blocks
- New Game dialog with map width/height (steps of 32), remembered in settings - map dimensions are per game; renderer chunks, minimap and camera follow them - content scales with area; grace period grows with the distance to the Hero Gate - treasure caves with rich gold, guarded by hero squads that hold their post - marked blocks: translucent yellow box with outline on all edges - savegame v2: packed run-length encoded tiles (256x256 map ~320 KB), v1 still loads - tests: savegame round trip on 96x128, 256x256 generation/simulation/save Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
925be17e82
commit
f710a19351
12 files changed
+588
-137
No files matched your search
+26
-2
@@ -7,7 +7,7 @@ use crate::settings::Settings;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const FORMAT: &str = "dungeoncraft-save";
|
||||
pub const VERSION: u32 = 1;
|
||||
pub const VERSION: u32 = 2; // 2: variable map size, several portals, packed tiles
|
||||
pub const AUTOSAVE_KEY: &str = "dc.autosave";
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -48,6 +48,7 @@ pub fn from_bytes(data: &[u8]) -> Result<Loaded, &'static str> {
|
||||
return Err("save.err_version");
|
||||
}
|
||||
let mut game = s.game;
|
||||
game.map.activate();
|
||||
game.recount();
|
||||
Ok(Loaded { game, camera: s.camera, settings: s.settings })
|
||||
}
|
||||
@@ -58,7 +59,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn save_roundtrip() {
|
||||
let mut g = Game::new(42);
|
||||
let mut g = Game::new(42, 96, 128);
|
||||
let (cx, cy) = g.map.core;
|
||||
g.mark_rect(cx - 5, cy - 5, cx + 8, cy + 8, true);
|
||||
for _ in 0..3000 {
|
||||
@@ -76,6 +77,9 @@ mod tests {
|
||||
assert!(l.game.map.t.iter().zip(g.map.t.iter()).all(|(a, b)| a.kind == b.kind && a.owner == b.owner && a.marked == b.marked));
|
||||
assert_eq!(l.settings.unwrap().language, "de");
|
||||
assert_eq!(l.camera.unwrap().tx, 10.0);
|
||||
assert_eq!((l.game.map.w, l.game.map.h), (96, 128));
|
||||
assert_eq!(l.game.map.portals.len(), g.map.portals.len());
|
||||
assert!(l.game.map.t.iter().zip(g.map.t.iter()).all(|(a, b)| a.var == b.var && a.hp == b.hp && a.food == b.food && a.gold == b.gold));
|
||||
// the loaded game keeps running identically
|
||||
let mut a = l.game;
|
||||
let mut b = g;
|
||||
@@ -87,4 +91,24 @@ mod tests {
|
||||
assert_eq!(a.ents.len(), b.ents.len());
|
||||
assert!(from_bytes(b"{\"format\":\"x\"}").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_map() {
|
||||
let t0 = std::time::Instant::now();
|
||||
let mut g = Game::new(7, 256, 256);
|
||||
let gen = t0.elapsed();
|
||||
assert_eq!(g.map.portals.len(), 16);
|
||||
assert_eq!(g.map.treasures.len(), 15);
|
||||
let (cx, cy) = g.map.core;
|
||||
g.mark_rect(cx - 5, cy - 5, cx + 8, cy + 8, true);
|
||||
let t1 = std::time::Instant::now();
|
||||
for _ in 0..(120 * 20) {
|
||||
g.update(0.05);
|
||||
}
|
||||
let sim = t1.elapsed();
|
||||
let json = to_json(&g, &Cam::new(0.0, 0.0), &Settings::default());
|
||||
println!("256x256: generate {:?}, 2 min simulated in {:?}, savegame {} KB", gen, sim, json.len() / 1024);
|
||||
let l = from_bytes(json.as_bytes()).expect("load");
|
||||
assert_eq!(l.game.map.t.len(), 256 * 256);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user