1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6pub type EntityId = u64;
7pub type Tick = u64;
8pub type Seq = u32;
9pub type SessionId = u64;
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct WorldCoord {
14 pub x: f32,
15 pub y: f32,
16 pub z: f32,
17 pub w: u32,
18 pub t: u32,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
23pub struct AimPoint {
24 pub x: f32,
25 pub y: f32,
26 #[serde(default)]
27 pub z: f32,
28}
29
30impl AimPoint {
31 pub fn xy(x: f32, y: f32) -> Self {
32 Self { x, y, z: 0.0 }
33 }
34}
35
36impl WorldCoord {
37 pub fn surface(x: f32, y: f32) -> Self {
38 Self {
39 x,
40 y,
41 z: 0.0,
42 w: 0,
43 t: 0,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
49pub struct Velocity2D {
50 pub vx: f32,
51 pub vy: f32,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
55pub struct Transform {
56 pub position: WorldCoord,
57 pub yaw: f32,
58 pub velocity: Velocity2D,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum LifeState {
64 Alive,
65 Dead,
66}
67
68impl Default for LifeState {
69 fn default() -> Self {
70 Self::Alive
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum TimeOfDayPhase {
78 Night,
79 Dawn,
80 Morning,
81 Midday,
82 Afternoon,
83 Evening,
84}
85
86impl TimeOfDayPhase {
87 pub const ALL: [Self; 6] = [
90 Self::Night,
91 Self::Dawn,
92 Self::Morning,
93 Self::Midday,
94 Self::Afternoon,
95 Self::Evening,
96 ];
97
98 pub fn label(self) -> &'static str {
99 match self {
100 Self::Night => "Night",
101 Self::Dawn => "Dawn",
102 Self::Morning => "Morning",
103 Self::Midday => "Midday",
104 Self::Afternoon => "Afternoon",
105 Self::Evening => "Evening",
106 }
107 }
108
109 pub fn snake_name(self) -> &'static str {
110 match self {
111 Self::Night => "night",
112 Self::Dawn => "dawn",
113 Self::Morning => "morning",
114 Self::Midday => "midday",
115 Self::Afternoon => "afternoon",
116 Self::Evening => "evening",
117 }
118 }
119
120 pub fn try_from_name(name: &str) -> Option<Self> {
121 Some(match name.to_ascii_lowercase().as_str() {
122 "night" => Self::Night,
123 "dawn" => Self::Dawn,
124 "morning" => Self::Morning,
125 "midday" | "mid_day" | "noon" => Self::Midday,
126 "afternoon" => Self::Afternoon,
127 "evening" | "dusk" => Self::Evening,
128 _ => return None,
129 })
130 }
131
132 pub fn from_name(name: &str) -> Self {
133 Self::try_from_name(name).unwrap_or(Self::Night)
134 }
135}
136
137pub fn normalize_world_clock_phase_names<I, S>(names: I) -> Vec<String>
139where
140 I: IntoIterator<Item = S>,
141 S: AsRef<str>,
142{
143 let mut out = Vec::new();
144 for name in names {
145 let Some(phase) = TimeOfDayPhase::try_from_name(name.as_ref()) else {
146 continue;
147 };
148 let id = phase.snake_name().to_string();
149 if !out.iter().any(|existing| existing == &id) {
150 out.push(id);
151 }
152 }
153 out
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
158pub struct WorldClock {
159 pub day: u64,
161 pub hour: u8,
162 pub minute: u8,
163 pub phase: TimeOfDayPhase,
164}
165
166impl Default for WorldClock {
167 fn default() -> Self {
168 Self {
169 day: 0,
170 hour: 8,
171 minute: 0,
172 phase: TimeOfDayPhase::Morning,
173 }
174 }
175}
176
177impl WorldClock {
178 pub fn display_time(self) -> String {
179 format!("{:02}:{:02}", self.hour, self.minute)
180 }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
185pub struct PrimaryAttributes {
186 pub strength: u16,
187 pub dexterity: u16,
188 pub intelligence: u16,
189 pub stamina: u16,
190 pub vitality: u16,
191 pub wisdom: u16,
192 pub charisma: u16,
193}
194
195impl Default for PrimaryAttributes {
196 fn default() -> Self {
197 Self {
198 strength: 150,
199 dexterity: 150,
200 intelligence: 150,
201 stamina: 150,
202 vitality: 150,
203 wisdom: 150,
204 charisma: 150,
205 }
206 }
207}
208
209impl PrimaryAttributes {
210 pub fn display(value: u16) -> u16 {
212 (value / 10).clamp(1, 100)
213 }
214
215 pub fn derived_preview(&self) -> DerivedPreview {
217 let str_d = Self::display(self.strength) as f32;
218 let dex_d = Self::display(self.dexterity) as f32;
219 let int_d = Self::display(self.intelligence) as f32;
220 let wis_d = Self::display(self.wisdom) as f32;
221 DerivedPreview {
222 attack_power: str_d * 1.2 + dex_d * 0.3,
223 spell_power: int_d * 1.1 + wis_d * 0.4,
224 evasion: dex_d * 0.8 + wis_d * 0.2,
225 carry_mass_max: str_d * 2.5,
226 sight_range_m: 12.0 + wis_d * 0.15 + dex_d * 0.05,
227 fov_deg: 120.0 + wis_d * 0.2,
228 hearing_range_m: 6.0
229 + wis_d * 0.08
230 + (PrimaryAttributes::display(self.stamina) as f32) * 0.04,
231 }
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq)]
237pub struct DerivedPreview {
238 pub attack_power: f32,
239 pub spell_power: f32,
240 pub evasion: f32,
241 pub carry_mass_max: f32,
242 pub sight_range_m: f32,
243 pub fov_deg: f32,
244 pub hearing_range_m: f32,
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
249pub struct SkillProgress {
250 pub level: u16,
251 #[serde(default)]
252 pub last_trained_tick: u64,
253}
254
255impl Default for SkillProgress {
256 fn default() -> Self {
257 Self {
258 level: 0,
259 last_trained_tick: 0,
260 }
261 }
262}
263
264impl SkillProgress {
265 pub fn display_tier(&self) -> u16 {
267 (self.level / 100).min(10)
268 }
269}
270
271#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
273#[serde(default)]
274pub struct ProgressionXp {
275 pub strength: f64,
276 pub dexterity: f64,
277 pub intelligence: f64,
278 pub stamina: f64,
279 pub vitality: f64,
280 pub wisdom: f64,
281 pub charisma: f64,
282 pub logging: f64,
283 pub mining: f64,
284 pub evocation: f64,
285 pub restoration: f64,
286 pub swords: f64,
287 pub archery: f64,
288 pub crafting: f64,
289 pub alchemy: f64,
290 pub cartography: f64,
291 #[serde(default)]
293 pub ability: std::collections::BTreeMap<String, f64>,
294}
295
296impl ProgressionXp {
297 pub fn bootstrap_new(baseline_display: u16, xp_base: f64, xp_growth: f64) -> Self {
299 let bootstrap = |display: f64| {
300 if display <= 1.0 {
301 0.0
302 } else {
303 xp_base * xp_growth.powf(display - 1.0)
304 }
305 };
306 let b = baseline_display as f64;
307 let primary = bootstrap(b);
308 Self {
309 strength: primary,
310 dexterity: primary,
311 intelligence: primary,
312 stamina: primary,
313 vitality: primary,
314 wisdom: primary,
315 charisma: primary,
316 ..Self::default()
317 }
318 }
319
320 pub fn is_empty(&self) -> bool {
321 self.strength == 0.0
322 && self.dexterity == 0.0
323 && self.intelligence == 0.0
324 && self.stamina == 0.0
325 && self.vitality == 0.0
326 && self.wisdom == 0.0
327 && self.charisma == 0.0
328 && self.logging == 0.0
329 && self.mining == 0.0
330 && self.evocation == 0.0
331 && self.restoration == 0.0
332 && self.swords == 0.0
333 && self.archery == 0.0
334 && self.crafting == 0.0
335 && self.alchemy == 0.0
336 && self.cartography == 0.0
337 && self.ability.is_empty()
338 }
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
343pub struct AbilityMasteryHud {
344 pub ability_id: String,
345 pub tier: u16,
347 pub level: u16,
349 pub xp: f64,
351 pub xp_to_next: f64,
353}
354
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
357#[serde(default)]
358pub struct PlayerSkills {
359 pub logging: SkillProgress,
360 pub mining: SkillProgress,
361 pub evocation: SkillProgress,
362 #[serde(default)]
363 pub restoration: SkillProgress,
364 pub swords: SkillProgress,
365 #[serde(default)]
366 pub archery: SkillProgress,
367 pub crafting: SkillProgress,
368 #[serde(default)]
369 pub alchemy: SkillProgress,
370 pub cartography: SkillProgress,
371}
372
373impl Default for PlayerSkills {
374 fn default() -> Self {
375 Self {
376 logging: SkillProgress::default(),
377 mining: SkillProgress::default(),
378 evocation: SkillProgress::default(),
379 restoration: SkillProgress::default(),
380 swords: SkillProgress::default(),
381 archery: SkillProgress::default(),
382 crafting: SkillProgress::default(),
383 alchemy: SkillProgress::default(),
384 cartography: SkillProgress::default(),
385 }
386 }
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
391pub struct PlayerVitals {
392 pub health: f32,
393 pub health_max: f32,
394 pub mana: f32,
395 pub mana_max: f32,
396 pub stamina: f32,
397 pub stamina_max: f32,
398 #[serde(default = "default_survival_pool_max")]
399 pub hunger: f32,
400 #[serde(default = "default_survival_pool_max")]
401 pub hunger_max: f32,
402 #[serde(default = "default_survival_pool_max")]
403 pub thirst: f32,
404 #[serde(default = "default_survival_pool_max")]
405 pub thirst_max: f32,
406 #[serde(default)]
407 pub coins: u32,
408 #[serde(default)]
409 pub deaths: u32,
410 #[serde(default)]
411 pub life_state: LifeState,
412 #[serde(default)]
414 pub winded: bool,
415}
416
417fn default_survival_pool_max() -> f32 {
418 100.0
419}
420
421impl Default for PlayerVitals {
422 fn default() -> Self {
423 Self::from_attributes(PrimaryAttributes::default())
424 }
425}
426
427impl PlayerVitals {
428 pub fn from_attributes(attrs: PrimaryAttributes) -> Self {
435 let vit_d = PrimaryAttributes::display(attrs.vitality) as f32;
436 let int_d = PrimaryAttributes::display(attrs.intelligence) as f32;
437 let wis_d = PrimaryAttributes::display(attrs.wisdom) as f32;
438 let sta_d = PrimaryAttributes::display(attrs.stamina) as f32;
439
440 let health_max = 50.0 + vit_d * 2.0;
441 let stamina_max = 30.0 + sta_d * 1.4;
442 let mana_max = 25.0 + int_d * 1.1 + wis_d * 1.3;
443 let hunger_max = 100.0;
444 let thirst_max = 100.0;
445 Self {
446 health: health_max,
447 health_max,
448 mana: mana_max,
449 mana_max,
450 stamina: stamina_max,
451 stamina_max,
452 hunger: hunger_max,
453 hunger_max,
454 thirst: thirst_max,
455 thirst_max,
456 coins: 0,
457 deaths: 0,
458 life_state: LifeState::Alive,
459 winded: false,
460 }
461 }
462
463 pub fn legacy_maxima(attrs: PrimaryAttributes) -> (f32, f32, f32) {
465 (
466 attrs.vitality as f32 / 5.0,
467 attrs.stamina as f32 / 5.0,
468 (attrs.intelligence as f32 + attrs.wisdom as f32) / 20.0,
469 )
470 }
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
475#[serde(default)]
476pub struct StoredVitalsState {
477 pub health: f32,
478 pub mana: f32,
479 pub stamina: f32,
480 pub hunger: f32,
481 pub thirst: f32,
482 pub coins: u32,
483 pub deaths: u32,
484 pub life_state: LifeState,
485 #[serde(default)]
486 pub winded: bool,
487}
488
489impl StoredVitalsState {
490 pub fn from_live(v: &PlayerVitals) -> Self {
491 Self {
492 health: v.health,
493 mana: v.mana,
494 stamina: v.stamina,
495 hunger: v.hunger,
496 thirst: v.thirst,
497 coins: v.coins,
498 deaths: v.deaths,
499 life_state: v.life_state,
500 winded: v.winded,
501 }
502 }
503
504 pub fn is_pristine(&self) -> bool {
506 self.health == 0.0
507 && self.mana == 0.0
508 && self.stamina == 0.0
509 && self.hunger == 0.0
510 && self.thirst == 0.0
511 && self.coins == 0
512 && self.deaths == 0
513 && self.life_state == LifeState::Alive
514 }
515
516 pub fn apply_to(self, attrs: PrimaryAttributes) -> PlayerVitals {
517 if self.is_pristine() {
518 return PlayerVitals::from_attributes(attrs);
519 }
520 let fresh = PlayerVitals::from_attributes(attrs);
521 let (legacy_hp, legacy_sta, legacy_mana) = PlayerVitals::legacy_maxima(attrs);
522
523 let scale = |current: f32, legacy_max: f32, new_max: f32| {
524 if legacy_max > 0.0 && new_max > legacy_max * 1.05 && current >= legacy_max * 0.95 {
525 let ratio = (current / legacy_max).clamp(0.0, 1.0);
526 (new_max * ratio).min(new_max)
527 } else {
528 current.min(new_max)
529 }
530 };
531
532 let mut v = fresh;
533 v.health = scale(self.health, legacy_hp, fresh.health_max);
534 v.mana = scale(self.mana, legacy_mana, fresh.mana_max);
535 v.stamina = scale(self.stamina, legacy_sta, fresh.stamina_max);
536 v.hunger = self.hunger.min(v.hunger_max);
537 v.thirst = self.thirst.min(v.thirst_max);
538 v.coins = self.coins;
539 v.deaths = self.deaths;
540 v.life_state = self.life_state;
541 v.winded = self.winded;
542 v
543 }
544}
545
546impl Default for StoredVitalsState {
547 fn default() -> Self {
548 Self::from_live(&PlayerVitals::default())
549 }
550}
551
552pub fn humanize_snake_id(id: &str) -> String {
556 id.split('_')
557 .filter(|part| !part.is_empty())
558 .map(|part| {
559 let mut chars = part.chars();
560 match chars.next() {
561 None => String::new(),
562 Some(first) => first.to_uppercase().chain(chars).collect(),
563 }
564 })
565 .collect::<Vec<_>>()
566 .join(" ")
567}
568
569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
571pub struct KnownAbility {
572 pub ability_id: String,
573 #[serde(default = "default_known_permanent")]
575 pub permanent: bool,
576 #[serde(default)]
578 pub expires_at_tick: Option<u64>,
579}
580
581fn default_known_permanent() -> bool {
582 true
583}
584
585impl KnownAbility {
586 pub fn permanent(ability_id: impl Into<String>) -> Self {
587 Self {
588 ability_id: ability_id.into(),
589 permanent: true,
590 expires_at_tick: None,
591 }
592 }
593
594 pub fn is_active(&self, tick: u64) -> bool {
595 if self.permanent {
596 return true;
597 }
598 match self.expires_at_tick {
599 Some(exp) => tick < exp,
600 None => false,
601 }
602 }
603}
604
605#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
607pub struct RotationPreset {
608 pub id: String,
609 pub label: String,
610 #[serde(default)]
611 pub abilities: Vec<String>,
612}
613
614impl RotationPreset {
615 pub fn melee_default(ability_id: impl Into<String>) -> Self {
616 let id = ability_id.into();
617 Self {
618 id: "melee".into(),
619 label: "Weapon".into(),
621 abilities: vec![id],
622 }
623 }
624
625 pub fn is_weapon_preset(&self) -> bool {
626 self.id == "melee"
627 }
628}
629
630#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
632pub struct StoredTargetSlot {
633 pub instance_id: Option<String>,
634 #[serde(default)]
635 pub preset_id: Option<String>,
636 #[serde(default)]
637 pub rotation_index: u32,
638 #[serde(default)]
639 pub auto_enabled: bool,
640}
641
642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
643#[serde(default)]
644pub struct StoredCombatProfile {
645 pub combat_target_instance_id: Option<String>,
647 pub in_combat: bool,
648 pub last_combat_tick: u64,
649 pub last_attack_tick: u64,
650 pub cooldowns_until_tick: BTreeMap<String, u64>,
651 #[serde(default = "default_auto_attack")]
652 pub auto_attack_enabled: bool,
653 #[serde(default)]
655 pub mainhand_template_id: Option<String>,
656 #[serde(default)]
658 pub mainhand_instance_id: Option<Uuid>,
659 #[serde(default)]
661 pub offhand_template_id: Option<String>,
662 #[serde(default)]
664 pub offhand_instance_id: Option<Uuid>,
665 #[serde(default)]
668 pub worn: Vec<(BodySlot, ItemStack)>,
669 #[serde(default)]
671 pub rotation_presets: Vec<RotationPreset>,
672 #[serde(default)]
674 pub target_slots: Vec<StoredTargetSlot>,
675 #[serde(default)]
677 pub known_blueprint_ids: Vec<String>,
678 #[serde(default)]
680 pub keychain: Vec<ItemStack>,
681 #[serde(default)]
683 pub whisper_pouch: Vec<ItemStack>,
684 #[serde(default)]
686 pub known_abilities: Vec<KnownAbility>,
687 #[serde(default)]
689 pub hotbar: Vec<Option<String>>,
690 #[serde(default)]
692 pub abilities_schema_version: u32,
693 #[serde(default)]
695 pub bank_balance_copper: u64,
696}
697
698fn default_auto_attack() -> bool {
699 true
700}
701
702impl Default for StoredCombatProfile {
703 fn default() -> Self {
704 Self {
705 combat_target_instance_id: None,
706 in_combat: false,
707 last_combat_tick: 0,
708 last_attack_tick: 0,
709 cooldowns_until_tick: BTreeMap::new(),
710 auto_attack_enabled: true,
711 mainhand_template_id: None,
712 mainhand_instance_id: None,
713 offhand_template_id: None,
714 offhand_instance_id: None,
715 worn: Vec::new(),
716 rotation_presets: Vec::new(),
717 target_slots: Vec::new(),
718 known_blueprint_ids: Vec::new(),
719 keychain: Vec::new(),
720 whisper_pouch: Vec::new(),
721 known_abilities: Vec::new(),
722 hotbar: Vec::new(),
723 abilities_schema_version: 0,
724 bank_balance_copper: 0,
725 }
726 }
727}
728
729#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
731#[serde(rename_all = "snake_case")]
732pub enum CombatCueKind {
733 Dodge,
734 Block,
735 AttackTelegraph,
736}
737
738#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
739pub struct CombatCueView {
740 pub kind: CombatCueKind,
741 pub until_tick: Tick,
743 #[serde(default)]
745 pub start_tick: Tick,
746 #[serde(default)]
748 pub ability_id: Option<String>,
749 #[serde(default)]
751 pub telegraph_kind: Option<CombatFxKind>,
752 #[serde(default)]
753 pub origin_x: Option<f32>,
754 #[serde(default)]
755 pub origin_y: Option<f32>,
756 #[serde(default)]
757 pub origin_z: Option<f32>,
758 #[serde(default)]
759 pub end_x: Option<f32>,
760 #[serde(default)]
761 pub end_y: Option<f32>,
762 #[serde(default)]
763 pub end_z: Option<f32>,
764 #[serde(default)]
765 pub yaw: Option<f32>,
766 #[serde(default)]
767 pub reach_m: Option<f32>,
768 #[serde(default)]
769 pub arc_deg: Option<f32>,
770 #[serde(default)]
771 pub radius_m: Option<f32>,
772}
773
774impl CombatCueView {
775 pub fn timing(kind: CombatCueKind, until_tick: Tick, start_tick: Tick) -> Self {
777 Self {
778 kind,
779 until_tick,
780 start_tick,
781 ability_id: None,
782 telegraph_kind: None,
783 origin_x: None,
784 origin_y: None,
785 origin_z: None,
786 end_x: None,
787 end_y: None,
788 end_z: None,
789 yaw: None,
790 reach_m: None,
791 arc_deg: None,
792 radius_m: None,
793 }
794 }
795}
796
797#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
798pub struct EntityState {
799 pub id: EntityId,
800 pub transform: Transform,
801 #[serde(default)]
803 pub label: String,
804 #[serde(default)]
805 pub vitals: Option<PlayerVitals>,
806 #[serde(default)]
808 pub attributes: Option<PrimaryAttributes>,
809 #[serde(default)]
810 pub skills: Option<PlayerSkills>,
811 #[serde(default)]
813 pub inside_building: Option<String>,
814 #[serde(default)]
816 pub tile_id: Option<String>,
817 #[serde(default)]
819 pub paperdoll_ref: Option<String>,
820 #[serde(default = "default_draw_scale")]
822 pub draw_scale: f32,
823 #[serde(default)]
825 pub presentation_state: Option<String>,
826 #[serde(default)]
828 pub sprite_mode: Option<String>,
829 #[serde(default)]
831 pub progression_xp: Option<ProgressionXp>,
832 #[serde(default)]
834 pub combat_cues: Vec<CombatCueView>,
835 #[serde(default)]
837 pub statuses: Vec<StatusEffectHud>,
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
842#[serde(rename_all = "snake_case")]
843pub enum ChatChannel {
844 Nearby,
846 Direct,
848 Whisper,
850 WhisperStone,
852}
853
854#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
856#[serde(rename_all = "snake_case")]
857pub enum ChatClarity {
858 #[default]
859 Clear,
860 Partial,
861 Heavy,
862}
863
864#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
865pub struct ChatMessage {
866 pub channel: ChatChannel,
867 pub from_entity: EntityId,
868 pub from_name: String,
869 pub text: String,
871 pub tick: Tick,
872 #[serde(default)]
874 pub to_entity: Option<EntityId>,
875 #[serde(default)]
876 pub clarity: ChatClarity,
877}
878
879#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
881pub enum Intent {
882 Move {
883 entity_id: EntityId,
884 forward: f32,
885 strafe: f32,
886 #[serde(default)]
888 vertical: f32,
889 #[serde(default)]
891 sprint: bool,
892 #[serde(default)]
894 sneak: bool,
895 seq: Seq,
896 },
897 Stop {
898 entity_id: EntityId,
899 seq: Seq,
900 },
901 Harvest {
902 entity_id: EntityId,
903 node_id: String,
904 seq: Seq,
905 },
906 Use {
907 entity_id: EntityId,
908 template_id: String,
909 seq: Seq,
910 },
911 UseGrant {
913 entity_id: EntityId,
914 grant_instance_id: Uuid,
915 target_instance_id: Uuid,
916 seq: Seq,
917 },
918 Say {
919 entity_id: EntityId,
920 channel: ChatChannel,
921 text: String,
922 #[serde(default)]
924 to_entity: Option<EntityId>,
925 seq: Seq,
926 },
927 Craft {
929 entity_id: EntityId,
930 blueprint_id: String,
931 #[serde(default)]
933 count: Option<u32>,
934 seq: Seq,
935 },
936 Interact {
938 entity_id: EntityId,
939 target_id: String,
940 seq: Seq,
941 },
942 ShopBuy {
944 entity_id: EntityId,
945 npc_id: String,
946 offer_id: String,
947 #[serde(default = "default_one")]
948 quantity: u32,
949 seq: Seq,
950 },
951 ShopSell {
953 entity_id: EntityId,
954 npc_id: String,
955 template_id: String,
956 #[serde(default = "default_one")]
957 quantity: u32,
958 seq: Seq,
959 },
960 ShopClose {
962 entity_id: EntityId,
963 npc_id: String,
964 seq: Seq,
965 },
966 TestDamage {
968 entity_id: EntityId,
969 amount: f32,
970 seq: Seq,
971 },
972 SetTarget {
974 entity_id: EntityId,
975 target_id: EntityId,
976 seq: Seq,
977 },
978 SetTargetSlot {
980 entity_id: EntityId,
981 slot_index: u8,
982 target_id: EntityId,
983 seq: Seq,
984 },
985 ClearTarget {
986 entity_id: EntityId,
987 seq: Seq,
988 },
989 ClearTargetSlot {
990 entity_id: EntityId,
991 slot_index: u8,
992 seq: Seq,
993 },
994 SetAutoAttack {
996 entity_id: EntityId,
997 slot_index: u8,
998 enabled: bool,
999 seq: Seq,
1000 },
1001 Attack {
1003 entity_id: EntityId,
1004 #[serde(default)]
1005 target_id: Option<EntityId>,
1006 #[serde(default)]
1007 weapon_slot: Option<u32>,
1008 seq: Seq,
1009 },
1010 Pickup {
1012 entity_id: EntityId,
1013 #[serde(default)]
1014 drop_id: Option<String>,
1015 seq: Seq,
1016 },
1017 Cast {
1020 entity_id: EntityId,
1021 ability_id: String,
1022 target_id: EntityId,
1023 #[serde(default)]
1024 target_point: Option<AimPoint>,
1025 seq: Seq,
1026 },
1027 BindActionSlot {
1029 entity_id: EntityId,
1030 slot_index: u8,
1031 ability_id: String,
1032 #[serde(default = "default_auto_attack")]
1033 auto_enabled: bool,
1034 seq: Seq,
1035 },
1036 UseActionSlot {
1038 entity_id: EntityId,
1039 slot_index: u8,
1040 seq: Seq,
1041 },
1042 Dodge {
1046 entity_id: EntityId,
1047 #[serde(default)]
1049 forward: f32,
1050 #[serde(default)]
1052 strafe: f32,
1053 seq: Seq,
1054 },
1055 Lunge {
1057 entity_id: EntityId,
1058 #[serde(default)]
1060 forward: f32,
1061 #[serde(default)]
1063 strafe: f32,
1064 seq: Seq,
1065 },
1066 DirectionalJump {
1068 entity_id: EntityId,
1069 #[serde(default)]
1071 forward: f32,
1072 #[serde(default)]
1074 strafe: f32,
1075 seq: Seq,
1076 },
1077 Block {
1079 entity_id: EntityId,
1080 #[serde(default = "default_block_enabled")]
1081 enabled: bool,
1082 seq: Seq,
1083 },
1084 EquipMainhand {
1088 entity_id: EntityId,
1089 #[serde(default)]
1090 template_id: Option<String>,
1091 #[serde(default)]
1092 instance_id: Option<Uuid>,
1093 seq: Seq,
1094 },
1095 EquipOffhand {
1097 entity_id: EntityId,
1098 #[serde(default)]
1099 template_id: Option<String>,
1100 #[serde(default)]
1101 instance_id: Option<Uuid>,
1102 seq: Seq,
1103 },
1104 EquipWorn {
1107 entity_id: EntityId,
1108 slot: BodySlot,
1109 #[serde(default)]
1110 instance_id: Option<Uuid>,
1111 seq: Seq,
1112 },
1113 MoveItem {
1115 entity_id: EntityId,
1116 item_instance_id: Uuid,
1117 from: InventoryLocation,
1118 to: InventoryLocation,
1119 #[serde(default)]
1121 to_parent_instance_id: Option<Uuid>,
1122 #[serde(default)]
1124 quantity: Option<u32>,
1125 seq: Seq,
1126 },
1127 PlaceContainer {
1129 entity_id: EntityId,
1130 item_instance_id: Uuid,
1131 seq: Seq,
1132 },
1133 PickupContainer {
1135 entity_id: EntityId,
1136 container_id: String,
1137 seq: Seq,
1138 },
1139 MovePlacedContainer {
1141 entity_id: EntityId,
1142 container_id: String,
1143 x: f32,
1144 y: f32,
1145 seq: Seq,
1146 },
1147 SetContainerLocked {
1149 entity_id: EntityId,
1150 location: InventoryLocation,
1152 locked: bool,
1153 seq: Seq,
1154 },
1155 DropItem {
1157 entity_id: EntityId,
1158 item_instance_id: Uuid,
1159 from: InventoryLocation,
1160 seq: Seq,
1161 },
1162 DestroyItem {
1164 entity_id: EntityId,
1165 item_instance_id: Uuid,
1166 from: InventoryLocation,
1167 #[serde(default)]
1169 quantity: Option<u32>,
1170 seq: Seq,
1171 },
1172 RenameContainer {
1174 entity_id: EntityId,
1175 item_instance_id: Uuid,
1176 location: InventoryLocation,
1177 name: String,
1178 seq: Seq,
1179 },
1180 UpsertRotationPreset {
1182 entity_id: EntityId,
1183 preset: RotationPreset,
1184 seq: Seq,
1185 },
1186 DeleteRotationPreset {
1188 entity_id: EntityId,
1189 preset_id: String,
1190 seq: Seq,
1191 },
1192 AssignSlotPreset {
1194 entity_id: EntityId,
1195 slot_index: u8,
1196 preset_id: String,
1197 seq: Seq,
1198 },
1199 SetHotbarSlot {
1202 entity_id: EntityId,
1203 slot: u8,
1205 #[serde(default)]
1207 ability_id: Option<String>,
1208 seq: Seq,
1209 },
1210 AdvanceRotation {
1212 entity_id: EntityId,
1213 slot_index: u8,
1214 seq: Seq,
1215 },
1216 NpcTalkOpen {
1218 entity_id: EntityId,
1219 npc_id: String,
1220 #[serde(default)]
1222 quest_id: Option<String>,
1223 seq: Seq,
1224 },
1225 NpcTalkSay {
1227 entity_id: EntityId,
1228 npc_id: String,
1229 message: String,
1230 seq: Seq,
1231 },
1232 NpcTalkClose {
1234 entity_id: EntityId,
1235 npc_id: String,
1236 seq: Seq,
1237 },
1238 AcceptQuest {
1240 entity_id: EntityId,
1241 quest_id: String,
1242 seq: Seq,
1243 },
1244 WithdrawQuest {
1246 entity_id: EntityId,
1247 quest_id: String,
1248 seq: Seq,
1249 },
1250 TrackQuest {
1252 entity_id: EntityId,
1253 quest_id: String,
1254 seq: Seq,
1255 },
1256 QuestGiveItem {
1258 entity_id: EntityId,
1259 npc_id: String,
1260 template_id: String,
1261 #[serde(default = "default_one")]
1262 quantity: u32,
1263 seq: Seq,
1264 },
1265 HireWorker {
1267 entity_id: EntityId,
1268 def_id: String,
1269 wage_copper_per_interval: u32,
1270 #[serde(default)]
1271 lodging_container_id: Option<String>,
1272 #[serde(default)]
1273 job_yaml: Option<String>,
1274 seq: Seq,
1275 },
1276 DismissWorker {
1278 entity_id: EntityId,
1279 worker_instance_id: String,
1280 seq: Seq,
1281 },
1282 SetWorkerJob {
1284 entity_id: EntityId,
1285 worker_instance_id: String,
1286 job_yaml: String,
1287 seq: Seq,
1288 },
1289 AssignWorkerLodging {
1291 entity_id: EntityId,
1292 worker_instance_id: String,
1293 lodging_container_id: String,
1294 seq: Seq,
1295 },
1296 SetWorkerMode {
1298 entity_id: EntityId,
1299 worker_instance_id: String,
1300 mode: String,
1301 seq: Seq,
1302 },
1303 EquipWorkerItem {
1309 entity_id: EntityId,
1310 worker_instance_id: String,
1311 item_instance_id: uuid::Uuid,
1312 slot: String,
1313 seq: Seq,
1314 },
1315 GiveWorkerItem {
1318 entity_id: EntityId,
1319 worker_instance_id: String,
1320 item_instance_id: uuid::Uuid,
1321 #[serde(default)]
1322 quantity: Option<u32>,
1323 seq: Seq,
1324 },
1325 TakeWorkerItem {
1327 entity_id: EntityId,
1328 worker_instance_id: String,
1329 item_instance_id: uuid::Uuid,
1330 #[serde(default)]
1331 quantity: Option<u32>,
1332 seq: Seq,
1333 },
1334 RenameHiredWorker {
1336 entity_id: EntityId,
1337 worker_instance_id: String,
1338 name: String,
1339 seq: Seq,
1340 },
1341 RenamePropertyPlot {
1343 entity_id: EntityId,
1344 plot_id: Uuid,
1345 label: String,
1346 seq: Seq,
1347 },
1348 TeachWorkerBlueprint {
1350 entity_id: EntityId,
1351 worker_instance_id: String,
1352 blueprint_id: String,
1353 seq: Seq,
1354 },
1355 AttendHiredWorker {
1357 entity_id: EntityId,
1358 worker_instance_id: String,
1359 attending: bool,
1360 seq: Seq,
1361 },
1362 BuyPlot {
1364 entity_id: EntityId,
1365 zone_id: String,
1366 x0: f32,
1367 y0: f32,
1368 x1: f32,
1369 y1: f32,
1370 seq: Seq,
1371 },
1372 BuyPlotAllFree {
1374 entity_id: EntityId,
1375 zone_id: String,
1376 seq: Seq,
1377 },
1378 SellPlotToCrown {
1380 entity_id: EntityId,
1381 plot_id: Uuid,
1382 seq: Seq,
1383 },
1384 Cultivate {
1386 entity_id: EntityId,
1387 x: f32,
1389 y: f32,
1390 seq: Seq,
1391 },
1392 PlantSeeds {
1394 entity_id: EntityId,
1395 seed_template_id: String,
1396 quantity: u32,
1397 seq: Seq,
1398 },
1399 SetPlotFarmPublic {
1401 entity_id: EntityId,
1402 plot_id: Uuid,
1403 public: bool,
1404 #[serde(default)]
1405 public_tax_discount_bps: u32,
1406 seq: Seq,
1407 },
1408 PlotFarmAllowUpsert {
1410 entity_id: EntityId,
1411 plot_id: Uuid,
1412 #[serde(default)]
1414 character_id: Option<Uuid>,
1415 #[serde(default)]
1417 character_name: String,
1418 #[serde(default)]
1419 tax_discount_bps: u32,
1420 seq: Seq,
1421 },
1422 PlotFarmAllowRemove {
1424 entity_id: EntityId,
1425 plot_id: Uuid,
1426 character_id: Uuid,
1427 seq: Seq,
1428 },
1429 StartPlotBuild {
1432 entity_id: EntityId,
1433 plot_id: Uuid,
1434 wall_material_id: String,
1435 roof_material_id: String,
1436 seq: Seq,
1437 },
1438 CancelPlotBuild {
1439 entity_id: EntityId,
1440 seq: Seq,
1441 },
1442 SetDoorLocked {
1445 entity_id: EntityId,
1446 door_id: String,
1447 locked: bool,
1448 seq: Seq,
1449 },
1450 EnterBuildingDoor {
1453 entity_id: EntityId,
1454 door_id: String,
1455 seq: Seq,
1456 },
1457 ExitBuildingDoor {
1460 entity_id: EntityId,
1461 door_id: String,
1462 seq: Seq,
1463 },
1464 ConfirmInteriorEdit {
1466 entity_id: EntityId,
1467 building_id: String,
1468 rooms: Vec<InteriorRoomEdit>,
1469 room_doors: Vec<InteriorRoomDoorEdit>,
1470 seq: Seq,
1471 },
1472 CancelInteriorEdit {
1473 entity_id: EntityId,
1474 building_id: String,
1475 seq: Seq,
1476 },
1477 BankDeposit {
1479 entity_id: EntityId,
1480 npc_id: String,
1481 #[serde(default)]
1483 amount_copper: u64,
1484 seq: Seq,
1485 },
1486 BankWithdraw {
1488 entity_id: EntityId,
1489 npc_id: String,
1490 #[serde(default)]
1492 amount_copper: u64,
1493 seq: Seq,
1494 },
1495 BankClose {
1497 entity_id: EntityId,
1498 npc_id: String,
1499 seq: Seq,
1500 },
1501 BankTransfer {
1503 entity_id: EntityId,
1504 npc_id: String,
1505 #[serde(default)]
1507 to_character_id: Option<Uuid>,
1508 #[serde(default)]
1510 to_name: String,
1511 amount_copper: u64,
1513 seq: Seq,
1514 },
1515 StorageStore {
1517 entity_id: EntityId,
1518 npc_id: String,
1519 item_instance_id: Uuid,
1520 #[serde(default)]
1521 quantity: Option<u32>,
1522 seq: Seq,
1523 },
1524 StorageTake {
1526 entity_id: EntityId,
1527 npc_id: String,
1528 item_instance_id: Uuid,
1529 #[serde(default)]
1530 quantity: Option<u32>,
1531 seq: Seq,
1532 },
1533 StorageShip {
1535 entity_id: EntityId,
1536 npc_id: String,
1537 dest_building_id: String,
1538 item_instance_id: Uuid,
1539 #[serde(default)]
1540 quantity: Option<u32>,
1541 seq: Seq,
1542 },
1543 StorageClose {
1545 entity_id: EntityId,
1546 npc_id: String,
1547 seq: Seq,
1548 },
1549 MarketList {
1552 entity_id: EntityId,
1553 npc_id: String,
1554 source: GoodsLocation,
1555 item_instance_id: Uuid,
1556 #[serde(default)]
1557 quantity: Option<u32>,
1558 unit_price_copper: u64,
1559 #[serde(default)]
1561 npc_price: bool,
1562 seq: Seq,
1563 },
1564 MarketReprice {
1566 entity_id: EntityId,
1567 npc_id: String,
1568 listing_id: Uuid,
1569 unit_price_copper: u64,
1570 seq: Seq,
1571 },
1572 MarketDelist {
1574 entity_id: EntityId,
1575 npc_id: String,
1576 listing_id: Uuid,
1577 dest: GoodsLocation,
1578 seq: Seq,
1579 },
1580 MarketBuy {
1582 entity_id: EntityId,
1583 npc_id: String,
1584 listing_id: Uuid,
1585 #[serde(default = "default_one")]
1586 quantity: u32,
1587 dest: GoodsLocation,
1588 seq: Seq,
1589 },
1590 MarketClose {
1592 entity_id: EntityId,
1593 npc_id: String,
1594 seq: Seq,
1595 },
1596 TradeRequest {
1598 entity_id: EntityId,
1599 peer_entity_id: EntityId,
1600 seq: Seq,
1601 },
1602 TradeRespond {
1604 entity_id: EntityId,
1605 peer_entity_id: EntityId,
1606 accept: bool,
1607 seq: Seq,
1608 },
1609 TradePresent {
1611 entity_id: EntityId,
1612 item_instance_id: Uuid,
1613 #[serde(default)]
1614 quantity: Option<u32>,
1615 seq: Seq,
1616 },
1617 TradeUnpresent {
1619 entity_id: EntityId,
1620 item_instance_id: Uuid,
1621 seq: Seq,
1622 },
1623 TradeSetReady {
1625 entity_id: EntityId,
1626 ready: bool,
1627 seq: Seq,
1628 },
1629 TradeCancel {
1631 entity_id: EntityId,
1632 seq: Seq,
1633 },
1634 DestroyWhisperStone {
1636 entity_id: EntityId,
1637 item_instance_id: Uuid,
1638 seq: Seq,
1639 },
1640 StowWhisperStone {
1642 entity_id: EntityId,
1643 item_instance_id: Uuid,
1644 seq: Seq,
1645 },
1646 DeliverWorkerToNearestStorage {
1649 entity_id: EntityId,
1650 worker_instance_id: String,
1651 seq: Seq,
1652 },
1653 CancelWorkerDelivery {
1655 entity_id: EntityId,
1656 worker_instance_id: String,
1657 seq: Seq,
1658 },
1659 DeconstructItem {
1661 entity_id: EntityId,
1662 item_instance_id: Uuid,
1663 from: InventoryLocation,
1664 #[serde(default)]
1666 quantity: Option<u32>,
1667 seq: Seq,
1668 },
1669}
1670
1671fn default_block_enabled() -> bool {
1672 true
1673}
1674
1675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1677pub struct StatusEffectHud {
1678 pub effect_id: String,
1679 pub label: String,
1680 #[serde(default)]
1681 pub polarity: String,
1682 #[serde(default)]
1683 pub icon_tile_id: Option<String>,
1684 #[serde(default)]
1686 pub dot_color: Option<String>,
1687 #[serde(default)]
1689 pub remaining_sec: Option<f32>,
1690 #[serde(default = "default_stack_count")]
1692 pub stack_count: u8,
1693}
1694
1695fn default_stack_count() -> u8 {
1696 1
1697}
1698
1699#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1701pub struct CombatTargetHud {
1702 pub entity_id: EntityId,
1703 #[serde(default)]
1704 pub label: String,
1705 #[serde(default)]
1706 pub level: u32,
1707 pub health: f32,
1708 pub health_max: f32,
1709 #[serde(default)]
1710 pub life_state: LifeState,
1711 #[serde(default)]
1712 pub distance_m: f32,
1713 #[serde(default)]
1714 pub statuses: Vec<StatusEffectHud>,
1715}
1716
1717#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1719#[serde(rename_all = "snake_case")]
1720pub enum TimedChannelKind {
1721 #[default]
1722 Cultivate,
1723 Plant,
1724 Harvest,
1725 Build,
1727 Craft,
1729}
1730
1731#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1733pub struct TimedChannelHud {
1734 #[serde(default)]
1735 pub label: String,
1736 #[serde(default)]
1737 pub channel: TimedChannelKind,
1738 #[serde(default)]
1739 pub cell_x: i32,
1740 #[serde(default)]
1741 pub cell_y: i32,
1742 #[serde(default)]
1744 pub x0: f32,
1745 #[serde(default)]
1746 pub y0: f32,
1747 #[serde(default)]
1748 pub x1: f32,
1749 #[serde(default)]
1750 pub y1: f32,
1751 #[serde(default)]
1752 pub ticks_remaining: u64,
1753 #[serde(default)]
1754 pub ticks_total: u64,
1755}
1756
1757impl TimedChannelHud {
1758 pub fn has_footprint(&self) -> bool {
1760 self.x1 > self.x0 && self.y1 > self.y0
1761 }
1762}
1763
1764#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1766#[serde(rename_all = "snake_case")]
1767pub enum PlotBuildMaterialSource {
1768 #[default]
1769 None,
1770 TownStorage,
1771 NearbyContainer,
1772}
1773
1774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1776pub struct BuildingMaterialView {
1777 pub id: String,
1778 pub display_name: String,
1779 #[serde(default)]
1780 pub can_wall: bool,
1781 #[serde(default)]
1782 pub can_roof: bool,
1783 #[serde(default)]
1784 pub wall_set: String,
1785 #[serde(default)]
1786 pub roof_set: String,
1787 #[serde(default = "default_material_tick_mult")]
1788 pub tick_mult: f32,
1789 #[serde(default)]
1790 pub wall_bom: Vec<BuildingBomLineView>,
1791 #[serde(default)]
1792 pub roof_bom: Vec<BuildingBomLineView>,
1793}
1794
1795fn default_material_tick_mult() -> f32 {
1796 1.0
1797}
1798
1799#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1800pub struct BuildingBomLineView {
1801 pub template_id: String,
1802 #[serde(default)]
1803 pub display_name: String,
1804 pub per_m2: f32,
1805}
1806
1807#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1809pub struct PlotBuildStockView {
1810 pub template_id: String,
1811 #[serde(default)]
1812 pub display_name: String,
1813 pub quantity: u32,
1814}
1815
1816#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1818pub struct PlotBuildOfferHud {
1819 pub plot_id: Uuid,
1820 #[serde(default)]
1821 pub pad_width_m: f32,
1822 #[serde(default)]
1823 pub pad_depth_m: f32,
1824 #[serde(default)]
1825 pub pad_ok: bool,
1826 #[serde(default)]
1827 pub pad_error: String,
1828 #[serde(default)]
1829 pub source: PlotBuildMaterialSource,
1830 #[serde(default)]
1831 pub source_label: String,
1832 #[serde(default)]
1833 pub available: Vec<PlotBuildStockView>,
1834 #[serde(default)]
1835 pub base_ticks: u32,
1836 #[serde(default)]
1837 pub tick_per_m2: u32,
1838}
1839
1840#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1842pub struct CastProgressHud {
1843 #[serde(default)]
1844 pub ability_id: String,
1845 #[serde(default)]
1846 pub ability_label: String,
1847 #[serde(default)]
1848 pub ticks_remaining: u64,
1849 #[serde(default)]
1850 pub ticks_total: u64,
1851}
1852
1853#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1855pub struct AbilityCooldownHud {
1856 #[serde(default)]
1857 pub ability_id: String,
1858 #[serde(default)]
1859 pub label: String,
1860 #[serde(default)]
1861 pub cd_ticks: u64,
1862 #[serde(default)]
1863 pub cd_total_ticks: u64,
1864}
1865
1866#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1868pub struct CombatSlotHud {
1869 pub slot_index: u8,
1870 #[serde(default)]
1871 pub target_entity_id: Option<EntityId>,
1872 #[serde(default)]
1873 pub target_label: Option<String>,
1874 #[serde(default)]
1875 pub target: Option<CombatTargetHud>,
1876 #[serde(default)]
1877 pub preset_id: Option<String>,
1878 #[serde(default)]
1879 pub preset_label: Option<String>,
1880 #[serde(default)]
1881 pub rotation: Vec<String>,
1882 #[serde(default)]
1883 pub rotation_index: u32,
1884 #[serde(default)]
1885 pub next_ability_id: Option<String>,
1886 #[serde(default)]
1887 pub auto_enabled: bool,
1888}
1889
1890#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1892pub struct DefensePieceHud {
1893 pub slot: BodySlot,
1894 pub label: String,
1895 pub template_id: String,
1896 #[serde(default)]
1897 pub armor_physical: f32,
1898 #[serde(default)]
1899 pub resists: Vec<(String, f32)>,
1900}
1901
1902impl Default for DefensePieceHud {
1903 fn default() -> Self {
1904 Self {
1905 slot: BodySlot::Head,
1906 label: String::new(),
1907 template_id: String::new(),
1908 armor_physical: 0.0,
1909 resists: Vec::new(),
1910 }
1911 }
1912}
1913
1914#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1916pub struct DefenseHud {
1917 pub armor_physical: f32,
1918 pub vitality_contribution: f32,
1919 pub total_mitigation_rating: f32,
1920 pub estimated_physical_dr: f32,
1922 #[serde(default)]
1923 pub resists: Vec<(String, f32)>,
1924 #[serde(default)]
1925 pub pieces: Vec<DefensePieceHud>,
1926}
1927
1928#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1930pub struct CombatHud {
1931 pub in_combat: bool,
1932 pub auto_attack: bool,
1934 pub has_los: bool,
1935 pub attack_cd_ticks: u64,
1936 #[serde(default)]
1937 pub ability_id: String,
1938 #[serde(default)]
1939 pub target_entity_id: Option<EntityId>,
1940 #[serde(default)]
1941 pub target_label: Option<String>,
1942 #[serde(default)]
1943 pub max_target_slots: u8,
1944 #[serde(default)]
1945 pub slots: Vec<CombatSlotHud>,
1946 #[serde(default)]
1947 pub rotation_presets: Vec<RotationPreset>,
1948 #[serde(default)]
1949 pub gcd_ticks: u64,
1950 #[serde(default)]
1951 pub mainhand_template_id: Option<String>,
1952 #[serde(default)]
1953 pub mainhand_label: Option<String>,
1954 #[serde(default)]
1956 pub mainhand_instance_id: Option<Uuid>,
1957 #[serde(default)]
1958 pub offhand_template_id: Option<String>,
1959 #[serde(default)]
1960 pub offhand_label: Option<String>,
1961 #[serde(default)]
1963 pub offhand_instance_id: Option<Uuid>,
1964 #[serde(default)]
1966 pub mainhand_hand_slots: u8,
1967 #[serde(default)]
1969 pub worn: Vec<(BodySlot, ItemStack)>,
1970 #[serde(default)]
1972 pub defense: Option<DefenseHud>,
1973 #[serde(default)]
1974 pub carry_mass: f32,
1975 #[serde(default)]
1976 pub carry_mass_max: f32,
1977 #[serde(default)]
1978 pub encumbrance: EncumbranceState,
1979 #[serde(default)]
1981 pub keychain: Vec<ItemStack>,
1982 #[serde(default)]
1984 pub whisper_pouch: Vec<ItemStack>,
1985 #[serde(default)]
1986 pub target: Option<CombatTargetHud>,
1987 #[serde(default)]
1988 pub cast: Option<CastProgressHud>,
1989 #[serde(default)]
1991 pub timed_channel: Option<TimedChannelHud>,
1992 #[serde(default)]
1994 pub plot_build: Option<PlotBuildOfferHud>,
1995 #[serde(default)]
1996 pub ability_cooldowns: Vec<AbilityCooldownHud>,
1997 #[serde(default)]
1998 pub blocking_active: bool,
1999 #[serde(default)]
2001 pub progression_xp: Option<ProgressionXp>,
2002 #[serde(default)]
2003 pub progression_baseline: u16,
2004 #[serde(default)]
2005 pub progression_xp_base: f64,
2006 #[serde(default)]
2007 pub progression_xp_growth: f64,
2008 #[serde(default)]
2009 pub attributes: Option<PrimaryAttributes>,
2010 #[serde(default)]
2011 pub skills: Option<PlayerSkills>,
2012 #[serde(default)]
2014 pub statuses: Vec<StatusEffectHud>,
2015 #[serde(default)]
2017 pub known_abilities: Vec<String>,
2018 #[serde(default)]
2020 pub ability_meta: Vec<AbilityMetaHud>,
2021 #[serde(default)]
2023 pub ability_mastery: Vec<AbilityMasteryHud>,
2024 #[serde(default)]
2027 pub hotbar: Vec<Option<String>>,
2028 #[serde(default)]
2030 pub max_abilities_per_rotation: u8,
2031 #[serde(default)]
2033 pub move_speed_mps: f32,
2034 #[serde(default)]
2036 pub move_speed_mult: f32,
2037}
2038
2039#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2041pub struct AbilityMetaHud {
2042 pub id: String,
2043 #[serde(default = "default_aim_mode_entity")]
2045 pub aim_mode: String,
2046 #[serde(default)]
2047 pub blast_radius_m: f32,
2048 #[serde(default)]
2049 pub allows_self: bool,
2050 #[serde(default)]
2051 pub is_heal: bool,
2052 #[serde(default = "default_auto_rotation_eligible")]
2055 pub auto_rotation_eligible: bool,
2056}
2057
2058fn default_auto_rotation_eligible() -> bool {
2059 true
2060}
2061
2062fn default_aim_mode_entity() -> String {
2063 "entity".into()
2064}
2065
2066pub const HOTBAR_ITEM_PREFIX: &str = "item:";
2068
2069pub fn hotbar_consumable_binding(template_id: &str) -> String {
2071 format!("{HOTBAR_ITEM_PREFIX}{}", template_id.trim())
2072}
2073
2074pub fn hotbar_consumable_template(binding: &str) -> Option<&str> {
2076 binding
2077 .strip_prefix(HOTBAR_ITEM_PREFIX)
2078 .map(str::trim)
2079 .filter(|id| !id.is_empty())
2080}
2081
2082pub fn hotbar_binding_is_consumable(binding: &str) -> bool {
2084 hotbar_consumable_template(binding).is_some()
2085}
2086
2087#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2089#[serde(rename_all = "snake_case")]
2090pub enum CombatFxKind {
2091 MeleeArc,
2092 Cone,
2093 Sphere,
2094 Beam,
2095 HitMarker,
2096}
2097
2098#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2100#[serde(rename_all = "snake_case")]
2101pub enum CombatFxHitOutcome {
2102 #[default]
2103 Hit,
2104 Blocked,
2105 Miss,
2106 Glance,
2107}
2108
2109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2111pub struct CombatFxHit {
2112 pub entity_id: EntityId,
2113 pub x: f32,
2114 pub y: f32,
2115 pub z: f32,
2116 #[serde(default)]
2117 pub outcome: CombatFxHitOutcome,
2118}
2119
2120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2122pub struct CombatFx {
2123 pub id: u64,
2124 pub kind: CombatFxKind,
2125 pub ability_id: String,
2126 pub caster_id: EntityId,
2127 pub origin_x: f32,
2128 pub origin_y: f32,
2129 pub origin_z: f32,
2130 #[serde(default)]
2131 pub end_x: Option<f32>,
2132 #[serde(default)]
2133 pub end_y: Option<f32>,
2134 #[serde(default)]
2135 pub end_z: Option<f32>,
2136 #[serde(default)]
2137 pub yaw: Option<f32>,
2138 #[serde(default)]
2139 pub reach_m: Option<f32>,
2140 #[serde(default)]
2141 pub arc_deg: Option<f32>,
2142 #[serde(default)]
2143 pub radius_m: Option<f32>,
2144 #[serde(default)]
2145 pub hits: Vec<CombatFxHit>,
2146 pub until_tick: u64,
2148 #[serde(default)]
2149 pub damage_type: String,
2150}
2151
2152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2154pub struct GroundHazardView {
2155 pub x: f32,
2156 pub y: f32,
2157 pub z: f32,
2158 pub radius_m: f32,
2159 pub expires_at_tick: u64,
2160 #[serde(default)]
2161 pub damage_type: String,
2162}
2163
2164#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2166#[serde(rename_all = "snake_case")]
2167pub enum WorkerModeView {
2168 Companion,
2169 Defender,
2170 JobLoop,
2171 Idle,
2174}
2175
2176#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2178#[serde(rename_all = "snake_case")]
2179pub enum WorkerStateView {
2180 Idle,
2181 Traveling,
2182 Working,
2183 Resting,
2184 Waiting,
2185 Strike,
2186 Dismissed,
2187}
2188
2189#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
2191pub struct WorkerVitalsSummary {
2192 pub health_pct: f32,
2193 pub stamina_pct: f32,
2194 #[serde(default)]
2195 pub mana_pct: f32,
2196 #[serde(default)]
2197 pub hunger_pct: f32,
2198 #[serde(default)]
2199 pub thirst_pct: f32,
2200}
2201
2202#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2204#[serde(rename_all = "snake_case")]
2205pub enum WorkerRouteKindView {
2206 #[default]
2207 HarvestLoop,
2208 Ordered,
2209}
2210
2211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2213pub struct WorkerRouteView {
2214 #[serde(default)]
2215 pub kind: WorkerRouteKindView,
2216 #[serde(default)]
2217 pub lodging_container_id: Option<String>,
2218 #[serde(default)]
2220 pub outbound_waypoints: Vec<WorkerRouteWaypointView>,
2221 #[serde(default)]
2223 pub harvest_nodes: Vec<String>,
2224 #[serde(default = "default_route_carry_ratio")]
2225 pub carry_return_ratio: f32,
2226 #[serde(default)]
2228 pub stops: Vec<WorkerRouteStopView>,
2229}
2230
2231fn default_route_carry_ratio() -> f32 {
2232 0.90
2233}
2234
2235fn default_true_view() -> bool {
2236 true
2237}
2238
2239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2241pub struct WorkerWithdrawItemView {
2242 pub template: String,
2243 #[serde(default)]
2245 pub qty: u32,
2246 #[serde(default)]
2248 pub all: bool,
2249}
2250
2251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2252pub struct WorkerRouteWaypointView {
2253 pub x: f32,
2254 pub y: f32,
2255 pub z: f32,
2256}
2257
2258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2267#[serde(rename_all = "snake_case")]
2268pub enum WorkerRouteStopView {
2269 Waypoint {
2270 x: f32,
2271 y: f32,
2272 #[serde(default)]
2273 z: f32,
2274 },
2275 HarvestNode {
2276 node_id: String,
2277 },
2278 DepositAt {
2279 container_id: String,
2280 #[serde(default)]
2281 filter: Option<Vec<String>>,
2282 },
2283 TradeWith {
2284 #[serde(default)]
2285 npc_id: Option<String>,
2286 template: String,
2287 #[serde(default = "default_true_view")]
2288 sell_all: bool,
2289 },
2290 ListOnMarket {
2292 template: String,
2293 #[serde(default = "default_true_view")]
2294 list_all: bool,
2295 #[serde(default)]
2296 hall_id: Option<String>,
2297 },
2298 WithdrawFrom {
2299 container_id: String,
2300 items: Vec<WorkerWithdrawItemView>,
2301 },
2302 CraftAt {
2303 device: String,
2304 blueprint: String,
2305 #[serde(default)]
2306 qty: Option<u32>,
2307 },
2308 CultivatePlot {
2309 plot_id: uuid::Uuid,
2310 },
2311 PlantPlot {
2312 plot_id: uuid::Uuid,
2313 seed_template: String,
2314 },
2315 HarvestPlot {
2316 plot_id: uuid::Uuid,
2317 },
2318 RestIfNeeded,
2319 Wait {
2320 #[serde(default)]
2321 wait_ticks: u64,
2322 },
2323}
2324
2325#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2327#[serde(rename_all = "snake_case")]
2328pub enum LedgerCategory {
2329 Workers,
2330 Hire,
2331 Train,
2332 ShopBuy,
2333 Taxes,
2334 WorkerSales,
2335 TraderSales,
2336 BankDeposit,
2337 BankWithdraw,
2338 BankTransferOut,
2339 BankTransferIn,
2340 BankTransferFee,
2341 StorageShipFee,
2342 PropertyBuy,
2344 PropertySell,
2346 TaxShare,
2348 MarketBuy,
2350 MarketSell,
2352 Other,
2353}
2354
2355impl LedgerCategory {
2356 pub fn as_str(self) -> &'static str {
2357 match self {
2358 Self::Workers => "workers",
2359 Self::Hire => "hire",
2360 Self::Train => "train",
2361 Self::ShopBuy => "shop_buy",
2362 Self::Taxes => "taxes",
2363 Self::WorkerSales => "worker_sales",
2364 Self::TraderSales => "trader_sales",
2365 Self::BankDeposit => "bank_deposit",
2366 Self::BankWithdraw => "bank_withdraw",
2367 Self::BankTransferOut => "bank_transfer_out",
2368 Self::BankTransferIn => "bank_transfer_in",
2369 Self::BankTransferFee => "bank_transfer_fee",
2370 Self::StorageShipFee => "storage_ship_fee",
2371 Self::PropertyBuy => "property_buy",
2372 Self::PropertySell => "property_sell",
2373 Self::TaxShare => "tax_share",
2374 Self::MarketBuy => "market_buy",
2375 Self::MarketSell => "market_sell",
2376 Self::Other => "other",
2377 }
2378 }
2379}
2380
2381#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2382pub struct LedgerEntryView {
2383 pub id: uuid::Uuid,
2384 pub game_day: u64,
2385 pub signed_copper: i64,
2386 pub category: LedgerCategory,
2387 #[serde(default)]
2388 pub label: String,
2389}
2390
2391#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2392pub struct LedgerPeriodTotals {
2393 #[serde(default)]
2395 pub expenses: std::collections::HashMap<String, u64>,
2396 #[serde(default)]
2398 pub income: std::collections::HashMap<String, u64>,
2399 pub expense_copper: u64,
2400 pub income_copper: u64,
2401 pub cash_flow_copper: i64,
2403}
2404
2405#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2406pub struct PlayerLedgerView {
2407 pub current_game_day: u64,
2408 #[serde(default)]
2409 pub period_day: LedgerPeriodTotals,
2410 #[serde(default)]
2411 pub period_week: LedgerPeriodTotals,
2412 #[serde(default)]
2413 pub period_month: LedgerPeriodTotals,
2414 #[serde(default)]
2415 pub period_lifetime: LedgerPeriodTotals,
2416 #[serde(default)]
2417 pub recent: Vec<LedgerEntryView>,
2418 #[serde(default)]
2420 pub wealth_on_person_copper: u64,
2421 #[serde(default)]
2423 pub wealth_in_storage_copper: u64,
2424 #[serde(default)]
2426 pub wealth_in_bank_copper: u64,
2427 #[serde(default)]
2429 pub wealth_total_copper: u64,
2430 #[serde(default)]
2432 pub wealth_in_property_copper: u64,
2433 #[serde(default)]
2435 pub wealth_net_worth_copper: u64,
2436 #[serde(default)]
2438 pub property_assets: Vec<PropertyAssetView>,
2439 #[serde(default)]
2441 pub property_market_nearby: Vec<PropertyMarketCompView>,
2442 #[serde(default)]
2444 pub live_expense_per_interval_copper: u64,
2445 #[serde(default)]
2447 pub live_income_route_est_per_loop_copper: u64,
2448 #[serde(default)]
2450 pub live_income_avg_per_interval_copper: u64,
2451 #[serde(default)]
2453 pub live_income_avg_window_intervals: u32,
2454 #[serde(default)]
2456 pub live_net_avg_per_interval_copper: i64,
2457}
2458
2459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2461pub struct PropertyAssetView {
2462 pub plot_id: Uuid,
2463 pub label: String,
2465 pub zone_id: String,
2466 #[serde(default)]
2467 pub zone_label: Option<String>,
2468 pub area_m2: f32,
2469 pub purchase_basis_copper: u64,
2471 pub upkeep_copper_per_day: u64,
2472}
2473
2474#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2476pub struct PropertyMarketCompView {
2477 pub day: u64,
2478 pub zone_id: String,
2479 #[serde(default)]
2480 pub zone_label: Option<String>,
2481 pub area_m2: f32,
2482 pub price_copper: u64,
2483 pub price_per_m2_copper: u64,
2485 pub kind: String,
2487 pub distance_m: f32,
2489}
2490
2491#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2493#[serde(rename_all = "snake_case")]
2494pub enum AnalyticsMetric {
2495 NpcKill,
2496 WildlifeKill,
2497 Harvest,
2498 QuestComplete,
2499 QuestAccept,
2500 QuestAbandon,
2501 PlayerDeath,
2502 Craft,
2503 WorkerHire,
2504 WorkerDismiss,
2505 WorkerTeach,
2506 NpcTalk,
2507 ShopBuy,
2508 ShopSell,
2509 PlaceContainer,
2510 PickupContainer,
2511 PickupDrop,
2512 ConsumableUse,
2513 AbilityUse,
2514 DistanceWalkedM,
2515 DoorUse,
2516 BuildingEnter,
2517}
2518
2519impl AnalyticsMetric {
2520 pub fn as_str(self) -> &'static str {
2521 match self {
2522 Self::NpcKill => "npc_kill",
2523 Self::WildlifeKill => "wildlife_kill",
2524 Self::Harvest => "harvest",
2525 Self::QuestComplete => "quest_complete",
2526 Self::QuestAccept => "quest_accept",
2527 Self::QuestAbandon => "quest_abandon",
2528 Self::PlayerDeath => "player_death",
2529 Self::Craft => "craft",
2530 Self::WorkerHire => "worker_hire",
2531 Self::WorkerDismiss => "worker_dismiss",
2532 Self::WorkerTeach => "worker_teach",
2533 Self::NpcTalk => "npc_talk",
2534 Self::ShopBuy => "shop_buy",
2535 Self::ShopSell => "shop_sell",
2536 Self::PlaceContainer => "place_container",
2537 Self::PickupContainer => "pickup_container",
2538 Self::PickupDrop => "pickup_drop",
2539 Self::ConsumableUse => "consumable_use",
2540 Self::AbilityUse => "ability_use",
2541 Self::DistanceWalkedM => "distance_walked_m",
2542 Self::DoorUse => "door_use",
2543 Self::BuildingEnter => "building_enter",
2544 }
2545 }
2546
2547 pub fn from_str_key(s: &str) -> Option<Self> {
2548 Some(match s {
2549 "npc_kill" => Self::NpcKill,
2550 "wildlife_kill" => Self::WildlifeKill,
2551 "harvest" => Self::Harvest,
2552 "quest_complete" => Self::QuestComplete,
2553 "quest_accept" => Self::QuestAccept,
2554 "quest_abandon" => Self::QuestAbandon,
2555 "player_death" => Self::PlayerDeath,
2556 "craft" => Self::Craft,
2557 "worker_hire" => Self::WorkerHire,
2558 "worker_dismiss" => Self::WorkerDismiss,
2559 "worker_teach" => Self::WorkerTeach,
2560 "npc_talk" => Self::NpcTalk,
2561 "shop_buy" => Self::ShopBuy,
2562 "shop_sell" => Self::ShopSell,
2563 "place_container" => Self::PlaceContainer,
2564 "pickup_container" => Self::PickupContainer,
2565 "pickup_drop" => Self::PickupDrop,
2566 "consumable_use" => Self::ConsumableUse,
2567 "ability_use" => Self::AbilityUse,
2568 "distance_walked_m" => Self::DistanceWalkedM,
2569 "door_use" => Self::DoorUse,
2570 "building_enter" => Self::BuildingEnter,
2571 _ => return None,
2572 })
2573 }
2574}
2575
2576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2577pub struct CareerMetricRow {
2578 pub subject_id: String,
2579 pub amount: u64,
2580}
2581
2582#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2584pub struct PlayerCareerView {
2585 pub current_game_day: u64,
2586 #[serde(default)]
2587 pub kills: Vec<CareerMetricRow>,
2588 #[serde(default)]
2589 pub harvests: Vec<CareerMetricRow>,
2590 pub quests_completed: u64,
2591 #[serde(default)]
2592 pub crafts: Vec<CareerMetricRow>,
2593 pub deaths: u64,
2594 pub npc_talks: u64,
2595 pub shop_buys: u64,
2596 pub shop_sells: u64,
2597 pub distance_m: u64,
2598 #[serde(default)]
2599 pub other: Vec<CareerMetricRow>,
2600}
2601
2602#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2607pub struct WorkerEquipmentView {
2608 #[serde(default)]
2609 pub mainhand: Option<ItemStack>,
2610 #[serde(default)]
2611 pub offhand: Option<ItemStack>,
2612 #[serde(default)]
2613 pub worn: Vec<(BodySlot, ItemStack)>,
2614}
2615
2616#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2618pub struct HiredWorkerView {
2619 pub instance_id: String,
2620 pub entity_id: EntityId,
2621 pub def_id: String,
2622 pub label: String,
2624 pub x: f32,
2625 pub y: f32,
2626 pub z: f32,
2627 pub mode: WorkerModeView,
2628 pub state: WorkerStateView,
2629 #[serde(default)]
2630 pub step_label: String,
2631 pub vitals: WorkerVitalsSummary,
2632 #[serde(default)]
2633 pub carry_pct: f32,
2634 #[serde(default)]
2635 pub last_error: Option<String>,
2636 pub wage_copper_per_interval: u32,
2637 #[serde(default)]
2639 pub effective_wage_copper: u32,
2640 #[serde(default)]
2642 pub wage_meters_walked: f32,
2643 #[serde(default)]
2645 pub lodging_container_id: Option<String>,
2646 #[serde(default)]
2648 pub route: Option<WorkerRouteView>,
2649 #[serde(default)]
2652 pub route_stop_index: Option<u32>,
2653 #[serde(default)]
2655 pub known_blueprint_ids: Vec<String>,
2656 #[serde(default = "default_worker_view_level")]
2658 pub level: u32,
2659 #[serde(default)]
2661 pub worker_xp: f64,
2662 #[serde(default)]
2664 pub inventory: Vec<ItemStack>,
2665 #[serde(default)]
2667 pub equipment: WorkerEquipmentView,
2668 #[serde(default)]
2671 pub issue_hint: Option<String>,
2672 #[serde(default)]
2675 pub has_blocking_issue: bool,
2676 #[serde(default)]
2678 pub harvest_node_issues: Vec<WorkerHarvestNodeIssueView>,
2679}
2680
2681#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2683pub struct WorkerHarvestNodeIssueView {
2684 pub node_id: String,
2685 pub issue: String,
2686}
2687
2688fn default_worker_view_level() -> u32 {
2689 1
2690}
2691
2692#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2694pub struct TickDelta {
2695 pub tick: Tick,
2696 pub entities: Vec<EntityState>,
2697 #[serde(default)]
2698 pub resource_nodes: Vec<ResourceNodeView>,
2699 #[serde(default)]
2700 pub buildings: Vec<BuildingView>,
2701 #[serde(default)]
2702 pub doors: Vec<DoorView>,
2703 #[serde(default)]
2704 pub npcs: Vec<NpcView>,
2705 #[serde(default)]
2707 pub inventory: Vec<ItemStack>,
2708 #[serde(default)]
2709 pub blueprints: Vec<BlueprintView>,
2710 #[serde(default)]
2712 pub building_materials: Vec<BuildingMaterialView>,
2713 #[serde(default)]
2714 pub world_clock: WorldClock,
2715 #[serde(default)]
2716 pub ground_drops: Vec<GroundDropView>,
2717 #[serde(default)]
2718 pub placed_containers: Vec<PlacedContainerView>,
2719 #[serde(default)]
2720 pub combat: Option<CombatHud>,
2721 #[serde(default)]
2722 pub interior_map: Option<InteriorMapView>,
2723 #[serde(default)]
2724 pub quest_log: Vec<QuestLogEntry>,
2725 #[serde(default)]
2726 pub hired_workers: Vec<HiredWorkerView>,
2727 #[serde(default)]
2728 pub interactables: Vec<InteractableView>,
2729 #[serde(default)]
2730 pub ledger: Option<PlayerLedgerView>,
2731 #[serde(default)]
2732 pub career: Option<PlayerCareerView>,
2733 #[serde(default)]
2735 pub combat_fx: Vec<CombatFx>,
2736 #[serde(default)]
2738 pub ground_hazards: Vec<GroundHazardView>,
2739 #[serde(default)]
2741 pub property_plots: Vec<PropertyPlotView>,
2742 #[serde(default)]
2744 pub terrain_overlays: Vec<TerrainZoneView>,
2745}
2746#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2747pub struct GroundDropView {
2748 pub id: String,
2749 pub template_id: String,
2750 pub quantity: u32,
2751 pub x: f32,
2752 pub y: f32,
2753 pub z: f32,
2754 #[serde(default)]
2756 pub tile_id: Option<String>,
2757 #[serde(default)]
2759 pub display_name: Option<String>,
2760 #[serde(default)]
2762 pub yaw: f32,
2763 #[serde(default)]
2765 pub pitch: f32,
2766 #[serde(default)]
2768 pub roll: f32,
2769 #[serde(default = "default_draw_scale")]
2771 pub draw_scale: f32,
2772 #[serde(default)]
2774 pub item_instance_id: Option<Uuid>,
2775 #[serde(default)]
2777 pub props: std::collections::BTreeMap<String, String>,
2778 #[serde(default)]
2780 pub status_bindings: Vec<ItemStatusBinding>,
2781}
2782
2783fn default_draw_scale() -> f32 {
2784 1.0
2785}
2786
2787#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2789pub struct Snapshot {
2790 pub tick: Tick,
2791 pub chunk_rev: u64,
2792 #[serde(default)]
2794 pub content_rev: u64,
2795 #[serde(default)]
2797 pub publish_rev: u64,
2798 pub entities: Vec<EntityState>,
2799 #[serde(default)]
2800 pub resource_nodes: Vec<ResourceNodeView>,
2801 #[serde(default)]
2803 pub world_x0: f32,
2804 #[serde(default)]
2805 pub world_y0: f32,
2806 #[serde(default)]
2808 pub world_width_m: f32,
2809 #[serde(default)]
2810 pub world_height_m: f32,
2811 #[serde(default)]
2812 pub buildings: Vec<BuildingView>,
2813 #[serde(default)]
2814 pub doors: Vec<DoorView>,
2815 #[serde(default)]
2816 pub npcs: Vec<NpcView>,
2817 #[serde(default)]
2818 pub inventory: Vec<ItemStack>,
2819 #[serde(default)]
2820 pub blueprints: Vec<BlueprintView>,
2821 #[serde(default)]
2823 pub building_materials: Vec<BuildingMaterialView>,
2824 #[serde(default)]
2825 pub world_clock: WorldClock,
2826 #[serde(default)]
2827 pub terrain_zones: Vec<TerrainZoneView>,
2828 #[serde(default)]
2829 pub z_platforms: Vec<ZPlatformView>,
2830 #[serde(default)]
2831 pub z_transitions: Vec<ZTransitionView>,
2832 #[serde(default)]
2833 pub ground_drops: Vec<GroundDropView>,
2834 #[serde(default)]
2835 pub placed_containers: Vec<PlacedContainerView>,
2836 #[serde(default)]
2837 pub combat: Option<CombatHud>,
2838 #[serde(default)]
2839 pub interior_map: Option<InteriorMapView>,
2840 #[serde(default)]
2841 pub quest_log: Vec<QuestLogEntry>,
2842 #[serde(default)]
2843 pub hired_workers: Vec<HiredWorkerView>,
2844 #[serde(default)]
2845 pub interactables: Vec<InteractableView>,
2846 #[serde(default)]
2847 pub ledger: Option<PlayerLedgerView>,
2848 #[serde(default)]
2849 pub career: Option<PlayerCareerView>,
2850 #[serde(default)]
2852 pub combat_fx: Vec<CombatFx>,
2853 #[serde(default)]
2855 pub ground_hazards: Vec<GroundHazardView>,
2856 #[serde(default)]
2858 pub property_zones: Vec<PropertyZoneView>,
2859 #[serde(default)]
2861 pub tax_zones: Vec<TaxZoneView>,
2862 #[serde(default)]
2864 pub boundary_zones: Vec<BoundaryZoneView>,
2865 #[serde(default)]
2867 pub encounter_zones: Vec<EncounterZoneView>,
2868 #[serde(default)]
2870 pub growth_zones: Vec<GrowthZoneView>,
2871 #[serde(default)]
2873 pub biome_zones: Vec<BiomeZoneView>,
2874 #[serde(default)]
2876 pub terrain_kind_nav: Vec<TerrainKindNavView>,
2877 #[serde(default)]
2879 pub property_plots: Vec<PropertyPlotView>,
2880 #[serde(default)]
2882 pub property_plot_settings: Option<PropertyPlotSettingsView>,
2883 #[serde(default)]
2886 pub item_catalog: Vec<ItemCatalogEntryView>,
2887}
2888
2889#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2891pub struct ItemCatalogEntryView {
2892 pub template_id: String,
2893 #[serde(default)]
2894 pub display_name: String,
2895 #[serde(default)]
2896 pub category: String,
2897 #[serde(default)]
2899 pub seed_for: Option<String>,
2900}
2901
2902impl ItemCatalogEntryView {
2903 pub fn is_harvest_node(&self) -> bool {
2904 self.category == "harvest_node"
2905 }
2906
2907 pub fn is_depositable_stack(&self) -> bool {
2909 !self.is_harvest_node()
2910 }
2911
2912 pub fn is_farm_seed(&self) -> bool {
2913 self.seed_for
2914 .as_deref()
2915 .is_some_and(|s| !s.trim().is_empty())
2916 || self.category == "seed"
2917 }
2918}
2919
2920#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2922pub struct ResourceNodeView {
2923 pub id: String,
2924 pub label: String,
2925 pub x: f32,
2926 pub y: f32,
2927 pub z: f32,
2928 pub item_template: String,
2929 #[serde(default = "default_node_state")]
2930 pub state: ResourceNodeState,
2931 #[serde(default = "default_blocking_view")]
2933 pub blocking: bool,
2934 #[serde(default = "default_blocking_radius_view")]
2936 pub blocking_radius_m: f32,
2937 #[serde(default)]
2939 pub harvest_off: bool,
2940 #[serde(default)]
2942 pub tile_id: Option<String>,
2943 #[serde(default)]
2945 pub yaw: f32,
2946 #[serde(default)]
2948 pub pitch: f32,
2949 #[serde(default)]
2951 pub roll: f32,
2952 #[serde(default = "default_draw_scale")]
2954 pub draw_scale: f32,
2955 #[serde(default)]
2957 pub sprite_mode: Option<String>,
2958 #[serde(default)]
2960 pub presentation_state: Option<String>,
2961 #[serde(default)]
2964 pub growth_progress: Option<f32>,
2965 #[serde(default)]
2967 pub channel_start_tick: Option<Tick>,
2968 #[serde(default)]
2969 pub channel_end_tick: Option<Tick>,
2970 #[serde(default)]
2972 pub harvest_drop_templates: Vec<String>,
2973}
2974
2975fn default_blocking_radius_view() -> f32 {
2976 0.8
2977}
2978
2979fn default_blocking_view() -> bool {
2980 true
2981}
2982
2983#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2984#[serde(rename_all = "snake_case")]
2985pub enum ResourceNodeState {
2986 Available,
2987 Harvesting,
2988 Cooldown,
2989}
2990fn default_node_state() -> ResourceNodeState {
2991 ResourceNodeState::Available
2992}
2993
2994#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2996#[serde(rename_all = "snake_case")]
2997pub enum ItemSpawnStateView {
2998 Spawned,
2999 PickedUp { respawn_at_tick: u64 },
3000 Consumed,
3001}
3002
3003#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3005pub struct ItemSpawnView {
3006 pub id: String,
3007 pub label: String,
3008 pub item_template: String,
3009 pub quantity: u32,
3010 pub x: f32,
3011 pub y: f32,
3012 pub z: f32,
3013 pub respawn_ticks: u32,
3014 #[serde(default)]
3015 pub building_id: Option<String>,
3016 pub state: ItemSpawnStateView,
3017 #[serde(default)]
3019 pub once_per_character: bool,
3020 #[serde(default)]
3022 pub collected_count: u32,
3023}
3024
3025#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3026#[serde(rename_all = "snake_case")]
3027pub enum ItemStatusBindingMode {
3028 OnHit,
3029 WhileEquipped,
3030}
3031
3032impl Default for ItemStatusBindingMode {
3033 fn default() -> Self {
3034 Self::OnHit
3035 }
3036}
3037
3038#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3040pub struct ItemStatusBinding {
3041 pub effect_id: String,
3042 #[serde(default)]
3043 pub mode: ItemStatusBindingMode,
3044 #[serde(default)]
3046 pub source: String,
3047 #[serde(default)]
3048 pub applied_at_tick: u64,
3049 #[serde(default)]
3052 pub expires_at_tick: Option<u64>,
3053}
3054
3055#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3056pub struct ItemStack {
3057 pub template_id: String,
3058 pub quantity: u32,
3059 #[serde(default)]
3061 pub item_instance_id: Option<Uuid>,
3062 #[serde(default)]
3064 pub props: BTreeMap<String, String>,
3065 #[serde(default)]
3067 pub status_bindings: Vec<ItemStatusBinding>,
3068 #[serde(default)]
3070 pub contents: Vec<ItemStack>,
3071 #[serde(default)]
3073 pub display_name: Option<String>,
3074 #[serde(default)]
3076 pub category: Option<String>,
3077 #[serde(default)]
3079 pub base_mass: Option<f32>,
3080 #[serde(default)]
3082 pub base_volume: Option<f32>,
3083 #[serde(default)]
3085 pub capacity_volume: Option<f32>,
3086 #[serde(default)]
3088 pub stackable: Option<bool>,
3089 #[serde(default)]
3091 pub world_placeable: Option<bool>,
3092 #[serde(default)]
3094 pub worker_lodging_capacity: Option<u32>,
3095 #[serde(default)]
3097 pub equip_slot: Option<BodySlot>,
3098 #[serde(default)]
3100 pub armor_physical: Option<f32>,
3101 #[serde(default)]
3103 pub resists: Vec<(String, f32)>,
3104 #[serde(default)]
3106 pub hand_slots: Option<u8>,
3107 #[serde(default)]
3109 pub listable: Option<bool>,
3110 #[serde(default)]
3112 pub base_value_copper: Option<u32>,
3113}
3114
3115impl ItemStack {
3116 pub fn simple(template_id: impl Into<String>, quantity: u32) -> Self {
3117 Self {
3118 template_id: template_id.into(),
3119 quantity,
3120 ..Default::default()
3121 }
3122 }
3123}
3124
3125impl Default for ItemStack {
3126 fn default() -> Self {
3127 Self {
3128 template_id: String::new(),
3129 quantity: 0,
3130 item_instance_id: None,
3131 props: BTreeMap::new(),
3132 status_bindings: Vec::new(),
3133 contents: Vec::new(),
3134 display_name: None,
3135 category: None,
3136 base_mass: None,
3137 base_volume: None,
3138 capacity_volume: None,
3139 stackable: None,
3140 world_placeable: None,
3141 worker_lodging_capacity: None,
3142 equip_slot: None,
3143 armor_physical: None,
3144 resists: Vec::new(),
3145 hand_slots: None,
3146 listable: None,
3147 base_value_copper: None,
3148 }
3149 }
3150}
3151
3152#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3154#[serde(rename_all = "snake_case")]
3155pub enum EncumbranceState {
3156 #[default]
3157 Light,
3158 Heavy,
3159 Orange,
3160 Over,
3161}
3162
3163impl EncumbranceState {
3164 pub fn label(self) -> &'static str {
3166 match self {
3167 Self::Light => "Light",
3168 Self::Heavy => "Heavy",
3169 Self::Orange => "Overloaded",
3170 Self::Over => "Over",
3171 }
3172 }
3173
3174 pub fn allows_sprint(self) -> bool {
3176 !matches!(self, Self::Over)
3177 }
3178}
3179
3180#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
3184#[serde(rename_all = "snake_case")]
3185pub enum BodySlot {
3186 Head,
3187 #[serde(alias = "body")]
3189 Chest,
3190 #[serde(alias = "arms")]
3192 Forearms,
3193 Legs,
3194 Feet,
3195 Cloak,
3196 Back,
3197 Waist,
3198 Earrings,
3199 Necklace,
3200 Eyeglasses,
3201 #[serde(rename = "ring_left_1", alias = "ring_left1")]
3203 RingLeft1,
3204 #[serde(rename = "ring_left_2", alias = "ring_left2")]
3205 RingLeft2,
3206 #[serde(rename = "ring_right_1", alias = "ring_right1")]
3207 RingRight1,
3208 #[serde(rename = "ring_right_2", alias = "ring_right2")]
3209 RingRight2,
3210}
3211
3212impl BodySlot {
3213 pub const ALL: [BodySlot; 15] = [
3215 BodySlot::Head,
3216 BodySlot::Chest,
3217 BodySlot::Forearms,
3218 BodySlot::Legs,
3219 BodySlot::Feet,
3220 BodySlot::Cloak,
3221 BodySlot::Back,
3222 BodySlot::Waist,
3223 BodySlot::Earrings,
3224 BodySlot::Necklace,
3225 BodySlot::Eyeglasses,
3226 BodySlot::RingLeft1,
3227 BodySlot::RingLeft2,
3228 BodySlot::RingRight1,
3229 BodySlot::RingRight2,
3230 ];
3231
3232 pub fn as_str(self) -> &'static str {
3233 match self {
3234 BodySlot::Head => "head",
3235 BodySlot::Chest => "chest",
3236 BodySlot::Forearms => "forearms",
3237 BodySlot::Legs => "legs",
3238 BodySlot::Feet => "feet",
3239 BodySlot::Cloak => "cloak",
3240 BodySlot::Back => "back",
3241 BodySlot::Waist => "waist",
3242 BodySlot::Earrings => "earrings",
3243 BodySlot::Necklace => "necklace",
3244 BodySlot::Eyeglasses => "eyeglasses",
3245 BodySlot::RingLeft1 => "ring_left_1",
3246 BodySlot::RingLeft2 => "ring_left_2",
3247 BodySlot::RingRight1 => "ring_right_1",
3248 BodySlot::RingRight2 => "ring_right_2",
3249 }
3250 }
3251}
3252
3253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3255#[serde(rename_all = "snake_case")]
3256pub enum InventoryLocation {
3257 Root,
3259 Worn { slot: BodySlot },
3261 Placed { container_id: String },
3263 Keychain,
3265 WhisperPouch,
3267}
3268
3269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3271pub struct PlacedContainerView {
3272 pub id: String,
3273 pub template_id: String,
3274 pub display_name: String,
3275 pub x: f32,
3276 pub y: f32,
3277 pub z: f32,
3278 pub locked: bool,
3279 #[serde(default)]
3281 pub accessible: bool,
3282 #[serde(default)]
3283 pub owner_character_id: Option<Uuid>,
3284 #[serde(default)]
3286 pub contents: Vec<ItemStack>,
3287 #[serde(default)]
3289 pub lock_id: Option<String>,
3290 #[serde(default)]
3292 pub capacity_volume: Option<f32>,
3293 #[serde(default)]
3295 pub item_instance_id: Option<Uuid>,
3296 #[serde(default)]
3298 pub tile_id: Option<String>,
3299 #[serde(default)]
3301 pub worker_lodging_capacity: Option<u32>,
3302 #[serde(default)]
3304 pub blocking: bool,
3305 #[serde(default)]
3307 pub blocking_radius_m: f32,
3308 #[serde(default)]
3311 pub building_id: Option<String>,
3312}
3313
3314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3315pub struct BlueprintIngredientView {
3316 pub template_id: String,
3317 pub quantity: u32,
3318 pub consumed: bool,
3320 #[serde(default)]
3322 pub display_name: String,
3323}
3324
3325#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3326pub struct ToolRequirementView {
3327 pub item: String,
3328 pub consumed: bool,
3330 #[serde(default)]
3332 pub display_name: String,
3333}
3334
3335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3336pub struct SkillRequirementView {
3337 pub skill: String,
3338 pub level: u32,
3339}
3340
3341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3342pub struct BlueprintView {
3343 pub id: String,
3344 pub label: String,
3345 pub output: String,
3346 pub output_qty: u32,
3347 pub craft_ticks: u32,
3348 pub inputs: Vec<BlueprintIngredientView>,
3349 #[serde(default)]
3351 pub station: Option<String>,
3352 #[serde(default)]
3353 pub category: Option<String>,
3354 #[serde(default)]
3355 pub required_tools: Vec<ToolRequirementView>,
3356 #[serde(default)]
3357 pub skill: Option<SkillRequirementView>,
3358 #[serde(default)]
3359 pub failure_chance: f32,
3360 #[serde(default)]
3362 pub worker_train_copper: u64,
3363 #[serde(default)]
3365 pub output_display_name: String,
3366 #[serde(default)]
3368 pub craft_tier: u32,
3369}
3370
3371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3373pub struct TerrainKindNavView {
3374 pub kind: TerrainKindView,
3375 #[serde(default = "default_move_speed_mult_one")]
3376 pub move_speed_mult: f32,
3377 #[serde(default)]
3378 pub impassable: bool,
3379}
3380
3381fn default_move_speed_mult_one() -> f32 {
3382 1.0
3383}
3384
3385#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3387#[serde(rename_all = "snake_case")]
3388pub enum TerrainKindView {
3389 #[default]
3390 Grass,
3391 Dirt,
3392 Tilled,
3393 Desert,
3394 Hill,
3395 Bog,
3396 Beach,
3397 ShallowWater,
3398 DeepWater,
3399 Trail,
3400 Road,
3401 Rock,
3402}
3403
3404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3405pub struct TerrainZoneView {
3406 pub id: String,
3407 pub x0: f32,
3408 pub y0: f32,
3409 pub x1: f32,
3410 pub y1: f32,
3411 #[serde(default)]
3412 pub kind: TerrainKindView,
3413 #[serde(default)]
3415 pub elevation: f32,
3416 #[serde(default)]
3419 pub glyph: Option<String>,
3420 #[serde(default)]
3422 pub color: Option<String>,
3423 #[serde(default)]
3425 pub tile_id: Option<String>,
3426 #[serde(default)]
3428 pub z_order: i32,
3429 #[serde(default)]
3431 pub channel_start_tick: Option<Tick>,
3432 #[serde(default)]
3433 pub channel_end_tick: Option<Tick>,
3434}
3435
3436#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3438pub struct ZoneRectView {
3439 pub x0: f32,
3440 pub y0: f32,
3441 pub x1: f32,
3442 pub y1: f32,
3443}
3444
3445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3447pub struct PropertyZoneView {
3448 pub id: String,
3449 #[serde(default)]
3451 pub label: Option<String>,
3452 pub rects: Vec<ZoneRectView>,
3453 #[serde(default)]
3454 pub z_order: i32,
3455 pub crown_price_copper: u64,
3456 pub upkeep_copper_per_day: u64,
3457 #[serde(default)]
3458 pub max_area_m2: Option<f32>,
3459 #[serde(default)]
3460 pub owner_tax_discount_bps: u32,
3461}
3462
3463#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3465pub struct TaxZoneView {
3466 pub id: String,
3467 #[serde(default)]
3468 pub label: Option<String>,
3469 pub rects: Vec<ZoneRectView>,
3470 #[serde(default)]
3471 pub z_order: i32,
3472 pub rate_bps: u32,
3473 #[serde(default)]
3474 pub flat_copper: u64,
3475 #[serde(default)]
3477 pub market_sales_tax_bps: u32,
3478 #[serde(default)]
3480 pub market_sales_flat_copper: u32,
3481}
3482
3483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3485pub struct BoundaryZoneView {
3486 pub id: String,
3487 #[serde(default)]
3488 pub label: Option<String>,
3489 pub rects: Vec<ZoneRectView>,
3490 #[serde(default)]
3491 pub z_order: i32,
3492 #[serde(default, skip_serializing_if = "Option::is_none")]
3493 pub jurisdiction_id: Option<String>,
3494 #[serde(default = "default_true")]
3495 pub worker_logistics: bool,
3496 #[serde(default)]
3497 pub security_tier: String,
3498 #[serde(default)]
3499 pub pvp_mode: String,
3500 #[serde(default = "default_true")]
3501 pub crime_enabled: bool,
3502 #[serde(default)]
3503 pub guard_response: bool,
3504 #[serde(default)]
3506 pub pass_through_props: bool,
3507 #[serde(default)]
3509 pub presence_mode: String,
3510}
3511
3512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3514pub struct EncounterZoneView {
3515 pub id: String,
3516 #[serde(default)]
3517 pub label: Option<String>,
3518 pub rects: Vec<ZoneRectView>,
3519 #[serde(default)]
3520 pub z_order: i32,
3521}
3522
3523fn default_true() -> bool {
3524 true
3525}
3526
3527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3529pub struct GrowthZoneView {
3530 pub id: String,
3531 #[serde(default)]
3532 pub label: Option<String>,
3533 pub rects: Vec<ZoneRectView>,
3534 #[serde(default)]
3535 pub z_order: i32,
3536 #[serde(default = "default_one_f32")]
3537 pub fertility: f32,
3538}
3539
3540fn default_one_f32() -> f32 {
3541 1.0
3542}
3543
3544#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3546pub struct BiomeZoneView {
3547 pub id: String,
3548 #[serde(default)]
3549 pub label: Option<String>,
3550 pub rects: Vec<ZoneRectView>,
3551 #[serde(default)]
3552 pub z_order: i32,
3553 pub biome_id: String,
3554}
3555
3556#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3558pub struct FarmGrantView {
3559 pub character_id: Uuid,
3560 #[serde(default)]
3562 pub character_label: String,
3563 pub tax_discount_bps: u32,
3564}
3565
3566#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3568pub struct PropertyPlotView {
3569 pub plot_id: Uuid,
3570 pub property_zone_id: String,
3571 #[serde(default)]
3572 pub zone_label: Option<String>,
3573 pub deed_instance_id: Uuid,
3574 pub x0: f32,
3575 pub y0: f32,
3576 pub x1: f32,
3577 pub y1: f32,
3578 pub upkeep_copper_per_day: u64,
3579 pub arrears_days: u32,
3580 #[serde(default)]
3582 pub is_mine: bool,
3583 #[serde(default)]
3585 pub may_farm: bool,
3586 #[serde(default)]
3588 pub purchase_basis_copper: u64,
3589 #[serde(default)]
3590 pub farm_public: bool,
3591 #[serde(default)]
3592 pub public_tax_discount_bps: u32,
3593 #[serde(default)]
3594 pub farm_allow: Vec<FarmGrantView>,
3595 #[serde(default)]
3597 pub owner_character_id: Option<Uuid>,
3598 #[serde(default)]
3599 pub owner_label: Option<String>,
3600 #[serde(default)]
3602 pub building_id: Option<String>,
3603 #[serde(default)]
3605 pub plot_code: String,
3606 #[serde(default)]
3608 pub label: String,
3609}
3610
3611#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3613pub struct PropertyPlotSettingsView {
3614 pub min_plot_area_m2: f32,
3615 pub tax_premium_weight: f32,
3616 pub sellback_bps: u32,
3617}
3618
3619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3621pub struct ZPlatformView {
3622 pub id: String,
3623 pub z: f32,
3624 pub x0: f32,
3625 pub y0: f32,
3626 pub x1: f32,
3627 pub y1: f32,
3628}
3629
3630#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3632pub struct ZTransitionView {
3633 pub id: String,
3634 pub z_from: f32,
3635 pub z_to: f32,
3636 pub x0: f32,
3637 pub y0: f32,
3638 pub x1: f32,
3639 pub y1: f32,
3640}
3641
3642#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3643pub struct BuildingView {
3644 pub id: String,
3645 pub label: String,
3646 pub x: f32,
3647 pub y: f32,
3648 pub width_m: f32,
3649 pub depth_m: f32,
3650 #[serde(default)]
3651 pub interior_blueprint: Option<String>,
3652 #[serde(default)]
3653 pub tags: Vec<String>,
3654 #[serde(default)]
3656 pub market_boundary_zone_ids: Vec<String>,
3657 #[serde(default)]
3659 pub market_max_volume: Option<f32>,
3660 #[serde(default)]
3663 pub wall_set: Option<String>,
3664 #[serde(default)]
3666 pub roof_set: Option<String>,
3667}
3668
3669pub const DEFAULT_BUILDING_ART_SET: &str = "classic_stone";
3672
3673impl BuildingView {
3674 pub fn effective_wall_set(&self) -> &str {
3675 self.wall_set
3676 .as_deref()
3677 .filter(|s| !s.is_empty())
3678 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3679 }
3680
3681 pub fn effective_roof_set(&self) -> &str {
3682 self.roof_set
3683 .as_deref()
3684 .filter(|s| !s.is_empty())
3685 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3686 }
3687}
3688
3689#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3690pub struct DoorView {
3691 pub id: String,
3692 pub building_id: String,
3693 pub x: f32,
3694 pub y: f32,
3695 #[serde(default)]
3696 pub open: bool,
3697 #[serde(default)]
3698 pub portal: Option<String>,
3699 #[serde(default)]
3702 pub locked: bool,
3703 #[serde(default = "default_door_accessible")]
3705 pub accessible: bool,
3706 #[serde(default)]
3707 pub lock_id: Option<Uuid>,
3708}
3709
3710fn default_door_accessible() -> bool {
3711 true
3712}
3713
3714#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3716pub struct InteriorRoomEdit {
3717 pub id: String,
3718 pub label: String,
3719 pub x0: f32,
3720 pub y0: f32,
3721 pub x1: f32,
3722 pub y1: f32,
3723}
3724
3725#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3726pub struct InteriorRoomDoorEdit {
3727 pub id: String,
3728 pub room_a: String,
3729 pub room_b: String,
3730 pub x: f32,
3731 pub y: f32,
3732}
3733
3734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3735pub struct InteriorRoomView {
3736 pub id: String,
3737 pub label: String,
3738 pub floor: i32,
3739 pub x0: f32,
3740 pub y0: f32,
3741 pub x1: f32,
3742 pub y1: f32,
3743 #[serde(default)]
3744 pub floor_color: Option<String>,
3745 #[serde(default)]
3746 pub floor_glyph: Option<String>,
3747}
3748
3749#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3750pub struct InteriorDoorView {
3751 pub id: String,
3752 pub room_a: String,
3753 pub room_b: String,
3754 pub x: f32,
3755 pub y: f32,
3756 pub kind: String,
3757 #[serde(default)]
3758 pub x_a: Option<f32>,
3759 #[serde(default)]
3760 pub y_a: Option<f32>,
3761 #[serde(default)]
3762 pub x_b: Option<f32>,
3763 #[serde(default)]
3764 pub y_b: Option<f32>,
3765}
3766
3767#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3768pub struct InteriorMapView {
3769 pub building_id: String,
3770 pub blueprint_id: String,
3771 pub background_color: String,
3772 #[serde(default)]
3773 pub default_floor_color: Option<String>,
3774 #[serde(default = "default_floor_height_view")]
3775 pub floor_height_m: f32,
3776 #[serde(default)]
3778 pub z_platforms: Vec<ZPlatformView>,
3779 #[serde(default)]
3780 pub z_transitions: Vec<ZTransitionView>,
3781 pub rooms: Vec<InteriorRoomView>,
3782 #[serde(default)]
3783 pub room_doors: Vec<InteriorDoorView>,
3784}
3785
3786fn default_floor_height_view() -> f32 {
3787 3.0
3788}
3789
3790#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3791pub struct NpcView {
3792 pub id: String,
3793 pub label: String,
3794 pub role: String,
3795 pub x: f32,
3796 pub y: f32,
3797 #[serde(default)]
3799 pub building_id: Option<String>,
3800 #[serde(default)]
3802 pub entity_id: Option<EntityId>,
3803 #[serde(default)]
3804 pub life_state: Option<LifeState>,
3805 #[serde(default)]
3806 pub hp_pct: Option<f32>,
3807 #[serde(default)]
3809 pub can_trade: bool,
3810 #[serde(default)]
3812 pub buy_templates: Vec<String>,
3813 #[serde(default)]
3815 pub tile_id: Option<String>,
3816 #[serde(default)]
3818 pub behavior_state: Option<String>,
3819 #[serde(default)]
3821 pub presentation_state: Option<String>,
3822 #[serde(default)]
3824 pub sprite_mode: Option<String>,
3825 #[serde(default)]
3827 pub paperdoll_ref: Option<String>,
3828 #[serde(default = "default_draw_scale")]
3830 pub draw_scale: f32,
3831 #[serde(default)]
3833 pub yaw: Option<f32>,
3834 #[serde(default)]
3836 pub perception_fov_deg: Option<f32>,
3837 #[serde(default)]
3839 pub perception_sight_m: Option<f32>,
3840 #[serde(default)]
3842 pub perception_hear_m: Option<f32>,
3843 #[serde(default)]
3845 pub quest_verbs: Vec<NpcQuestVerb>,
3846}
3847
3848#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
3850pub struct NpcQuestVerb {
3851 pub quest_id: String,
3852 pub label: String,
3854 pub kind: String,
3855}
3856
3857impl NpcQuestVerb {
3858 pub const KIND_OFFER: &'static str = "offer";
3859 pub const KIND_TALK: &'static str = "talk";
3860 pub const KIND_GIVE: &'static str = "give";
3861}
3862
3863#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3864pub struct UseResult {
3865 pub template_id: String,
3866 pub hunger_restored: f32,
3867 pub thirst_restored: f32,
3868 #[serde(default)]
3869 pub health_restored: f32,
3870 #[serde(default)]
3871 pub mana_restored: f32,
3872 #[serde(default)]
3873 pub cleared_dot_ids: Vec<String>,
3874}
3875
3876#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3877pub struct CraftResult {
3878 pub blueprint_id: String,
3879 pub outputs: Vec<ItemStack>,
3880 pub consumed: Vec<ItemStack>,
3881 #[serde(default = "default_one")]
3883 pub batch_index: u32,
3884 #[serde(default = "default_one")]
3886 pub batch_total: u32,
3887}
3888
3889fn default_one() -> u32 {
3890 1
3891}
3892
3893#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3894pub struct DeathNotice {
3895 pub entity_id: EntityId,
3896 pub respawn_x: f32,
3897 pub respawn_y: f32,
3898 pub message: String,
3899}
3900
3901#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3902pub struct InteractionNotice {
3903 pub target_id: String,
3904 pub message: String,
3905 #[serde(default)]
3906 pub coins_delta: i32,
3907 #[serde(default)]
3908 pub inventory_delta: Vec<ItemStack>,
3909}
3910
3911#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3912#[serde(rename_all = "snake_case")]
3913pub enum NpcTalkTrustFlag {
3914 Stranger,
3915 Acquainted,
3916 Trusted,
3917}
3918
3919#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3920#[serde(rename_all = "snake_case")]
3921pub enum NpcTalkDepth {
3922 #[default]
3923 Full,
3924 Brief,
3925 Unavailable,
3926}
3927
3928#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3929pub struct NpcTalkOpened {
3930 pub npc_id: String,
3931 pub npc_label: String,
3932 pub greeting: String,
3933 pub trust_flag: NpcTalkTrustFlag,
3934 #[serde(default)]
3935 pub talk_depth: NpcTalkDepth,
3936 #[serde(default = "default_true")]
3937 pub trade_allowed: bool,
3938 #[serde(default)]
3940 pub suggested_topics: Vec<String>,
3941}
3942
3943#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3944pub struct NpcTalkPending {
3945 pub npc_id: String,
3946}
3947
3948#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3949pub struct NpcTalkReply {
3950 pub npc_id: String,
3951 pub line: String,
3952 pub trust_flag: NpcTalkTrustFlag,
3953 #[serde(default)]
3954 pub wind_down: bool,
3955 #[serde(default)]
3956 pub trade_disabled: bool,
3957}
3958
3959#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3960pub struct NpcTalkClosed {
3961 pub npc_id: String,
3962}
3963
3964#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3965pub struct NpcTalkError {
3966 pub npc_id: String,
3967 pub reason: String,
3968}
3969
3970#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3971#[serde(rename_all = "snake_case")]
3972pub enum QuestStatusView {
3973 Available,
3974 Active,
3975 Completed,
3976}
3977
3978#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3979pub struct QuestObjectiveProgress {
3980 pub label: String,
3981 pub current: u32,
3982 pub required: u32,
3983 pub done: bool,
3984 #[serde(default)]
3988 pub kind: String,
3989 #[serde(default)]
3990 pub npc_ref: Option<String>,
3991 #[serde(default)]
3992 pub item_template: Option<String>,
3993 #[serde(default)]
3994 pub blueprint_id: Option<String>,
3995 #[serde(default)]
3996 pub building_id: Option<String>,
3997}
3998
3999#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
4001pub struct QuestRewardItemView {
4002 pub template_id: String,
4003 #[serde(default)]
4005 pub display_name: String,
4006 pub quantity: u32,
4007}
4008
4009#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
4011pub struct QuestRewardView {
4012 #[serde(default)]
4013 pub coins: u32,
4014 #[serde(default)]
4015 pub items: Vec<QuestRewardItemView>,
4016}
4017
4018impl QuestRewardView {
4019 pub fn is_empty(&self) -> bool {
4020 self.coins == 0 && self.items.is_empty()
4021 }
4022}
4023
4024#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
4025#[serde(rename_all = "snake_case")]
4026pub enum QuestStepStatusView {
4027 #[default]
4028 Pending,
4029 Current,
4030 Done,
4031}
4032
4033#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4035pub struct QuestStepView {
4036 pub id: String,
4037 pub title: String,
4038 #[serde(default)]
4039 pub status: QuestStepStatusView,
4040 #[serde(default)]
4041 pub reward: QuestRewardView,
4042}
4043
4044#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4045pub struct QuestLogEntry {
4046 pub quest_id: String,
4047 pub title: String,
4048 pub description: String,
4049 pub status: QuestStatusView,
4050 #[serde(default)]
4051 pub current_step_id: Option<String>,
4052 #[serde(default)]
4053 pub current_step_title: String,
4054 #[serde(default)]
4055 pub current_step_index: u32,
4056 #[serde(default)]
4057 pub objectives: Vec<QuestObjectiveProgress>,
4058 #[serde(default)]
4060 pub current_step_reward: QuestRewardView,
4061 #[serde(default)]
4063 pub completion_reward: QuestRewardView,
4064 #[serde(default)]
4066 pub steps: Vec<QuestStepView>,
4067 #[serde(default)]
4068 pub is_tracked: bool,
4069 #[serde(default)]
4070 pub can_withdraw: bool,
4071}
4072
4073#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4074pub struct InteractableView {
4075 pub id: String,
4076 pub kind: String,
4077 pub label: String,
4078 pub x: f32,
4079 pub y: f32,
4080 pub z: f32,
4081 #[serde(default)]
4082 pub board_id: Option<String>,
4083}
4084
4085#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4086pub struct QuestOffer {
4087 pub quest_id: String,
4088 pub title: String,
4089 pub description: String,
4090 #[serde(default)]
4091 pub step_count: u32,
4092}
4093
4094#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4095pub struct QuestCatalogEntry {
4096 pub quest_id: String,
4097 pub title: String,
4098 pub description: String,
4099 pub step_count: u32,
4100 #[serde(default)]
4101 pub board_ids: Vec<String>,
4102}
4103
4104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4105pub struct QuestCatalogUpdated {
4106 pub revision: u64,
4107 pub game_day: String,
4108 #[serde(default)]
4109 pub accepted: Vec<QuestCatalogEntry>,
4110 #[serde(default)]
4111 pub retired: Vec<String>,
4112}
4113
4114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4115pub struct QuestNotice {
4116 pub quest_id: String,
4117 pub title: String,
4118 pub message: String,
4119}
4120
4121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4122#[serde(rename_all = "snake_case")]
4123pub enum ShopOfferKind {
4124 Item,
4125 Blueprint,
4126}
4127
4128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4129pub struct ShopOffer {
4130 pub offer_id: String,
4131 pub kind: ShopOfferKind,
4132 pub label: String,
4133 #[serde(default)]
4134 pub template_id: Option<String>,
4135 #[serde(default)]
4136 pub blueprint_id: Option<String>,
4137 pub price_copper: u32,
4138 #[serde(default)]
4139 pub affordable: bool,
4140 #[serde(default)]
4141 pub already_owned: bool,
4142}
4143
4144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4145pub struct ShopBuyLine {
4146 pub template_id: String,
4147 pub label: String,
4148 pub quantity: u32,
4149 pub price_copper: u32,
4150}
4151
4152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4154pub struct BankPanel {
4155 pub npc_id: String,
4156 pub npc_label: String,
4157 pub bank_balance_copper: u64,
4158 pub on_person_copper: u64,
4159 #[serde(default)]
4161 pub pending_outgoing_copper: u64,
4162 #[serde(default)]
4163 pub transfer_fee_bps: u32,
4164 #[serde(default)]
4165 pub transfer_clear_ticks: u64,
4166}
4167
4168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4170pub struct StoragePanel {
4171 pub npc_id: String,
4172 pub npc_label: String,
4173 pub building_id: String,
4174 pub building_label: String,
4175 pub used_volume: f32,
4176 pub max_volume: f32,
4177 #[serde(default)]
4178 pub contents: Vec<ItemStack>,
4179 #[serde(default)]
4181 pub ship_destinations: Vec<StorageShipDest>,
4182}
4183
4184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4185pub struct StorageShipDest {
4186 pub building_id: String,
4187 pub label: String,
4188 pub distance_m: f32,
4189 pub fee_copper: u64,
4190 pub travel_ticks: u64,
4191}
4192
4193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4196pub enum GoodsLocation {
4197 Person,
4199 TownStorage { building_id: String },
4202}
4203
4204#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4207pub struct MarketListingView {
4208 pub listing_id: Uuid,
4209 pub seller_character_id: Uuid,
4210 pub seller_label: String,
4212 pub hall_building_id: String,
4213 pub hall_label: String,
4214 pub template_id: String,
4215 pub display_name: String,
4216 #[serde(default)]
4218 pub category: String,
4219 pub quantity: u32,
4220 pub unit_price_copper: u64,
4221 pub line_total_copper: u64,
4223 #[serde(default)]
4225 pub npc_price: bool,
4226 #[serde(default)]
4229 pub npc_dump_unit_copper: Option<u32>,
4230 pub mine: bool,
4232}
4233
4234#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4236pub struct MarketListVault {
4237 pub building_id: String,
4238 pub building_label: String,
4240 #[serde(default)]
4241 pub contents: Vec<ItemStack>,
4242}
4243
4244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4247pub struct MarketPanel {
4248 pub npc_id: String,
4249 pub npc_label: String,
4250 pub building_id: String,
4251 pub building_label: String,
4252 pub used_volume: f32,
4254 pub max_volume: f32,
4255 #[serde(default)]
4258 pub listings: Vec<MarketListingView>,
4259 #[serde(default)]
4261 pub tax_bps: u32,
4262 #[serde(default)]
4263 pub tax_flat_copper: u32,
4264 #[serde(default)]
4266 pub list_vaults: Vec<MarketListVault>,
4267}
4268
4269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4270pub struct ShopCatalog {
4271 pub npc_id: String,
4272 pub npc_label: String,
4273 #[serde(default)]
4274 pub sells: Vec<ShopOffer>,
4275 #[serde(default)]
4276 pub buys: Vec<ShopBuyLine>,
4277}
4278
4279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4280pub struct HarvestResult {
4281 pub node_id: String,
4282 pub quantity: u32,
4284 pub item_template: String,
4285 #[serde(default)]
4288 pub item_instance_id: Option<Uuid>,
4289}
4290
4291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4293pub struct Envelope<T> {
4294 pub protocol_version: u16,
4295 pub payload: T,
4296}
4297
4298impl<T> Envelope<T> {
4299 pub fn new(payload: T) -> Self {
4300 Self {
4301 protocol_version: crate::PROTOCOL_VERSION,
4302 payload,
4303 }
4304 }
4305}
4306
4307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4309pub struct Hello {
4310 pub client_name: String,
4311 pub protocol_version: u16,
4312 #[serde(default)]
4313 pub auth: AuthCredential,
4314 #[serde(default)]
4316 pub character_id: Option<Uuid>,
4317}
4318
4319#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4322#[serde(rename_all = "snake_case")]
4323pub enum AuthCredential {
4324 DevLocal,
4325 Session { token: String },
4326 ApiToken { token: String, character_id: Uuid },
4327}
4328
4329impl Default for AuthCredential {
4330 fn default() -> Self {
4331 Self::DevLocal
4332 }
4333}
4334
4335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4336pub struct Welcome {
4337 pub session_id: SessionId,
4338 pub entity_id: EntityId,
4339 pub snapshot: Snapshot,
4340}
4341
4342#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4343pub enum ServerMessage {
4344 Welcome(Welcome),
4345 ContentUpdated(Snapshot),
4347 Tick(TickDelta),
4348 IntentAck {
4349 entity_id: EntityId,
4350 seq: Seq,
4351 tick: Tick,
4352 },
4353 Chat(ChatMessage),
4354 HarvestResult(HarvestResult),
4355 UseResult(UseResult),
4356 CraftResult(CraftResult),
4357 Death(DeathNotice),
4358 Interaction(InteractionNotice),
4359 ShopOpened(ShopCatalog),
4360 NpcTalkOpened(NpcTalkOpened),
4361 NpcTalkPending(NpcTalkPending),
4362 NpcTalkReply(NpcTalkReply),
4363 NpcTalkClosed(NpcTalkClosed),
4364 NpcTalkError(NpcTalkError),
4365 QuestOffer(QuestOffer),
4366 QuestAccepted(QuestNotice),
4367 QuestWithdrawn(QuestNotice),
4368 QuestStepCompleted(QuestNotice),
4369 QuestCompleted(QuestNotice),
4370 QuestCatalogUpdated(QuestCatalogUpdated),
4371 BankOpened(BankPanel),
4373 StorageOpened(StoragePanel),
4375 MarketOpened(MarketPanel),
4377 TradeOpened(TradePanel),
4379 TradeClosed {
4381 reason: String,
4382 },
4383 ConnectRejected {
4386 reason: String,
4387 },
4388}
4389
4390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4392pub struct TradePanel {
4393 pub peer_entity_id: EntityId,
4394 pub peer_name: String,
4395 pub my_presented: Vec<ItemStack>,
4396 pub their_presented: Vec<ItemStack>,
4397 pub i_ready: bool,
4398 pub they_ready: bool,
4399 pub my_mass_after: f32,
4401 pub my_mass_max: f32,
4402 pub my_encumbrance_after: EncumbranceState,
4403 pub overburden_warning: bool,
4405}
4406
4407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4408pub enum ClientMessage {
4409 Hello(Hello),
4410 Intent(Intent),
4411 Disconnect,
4412}
4413
4414#[cfg(test)]
4415mod tests {
4416 use super::*;
4417
4418 #[test]
4419 fn pristine_vitals_state_yields_full_pools() {
4420 let attrs = PrimaryAttributes::default();
4421 let vitals = StoredVitalsState::default().apply_to(attrs);
4422 assert!(vitals.health > 0.0);
4423 assert_eq!(vitals.health, vitals.health_max);
4424 assert!((vitals.mana_max - 61.0).abs() < 0.01);
4425 }
4426
4427 #[test]
4428 fn humanize_snake_id_title_cases_parts() {
4429 assert_eq!(humanize_snake_id("heal_touch"), "Heal Touch");
4430 assert_eq!(humanize_snake_id("fireball"), "Fireball");
4431 assert_eq!(humanize_snake_id("cone_frost"), "Cone Frost");
4432 }
4433
4434 #[test]
4435 fn world_clock_phase_names_normalize_aliases_and_drop_unknown() {
4436 assert_eq!(TimeOfDayPhase::try_from_name("noon"), Some(TimeOfDayPhase::Midday));
4437 assert_eq!(TimeOfDayPhase::from_name("nope"), TimeOfDayPhase::Night);
4438 assert_eq!(
4439 normalize_world_clock_phase_names(["Morning", "dusk", "bogus", "morning"]),
4440 vec!["morning".to_string(), "evening".to_string()]
4441 );
4442 }
4443
4444 #[test]
4445 fn saved_vitals_scale_when_pool_max_increases() {
4446 let mut attrs = PrimaryAttributes::default();
4447 attrs.intelligence = 140;
4448 attrs.wisdom = 140;
4449 let saved = StoredVitalsState {
4450 health: 100.0,
4451 mana: 14.0,
4452 stamina: 100.0,
4453 ..StoredVitalsState::default()
4454 };
4455 let vitals = saved.apply_to(attrs);
4456 assert!(vitals.mana_max > 55.0);
4457 assert!(
4458 (vitals.mana - vitals.mana_max).abs() < 0.01,
4459 "full legacy mana bar migrates to full new bar"
4460 );
4461 }
4462
4463 #[test]
4464 fn empty_vitals_state_is_pristine() {
4465 let pristine = StoredVitalsState {
4466 health: 0.0,
4467 mana: 0.0,
4468 stamina: 0.0,
4469 hunger: 0.0,
4470 thirst: 0.0,
4471 coins: 0,
4472 deaths: 0,
4473 life_state: LifeState::Alive,
4474 winded: false,
4475 };
4476 assert!(pristine.is_pristine());
4477 let vitals = pristine.apply_to(PrimaryAttributes::default());
4478 assert!(vitals.health > 0.0);
4479 }
4480
4481 #[test]
4482 fn stored_vitals_roundtrip_preserves_partial_pools() {
4483 let attrs = PrimaryAttributes::default();
4484 let mut live = PlayerVitals::from_attributes(attrs);
4485 live.health = 25.0;
4486 live.hunger = 77.0;
4487 live.deaths = 2;
4488 live.winded = true;
4489 let stored = StoredVitalsState::from_live(&live);
4490 let restored = stored.apply_to(attrs);
4491 assert!(
4492 (restored.health - 25.0).abs() < 0.01,
4493 "partial HP below cap stays absolute"
4494 );
4495 assert_eq!(restored.hunger, 77.0);
4496 assert_eq!(restored.deaths, 2);
4497 assert!(restored.winded);
4498 }
4499
4500 #[test]
4501 fn skill_tiers_start_at_zero() {
4502 let skill = SkillProgress::default();
4503 assert_eq!(skill.level, 0);
4504 assert_eq!(skill.display_tier(), 0);
4505 let trained = SkillProgress {
4506 level: 250,
4507 last_trained_tick: 1,
4508 };
4509 assert_eq!(trained.display_tier(), 2);
4510 }
4511
4512 #[test]
4513 fn quest_server_messages_roundtrip_json() {
4514 use crate::codec::{Codec, PostcardCodec};
4515
4516 let offer = ServerMessage::QuestOffer(QuestOffer {
4517 quest_id: "ada_goblin_hunt".into(),
4518 title: "Goblin Trouble".into(),
4519 description: "Help Ada".into(),
4520 step_count: 3,
4521 });
4522 let notice = ServerMessage::QuestAccepted(QuestNotice {
4523 quest_id: "ada_goblin_hunt".into(),
4524 title: "Goblin Trouble".into(),
4525 message: "Quest accepted".into(),
4526 });
4527 for msg in [offer, notice] {
4528 let bytes = PostcardCodec.encode(&msg).unwrap();
4529 let decoded: ServerMessage = PostcardCodec.decode(&bytes).unwrap();
4530 assert_eq!(decoded, msg);
4531 }
4532 }
4533
4534 #[test]
4535 fn hotbar_consumable_binding_roundtrips() {
4536 let binding = hotbar_consumable_binding("vegetable_soup");
4537 assert_eq!(binding, "item:vegetable_soup");
4538 assert!(hotbar_binding_is_consumable(&binding));
4539 assert_eq!(hotbar_consumable_template(&binding), Some("vegetable_soup"));
4540 assert!(!hotbar_binding_is_consumable("fireball"));
4541 assert_eq!(hotbar_consumable_template("fireball"), None);
4542 }
4543}