use std::collections::HashMap; use api::game_actions::SpawnToken; use game::{ Game, GameImpl, chat_message::ChatMessage, entry::{ActionDefinition, DiceRoll}, }; use serde::{Deserialize, Serialize}; use tokio::sync::{broadcast, mpsc}; pub mod api; pub mod game; pub mod pathfinder2r_impl; pub mod user; #[derive(Serialize, Deserialize)] pub struct GameServer { game: Game, chat: Vec<(String, ChatMessage)>, // 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 users: HashMap, // TODO: JEESH REPLACE THIS WITH A DATABASE AND PROPER SECURITY ONCE ITS DONE PLEASE GOD creds: HashMap, } impl GameServer { pub fn new() -> Self { let mut creds = HashMap::new(); creds.insert("rusty".to_string(), "".to_string()); creds.insert("test".to_string(), "test".to_string()); creds.insert("dragonfly".to_string(), "dragonfly".to_string()); Self { game: Game::new(), chat: vec![( "Server".to_string(), ChatMessage::new("a weapon description".to_string()) .id(1) .character(Some("Sword or something".to_string())) .with_action( ActionDefinition::new("weapon/attack".to_string()) .display_name(Some("Attack +7".to_string())) .with_roll(DiceRoll::new("Pierce".to_string(), 12, 1).constant(1)) .with_roll(DiceRoll::new("Fire".to_string(), 4, 2).enabled(false)), ) .with_action(ActionDefinition::new("Attack +3".to_string()).with_roll(DiceRoll::new( "Base".to_string(), 20, 1, ))) .with_action(ActionDefinition::new("Attack -1".to_string())), )], users: HashMap::new(), creds, } } pub async fn server_loop( mut self, mut msgs: mpsc::Receiver<(String, api::Request)>, broadcast: broadcast::Sender<(Option, api::Response)>, ) { while let Some(req) = msgs.recv().await { // TODO: do stuff yo! let (id, req) = req; println!("Got message from {}: {:?}", &id, &req); match req { // ignore errors, should probably be blocked before they are sent here api::Request::Error => {} api::Request::Login(login) => { println!("login req from {}: {:?}", &id, &login); if !self.users.contains_key(&login.username) && self.creds .get(&login.username) .map(|p| p == &login.password) .unwrap_or(false) { self.users.insert(login.username.clone(), login.username == "rusty"); // rusty will be admin for now :) _ = broadcast.send(( Some(id), api::Response::Login(api::login::LoginResult { success: true, username: login.username, }), )); } else { _ = broadcast.send(( Some(id.clone()), api::Response::Login(api::login::LoginResult { success: false, username: login.username, }), )); } } api::Request::Message(mut msg) => { if msg.id == 0 || msg.id > self.chat.len() { msg.id = self.chat.len() + 1; // set the message id, 0 is invalid } else if id == self.chat[msg.id - 1].0 || *self.users.get(&id).unwrap_or(&false) { self.chat[msg.id - 1] = (id.clone(), msg.clone()); } else { // if its an edit message and editor is not the owner, skip continue; } // Force the sender id to be the new id of the message, even if an id was provided msg.source = id.clone(); self.chat.push((id.clone(), msg.clone())); if msg.whisper.is_some() { _ = broadcast.send((Some(id.clone()), api::Response::Message(msg.clone()))); } _ = broadcast.send((msg.whisper.clone(), api::Response::Message(msg))); } api::Request::GetChatHistory { mut amount, from: last_msg, } => { if amount == 0 { amount = self.chat.len(); } let history: Vec = self .chat .iter() .skip(last_msg) .take(amount) .map(|m| m.1.clone()) .collect(); _ = broadcast.send((Some(id), api::Response::GetChatHistory(history))); } api::Request::GetLastMessages { mut amount } => { if amount == 0 { amount = self.chat.len(); } let start = if amount >= self.chat.len() { 0 } else { self.chat.len() - amount }; let history: Vec = self.chat.iter().skip(start).map(|m| m.1.clone()).collect(); _ = broadcast.send((Some(id), api::Response::GetChatHistory(history))); } api::Request::GetTokens { scene } => { for token_id in self.game.available_tokens(scene) { if let Some(ti) = self.game.token_info(0, token_id) { _ = broadcast.send(( Some(id.clone()), api::Response::SpawnToken(SpawnToken { token_id: token_id, x: ti.x, y: ti.y, img: ti.img_source.clone(), }), )); } } } api::Request::GetCurrentScene => { let scene = self.game.current_scene(); let scene_tokens = self .game .available_tokens(scene) .iter() .map(|id| self.game.token_info(0, *id).map(|info| (id, info))) .flatten() .map(|(id, info)| SpawnToken { token_id: *id, x: info.x, y: info.y, img: info.img_source.clone(), }) .collect::>(); _ = broadcast.send(( Some(id.clone()), api::Response::ShowScene { scene: scene, tokens: scene_tokens, }, )); } api::Request::SpawnToken { map_id, character, x, y, img_path, } => { let token_id = self.game.create_token(map_id, character, img_path.clone(), x, y); _ = broadcast.send(( Some(id.clone()), api::Response::SpawnToken(SpawnToken { token_id, x, y, img: img_path.clone() }), )); } api::Request::MoveToken { token_id, x, y } => { // TODO: add check to make sure the actor is authorized to move the token if self.game.move_token(0, token_id, x, y) { // TODO: maybe chage move_token to return optional x,y values if succeeded to make sure the token is where it was going to be _ = broadcast.send((None, api::Response::MoveToken { token_id, x, y })); } } api::Request::ActionResult(result) => { let msg = ChatMessage::new( result .results .iter() // .map(|d| &d.result_text) .fold(String::new(), |a, b| { a + &format!("{}: {} = {}\n", &b.name, &b.result_text, b.result) }), ) .character(Some(result.name)) .source(id.clone()) .targets(Some(result.targets)) .id(self.chat.len() + 1); self.chat.push((id, msg.clone())); _ = broadcast.send((None, api::Response::Message(msg))); } api::Request::CreateCharacter => { // check if user is admin if self.users.get(&id).map(|a| *a).unwrap_or(false) { let new_id = self.game.create_character(); // return the new id with the character i think _ = broadcast.send((Some(id), api::Response::CharacterCreated(new_id))); } } api::Request::Quit => { if self.users.contains_key(&id) { self.users.remove(&id); } _ = broadcast.send((None, api::Response::Quit { id })); } api::Request::Kick(id) => { if self.users.contains_key(&id) { self.users.remove(&id); } _ = broadcast.send((Some(id), api::Response::Shutdown)); } api::Request::Shutdown => break, api::Request::CharacterDisplay { id } => todo!(), api::Request::CharacterInputs { id } => todo!(), api::Request::CharacterGetField { id, field } => todo!(), api::Request::CharacterSetField { id, field, val } => todo!(), api::Request::CharacterAssign { id, user } => todo!(), } } _ = broadcast.send((None, api::Response::Shutdown)); } }