//! Shared game/protocol types that are independent of rendering, audio, and editor code. //! //! This crate is intentionally small in M2. It provides stable tick/input/event //! shapes that local standalone play can use now and future networking can build on. use bevy::prelude::*; use serde::{Deserialize, Serialize}; /// Fixed simulation tick index. #[derive( Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect, Serialize, Deserialize, )] #[reflect(Default, Debug, Hash, Serialize, Deserialize)] pub struct SimTick(pub u64); impl SimTick { pub const ZERO: Self = Self(0); pub const fn next(self) -> Self { Self(self.0 + 1) } } /// The target fixed simulation rate for gameplay systems. pub const SIM_TICK_RATE_HZ: u32 = 64; /// The target fixed simulation delta in seconds. pub const SIM_DELTA_SECS: f32 = 1.0 / SIM_TICK_RATE_HZ as f32; /// Stable identifier for a player in protocol messages. /// /// M2 only has a local player, but keeping the ID explicit avoids baking local /// single-player assumptions into the simulation layer. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] #[reflect(Default, Debug, Hash, Serialize, Deserialize)] pub struct PlayerId(pub u64); /// Player input expressed as gameplay intent, not device state. #[derive(Component, Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)] #[reflect(Component, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct PlayerInputIntent { /// Local-space movement intent: X is right, Y is forward. pub movement: Vec2, /// Accumulated look delta in device pixels since the sim/client last consumed it. pub look_delta: Vec2, /// Edge-triggered jump command, consumed by the simulation. pub jump_pressed: bool, /// Level-triggered sprint modifier. pub sprint_held: bool, /// Level-triggered crouch modifier. pub crouch_held: bool, } impl Default for PlayerInputIntent { fn default() -> Self { Self { movement: Vec2::ZERO, look_delta: Vec2::ZERO, jump_pressed: false, sprint_held: false, crouch_held: false, } } } /// Command envelope suitable for local replay or future transport. #[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)] #[reflect(Debug, PartialEq, Serialize, Deserialize)] pub struct PlayerCommand { pub tick: SimTick, pub player: PlayerId, pub intent: PlayerInputIntent, } /// Minimal client-to-server message vocabulary reserved for M3 networking. #[derive(Debug, Clone, PartialEq, Reflect, Serialize, Deserialize)] #[reflect(Debug, PartialEq, Serialize, Deserialize)] pub enum ClientMessage { Input(PlayerCommand), Join { requested_player: PlayerId }, } /// Minimal server-to-client event vocabulary reserved for M3 networking. #[derive(Debug, Clone, PartialEq, Reflect, Serialize, Deserialize)] #[reflect(Debug, PartialEq, Serialize, Deserialize)] pub enum ServerEvent { Accepted { player: PlayerId, tick: SimTick }, PlayerSpawned { player: PlayerId }, PlayerDespawned { player: PlayerId }, } /// Registers protocol types used in reflected scenes/resources and future messages. pub struct ProtocolPlugin; impl Plugin for ProtocolPlugin { fn build(&self, app: &mut App) { app.register_type::() .register_type::() .register_type::() .register_type::() .register_type::() .register_type::(); } }