aboutsummaryrefslogtreecommitdiff
path: root/core/src/data/game
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/data/game')
-rw-r--r--core/src/data/game/cli/cli_command.rs35
-rw-r--r--core/src/data/game/cli/mod.rs1
-rw-r--r--core/src/data/game/implements.rs99
-rw-r--r--core/src/data/game/mod.rs6
-rw-r--r--core/src/data/game/runtime/implements.rs324
-rw-r--r--core/src/data/game/runtime/mod.rs2
-rw-r--r--core/src/data/game/runtime/structs.rs40
-rw-r--r--core/src/data/game/structs.rs29
-rw-r--r--core/src/data/game/types.rs7
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