Statistics with a chart at the end of a game, 64 achievements kept locally and on the server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 23:36:19 +01:00
1 parent 193b199875
commit 9931ca9b54
37 files changed
+1833 -4

No files matched your search

+49
View File
@@ -1,5 +1,6 @@
// DungeonCraft – dungeon building & defence in the browser (Rust + macroquad, WASM).
mod achievements;
mod audio;
mod campaign;
mod campaign_doc;
@@ -41,6 +42,7 @@ mod traps;
mod sound;
mod surface;
mod spells;
mod stats;
mod touch;
mod ui;
mod voice;
@@ -847,6 +849,12 @@ async fn main() {
let mut r3d = R3d::new();
let mut sounds = SoundBank::new();
let mut music = MusicDirector::new();
// lifetime statistics and achievements (browser storage, merged with the server after a login)
let stored_profile: achievements::Profile = platform::storage_get(achievements::PROFILE_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default();
let mut tracker = achievements::Tracker::new(stored_profile);
let mut want_profile = false; // fetch the server's profile when the client is free
let mut profile_store_t = 0.0f32;
let mut profile_upload_t = 0.0f32;
let mut ui = Ui::new();
let mut menu = Menu::new();
let mut cloud = Cloud::new(); // savegame service; files + browser autosave when unreachable
@@ -992,6 +1000,7 @@ async fn main() {
}
}
CloudEvent::LoggedIn => {
want_profile = true;
if menu.page == Some(Page::Login) {
menu.page = Some(Page::Title);
}
@@ -1939,8 +1948,47 @@ app.pending_briefing = briefing_of(&campaign, i);
| Some(Page::Account)
| Some(Page::Bestiary { from_title: true, .. })
| Some(Page::Match { from_title: true })
| Some(Page::Achievements { from_title: true })
);
music.update(&app.game, on_title, (app.cam.rx, app.cam.rz));
// ---------------- achievements: count the game, unlock, store and share the profile
{
let ctx = achievements::Context { coop: app.coop, editor_test: app.editor_testing };
let unix = miniquad::date::now() as u64;
let counting = !on_title && (playing || app.game.state != 0);
for id in if counting { tracker.update(&app.game, &ctx, dt, unix) } else { Vec::new() } {
let (name, _) = achievements::texts(&id);
app.game.msgs.push(game::Msg { key: "msg.achievement", args: vec![name] });
audio::play_sfx(game::Sfx::Victory.index(), 0.8, 0.0);
}
if let Some(json) = cloud.profile_loaded.take() {
if let Ok(p) = serde_json::from_str::<achievements::Profile>(&json) {
tracker.profile.merge(&p);
tracker.profile.check_tiers(unix);
tracker.dirty = true;
tracker.upload = tracker.profile != p;
}
}
if want_profile && cloud.session.is_some() && cloud.load_profile() {
want_profile = false;
}
profile_store_t += dt;
profile_upload_t += dt;
if tracker.dirty && (profile_store_t > 10.0 || tracker.upload) {
profile_store_t = 0.0;
tracker.dirty = false;
platform::storage_set(achievements::PROFILE_KEY, &serde_json::to_string(&tracker.profile).unwrap_or_default());
}
if tracker.upload && cloud.session.is_some() && profile_upload_t > 30.0 && !want_profile {
if cloud.save_profile(&serde_json::to_string(&tracker.profile).unwrap_or_default()) {
tracker.upload = false;
profile_upload_t = 0.0;
}
}
if matches!(menu.page, Some(Page::Achievements { .. })) {
menu.profile = tracker.profile.clone();
}
}
for m in app.game.msgs.drain(..) {
if app.settings.voice && voice::speaks(m.key) {
voice.say(&i18n::trf(m.key, &m.args), now, 12);
@@ -2039,6 +2087,7 @@ fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut M
| Some(Page::Account)
| Some(Page::Bestiary { from_title: true, .. })
| Some(Page::Match { from_title: true })
| Some(Page::Achievements { from_title: true })
);
if !title_screen {
let info = if over_ui || (touch && inp.inspect.is_none()) {