diff options
Diffstat (limited to 'core/src/data/game')
| -rw-r--r-- | core/src/data/game/cli/cli_command.rs | 35 | ||||
| -rw-r--r-- | core/src/data/game/cli/mod.rs | 1 | ||||
| -rw-r--r-- | core/src/data/game/implements.rs | 99 | ||||
| -rw-r--r-- | core/src/data/game/mod.rs | 6 | ||||
| -rw-r--r-- | core/src/data/game/runtime/implements.rs | 324 | ||||
| -rw-r--r-- | core/src/data/game/runtime/mod.rs | 2 | ||||
| -rw-r--r-- | core/src/data/game/runtime/structs.rs | 40 | ||||
| -rw-r--r-- | core/src/data/game/structs.rs | 29 | ||||
| -rw-r--r-- | core/src/data/game/types.rs | 7 |
9 files changed, 543 insertions, 0 deletions
diff --git a/core/src/data/game/cli/cli_command.rs b/core/src/data/game/cli/cli_command.rs new file mode 100644 index 0000000..1d79515 --- /dev/null +++ b/core/src/data/game/cli/cli_command.rs @@ -0,0 +1,35 @@ +use std::sync::{Arc, Mutex}; +use clap::{Parser, Subcommand}; +use nogamepads::entry_mutex; +use crate::data::game::runtime::structs::GameRuntime; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct GameCli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + + #[command(about = "Clean the screen")] + Clear, + + #[command(about = "Close the game")] + Close +} + +pub fn process_game_cli(runtime: Arc<Mutex<GameRuntime>>, cmd: GameCli) { + match cmd.command { + Commands::Clear => { + + } + + Commands::Close => { + entry_mutex!(runtime, |guard| { + guard.close_game(); + }) + } + } +}
\ No newline at end of file diff --git a/core/src/data/game/cli/mod.rs b/core/src/data/game/cli/mod.rs new file mode 100644 index 0000000..043f7b8 --- /dev/null +++ b/core/src/data/game/cli/mod.rs @@ -0,0 +1 @@ +pub mod cli_command;
\ No newline at end of file diff --git a/core/src/data/game/implements.rs b/core/src/data/game/implements.rs new file mode 100644 index 0000000..ad86572 --- /dev/null +++ b/core/src/data/game/implements.rs @@ -0,0 +1,99 @@ +use std::sync::{Arc, Mutex}; +use nogamepads::entry_mutex; +use crate::data::game::runtime::structs::{GameControlRuntime, GameRuntime, GameRuntimeData}; +use crate::data::game::structs::{GameControlData, GameData, GameRuntimeDataArchive}; +use crate::data::game::types::{GameInfo, Players}; +use crate::data::player::structs::Player; + +impl Default for GameData { + fn default() -> Self { + GameData::new() + } +} + +impl GameData { + + /// Create new game data + pub fn new() -> GameData { + let mut game = GameData { + info: GameInfo::default(), + control: GameControlData::default(), + archive: GameRuntimeDataArchive::default(), + }; + + game.name("Mini Hero".to_string()); + game.version(env!("PROJECT_VERSION").to_string()); + game + } + + /// Add or modify game name information + pub fn name(&mut self, name: String) -> &mut GameData { + self.info("Game_Name".to_string(), name); + self + } + + /// Add or modify game version information + pub fn version(&mut self, version: String) -> &mut GameData { + self.info("Version".to_string(), version); + self + } + + /// Add or modify information for a specific entry + pub fn info(&mut self, name: String, value: String) -> &mut GameData { + self.info.insert(name, value); + self + } + + /// Read game runtime archive data + pub fn load_data(&mut self, storage: GameRuntimeDataArchive) -> &mut GameData { + self.archive = storage; + self + } + + /// Build the game-side runtime using game data + pub fn runtime(self) -> Arc<Mutex<GameRuntime>> { + let runtime = GameRuntime { + info: self.info, + data: self.archive.into(), + control: GameControlRuntime { + keys: self.control, + ..Default::default() + }, + + writer_count: 0, + reader_count: 0, + }; + Arc::new(Mutex::new(runtime)) + } +} + +impl From<GameRuntimeDataArchive> for GameRuntimeData { + fn from(archive: GameRuntimeDataArchive) -> Self { + let banned_mutex = Players::default(); + entry_mutex!(banned_mutex, |guard| { + for account in archive.banned { + let player_info = Player::from(account.clone()); + guard.entry(account).or_insert_with(|| player_info); + } + }); + GameRuntimeData { + players_banned : banned_mutex, + ..Self::default() + } + } +} + +impl From<GameRuntimeData> for GameRuntimeDataArchive { + fn from(data: GameRuntimeData) -> Self { + let mut banned = Vec::new(); + entry_mutex!(data.players_online, |guard| { + for account in guard.keys().into_iter() { + banned.push(account.to_owned()); + } + }); + + GameRuntimeDataArchive { + banned + } + } +}
\ No newline at end of file diff --git a/core/src/data/game/mod.rs b/core/src/data/game/mod.rs new file mode 100644 index 0000000..4a0a3d4 --- /dev/null +++ b/core/src/data/game/mod.rs @@ -0,0 +1,6 @@ +pub mod cli; +pub mod runtime; + +pub mod implements; +pub mod structs; +pub mod types;
\ No newline at end of file diff --git a/core/src/data/game/runtime/implements.rs b/core/src/data/game/runtime/implements.rs new file mode 100644 index 0000000..62038ba --- /dev/null +++ b/core/src/data/game/runtime/implements.rs @@ -0,0 +1,324 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering::SeqCst; +use log::{info, trace, warn}; +use nogamepads::entry_mutex; +use crate::data::game::runtime::structs::{GameControlRuntime, GameRuntime, GameRuntimeData}; +use crate::data::game::types::Players; +use crate::data::message::enums::{JoinFailedMessage, ControlMessage, ExitReason, GameMessage}; +use crate::data::message::enums::JoinFailedMessage::{ContainIdenticalPlayer, GameLocked, PlayerBanned}; +use crate::data::message::enums::ControlMessage::{Axis, Dir, Msg, Pressed, Released}; +use crate::data::message::enums::GameMessage::LetExit; +use crate::data::message::traits::MessageManager; +use crate::data::player::structs::{Account, Player}; +use crate::service::service_types::ServiceType; +use crate::service::service_types::ServiceType::TCPConnection; + +impl GameRuntime { + + /// Attempt to have the specified player join the game + pub fn try_join_player(&mut self, player: Player) -> Result<(), JoinFailedMessage> { + let join = self.can_join_game(&player.account); + match join { + Ok(_) => { + self.data.sign_player_online_status(&player, TCPConnection, true); + trace!("[Game Runtime] Player \"{}\" joined", player.account); + Ok(()) + } + Err(why) => { + warn!("[Game Runtime] Player \"{}\" join failed: {:?}", player.account, why); + Err(why) + } + } + } + + fn can_join_game(&self, account: &Account) -> Result<bool, JoinFailedMessage> { + + if self.is_game_locked() { + Err(GameLocked) + } else if self.data.is_account_banned(account) { + Err(PlayerBanned) + } else if self.data.is_account_online(account) { + Err(ContainIdenticalPlayer) + } else { + Ok(true) + } + } + + /// Request an account to exit + pub fn let_account_exit(&mut self, account: &Account, reason: ExitReason, service_type: ServiceType) { + // Send a leave message to the pad_client and wait for it to actively disconnect + if self.data.is_account_online(account) { + self.send((account.clone(), LetExit(reason)), account.clone(), service_type); + } + } + + /// Check if the game is locked + pub fn is_game_locked(&self) -> bool { + self.data.locked.load(SeqCst) + } + + /// Lock the game + pub fn lock_game(&self) { + if !self.data.locked.load(SeqCst) { + self.data.locked.store(true, SeqCst); + info!("[Game Runtime] Game locked!"); + } + } + + /// Unlock the game + pub fn unlock_game(&self) { + if self.data.locked.load(SeqCst) { + self.data.locked.store(false, SeqCst); + info!("[Game Runtime] Game unlocked!"); + } + } + + /// Close the Game + pub fn close_game(&self) { + if !self.data.close.load(SeqCst) { + self.data.close.store(true, SeqCst); + info!("[Game Runtime] Game closed!"); + } + } +} + +/// Message manager for game pad_client runtime +/// After the service starts, it can be accessed or relevant messages can be stored. +impl MessageManager<(Account, ControlMessage), (Account, GameMessage), Account> for GameRuntime { + fn borrow_received_list_mut(&mut self) -> &mut HashMap<(ServiceType, Account), VecDeque<(Account, ControlMessage)>> { + &mut self.data.received + } + + fn borrow_send_list_mut(&mut self) -> &mut HashMap<(ServiceType, Account), VecDeque<(Account, GameMessage)>> { + &mut self.data.send + } + + fn pop_from_send_list(&mut self, key: Account, service: ServiceType) -> Option<(Account, GameMessage)> { + let key = (service, key); + self.borrow_send_list_mut() + .entry(key) + .or_insert_with(VecDeque::new) + .pop_front() + } + + fn put_into_receive_list(&mut self, message: (Account, ControlMessage), _key: Account, _service: ServiceType) { + let result = self.control.process_control_message(&message.0, message.1); + if result.is_err() { + let result = result.unwrap_err(); + warn!("[Game Runtime] Can't process message: {:?}", result); + drop(result); + } + } +} + +impl Default for GameRuntimeData { + fn default() -> Self { + Self { + received: Default::default(), + send: Default::default(), + players_online: Players::default(), + players_banned: Players::default(), + + locked: AtomicBool::new(false), + close: AtomicBool::new(false) + } + } +} + +impl GameRuntimeData { + + /// Mark a player as online + pub fn sign_player_online_status(&mut self, player: &Player, service_type: ServiceType, value: bool) { + let online = self.is_account_online(&player.account); + if online && !value { + + // Remove player + entry_mutex!(self.players_online, |guard| { + guard.remove_entry(&player.account); + }); + + info!("[Game Runtime] Signed player \"{}\" is [OFFLINE]!", player.account); + + // Reset runtime + let key = (service_type, player.account.clone()); + let get_received = self.received.get_mut(&key); + let get_send = self.send.get_mut(&key); + if let Some(mut list) = get_received { + list.clear(); + } + if let Some(mut list) = get_send { + list.clear(); + } + + } else if !online & value { + + // Insert player + entry_mutex!(self.players_online, |guard| { + guard.entry(player.account.clone()) + .or_insert_with(|| player.clone()); + }); + + info!("[Game Runtime] Signed player \"{}\" is [ONLINE]!", player.account); + } + } + + /// Returns all online accounts + pub fn online_accounts(&self) -> Vec<Account> { + let mut vec = Vec::new(); + entry_mutex!(self.players_online, |guard| { + for account in guard.keys().into_iter() { + vec.push(account.clone()); + } + }); + vec + } + + /// Check if specified account is online + pub fn is_account_online(&self, account: &Account) -> bool { + entry_mutex!(self.players_online, |guard| { + if guard.contains_key(account) { + return true; + } + }); + false + } + + /// Returns all banned accounts + pub fn banned_accounts(&self) -> Vec<Account> { + let mut vec = Vec::new(); + entry_mutex!(self.players_banned, |guard| { + for account in guard.keys().into_iter() { + vec.push(account.clone()); + } + }); + vec + } + + /// Check if account is banned + pub fn is_account_banned(&self, account: &Account) -> bool { + entry_mutex!(self.players_banned, |guard| { + if guard.contains_key(account) { + true; + } + }); + false + } +} + +impl GameControlRuntime { + + /// Process a control message + fn process_control_message(&mut self, who: &Account, msg: ControlMessage) -> Result<(), ControlMessage> { + match msg { + Msg(_) => { + self.send_event(who, msg); + Ok(()) + } + + Pressed(button_key) => { + let key_valid = self.check_key(&self.keys.button_keys, &button_key); + if key_valid { + Self::change_value(&mut self.button, button_key, who, true); + self.send_event(who, msg); + trace!("[Control Runtime] Player \"{}\" pressed btn_{}", &who.id, button_key); + } else { + warn!("[Control Runtime] Key btn_{} not registered!", button_key); + } + Ok(()) + } + + Released(button_key) => { + if self.check_key(&self.keys.button_keys, &button_key) { + Self::change_value(&mut self.button, button_key, who, false); + self.send_event(who, msg); + trace!("[Control Runtime] Player \"{}\" released btn_{}", &who.id, button_key); + } else { + warn!("[Control Runtime] Key btn_{} not registered!", button_key); + } + Ok(()) + } + + Axis(axis_key, axis) => { + if self.check_key(&self.keys.button_keys, &axis_key) { + Self::change_value(&mut self.axes, axis_key, who, axis); + trace!("[Control Runtime] Player \"{}\" changed ax_{} to ({})", &who.id, axis_key, axis); + } else { + warn!("[Control Runtime] Key ax_{} not registered!", axis_key); + } + Ok(()) + } + + Dir(dir_key, dir) => { + if self.check_key(&self.keys.button_keys, &dir_key) { + Self::change_value(&mut self.directions, dir_key, who, dir); + trace!("[Control Runtime] Player \"{}\" changed dir_{} to ({}, {})", &who.id, dir_key, dir.0, dir.1); + } else { + warn!("[Control Runtime] Key dir_{} not registered!", dir_key); + } + Ok(()) + } + + _ => { + Err(msg) + } + } + } + + /// Pop an event message + pub fn pop_event(&mut self, game_runtime: &GameRuntime) -> Option<(Account, ControlMessage)> { + let pop = self.events.pop_front(); + if pop.is_some() { + let (account, msg) = pop.unwrap(); + if game_runtime.data.is_account_online(&account) { + trace!("[Control Runtime] Message: {:?} from \"{}\" ", &msg, account); + Some((account, msg)) + } else { + warn!("[Control Runtime] Invalid message: Player \"{}\" is not online!", account); + None + } + } else { + None + } + } + + /// Get specified player's direction value + pub fn get_direction(&self, who: &Account, key: &u8) -> Option<(f64, f64)> { + Self::get(&self.directions, who, key) + } + + /// Get specified player's axis value + pub fn get_axis(&self, who: &Account, key: &u8) -> Option<f64> { + Self::get(&self.axes, who, key) + } + + /// Get specified player's button status + pub fn get_button_status(&self, who: &Account, key: &u8) -> Option<bool> { + Self::get(&self.button, who, key) + } + + fn check_key(&self, map: &HashMap<u8, String>, key: &u8) -> bool { + map.contains_key(key) + } + + fn get<V: Clone>(map: &HashMap<u8, HashMap<Account, V>>, who: &Account, key: &u8) -> Option<V> { + let key = map.get(key); + if key.is_some() { + let value = key.unwrap().get(who); + if value.is_some() { + let result = value.unwrap(); + Some(result.clone()) + } else { None } + } else { None } + } + + fn change_value<T>(map: &mut HashMap<u8, HashMap<Account, T>>, key: u8, who: &Account, msg: T) { + map.entry(key) + .or_insert_with(HashMap::new) + .insert(who.clone(), msg); + } + + fn send_event(&mut self, who: &Account, msg: ControlMessage) { + self.events.push_back((who.clone(), msg)); + } +}
\ No newline at end of file diff --git a/core/src/data/game/runtime/mod.rs b/core/src/data/game/runtime/mod.rs new file mode 100644 index 0000000..0ff870f --- /dev/null +++ b/core/src/data/game/runtime/mod.rs @@ -0,0 +1,2 @@ +pub mod implements; +pub mod structs;
\ No newline at end of file diff --git a/core/src/data/game/runtime/structs.rs b/core/src/data/game/runtime/structs.rs new file mode 100644 index 0000000..bf68424 --- /dev/null +++ b/core/src/data/game/runtime/structs.rs @@ -0,0 +1,40 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::AtomicBool; +use crate::data::game::structs::GameControlData; +use crate::data::game::types::{GameInfo, Players}; +use crate::data::message::enums::{ControlMessage, GameMessage}; +use crate::data::player::structs::Account; +use crate::service::service_types::ServiceType; + +/// Game pad_client runtime +/// Stores the game state, player information, and all data involved in controller-side interactions during runtime +pub struct GameRuntime { + + pub info: GameInfo, + pub data: GameRuntimeData, + pub control: GameControlRuntime, + + pub writer_count: i32, + pub reader_count: i32, +} + +pub struct GameRuntimeData { + + pub(crate) received: HashMap<(ServiceType, Account), VecDeque<(Account, ControlMessage)>>, + pub(crate) send: HashMap<(ServiceType, Account), VecDeque<(Account, GameMessage)>>, + + pub(crate) players_online: Players, + pub(crate) players_banned: Players, + + pub locked: AtomicBool, + pub close: AtomicBool, +} + +#[derive(Default)] +pub struct GameControlRuntime { + pub(crate) keys: GameControlData, + pub(crate) directions : HashMap<u8, HashMap<Account, (f64, f64)>>, + pub(crate) axes : HashMap<u8, HashMap<Account, f64>>, + pub(crate) button : HashMap<u8, HashMap<Account, bool>>, + pub(crate) events : VecDeque<(Account, ControlMessage)> +}
\ No newline at end of file diff --git a/core/src/data/game/structs.rs b/core/src/data/game/structs.rs new file mode 100644 index 0000000..52fc0f1 --- /dev/null +++ b/core/src/data/game/structs.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; +use crate::data::game::types::GameInfo; +use crate::data::player::structs::Account; + +/// Game pad_client data +/// Describes the basic information of the game pad_client +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct GameData { + pub info: GameInfo, + pub control: GameControlData, + pub archive: GameRuntimeDataArchive +} + +/// Game control information +/// Describes the buttons, axes, and directions that can be controlled. +#[derive(Default, Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct GameControlData { + pub direction_keys : HashMap<u8, String>, + pub axis_keys : HashMap<u8, String>, + pub button_keys : HashMap<u8, String>, +} + +/// Archive of game runtime data +/// The game pad_client can convert data into this structure for persistence. +#[derive(Default, Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct GameRuntimeDataArchive { + pub banned: Vec<Account> +}
\ No newline at end of file diff --git a/core/src/data/game/types.rs b/core/src/data/game/types.rs new file mode 100644 index 0000000..8af17d3 --- /dev/null +++ b/core/src/data/game/types.rs @@ -0,0 +1,7 @@ +use std::collections::HashMap; +use std::sync::Mutex; +use crate::data::player::structs::{Account, Player}; + +pub(crate) type GameInfo = HashMap<String, String>; + +pub(crate) type Players = Mutex<HashMap<Account, Player>>;
\ No newline at end of file |
