mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
Savestate stuff.
This commit is contained in:
parent
29b0296528
commit
0e8c97ccab
11 changed files with 252 additions and 42 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
target
|
||||
mat_data.txt
|
||||
mat_data.txt
|
||||
save_state
|
||||
|
|
2
noita-proxy/Cargo.lock
generated
2
noita-proxy/Cargo.lock
generated
|
@ -1997,7 +1997,7 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
|||
|
||||
[[package]]
|
||||
name = "noita-proxy"
|
||||
version = "0.15.3"
|
||||
version = "0.16.0"
|
||||
dependencies = [
|
||||
"argh",
|
||||
"bincode",
|
||||
|
|
|
@ -5,7 +5,7 @@ resolver = "2"
|
|||
[package]
|
||||
name = "noita-proxy"
|
||||
description = "Noita Entangled Worlds companion app."
|
||||
version = "0.15.3"
|
||||
version = "0.16.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
|
|
@ -88,3 +88,10 @@ connect_settings_autostart = Start the game automatically
|
|||
Higher-values-result-in-less-performance-impact = Higher values result in less performance impact.
|
||||
World-will-be-synced-every-this-many-frames = World will be synced every this many frames.
|
||||
|
||||
|
||||
## Savestate
|
||||
|
||||
New-game = New game
|
||||
Continue = Continue
|
||||
savestate_desc = Savestate from a previous run has been detected. Do you wish to continue that run, or to start a new game (and reset the savestate)?
|
||||
An-in-progress-run-has-been-detected = An in-progress run has been detected.
|
|
@ -88,3 +88,10 @@ connect_settings_autostart = ゲームを自動的に開始する
|
|||
Higher-values-result-in-less-performance-impact = Higher values result in less performance impact.
|
||||
World-will-be-synced-every-this-many-frames = World will be synced every this many frames.
|
||||
|
||||
|
||||
## Savestate
|
||||
|
||||
New-game = New game
|
||||
Continue = Continue
|
||||
savestate_desc = Savestate from a previous run has been detected. Do you wish to continue that run, or to start a new game (and reset the savestate)?
|
||||
An-in-progress-run-has-been-detected = An in-progress run has been detected.
|
|
@ -69,4 +69,10 @@ error_lobby_does_not_exist = Лобби не существует.
|
|||
## Game settings
|
||||
|
||||
Higher-values-result-in-less-performance-impact = Higher values result in less performance impact
|
||||
World-will-be-synced-every-this-many-frames = World will be synced every this many frames
|
||||
World-will-be-synced-every-this-many-frames = World will be synced every this many frames
|
||||
## Savestate
|
||||
|
||||
New-game = New game
|
||||
Continue = Continue
|
||||
savestate_desc = Savestate from a previous run has been detected. Do you wish to continue that run, or to start a new game (and reset the savestate)?
|
||||
An-in-progress-run-has-been-detected = An in-progress run has been detected.
|
|
@ -2,4 +2,5 @@
|
|||
pub mod mod_manager;
|
||||
pub mod noita_launcher;
|
||||
pub mod releases;
|
||||
pub mod save_state;
|
||||
pub mod self_update;
|
||||
|
|
89
noita-proxy/src/bookkeeping/save_state.rs
Normal file
89
noita-proxy/src/bookkeeping/save_state.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{self, AtomicBool},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
pub trait SaveStateEntry: bitcode::Encode + bitcode::DecodeOwned {
|
||||
const FILENAME: &'static str;
|
||||
}
|
||||
|
||||
struct SaveStateInner {
|
||||
game_started: AtomicBool,
|
||||
}
|
||||
|
||||
/// Allows persisting extra run state (like chunks). Cleared between runs.
|
||||
#[derive(Clone)]
|
||||
pub struct SaveState {
|
||||
inner: Arc<SaveStateInner>,
|
||||
path: PathBuf,
|
||||
has_savestate: bool,
|
||||
}
|
||||
|
||||
impl SaveState {
|
||||
pub(crate) fn new(path: PathBuf) -> Self {
|
||||
let has_savestate = path.join("run_info.bit").exists();
|
||||
info!("Has savestate: {has_savestate}");
|
||||
if let Err(err) = fs::create_dir_all(&path) {
|
||||
error!("Error while creating directories: {err}");
|
||||
}
|
||||
let path = path.canonicalize().unwrap_or(path);
|
||||
info!("Will save to: {}", path.display());
|
||||
Self {
|
||||
path,
|
||||
inner: Arc::new(SaveStateInner {
|
||||
game_started: false.into(),
|
||||
}),
|
||||
has_savestate,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn save<D: SaveStateEntry>(&self, data: &D) {
|
||||
if !self.inner.game_started.load(atomic::Ordering::SeqCst) {
|
||||
info!("Skipping save of {}, game not started yet", D::FILENAME);
|
||||
return;
|
||||
}
|
||||
|
||||
let path = self.path_for_filename(D::FILENAME);
|
||||
let encoded = bitcode::encode(data);
|
||||
if let Err(err) = fs::write(&path, encoded) {
|
||||
error!("Error while saving to {:?}: {err}", D::FILENAME);
|
||||
}
|
||||
info!("Saved {}", path.display());
|
||||
}
|
||||
|
||||
pub(crate) fn load<D: SaveStateEntry>(&self) -> Option<D> {
|
||||
let path = self.path_for_filename(D::FILENAME);
|
||||
let data = fs::read(&path)
|
||||
.inspect_err(|err| warn!("Could not read {:?}: {err}", D::FILENAME))
|
||||
.ok()?;
|
||||
bitcode::decode(&data)
|
||||
.inspect_err(|err| error!("Could not decode {:?}: {err}", D::FILENAME))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub(crate) fn mark_game_started(&self) {
|
||||
self.inner
|
||||
.game_started
|
||||
.store(true, atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub(crate) fn reset(&self) {
|
||||
fs::remove_dir_all(&self.path).ok();
|
||||
fs::create_dir_all(&self.path).ok();
|
||||
}
|
||||
|
||||
/// true if had a savestate initially.
|
||||
pub(crate) fn has_savestate(&self) -> bool {
|
||||
self.has_savestate
|
||||
}
|
||||
|
||||
fn path_for_filename(&self, filename: &str) -> PathBuf {
|
||||
self.path.join(format!("{filename}.bit"))
|
||||
}
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
use std::{
|
||||
fmt::Display,
|
||||
net::SocketAddr,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
time::Duration,
|
||||
fmt::Display, net::SocketAddr, ops::Deref, sync::{atomic::Ordering, Arc}, thread::JoinHandle, time::Duration
|
||||
};
|
||||
|
||||
use bitcode::{Decode, Encode};
|
||||
use bookkeeping::noita_launcher::{LaunchTokenResult, NoitaLauncher};
|
||||
use bookkeeping::{noita_launcher::{LaunchTokenResult, NoitaLauncher}, save_state::SaveState};
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
use eframe::egui::{
|
||||
self, Align2, Button, Color32, Context, DragValue, FontDefinitions, FontFamily, InnerResponse,
|
||||
|
@ -15,7 +12,7 @@ use eframe::egui::{
|
|||
use egui_plot::{Plot, PlotPoint, PlotUi, Text};
|
||||
use lang::{set_current_locale, tr, LANGS};
|
||||
use mod_manager::{Modmanager, ModmanagerSettings};
|
||||
use net::{omni::PeerVariant, steam_networking::ExtraPeerState, NetManagerInit};
|
||||
use net::{omni::PeerVariant, steam_networking::ExtraPeerState, NetManagerInit, RunInfo};
|
||||
use self_update::SelfUpdateManager;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use steamworks::{LobbyId, SteamAPIInitError};
|
||||
|
@ -61,11 +58,28 @@ impl Default for GameSettings {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct NetManStopOnDrop(pub Arc<net::NetManager>, Option<JoinHandle<()>>);
|
||||
|
||||
impl Deref for NetManStopOnDrop {
|
||||
type Target = Arc<net::NetManager>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetManStopOnDrop {
|
||||
fn drop(&mut self) {
|
||||
self.0.continue_running.store(false, Ordering::Relaxed);
|
||||
self.1.take().unwrap().join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
enum AppState {
|
||||
Connect,
|
||||
ModManager,
|
||||
Netman {
|
||||
netman: Arc<net::NetManager>,
|
||||
netman: NetManStopOnDrop,
|
||||
noita_launcher: NoitaLauncher,
|
||||
},
|
||||
Error {
|
||||
|
@ -73,6 +87,7 @@ enum AppState {
|
|||
},
|
||||
SelfUpdate,
|
||||
LangPick,
|
||||
AskSavestateReset,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -103,7 +118,8 @@ pub struct App {
|
|||
state: AppState,
|
||||
modmanager: Modmanager,
|
||||
steam_state: Result<steam_helper::SteamState, SteamAPIInitError>,
|
||||
saved_state: AppSavedState,
|
||||
app_saved_state: AppSavedState,
|
||||
run_save_state: SaveState,
|
||||
modmanager_settings: ModmanagerSettings,
|
||||
self_update: SelfUpdateManager,
|
||||
show_map_plot: bool,
|
||||
|
@ -170,7 +186,7 @@ impl App {
|
|||
state,
|
||||
modmanager: Modmanager::default(),
|
||||
steam_state: steam_helper::SteamState::new(),
|
||||
saved_state,
|
||||
app_saved_state: saved_state,
|
||||
modmanager_settings,
|
||||
self_update: SelfUpdateManager::new(),
|
||||
show_map_plot: false,
|
||||
|
@ -178,6 +194,7 @@ impl App {
|
|||
lobby_id_field: "".to_string(),
|
||||
args,
|
||||
can_start_automatically: false,
|
||||
run_save_state: SaveState::new("./save_state/".into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,13 +204,14 @@ impl App {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let my_nickname = self.saved_state.nickname.clone().or(steam_nickname);
|
||||
NetManagerInit { my_nickname }
|
||||
let my_nickname = self.app_saved_state.nickname.clone().or(steam_nickname);
|
||||
NetManagerInit { my_nickname, save_state: self.run_save_state.clone() }
|
||||
}
|
||||
|
||||
fn change_state_to_netman(&mut self, netman: Arc<net::NetManager>) {
|
||||
let handle = netman.clone().start();
|
||||
self.state = AppState::Netman {
|
||||
netman,
|
||||
netman: NetManStopOnDrop(netman, Some(handle)),
|
||||
noita_launcher: NoitaLauncher::new(
|
||||
&self.modmanager_settings.game_exe_path,
|
||||
self.args.launch_cmd.as_deref(),
|
||||
|
@ -207,15 +225,21 @@ impl App {
|
|||
let peer = Peer::host(bind_addr, None).unwrap();
|
||||
let netman = net::NetManager::new(PeerVariant::Tangled(peer), self.get_netman_init());
|
||||
self.set_netman_settings(&netman);
|
||||
netman.clone().start();
|
||||
self.change_state_to_netman(netman);
|
||||
}
|
||||
|
||||
fn set_netman_settings(&mut self, netman: &Arc<net::NetManager>) {
|
||||
let run_info: Option<RunInfo> = self.run_save_state.load();
|
||||
let mut settings = netman.settings.lock().unwrap();
|
||||
*settings = self.saved_state.game_settings.clone();
|
||||
if !self.saved_state.game_settings.use_constant_seed {
|
||||
settings.seed = rand::random();
|
||||
*settings = self.app_saved_state.game_settings.clone();
|
||||
if !self.app_saved_state.game_settings.use_constant_seed {
|
||||
if let Some(info) = run_info {
|
||||
settings.seed = info.seed;
|
||||
info!("Using saved seed: {}", settings.seed);
|
||||
} else {
|
||||
settings.seed = rand::random();
|
||||
info!("Using random seed: {}", settings.seed);
|
||||
}
|
||||
} else {
|
||||
info!("Using constant seed: {}", settings.seed);
|
||||
}
|
||||
|
@ -225,7 +249,6 @@ impl App {
|
|||
fn start_connect(&mut self, addr: SocketAddr) {
|
||||
let peer = Peer::connect(addr, None).unwrap();
|
||||
let netman = net::NetManager::new(PeerVariant::Tangled(peer), self.get_netman_init());
|
||||
netman.clone().start();
|
||||
self.change_state_to_netman(netman);
|
||||
}
|
||||
|
||||
|
@ -236,7 +259,6 @@ impl App {
|
|||
);
|
||||
let netman = net::NetManager::new(PeerVariant::Steam(peer), self.get_netman_init());
|
||||
self.set_netman_settings(&netman);
|
||||
netman.clone().start();
|
||||
self.change_state_to_netman(netman);
|
||||
}
|
||||
|
||||
|
@ -253,13 +275,12 @@ impl App {
|
|||
);
|
||||
|
||||
let netman = net::NetManager::new(PeerVariant::Steam(peer), self.get_netman_init());
|
||||
netman.clone().start();
|
||||
self.change_state_to_netman(netman);
|
||||
}
|
||||
|
||||
fn connect_screen(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if self.saved_state.times_started % 20 == 0 {
|
||||
if self.app_saved_state.times_started % 20 == 0 {
|
||||
let image = egui::Image::new(egui::include_image!("../assets/longleg.png"))
|
||||
.texture_options(TextureOptions::NEAREST);
|
||||
image.paint_at(ui, ui.ctx().screen_rect());
|
||||
|
@ -293,7 +314,7 @@ impl App {
|
|||
ui.set_min_size(ui.available_size());
|
||||
|
||||
let lang_label = self
|
||||
.saved_state
|
||||
.app_saved_state
|
||||
.lang_id
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
|
@ -309,7 +330,7 @@ impl App {
|
|||
}
|
||||
let secret_active = ui.input(|i| i.modifiers.ctrl && i.key_down(Key::D));
|
||||
if secret_active && ui.button("reset all data").clicked() {
|
||||
self.saved_state = Default::default();
|
||||
self.app_saved_state = Default::default();
|
||||
self.modmanager_settings = Default::default();
|
||||
self.state = AppState::LangPick;
|
||||
}
|
||||
|
@ -370,8 +391,8 @@ impl App {
|
|||
self.start_server();
|
||||
}
|
||||
|
||||
ui.text_edit_singleline(&mut self.saved_state.addr);
|
||||
let addr = self.saved_state.addr.parse();
|
||||
ui.text_edit_singleline(&mut self.app_saved_state.addr);
|
||||
let addr = self.app_saved_state.addr.parse();
|
||||
ui.add_enabled_ui(addr.is_ok(), |ui| {
|
||||
if ui.button(tr("ip_connect")).clicked() {
|
||||
if let Ok(addr) = addr {
|
||||
|
@ -386,7 +407,7 @@ impl App {
|
|||
|
||||
fn show_game_settings(&mut self, ui: &mut Ui) {
|
||||
heading_with_underline(ui, tr("connect_settings"));
|
||||
let game_settings = &mut self.saved_state.game_settings;
|
||||
let game_settings = &mut self.app_saved_state.game_settings;
|
||||
|
||||
ui.label(tr("connect_settings_debug"));
|
||||
ui.checkbox(
|
||||
|
@ -445,7 +466,7 @@ impl App {
|
|||
|
||||
heading_with_underline(ui, tr("connect_settings_local"));
|
||||
ui.checkbox(
|
||||
&mut self.saved_state.start_game_automatically,
|
||||
&mut self.app_saved_state.start_game_automatically,
|
||||
tr("connect_settings_autostart"),
|
||||
);
|
||||
}
|
||||
|
@ -494,6 +515,14 @@ impl App {
|
|||
|
||||
ctx.set_fonts(font_definitions);
|
||||
}
|
||||
|
||||
fn switch_to_connect(&mut self) {
|
||||
self.state = if self.run_save_state.has_savestate() {
|
||||
AppState::AskSavestateReset
|
||||
} else {
|
||||
AppState::Connect
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_bg(ui: &mut Ui) {
|
||||
|
@ -596,7 +625,7 @@ impl eframe::App for App {
|
|||
if accept_local && !local_connected {
|
||||
match noita_launcher.launch_token() {
|
||||
LaunchTokenResult::Ok(mut token) => {
|
||||
let start_auto = self.can_start_automatically && self.saved_state.start_game_automatically;
|
||||
let start_auto = self.can_start_automatically && self.app_saved_state.start_game_automatically;
|
||||
if start_auto || ui.button(tr("launcher_start_game")).clicked() {
|
||||
info!("Starting the game now");
|
||||
token.start_game();
|
||||
|
@ -646,7 +675,7 @@ impl eframe::App for App {
|
|||
egui::Window::new(tr("connect_settings")).open(&mut show).show(ctx, |ui| {
|
||||
self.show_game_settings(ui);
|
||||
if ui.button(tr("netman_apply_settings")).clicked() {
|
||||
*netman.pending_settings.lock().unwrap() = self.saved_state.game_settings.clone();
|
||||
*netman.pending_settings.lock().unwrap() = self.app_saved_state.game_settings.clone();
|
||||
}
|
||||
});
|
||||
self.show_settings = show;
|
||||
|
@ -677,7 +706,7 @@ impl eframe::App for App {
|
|||
)
|
||||
});
|
||||
if self.modmanager.is_done() {
|
||||
self.state = AppState::Connect;
|
||||
self.switch_to_connect();
|
||||
}
|
||||
}
|
||||
AppState::SelfUpdate => {
|
||||
|
@ -700,7 +729,7 @@ impl eframe::App for App {
|
|||
ui.set_max_width(200.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
if ui.button(lang.name()).clicked() {
|
||||
self.saved_state.lang_id = Some(lang.id());
|
||||
self.app_saved_state.lang_id = Some(lang.id());
|
||||
set_current_locale(lang.id())
|
||||
}
|
||||
});
|
||||
|
@ -710,11 +739,29 @@ impl eframe::App for App {
|
|||
}
|
||||
});
|
||||
}
|
||||
AppState::AskSavestateReset => {
|
||||
egui::Window::new(tr("An-in-progress-run-has-been-detected"))
|
||||
.auto_sized()
|
||||
.anchor(Align2::CENTER_CENTER, [0.0, 0.0])
|
||||
.show(ctx, |ui| {
|
||||
ui.label(tr("savestate_desc"));
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button(tr("Continue")).clicked() {
|
||||
self.state = AppState::Connect;
|
||||
}
|
||||
if ui.button(tr("New-game")).clicked() {
|
||||
self.state = AppState::Connect;
|
||||
self.run_save_state.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
eframe::set_value(storage, eframe::APP_KEY, &self.saved_state);
|
||||
eframe::set_value(storage, eframe::APP_KEY, &self.app_saved_state);
|
||||
eframe::set_value(storage, MODMANAGER, &self.modmanager_settings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use bitcode::{Decode, Encode};
|
||||
use messages::{MessageRequest, NetMsg};
|
||||
use omni::OmniPeerId;
|
||||
use proxy_opt::ProxyOpt;
|
||||
|
@ -8,7 +9,7 @@ use std::{
|
|||
io::{self, Write},
|
||||
net::{SocketAddr, TcpListener, TcpStream},
|
||||
sync::{atomic::AtomicBool, Arc, Mutex},
|
||||
thread,
|
||||
thread::{self, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::debug;
|
||||
|
@ -18,7 +19,10 @@ use tangled::Reliability;
|
|||
use tracing::{error, info, warn};
|
||||
use tungstenite::{accept, WebSocket};
|
||||
|
||||
use crate::GameSettings;
|
||||
use crate::{
|
||||
bookkeeping::save_state::{SaveState, SaveStateEntry},
|
||||
GameSettings,
|
||||
};
|
||||
|
||||
pub mod messages;
|
||||
mod proxy_opt;
|
||||
|
@ -48,6 +52,15 @@ pub(crate) fn ws_encode_mod(peer: omni::OmniPeerId, data: &[u8]) -> tungstenite:
|
|||
tungstenite::Message::Binary(buf)
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
pub(crate) struct RunInfo {
|
||||
pub(crate) seed: u64,
|
||||
}
|
||||
|
||||
impl SaveStateEntry for RunInfo {
|
||||
const FILENAME: &'static str = "run_info";
|
||||
}
|
||||
|
||||
pub(crate) struct NetInnerState {
|
||||
pub(crate) ws: Option<WebSocket<TcpStream>>,
|
||||
world: WorldManager,
|
||||
|
@ -75,6 +88,7 @@ pub mod omni;
|
|||
|
||||
pub struct NetManagerInit {
|
||||
pub my_nickname: Option<String>,
|
||||
pub save_state: SaveState,
|
||||
}
|
||||
|
||||
pub struct NetManager {
|
||||
|
@ -157,7 +171,11 @@ impl NetManager {
|
|||
|
||||
let mut state = NetInnerState {
|
||||
ws: None,
|
||||
world: WorldManager::new(self.is_host(), self.peer.my_id().unwrap()),
|
||||
world: WorldManager::new(
|
||||
self.is_host(),
|
||||
self.peer.my_id().unwrap(),
|
||||
self.init_settings.save_state.clone(),
|
||||
),
|
||||
};
|
||||
|
||||
let mut last_iter = Instant::now();
|
||||
|
@ -299,6 +317,7 @@ impl NetManager {
|
|||
}
|
||||
|
||||
fn on_ws_connection(self: &Arc<NetManager>, state: &mut NetInnerState) {
|
||||
self.init_settings.save_state.mark_game_started();
|
||||
info!("New stream connected");
|
||||
let stream_ref = &state.ws.as_ref().unwrap().get_ref();
|
||||
stream_ref.set_nonblocking(true).ok();
|
||||
|
@ -378,7 +397,7 @@ impl NetManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn start(self: Arc<NetManager>) {
|
||||
pub fn start(self: Arc<NetManager>) -> JoinHandle<()> {
|
||||
info!("Starting netmanager");
|
||||
thread::spawn(move || {
|
||||
let result = self.clone().start_inner();
|
||||
|
@ -388,7 +407,7 @@ impl NetManager {
|
|||
}
|
||||
self.stopped
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn resend_game_settings(&self) {
|
||||
|
@ -408,6 +427,7 @@ impl NetManager {
|
|||
Some("game_over") => {
|
||||
if self.is_host() {
|
||||
info!("Game over, resending game settings");
|
||||
self.init_settings.save_state.reset();
|
||||
{
|
||||
let mut settings = self.pending_settings.lock().unwrap().clone();
|
||||
if !settings.use_constant_seed {
|
||||
|
@ -453,3 +473,17 @@ impl NetManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetManager {
|
||||
fn drop(&mut self) {
|
||||
if self.is_host() {
|
||||
let run_info = RunInfo {
|
||||
seed: self.settings.lock().unwrap().seed,
|
||||
};
|
||||
self.init_settings.save_state.save(&run_info);
|
||||
info!("Saved run info");
|
||||
} else {
|
||||
info!("Skip saving run info: not a host");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ use world_model::{ChunkCoord, ChunkData, ChunkDelta, WorldModel};
|
|||
|
||||
pub use world_model::encoding::NoitaWorldUpdate;
|
||||
|
||||
use crate::bookkeeping::save_state::{SaveState, SaveStateEntry};
|
||||
|
||||
use super::{
|
||||
messages::{Destination, MessageRequest},
|
||||
omni::OmniPeerId,
|
||||
|
@ -109,6 +111,7 @@ impl ChunkState {
|
|||
pub(crate) struct WorldManager {
|
||||
is_host: bool,
|
||||
my_peer_id: OmniPeerId,
|
||||
save_state: SaveState,
|
||||
/// We receive changes from other clients here, intending to send them to Noita.
|
||||
inbound_model: WorldModel,
|
||||
/// We use that to create changes to be sent to other clients.
|
||||
|
@ -129,15 +132,17 @@ pub(crate) struct WorldManager {
|
|||
}
|
||||
|
||||
impl WorldManager {
|
||||
pub(crate) fn new(is_host: bool, my_peer_id: OmniPeerId) -> Self {
|
||||
pub(crate) fn new(is_host: bool, my_peer_id: OmniPeerId, save_state: SaveState) -> Self {
|
||||
let chunk_storage = save_state.load().unwrap_or_default();
|
||||
WorldManager {
|
||||
is_host,
|
||||
my_peer_id,
|
||||
save_state,
|
||||
inbound_model: Default::default(),
|
||||
outbound_model: Default::default(),
|
||||
authority_map: Default::default(),
|
||||
// TODO this needs to be persisted between proxy restarts.
|
||||
chunk_storage: Default::default(),
|
||||
chunk_storage,
|
||||
chunk_state: Default::default(),
|
||||
emitted_messages: Default::default(),
|
||||
current_update: 0,
|
||||
|
@ -423,6 +428,19 @@ impl WorldManager {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for WorldManager {
|
||||
fn drop(&mut self) {
|
||||
if self.is_host {
|
||||
self.save_state.save(&self.chunk_storage);
|
||||
info!("Saved chunk data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SaveStateEntry for FxHashMap<ChunkCoord, ChunkData> {
|
||||
const FILENAME: &'static str = "world_chunks";
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{fs::File, io::BufReader};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue