1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4pub type EntityId = u64;
5pub type Tick = u64;
6pub type Seq = u32;
7pub type SessionId = u64;
8
9#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
11pub struct WorldCoord {
12 pub x: f32,
13 pub y: f32,
14 pub z: f32,
15 pub w: u32,
16 pub t: u32,
17}
18
19impl WorldCoord {
20 pub fn surface(x: f32, y: f32) -> Self {
21 Self {
22 x,
23 y,
24 z: 0.0,
25 w: 0,
26 t: 0,
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
32pub struct Velocity2D {
33 pub vx: f32,
34 pub vy: f32,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
38pub struct Transform {
39 pub position: WorldCoord,
40 pub yaw: f32,
41 pub velocity: Velocity2D,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum LifeState {
47 Alive,
48 Dead,
49}
50
51impl Default for LifeState {
52 fn default() -> Self {
53 Self::Alive
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
59pub struct PlayerVitals {
60 pub health: f32,
61 pub health_max: f32,
62 pub mana: f32,
63 pub mana_max: f32,
64 pub stamina: f32,
65 pub stamina_max: f32,
66 #[serde(default)]
67 pub coins: u32,
68 #[serde(default)]
69 pub deaths: u32,
70 #[serde(default)]
71 pub life_state: LifeState,
72}
73
74impl Default for PlayerVitals {
75 fn default() -> Self {
76 Self {
77 health: 100.0,
78 health_max: 100.0,
79 mana: 50.0,
80 mana_max: 50.0,
81 stamina: 100.0,
82 stamina_max: 100.0,
83 coins: 0,
84 deaths: 0,
85 life_state: LifeState::Alive,
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
91pub struct EntityState {
92 pub id: EntityId,
93 pub transform: Transform,
94 #[serde(default)]
96 pub label: String,
97 #[serde(default)]
98 pub vitals: Option<PlayerVitals>,
99 #[serde(default)]
101 pub inside_building: Option<String>,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(rename_all = "snake_case")]
107pub enum ChatChannel {
108 Nearby,
109 WhisperStone,
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub struct ChatMessage {
114 pub channel: ChatChannel,
115 pub from_entity: EntityId,
116 pub from_name: String,
117 pub text: String,
118 pub tick: Tick,
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub enum Intent {
124 Move {
125 entity_id: EntityId,
126 forward: f32,
127 strafe: f32,
128 seq: Seq,
129 },
130 Stop {
131 entity_id: EntityId,
132 seq: Seq,
133 },
134 Harvest {
135 entity_id: EntityId,
136 node_id: String,
137 seq: Seq,
138 },
139 Use {
140 entity_id: EntityId,
141 item_instance_id: Uuid,
142 seq: Seq,
143 },
144 Say {
145 entity_id: EntityId,
146 channel: ChatChannel,
147 text: String,
148 seq: Seq,
149 },
150 Craft {
152 entity_id: EntityId,
153 blueprint_id: String,
154 seq: Seq,
155 },
156 Interact {
158 entity_id: EntityId,
159 target_id: String,
160 seq: Seq,
161 },
162 TestDamage {
164 entity_id: EntityId,
165 amount: f32,
166 seq: Seq,
167 },
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
172pub struct TickDelta {
173 pub tick: Tick,
174 pub entities: Vec<EntityState>,
175 #[serde(default)]
176 pub resource_nodes: Vec<ResourceNodeView>,
177 #[serde(default)]
178 pub buildings: Vec<BuildingView>,
179 #[serde(default)]
180 pub doors: Vec<DoorView>,
181 #[serde(default)]
182 pub npcs: Vec<NpcView>,
183 #[serde(default)]
185 pub inventory: Vec<ItemStack>,
186 #[serde(default)]
187 pub blueprints: Vec<BlueprintView>,
188}
189
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192pub struct Snapshot {
193 pub tick: Tick,
194 pub chunk_rev: u64,
195 pub entities: Vec<EntityState>,
196 #[serde(default)]
197 pub resource_nodes: Vec<ResourceNodeView>,
198 #[serde(default)]
200 pub world_width_m: f32,
201 #[serde(default)]
202 pub world_height_m: f32,
203 #[serde(default)]
204 pub buildings: Vec<BuildingView>,
205 #[serde(default)]
206 pub doors: Vec<DoorView>,
207 #[serde(default)]
208 pub npcs: Vec<NpcView>,
209 #[serde(default)]
210 pub inventory: Vec<ItemStack>,
211 #[serde(default)]
212 pub blueprints: Vec<BlueprintView>,
213}
214
215#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub struct ResourceNodeView {
218 pub id: String,
219 pub label: String,
220 pub x: f32,
221 pub y: f32,
222 pub z: f32,
223 pub item_template: String,
224 #[serde(default = "default_node_state")]
225 pub state: ResourceNodeState,
226 #[serde(default)]
228 pub blocking: bool,
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(rename_all = "snake_case")]
233pub enum ResourceNodeState {
234 Available,
235 Harvesting,
236 Cooldown,
237}
238
239fn default_node_state() -> ResourceNodeState {
240 ResourceNodeState::Available
241}
242
243#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244pub struct ItemStack {
245 pub template_id: String,
246 pub quantity: u32,
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct BlueprintIngredientView {
251 pub template_id: String,
252 pub quantity: u32,
253 pub consumed: bool,
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258pub struct ToolRequirementView {
259 pub item: String,
260 pub consumed: bool,
262}
263
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
265pub struct SkillRequirementView {
266 pub skill: String,
267 pub level: u32,
268}
269
270#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
271pub struct BlueprintView {
272 pub id: String,
273 pub label: String,
274 pub output: String,
275 pub output_qty: u32,
276 pub craft_ticks: u32,
277 pub inputs: Vec<BlueprintIngredientView>,
278 #[serde(default)]
280 pub station: Option<String>,
281 #[serde(default)]
282 pub category: Option<String>,
283 #[serde(default)]
284 pub required_tools: Vec<ToolRequirementView>,
285 #[serde(default)]
286 pub skill: Option<SkillRequirementView>,
287 #[serde(default)]
288 pub failure_chance: f32,
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292pub struct BuildingView {
293 pub id: String,
294 pub label: String,
295 pub x: f32,
296 pub y: f32,
297 pub width_m: f32,
298 pub depth_m: f32,
299 #[serde(default)]
301 pub interior_origin_x: f32,
302 #[serde(default)]
303 pub interior_origin_y: f32,
304}
305
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307pub struct DoorView {
308 pub id: String,
309 pub building_id: String,
310 pub x: f32,
311 pub y: f32,
312 pub open: bool,
313 #[serde(default)]
314 pub is_exit: bool,
315}
316
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub struct NpcView {
319 pub id: String,
320 pub label: String,
321 pub role: String,
322 pub x: f32,
323 pub y: f32,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub building_id: Option<String>,
326}
327
328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
329pub struct CraftResult {
330 pub blueprint_id: String,
331 pub outputs: Vec<ItemStack>,
332 pub consumed: Vec<ItemStack>,
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336pub struct DeathNotice {
337 pub entity_id: EntityId,
338 pub respawn_x: f32,
339 pub respawn_y: f32,
340 pub message: String,
341}
342
343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
344pub struct InteractionNotice {
345 pub target_id: String,
346 pub message: String,
347 #[serde(default)]
348 pub coins_delta: i32,
349 #[serde(default)]
350 pub inventory_delta: Vec<ItemStack>,
351}
352
353#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
354pub struct HarvestResult {
355 pub node_id: String,
356 pub quantity: u32,
358 pub item_template: String,
359 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub item_instance_id: Option<Uuid>,
362}
363
364#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
366pub struct Envelope<T> {
367 pub protocol_version: u16,
368 pub payload: T,
369}
370
371impl<T> Envelope<T> {
372 pub fn new(payload: T) -> Self {
373 Self {
374 protocol_version: crate::PROTOCOL_VERSION,
375 payload,
376 }
377 }
378}
379
380#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
382pub struct Hello {
383 pub client_name: String,
384 pub protocol_version: u16,
385 #[serde(default)]
386 pub auth: AuthCredential,
387 #[serde(default)]
389 pub character_id: Option<Uuid>,
390}
391
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
395#[serde(rename_all = "snake_case")]
396pub enum AuthCredential {
397 DevLocal,
398 Session { token: String },
399 ApiToken { token: String, character_id: Uuid },
400}
401
402impl Default for AuthCredential {
403 fn default() -> Self {
404 Self::DevLocal
405 }
406}
407
408#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409pub struct Welcome {
410 pub session_id: SessionId,
411 pub entity_id: EntityId,
412 pub snapshot: Snapshot,
413}
414
415#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
416pub enum ServerMessage {
417 Welcome(Welcome),
418 Tick(TickDelta),
419 IntentAck {
420 entity_id: EntityId,
421 seq: Seq,
422 tick: Tick,
423 },
424 Chat(ChatMessage),
425 HarvestResult(HarvestResult),
426 CraftResult(CraftResult),
427 Death(DeathNotice),
428 Interaction(InteractionNotice),
429}
430
431#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
432pub enum ClientMessage {
433 Hello(Hello),
434 Intent(Intent),
435 Disconnect,
436}