Blacksite/crates/protocol/src/lib.rs
Rbanh f29712d158
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run
Initial project import
2026-06-05 21:44:45 -04:00

116 lines
3.6 KiB
Rust

//! 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::<SimTick>()
.register_type::<PlayerId>()
.register_type::<PlayerInputIntent>()
.register_type::<PlayerCommand>()
.register_type::<ClientMessage>()
.register_type::<ServerEvent>();
}
}