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

+43 -1
View File
@@ -1,5 +1,6 @@
// Browser integration via our own miniquad JS plugin (www/dc_plugin.js):
// persistent key/value storage (localStorage). Native builds use an in-memory fallback.
// persistent key/value storage (localStorage), a cache that is written to storage when the
// page is hidden or closed, file download and file upload. Native builds use simple fallbacks.
#[cfg(target_arch = "wasm32")]
mod imp {
@@ -7,6 +8,11 @@ mod imp {
fn dc_storage_get(key: *const u8, key_len: u32) -> i32;
fn dc_storage_take(dst: *mut u8);
fn dc_storage_set(key: *const u8, key_len: u32, val: *const u8, val_len: u32);
fn dc_cache_set(key: *const u8, key_len: u32, val: *const u8, val_len: u32);
fn dc_download(name: *const u8, name_len: u32, data: *const u8, data_len: u32);
fn dc_open_file();
fn dc_file_poll() -> i32;
fn dc_file_take(dst: *mut u8);
}
pub fn storage_get(key: &str) -> Option<String> {
@@ -24,6 +30,32 @@ mod imp {
pub fn storage_set(key: &str, val: &str) {
unsafe { dc_storage_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) }
}
// value is written to storage by JS when the page gets hidden or closed
pub fn cache_on_leave(key: &str, val: &str) {
unsafe { dc_cache_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) }
}
pub fn download(name: &str, data: &[u8]) {
unsafe { dc_download(name.as_ptr(), name.len() as u32, data.as_ptr(), data.len() as u32) }
}
// opens the browser file picker; poll with take_file()
pub fn open_file() {
unsafe { dc_open_file() }
}
pub fn take_file() -> Option<Vec<u8>> {
unsafe {
let n = dc_file_poll();
if n < 0 {
return None;
}
let mut buf = vec![0u8; n as usize];
dc_file_take(buf.as_mut_ptr());
Some(buf)
}
}
}
#[cfg(not(target_arch = "wasm32"))]
@@ -39,6 +71,16 @@ mod imp {
pub fn storage_set(key: &str, val: &str) {
STORE.with(|s| s.borrow_mut().insert(key.to_string(), val.to_string()));
}
pub fn cache_on_leave(key: &str, val: &str) {
storage_set(key, val);
}
pub fn download(name: &str, data: &[u8]) {
let _ = std::fs::write(name, data);
}
pub fn open_file() {}
pub fn take_file() -> Option<Vec<u8>> {
None
}
}
pub use imp::*;