use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub type EntityId = u64;
pub type Tick = u64;
pub type Seq = u32;
pub type SessionId = u64;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct WorldCoord {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: u32,
pub t: u32,
}
impl WorldCoord {
pub fn surface(x: f32, y: f32) -> Self {
Self {
x,
y,
z: 0.0,
w: 0,
t: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Velocity2D {
pub vx: f32,
pub vy: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Transform {
pub position: WorldCoord,
pub yaw: f32,
pub velocity: Velocity2D,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LifeState {
Alive,
Dead,
}
impl Default for LifeState {
fn default() -> Self {
Self::Alive
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct PlayerVitals {
pub health: f32,
pub health_max: f32,
pub mana: f32,
pub mana_max: f32,
pub stamina: f32,
pub stamina_max: f32,
#[serde(default)]
pub coins: u32,
#[serde(default)]
pub deaths: u32,
#[serde(default)]
pub life_state: LifeState,
}
impl Default for PlayerVitals {
fn default() -> Self {
Self {
health: 100.0,
health_max: 100.0,
mana: 50.0,
mana_max: 50.0,
stamina: 100.0,
stamina_max: 100.0,
coins: 0,
deaths: 0,
life_state: LifeState::Alive,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EntityState {
pub id: EntityId,
pub transform: Transform,
#[serde(default)]
pub label: String,
#[serde(default)]
pub vitals: Option<PlayerVitals>,
#[serde(default)]
pub inside_building: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChatChannel {
Nearby,
WhisperStone,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChatMessage {
pub channel: ChatChannel,
pub from_entity: EntityId,
pub from_name: String,
pub text: String,
pub tick: Tick,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Intent {
Move {
entity_id: EntityId,
forward: f32,
strafe: f32,
seq: Seq,
},
Stop {
entity_id: EntityId,
seq: Seq,
},
Harvest {
entity_id: EntityId,
node_id: String,
seq: Seq,
},
Use {
entity_id: EntityId,
item_instance_id: Uuid,
seq: Seq,
},
Say {
entity_id: EntityId,
channel: ChatChannel,
text: String,
seq: Seq,
},
Craft {
entity_id: EntityId,
blueprint_id: String,
seq: Seq,
},
Interact {
entity_id: EntityId,
target_id: String,
seq: Seq,
},
TestDamage {
entity_id: EntityId,
amount: f32,
seq: Seq,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TickDelta {
pub tick: Tick,
pub entities: Vec<EntityState>,
#[serde(default)]
pub resource_nodes: Vec<ResourceNodeView>,
#[serde(default)]
pub buildings: Vec<BuildingView>,
#[serde(default)]
pub doors: Vec<DoorView>,
#[serde(default)]
pub npcs: Vec<NpcView>,
#[serde(default)]
pub inventory: Vec<ItemStack>,
#[serde(default)]
pub blueprints: Vec<BlueprintView>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Snapshot {
pub tick: Tick,
pub chunk_rev: u64,
pub entities: Vec<EntityState>,
#[serde(default)]
pub resource_nodes: Vec<ResourceNodeView>,
#[serde(default)]
pub world_width_m: f32,
#[serde(default)]
pub world_height_m: f32,
#[serde(default)]
pub buildings: Vec<BuildingView>,
#[serde(default)]
pub doors: Vec<DoorView>,
#[serde(default)]
pub npcs: Vec<NpcView>,
#[serde(default)]
pub inventory: Vec<ItemStack>,
#[serde(default)]
pub blueprints: Vec<BlueprintView>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceNodeView {
pub id: String,
pub label: String,
pub x: f32,
pub y: f32,
pub z: f32,
pub item_template: String,
#[serde(default = "default_node_state")]
pub state: ResourceNodeState,
#[serde(default)]
pub blocking: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResourceNodeState {
Available,
Harvesting,
Cooldown,
}
fn default_node_state() -> ResourceNodeState {
ResourceNodeState::Available
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ItemStack {
pub template_id: String,
pub quantity: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlueprintIngredientView {
pub template_id: String,
pub quantity: u32,
pub consumed: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolRequirementView {
pub item: String,
pub consumed: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SkillRequirementView {
pub skill: String,
pub level: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlueprintView {
pub id: String,
pub label: String,
pub output: String,
pub output_qty: u32,
pub craft_ticks: u32,
pub inputs: Vec<BlueprintIngredientView>,
#[serde(default)]
pub station: Option<String>,
#[serde(default)]
pub category: Option<String>,
#[serde(default)]
pub required_tools: Vec<ToolRequirementView>,
#[serde(default)]
pub skill: Option<SkillRequirementView>,
#[serde(default)]
pub failure_chance: f32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BuildingView {
pub id: String,
pub label: String,
pub x: f32,
pub y: f32,
pub width_m: f32,
pub depth_m: f32,
#[serde(default)]
pub interior_origin_x: f32,
#[serde(default)]
pub interior_origin_y: f32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DoorView {
pub id: String,
pub building_id: String,
pub x: f32,
pub y: f32,
pub open: bool,
#[serde(default)]
pub is_exit: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NpcView {
pub id: String,
pub label: String,
pub role: String,
pub x: f32,
pub y: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub building_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CraftResult {
pub blueprint_id: String,
pub outputs: Vec<ItemStack>,
pub consumed: Vec<ItemStack>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeathNotice {
pub entity_id: EntityId,
pub respawn_x: f32,
pub respawn_y: f32,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InteractionNotice {
pub target_id: String,
pub message: String,
#[serde(default)]
pub coins_delta: i32,
#[serde(default)]
pub inventory_delta: Vec<ItemStack>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HarvestResult {
pub node_id: String,
pub quantity: u32,
pub item_template: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item_instance_id: Option<Uuid>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Envelope<T> {
pub protocol_version: u16,
pub payload: T,
}
impl<T> Envelope<T> {
pub fn new(payload: T) -> Self {
Self {
protocol_version: crate::PROTOCOL_VERSION,
payload,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Hello {
pub client_name: String,
pub protocol_version: u16,
#[serde(default)]
pub auth: AuthCredential,
#[serde(default)]
pub character_id: Option<Uuid>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthCredential {
DevLocal,
Session { token: String },
ApiToken { token: String, character_id: Uuid },
}
impl Default for AuthCredential {
fn default() -> Self {
Self::DevLocal
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Welcome {
pub session_id: SessionId,
pub entity_id: EntityId,
pub snapshot: Snapshot,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ServerMessage {
Welcome(Welcome),
Tick(TickDelta),
IntentAck {
entity_id: EntityId,
seq: Seq,
tick: Tick,
},
Chat(ChatMessage),
HarvestResult(HarvestResult),
CraftResult(CraftResult),
Death(DeathNotice),
Interaction(InteractionNotice),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ClientMessage {
Hello(Hello),
Intent(Intent),
Disconnect,
}