basic save for game server

This commit is contained in:
Rusty Striker 2025-07-14 21:48:48 +03:00
parent 2a36485b1b
commit 98ee910ce1
Signed by: RustyStriker
GPG key ID: 87E4D691632DFF15
3 changed files with 38 additions and 3 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/data

View file

@ -7,17 +7,24 @@ use game::{
entry::{ActionDefinition, DiceRoll}, entry::{ActionDefinition, DiceRoll},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast, mpsc}; use tokio::{
sync::{broadcast, mpsc},
time::Instant,
};
pub mod api; pub mod api;
pub mod game; pub mod game;
pub mod pathfinder2r_impl; pub mod pathfinder2r_impl;
pub mod user; pub mod user;
pub const GAME_SAVE_FILE: &str = "data/game.json";
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct GameServer { pub struct GameServer {
game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>, game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>,
chat: Vec<(String, ChatMessage)>, chat: Vec<(String, ChatMessage)>,
#[serde(skip, default = "default_last_save_time")]
last_save_time: Instant,
// we dont want to save the logged users as it will always be empty when the server restarts // we dont want to save the logged users as it will always be empty when the server restarts
#[serde(skip)] #[serde(skip)]
/// Logged in users, bool value used to know if the user is an admin or not /// Logged in users, bool value used to know if the user is an admin or not
@ -25,6 +32,10 @@ pub struct GameServer {
// TODO: JEESH REPLACE THIS WITH A DATABASE AND PROPER SECURITY ONCE ITS DONE PLEASE GOD // TODO: JEESH REPLACE THIS WITH A DATABASE AND PROPER SECURITY ONCE ITS DONE PLEASE GOD
creds: HashMap<String, String>, creds: HashMap<String, String>,
} }
/// This is needed for the default value in GameServer.last_save_time
fn default_last_save_time() -> Instant {
Instant::now()
}
impl GameServer { impl GameServer {
pub fn new() -> Self { pub fn new() -> Self {
let mut creds = HashMap::new(); let mut creds = HashMap::new();
@ -51,11 +62,19 @@ impl GameServer {
))) )))
.with_action(ActionDefinition::new("Attack -1".to_string())), .with_action(ActionDefinition::new("Attack -1".to_string())),
)], )],
last_save_time: Instant::now(),
users: HashMap::new(), users: HashMap::new(),
creds, creds,
} }
} }
pub async fn save(&self) {
let _ = tokio::fs::create_dir("data").await;
if let Ok(json) = serde_json::to_string(self) {
let _ = tokio::fs::write(GAME_SAVE_FILE, json.as_bytes()).await;
}
}
pub async fn server_loop( pub async fn server_loop(
mut self, mut self,
mut msgs: mpsc::Receiver<(String, api::Request)>, mut msgs: mpsc::Receiver<(String, api::Request)>,
@ -206,7 +225,11 @@ impl GameServer {
} }
_ = broadcast.send((Some(id), api::Response::Shutdown)); _ = broadcast.send((Some(id), api::Response::Shutdown));
} }
api::Request::Shutdown => break, api::Request::Shutdown => {
if *self.users.get(&id).unwrap_or(&false) {
break;
}
}
api::Request::CharacterDisplay { id: _ } => {} api::Request::CharacterDisplay { id: _ } => {}
api::Request::CharacterInputs { id: _ } => todo!(), api::Request::CharacterInputs { id: _ } => todo!(),
api::Request::CharacterGetField { id: _, field: _ } => todo!(), api::Request::CharacterGetField { id: _, field: _ } => todo!(),
@ -217,8 +240,14 @@ impl GameServer {
} => todo!(), } => todo!(),
api::Request::CharacterAssign { id: _, user: _ } => todo!(), api::Request::CharacterAssign { id: _, user: _ } => todo!(),
} }
// Auto save every 5 minutes, once a request is received
// TODO: Export the auto save time
if self.last_save_time.elapsed().as_secs() > 5 * 60 {
self.save().await;
}
} }
_ = broadcast.send((None, api::Response::Shutdown)); _ = broadcast.send((None, api::Response::Shutdown));
self.save().await; // Save the game one last time :)
} }
fn get_scene_list(&self, id: &str) -> api::Response { fn get_scene_list(&self, id: &str) -> api::Response {

View file

@ -30,7 +30,12 @@ async fn main() {
); );
let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap(); let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap();
let game = open_tavern::GameServer::new(); let game = tokio::fs::read_to_string(open_tavern::GAME_SAVE_FILE)
.await
.map(|s| serde_json::from_str(&s).ok())
.ok()
.flatten()
.unwrap_or(open_tavern::GameServer::new());
tokio::spawn(game.server_loop(mrecv, bsend)); tokio::spawn(game.server_loop(mrecv, bsend));
println!("Server is running at http://localhost:3001"); println!("Server is running at http://localhost:3001");