Savegames hold only the game state: no settings, no camera (settings stay in local storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 07:54:36 +01:00
1 parent 68e878265c
commit 7709226a58
4 files changed
+30 -30

No files matched your search

+1 -1
View File
@@ -228,7 +228,7 @@ mod tests {
assert!(g.map.shown(out), "own ground beyond the lifted start is known");
assert!(g.map.explored.iter().filter(|&&b| b).count() > explored);
// savegame round trip
let json = crate::save::to_json(&g, &crate::render3d::Cam::new(0.0, 0.0), &crate::settings::Settings::default());
let json = crate::save::to_json(&g);
let l = crate::save::from_bytes(json.as_bytes()).unwrap();
assert_eq!(l.game.map.explored, g.map.explored);
// the seer's eye opens a far area for a while
+10 -10
View File
@@ -609,20 +609,20 @@ impl App {
self.status = Some((text, now));
}
// Load a savegame (settings in the savegame are always applied)
// Load a savegame (only the game state; the settings stay as they are). The camera looks at
// the heart (screenshot scenes bring their own camera).
fn load_bytes(&mut self, bytes: &[u8], ui: &mut Ui, menu: &mut Menu, r3d: &mut R3d, now: f64) {
match save::from_bytes(bytes) {
Ok(l) => {
self.game = l.game;
if let Some(c) = l.camera {
self.cam = c;
self.cam.snap();
}
if let Some(s) = l.settings {
self.settings = s;
apply_settings(&self.settings, r3d);
store_settings(&self.settings);
match l.camera {
Some(c) => self.cam = c,
None => {
let (cx, cy) = self.game.map.core;
self.cam = Cam::new(cx as f32 + 1.5, cy as f32 + 1.5);
}
}
self.cam.snap();
apply_game_settings(&mut self.game, &self.settings);
self.game.coop = false;
self.coop = false;
@@ -638,7 +638,7 @@ impl App {
}
fn autosave_json(&self) -> String {
save::to_json(&self.game, &self.cam, &self.settings)
save::to_json(&self.game)
}
fn connect_match(&mut self, cloud: &Cloud) -> bool {
+18 -18
View File
@@ -1,9 +1,9 @@
// Savegames: JSON with format tag and version, containing the game state, the camera
// and the player's settings (settings are always applied when a savegame is loaded).
// Savegames: JSON with format tag and version, containing the game state - nothing else. The
// player's settings live in the browser's local storage; settings in older savegames are
// ignored. Only the screenshot scenes (scenes.rs) also carry a camera.
use crate::game::Game;
use crate::render3d::Cam;
use crate::settings::Settings;
use serde::{Deserialize, Serialize};
pub const FORMAT: &str = "dungeoncraft-save";
@@ -15,8 +15,8 @@ struct SaveOut<'a> {
format: &'a str,
version: u32,
game: &'a Game,
camera: &'a Cam,
settings: &'a Settings,
#[serde(skip_serializing_if = "Option::is_none")]
camera: Option<&'a Cam>,
}
#[derive(Deserialize)]
@@ -25,17 +25,21 @@ struct SaveIn {
version: u32,
game: Game,
camera: Option<Cam>,
settings: Option<Settings>,
}
pub struct Loaded {
pub game: Game,
pub camera: Option<Cam>,
pub settings: Option<Settings>,
}
pub fn to_json(game: &Game, camera: &Cam, settings: &Settings) -> String {
serde_json::to_string(&SaveOut { format: FORMAT, version: VERSION, game, camera, settings }).unwrap_or_default()
pub fn to_json(game: &Game) -> String {
serde_json::to_string(&SaveOut { format: FORMAT, version: VERSION, game, camera: None }).unwrap_or_default()
}
// a screenshot scene: the game and where the camera looks
#[allow(dead_code)] // scenes.rs (tests) only
pub fn to_json_with_camera(game: &Game, camera: &Cam) -> String {
serde_json::to_string(&SaveOut { format: FORMAT, version: VERSION, game, camera: Some(camera) }).unwrap_or_default()
}
// Err contains a language key describing the problem
@@ -50,7 +54,7 @@ pub fn from_bytes(data: &[u8]) -> Result<Loaded, &'static str> {
let mut game = s.game;
game.map.activate();
game.recount();
Ok(Loaded { game, camera: s.camera, settings: s.settings })
Ok(Loaded { game, camera: s.camera })
}
#[cfg(test)]
@@ -65,18 +69,14 @@ mod tests {
for _ in 0..3000 {
g.update(0.05);
}
let cam = Cam::new(10.0, 20.0);
let mut settings = Settings::default();
settings.language = "de".into();
let json = to_json(&g, &cam, &settings);
let json = to_json(&g);
println!("savegame size: {} KB", json.len() / 1024);
let l = from_bytes(json.as_bytes()).expect("load");
assert_eq!(l.game.time, g.time);
assert_eq!(l.game.ents.len(), g.ents.len());
assert_eq!(l.game.gold, g.gold);
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!(l.camera.is_none() && !json.contains("\"settings\"") && !json.contains("\"camera\""), "only the game state");
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));
@@ -107,7 +107,7 @@ mod tests {
g.update(0.05);
}
let sim = t1.elapsed();
let json = to_json(&g, &Cam::new(0.0, 0.0), &Settings::default());
let json = to_json(&g);
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);
@@ -132,7 +132,7 @@ mod tests {
let (ex, ey) = (cx + 1, cy + 1);
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();
std::fs::write(path, to_json_with_camera(&g, &cam)).unwrap();
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ fn save(name: &str, g: &mut Game, x: f32, z: f32, zoom: f32, yaw: f32, fog: bool
let mut s = Settings::default();
s.language = "en".into();
s.hints = false;
std::fs::write(format!("/tmp/scenes/{}.json", name), crate::save::to_json(g, &cam, &s)).unwrap();
std::fs::write(format!("/tmp/scenes/{}.json", name), crate::save::to_json_with_camera(g, &cam)).unwrap();
}
fn run(g: &mut Game, secs: f32) {