From 53be9178d788c9895beef2ab4b47bb79f72e62b5 Mon Sep 17 00:00:00 2001 From: Martin Indra Date: Mon, 11 Sep 2023 22:38:39 +0200 Subject: [PATCH] Add in-game network messages (#717) --- Cargo.lock | 4 + crates/messages/Cargo.toml | 9 ++ crates/messages/src/players/entity.rs | 20 ++++ crates/messages/src/players/geom.rs | 106 +++++++++++++++++++ crates/messages/src/players/mod.rs | 35 +++++++ crates/messages/src/players/path.rs | 60 +++++++++++ crates/pathing/src/chain.rs | 6 +- crates/pathing/src/dijkstra.rs | 2 +- crates/pathing/src/finder.rs | 2 +- crates/pathing/src/funnel.rs | 3 +- crates/pathing/src/path.rs | 144 +------------------------- crates/pathing/src/pplugin.rs | 4 +- crates/types/Cargo.toml | 1 + crates/types/src/lib.rs | 1 + crates/types/src/objects.rs | 19 +++- crates/types/src/path.rs | 136 ++++++++++++++++++++++++ crates/types/src/player.rs | 5 +- 17 files changed, 399 insertions(+), 158 deletions(-) create mode 100644 crates/messages/src/players/entity.rs create mode 100644 crates/messages/src/players/geom.rs create mode 100644 crates/messages/src/players/path.rs create mode 100644 crates/types/src/path.rs diff --git a/Cargo.lock b/Cargo.lock index 24a59bfa9..3fd54acab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2520,7 +2520,10 @@ dependencies = [ name = "de_messages" version = "0.1.0-dev" dependencies = [ + "bevy", "bincode", + "de_types", + "glam", "thiserror", ] @@ -2678,6 +2681,7 @@ dependencies = [ name = "de_types" version = "0.1.0-dev" dependencies = [ + "bincode", "enum-iterator", "enum-map", "glam", diff --git a/crates/messages/Cargo.toml b/crates/messages/Cargo.toml index bc2938b74..f89a86fb2 100644 --- a/crates/messages/Cargo.toml +++ b/crates/messages/Cargo.toml @@ -11,6 +11,15 @@ homepage.workspace = true license.workspace = true categories.workspace = true +[features] +bevy = ["dep:bevy"] + [dependencies] +# DE +de_types.workspace = true + +# Other +bevy = { workspace = true, optional = true } bincode.workspace = true +glam.workspace = true thiserror.workspace = true diff --git a/crates/messages/src/players/entity.rs b/crates/messages/src/players/entity.rs new file mode 100644 index 000000000..33861c88d --- /dev/null +++ b/crates/messages/src/players/entity.rs @@ -0,0 +1,20 @@ +#[cfg(feature = "bevy")] +use bevy::ecs::entity::Entity; +use bincode::{Decode, Encode}; + +/// Bevy ECS Entity derived identification of an entity. +#[derive(Debug, Encode, Decode)] +pub struct EntityNet(u32); + +impl EntityNet { + pub fn index(&self) -> u32 { + self.0 + } +} + +#[cfg(feature = "bevy")] +impl From for EntityNet { + fn from(entity: Entity) -> Self { + Self(entity.index()) + } +} diff --git a/crates/messages/src/players/geom.rs b/crates/messages/src/players/geom.rs new file mode 100644 index 000000000..28f8dc15c --- /dev/null +++ b/crates/messages/src/players/geom.rs @@ -0,0 +1,106 @@ +#[cfg(feature = "bevy")] +use bevy::transform::components::Transform; +use bincode::{Decode, Encode}; +#[cfg(feature = "bevy")] +use glam::Quat; +use glam::{Vec2, Vec3, Vec4}; + +#[derive(Debug, Encode, Decode)] +pub struct TransformNet { + translation: Vec3Net, + rotation: Vec4Net, + scale: Vec3Net, +} + +#[cfg(feature = "bevy")] +impl From for TransformNet { + fn from(transform: Transform) -> Self { + Self { + translation: transform.translation.into(), + rotation: Vec4Net { + x: transform.rotation.x, + y: transform.rotation.y, + z: transform.rotation.z, + w: transform.rotation.w, + }, + scale: transform.scale.into(), + } + } +} + +#[cfg(feature = "bevy")] +impl From for Transform { + fn from(transform: TransformNet) -> Self { + Self { + translation: transform.translation.into(), + rotation: Quat::from_vec4(transform.rotation.into()), + scale: transform.scale.into(), + } + } +} + +#[derive(Clone, Copy, Debug, Encode, Decode)] +pub struct Vec2Net { + x: f32, + y: f32, +} + +impl From for Vec2Net { + fn from(vec: Vec2) -> Self { + Self { x: vec.x, y: vec.y } + } +} + +impl From for Vec2 { + fn from(vec: Vec2Net) -> Self { + Self::new(vec.x, vec.y) + } +} + +#[derive(Clone, Copy, Debug, Encode, Decode)] +pub struct Vec3Net { + x: f32, + y: f32, + z: f32, +} + +impl From for Vec3Net { + fn from(vec: Vec3) -> Self { + Self { + x: vec.x, + y: vec.y, + z: vec.z, + } + } +} + +impl From for Vec3 { + fn from(vec: Vec3Net) -> Self { + Self::new(vec.x, vec.y, vec.z) + } +} + +#[derive(Clone, Copy, Debug, Encode, Decode)] +pub struct Vec4Net { + x: f32, + y: f32, + z: f32, + w: f32, +} + +impl From for Vec4Net { + fn from(vec: Vec4) -> Self { + Self { + x: vec.x, + y: vec.y, + z: vec.z, + w: vec.w, + } + } +} + +impl From for Vec4 { + fn from(vec: Vec4Net) -> Self { + Self::new(vec.x, vec.y, vec.z, vec.w) + } +} diff --git a/crates/messages/src/players/mod.rs b/crates/messages/src/players/mod.rs index 8cec4769b..84656b151 100644 --- a/crates/messages/src/players/mod.rs +++ b/crates/messages/src/players/mod.rs @@ -1,7 +1,14 @@ use bincode::{Decode, Encode}; pub use chat::{ChatMessage, ChatMessageError, MAX_CHAT_LEN}; +use de_types::{objects::ActiveObjectType, player::Player}; +pub use entity::EntityNet; +pub use geom::{TransformNet, Vec2Net, Vec3Net, Vec4Net}; +pub use path::{PathError, PathNet}; mod chat; +mod entity; +mod geom; +mod path; /// Messages to be sent by a player/client or occasionally the game server to /// other players. @@ -42,7 +49,35 @@ impl<'a> BorrowedFromPlayers<'a> { /// Message to be sent by a player/client or occasionally the game server to /// the game server for the distribution to other game players. +/// +/// All messages controlling an active entity / object must be local on the +/// sending computer. #[derive(Debug, Encode, Decode)] pub enum ToPlayers { Chat(ChatMessage), + /// Spawn a new active object on the map. + Spawn { + entity: EntityNet, + player: Player, + object_type: ActiveObjectType, + transform: TransformNet, + }, + /// Despawn an active object type. + Despawn { + entity: EntityNet, + }, + /// Set path to be followed for an object. Any preexisting path will be + /// replaced by this one. + SetPath { + entity: EntityNet, + waypoints: PathNet, + }, + /// Instantaneously transform an object. + /// + /// This has no effect on scheduled path as it just moves the object which + /// then continues following the path. + Transform { + entity: EntityNet, + transform: TransformNet, + }, } diff --git a/crates/messages/src/players/path.rs b/crates/messages/src/players/path.rs new file mode 100644 index 000000000..ef4adb1e2 --- /dev/null +++ b/crates/messages/src/players/path.rs @@ -0,0 +1,60 @@ +use std::mem::size_of; + +use bincode::{Decode, Encode}; +use de_types::path::Path; +use glam::Vec2; +use thiserror::Error; + +use super::Vec2Net; + +const MAX_PATH_SIZE: usize = 480; + +#[derive(Debug, Encode, Decode)] +pub struct PathNet(Vec); + +impl TryFrom<&Path> for PathNet { + type Error = PathError; + + fn try_from(path: &Path) -> Result { + let waypoints = path.waypoints(); + + if waypoints.is_empty() { + return Err(PathError::Empty); + } + + let size = waypoints.len() * size_of::(); + if size > MAX_PATH_SIZE { + return Err(PathError::TooLarge { + size, + max_size: MAX_PATH_SIZE, + }); + } + + Ok(Self(waypoints.iter().map(|&p| p.into()).collect())) + } +} + +impl From<&PathNet> for Path { + fn from(path: &PathNet) -> Self { + let mut waypoints: Vec = Vec::with_capacity(path.0.len()); + let mut length = 0.; + + for &point in &path.0 { + let point = point.into(); + if let Some(prev) = waypoints.last() { + length += prev.distance(point); + } + waypoints.push(point); + } + + Path::new(length, waypoints) + } +} + +#[derive(Debug, Error)] +pub enum PathError { + #[error("The path is empty")] + Empty, + #[error("Too many path way-points: {size} bytes > {max_size} bytes")] + TooLarge { size: usize, max_size: usize }, +} diff --git a/crates/pathing/src/chain.rs b/crates/pathing/src/chain.rs index 689d11d3b..8b03e914c 100644 --- a/crates/pathing/src/chain.rs +++ b/crates/pathing/src/chain.rs @@ -3,12 +3,10 @@ use std::rc::Rc; +use de_types::path::Path; use parry2d::math::Point; -use crate::{ - geometry::{which_side, Side}, - path::Path, -}; +use crate::geometry::{which_side, Side}; /// A linked list of points which keeps track of its length in meters. pub(crate) struct PointChain { diff --git a/crates/pathing/src/dijkstra.rs b/crates/pathing/src/dijkstra.rs index b0b0b6519..4f9067ef8 100644 --- a/crates/pathing/src/dijkstra.rs +++ b/crates/pathing/src/dijkstra.rs @@ -4,13 +4,13 @@ use std::{cmp::Ordering, collections::BinaryHeap}; use ahash::AHashSet; use bevy::utils::FloatOrd; +use de_types::path::Path; use parry2d::{math::Point, na, query::PointQuery, shape::Segment}; use crate::{ funnel::Funnel, geometry::{orient, which_side, Side}, graph::VisibilityGraph, - path::Path, PathQueryProps, }; diff --git a/crates/pathing/src/finder.rs b/crates/pathing/src/finder.rs index 3d01e2b8c..dc2148849 100644 --- a/crates/pathing/src/finder.rs +++ b/crates/pathing/src/finder.rs @@ -3,6 +3,7 @@ use ahash::AHashMap; use bevy::prelude::{debug, info}; use de_map::size::MapBounds; +use de_types::path::Path; use parry2d::{ math::Point, na, @@ -16,7 +17,6 @@ use crate::{ dijkstra::{find_path, PointContext}, exclusion::ExclusionArea, graph::VisibilityGraph, - path::Path, utils::HashableSegment, PathTarget, }; diff --git a/crates/pathing/src/funnel.rs b/crates/pathing/src/funnel.rs index a58843ac8..613518cb7 100644 --- a/crates/pathing/src/funnel.rs +++ b/crates/pathing/src/funnel.rs @@ -4,9 +4,10 @@ use std::rc::Rc; +use de_types::path::Path; use parry2d::{math::Point, shape::Segment}; -use crate::{chain::PointChain, geometry::Side, path::Path}; +use crate::{chain::PointChain, geometry::Side}; /// The funnel consists of a tail and left & right bounds. /// diff --git a/crates/pathing/src/path.rs b/crates/pathing/src/path.rs index 45d05f137..a8cd7731d 100644 --- a/crates/pathing/src/path.rs +++ b/crates/pathing/src/path.rs @@ -1,8 +1,7 @@ //! Tools and struts for working with paths on the surface of a map. -#[cfg(debug_assertions)] -use approx::assert_abs_diff_eq; use bevy::prelude::Component; +use de_types::path::Path; use glam::Vec2; const CURRENT_SEGMENT_BIAS: f32 = 4.; @@ -105,95 +104,6 @@ impl ScheduledPath { } } -/// A path on the map defined by a sequence of way points. Start and target -/// position are included. -pub struct Path { - length: f32, - waypoints: Vec, -} - -impl Path { - /// Creates a path on line `from` -> `to`. - pub(crate) fn straight>(from: P, to: P) -> Self { - let waypoints = vec![to.into(), from.into()]; - Self::new(waypoints[0].distance(waypoints[1]), waypoints) - } - - /// Creates a new path. - /// - /// # Panics - /// - /// May panic if sum of distances of `waypoints` is not equal to provided - /// `length`. - pub(crate) fn new(length: f32, waypoints: Vec) -> Self { - #[cfg(debug_assertions)] - { - assert_abs_diff_eq!( - waypoints - .windows(2) - .map(|pair| (pair[1] - pair[0]).length()) - .sum::(), - length, - epsilon = 0.001, - ); - } - Self { length, waypoints } - } - - /// Returns the length of the path in meters. - pub(crate) fn length(&self) -> f32 { - self.length - } - - /// Returns a sequence of the remaining path way points. The last way point - /// corresponds to the start of the path and vice versa. - pub(crate) fn waypoints(&self) -> &[Vec2] { - self.waypoints.as_slice() - } - - /// Returns a path shortened by `amount` from the end. Returns None - /// `amount` is longer than the path. - pub(crate) fn truncated(mut self, mut amount: f32) -> Option { - if amount == 0. { - return Some(self); - } else if amount >= self.length { - return None; - } - - for i in 1..self.waypoints.len() { - debug_assert!(self.length > 0.); - debug_assert!(amount > 0.); - - let last = self.waypoints[i - 1]; - let preceeding = self.waypoints[i]; - - let diff = last - preceeding; - let diff_len = diff.length(); - - if diff_len < amount { - self.length -= diff_len; - amount -= diff_len; - } else if diff_len > amount { - self.length -= amount; - let mut waypoints = Vec::with_capacity(self.waypoints.len() - i + 1); - let fraction = (diff_len - amount) / diff_len; - waypoints.push(preceeding + fraction * diff); - waypoints.extend_from_slice(&self.waypoints[i..]); - return Some(Self::new(self.length, waypoints)); - } else if diff_len == amount { - self.length -= amount; - let mut waypoints = Vec::with_capacity(self.waypoints.len() - i); - waypoints.extend_from_slice(&self.waypoints[i..]); - return Some(Self::new(self.length, waypoints)); - } - } - - // This might happen due to rounding errors. Otherwise, this code - // should be unreachable. - None - } -} - #[cfg(test)] mod tests { use super::*; @@ -242,56 +152,4 @@ mod tests { (Vec2::new(4., 1.), 1.) ); } - - #[test] - fn test_path() { - let path = Path::new( - 8., - vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], - ); - assert_eq!(path.length(), 8.); - assert_eq!(path.waypoints().len(), 3); - - let path = Path::straight(Vec2::new(10., 11.), Vec2::new(22., 11.)); - assert_eq!(path.length(), 12.); - assert_eq!(path.waypoints().len(), 2); - } - - #[test] - fn test_truncated() { - let path = Path::new( - 8., - vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], - ) - .truncated(2.) - .unwrap(); - assert_eq!(path.length(), 6.); - assert_eq!(path.waypoints(), vec![Vec2::new(3., 2.), Vec2::new(3., 8.)]); - - let path = Path::new( - 8., - vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], - ) - .truncated(1.) - .unwrap(); - assert_eq!(path.length(), 7.); - assert_eq!( - path.waypoints(), - vec![Vec2::new(2., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)] - ); - - assert!(Path::new( - 8., - vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], - ) - .truncated(8.) - .is_none()); - - assert!(Path::new( - 8., - vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], - ) - .truncated(10.) - .is_none()); - } } diff --git a/crates/pathing/src/pplugin.rs b/crates/pathing/src/pplugin.rs index d97189bf1..f70fbaa68 100644 --- a/crates/pathing/src/pplugin.rs +++ b/crates/pathing/src/pplugin.rs @@ -10,12 +10,12 @@ use de_core::{ schedule::{PostMovement, PreMovement}, state::AppState, }; -use de_types::projection::ToFlat; +use de_types::{path::Path, projection::ToFlat}; use futures_lite::future; use crate::{ fplugin::{FinderRes, FinderSet, PathFinderUpdatedEvent}, - path::{Path, ScheduledPath}, + path::ScheduledPath, PathQueryProps, PathTarget, }; diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 6b317b275..efdd495ea 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true categories.workspace = true [dependencies] +bincode.workspace = true enum-iterator.workspace = true enum-map.workspace = true glam.workspace = true diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 3c32e438a..9de5a8476 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -5,5 +5,6 @@ //! used outside of the game itself (for example in DE Connector). pub mod objects; +pub mod path; pub mod player; pub mod projection; diff --git a/crates/types/src/objects.rs b/crates/types/src/objects.rs index 08d531457..2d064d7b2 100644 --- a/crates/types/src/objects.rs +++ b/crates/types/src/objects.rs @@ -3,6 +3,7 @@ use std::fmt; +use bincode::{Decode, Encode}; use enum_iterator::Sequence; use enum_map::Enum; use serde::{Deserialize, Serialize}; @@ -12,7 +13,7 @@ pub const PLAYER_MAX_BUILDINGS: u32 = 128; /// Maximum number of units belonging to a single player. pub const PLAYER_MAX_UNITS: u32 = 1024; -#[derive(Enum, Sequence, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Encode, Decode, Enum, Sequence, Copy, Clone, PartialEq, Eq, Hash)] pub enum ObjectType { Active(ActiveObjectType), Inactive(InactiveObjectType), @@ -27,7 +28,9 @@ impl fmt::Display for ObjectType { } } -#[derive(Enum, Sequence, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive( + Debug, Encode, Decode, Enum, Sequence, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, +)] pub enum InactiveObjectType { Tree, } @@ -40,7 +43,9 @@ impl fmt::Display for InactiveObjectType { } } -#[derive(Enum, Sequence, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive( + Debug, Encode, Decode, Enum, Sequence, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, +)] pub enum ActiveObjectType { Building(BuildingType), Unit(UnitType), @@ -55,7 +60,9 @@ impl fmt::Display for ActiveObjectType { } } -#[derive(Enum, Sequence, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive( + Debug, Encode, Decode, Enum, Sequence, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, +)] pub enum BuildingType { Base, PowerHub, @@ -70,7 +77,9 @@ impl fmt::Display for BuildingType { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Enum, Sequence, Hash)] +#[derive( + Debug, Encode, Decode, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Enum, Sequence, Hash, +)] pub enum UnitType { Attacker, } diff --git a/crates/types/src/path.rs b/crates/types/src/path.rs new file mode 100644 index 000000000..51d80e9a9 --- /dev/null +++ b/crates/types/src/path.rs @@ -0,0 +1,136 @@ +use glam::Vec2; + +/// A path on the map defined by a sequence of way points. Start and target +/// position are included. +pub struct Path { + length: f32, + waypoints: Vec, +} + +impl Path { + /// Creates a path on line `from` -> `to`. + pub fn straight>(from: P, to: P) -> Self { + let waypoints = vec![to.into(), from.into()]; + Self::new(waypoints[0].distance(waypoints[1]), waypoints) + } + + /// Creates a new path. + /// + /// # Panics + /// + /// May panic if sum of distances of `waypoints` is not equal to provided + /// `length`. + pub fn new(length: f32, waypoints: Vec) -> Self { + Self { length, waypoints } + } + + /// Returns the length of the path in meters. + pub fn length(&self) -> f32 { + self.length + } + + /// Returns a sequence of the remaining path way points. The last way point + /// corresponds to the start of the path and vice versa. + pub fn waypoints(&self) -> &[Vec2] { + self.waypoints.as_slice() + } + + /// Returns a path shortened by `amount` from the end. Returns None + /// `amount` is longer than the path. + pub fn truncated(mut self, mut amount: f32) -> Option { + if amount == 0. { + return Some(self); + } else if amount >= self.length { + return None; + } + + for i in 1..self.waypoints.len() { + debug_assert!(self.length > 0.); + debug_assert!(amount > 0.); + + let last = self.waypoints[i - 1]; + let preceeding = self.waypoints[i]; + + let diff = last - preceeding; + let diff_len = diff.length(); + + if diff_len < amount { + self.length -= diff_len; + amount -= diff_len; + } else if diff_len > amount { + self.length -= amount; + let mut waypoints = Vec::with_capacity(self.waypoints.len() - i + 1); + let fraction = (diff_len - amount) / diff_len; + waypoints.push(preceeding + fraction * diff); + waypoints.extend_from_slice(&self.waypoints[i..]); + return Some(Self::new(self.length, waypoints)); + } else if diff_len == amount { + self.length -= amount; + let mut waypoints = Vec::with_capacity(self.waypoints.len() - i); + waypoints.extend_from_slice(&self.waypoints[i..]); + return Some(Self::new(self.length, waypoints)); + } + } + + // This might happen due to rounding errors. Otherwise, this code + // should be unreachable. + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path() { + let path = Path::new( + 8., + vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], + ); + assert_eq!(path.length(), 8.); + assert_eq!(path.waypoints().len(), 3); + + let path = Path::straight(Vec2::new(10., 11.), Vec2::new(22., 11.)); + assert_eq!(path.length(), 12.); + assert_eq!(path.waypoints().len(), 2); + } + + #[test] + fn test_truncated() { + let path = Path::new( + 8., + vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], + ) + .truncated(2.) + .unwrap(); + assert_eq!(path.length(), 6.); + assert_eq!(path.waypoints(), vec![Vec2::new(3., 2.), Vec2::new(3., 8.)]); + + let path = Path::new( + 8., + vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], + ) + .truncated(1.) + .unwrap(); + assert_eq!(path.length(), 7.); + assert_eq!( + path.waypoints(), + vec![Vec2::new(2., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)] + ); + + assert!(Path::new( + 8., + vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], + ) + .truncated(8.) + .is_none()); + + assert!(Path::new( + 8., + vec![Vec2::new(1., 2.), Vec2::new(3., 2.), Vec2::new(3., 8.)], + ) + .truncated(10.) + .is_none()); + } +} diff --git a/crates/types/src/player.rs b/crates/types/src/player.rs index 33546cff1..906664a9a 100644 --- a/crates/types/src/player.rs +++ b/crates/types/src/player.rs @@ -1,9 +1,12 @@ use core::fmt; use std::cmp::Ordering; +use bincode::{Decode, Encode}; use serde::{Deserialize, Serialize}; -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive( + Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Encode, Decode, +)] pub enum Player { #[default] Player1,