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
/data

View file

@ -7,17 +7,24 @@ use game::{
entry::{ActionDefinition, DiceRoll},
};
use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast, mpsc};
use tokio::{
sync::{broadcast, mpsc},
time::Instant,
};
pub mod api;
pub mod game;
pub mod pathfinder2r_impl;
pub mod user;
pub const GAME_SAVE_FILE: &str = "data/game.json";
#[derive(Serialize, Deserialize)]
pub struct GameServer {
game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>,
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
#[serde(skip)]
/// 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
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 {
pub fn new() -> Self {
let mut creds = HashMap::new();
@ -51,11 +62,19 @@ impl GameServer {
)))
.with_action(ActionDefinition::new("Attack -1".to_string())),
)],
last_save_time: Instant::now(),
users: HashMap::new(),
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(
mut self,
mut msgs: mpsc::Receiver<(String, api::Request)>,
@ -206,7 +225,11 @@ impl GameServer {
}
_ = 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::CharacterInputs { id: _ } => todo!(),
api::Request::CharacterGetField { id: _, field: _ } => todo!(),
@ -217,8 +240,14 @@ impl GameServer {
} => 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));
self.save().await; // Save the game one last time :)
}
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 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));
println!("Server is running at http://localhost:3001");