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 fn label(self) -> &'static str {
88 match self {
89 Self::Night => "Night",
90 Self::Dawn => "Dawn",
91 Self::Morning => "Morning",
92 Self::Midday => "Midday",
93 Self::Afternoon => "Afternoon",
94 Self::Evening => "Evening",
95 }
96 }
97
98 pub fn from_name(name: &str) -> Self {
99 match name.to_ascii_lowercase().as_str() {
100 "dawn" => Self::Dawn,
101 "morning" => Self::Morning,
102 "midday" | "mid_day" | "noon" => Self::Midday,
103 "afternoon" => Self::Afternoon,
104 "evening" | "dusk" => Self::Evening,
105 _ => Self::Night,
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
112pub struct WorldClock {
113 pub day: u64,
115 pub hour: u8,
116 pub minute: u8,
117 pub phase: TimeOfDayPhase,
118}
119
120impl Default for WorldClock {
121 fn default() -> Self {
122 Self {
123 day: 0,
124 hour: 8,
125 minute: 0,
126 phase: TimeOfDayPhase::Morning,
127 }
128 }
129}
130
131impl WorldClock {
132 pub fn display_time(self) -> String {
133 format!("{:02}:{:02}", self.hour, self.minute)
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
139pub struct PrimaryAttributes {
140 pub strength: u16,
141 pub dexterity: u16,
142 pub intelligence: u16,
143 pub stamina: u16,
144 pub vitality: u16,
145 pub wisdom: u16,
146 pub charisma: u16,
147}
148
149impl Default for PrimaryAttributes {
150 fn default() -> Self {
151 Self {
152 strength: 150,
153 dexterity: 150,
154 intelligence: 150,
155 stamina: 150,
156 vitality: 150,
157 wisdom: 150,
158 charisma: 150,
159 }
160 }
161}
162
163impl PrimaryAttributes {
164 pub fn display(value: u16) -> u16 {
166 (value / 10).clamp(1, 100)
167 }
168
169 pub fn derived_preview(&self) -> DerivedPreview {
171 let str_d = Self::display(self.strength) as f32;
172 let dex_d = Self::display(self.dexterity) as f32;
173 let int_d = Self::display(self.intelligence) as f32;
174 let wis_d = Self::display(self.wisdom) as f32;
175 DerivedPreview {
176 attack_power: str_d * 1.2 + dex_d * 0.3,
177 spell_power: int_d * 1.1 + wis_d * 0.4,
178 evasion: dex_d * 0.8 + wis_d * 0.2,
179 carry_mass_max: str_d * 2.5,
180 sight_range_m: 12.0 + wis_d * 0.15 + dex_d * 0.05,
181 fov_deg: 120.0 + wis_d * 0.2,
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq)]
188pub struct DerivedPreview {
189 pub attack_power: f32,
190 pub spell_power: f32,
191 pub evasion: f32,
192 pub carry_mass_max: f32,
193 pub sight_range_m: f32,
194 pub fov_deg: f32,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
199pub struct SkillProgress {
200 pub level: u16,
201 #[serde(default)]
202 pub last_trained_tick: u64,
203}
204
205impl Default for SkillProgress {
206 fn default() -> Self {
207 Self {
208 level: 0,
209 last_trained_tick: 0,
210 }
211 }
212}
213
214impl SkillProgress {
215 pub fn display_tier(&self) -> u16 {
217 (self.level / 100).min(10)
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
223#[serde(default)]
224pub struct ProgressionXp {
225 pub strength: f64,
226 pub dexterity: f64,
227 pub intelligence: f64,
228 pub stamina: f64,
229 pub vitality: f64,
230 pub wisdom: f64,
231 pub charisma: f64,
232 pub logging: f64,
233 pub mining: f64,
234 pub evocation: f64,
235 pub restoration: f64,
236 pub swords: f64,
237 pub archery: f64,
238 pub crafting: f64,
239 pub alchemy: f64,
240 pub cartography: f64,
241 #[serde(default)]
243 pub ability: std::collections::BTreeMap<String, f64>,
244}
245
246impl ProgressionXp {
247 pub fn bootstrap_new(baseline_display: u16, xp_base: f64, xp_growth: f64) -> Self {
249 let bootstrap = |display: f64| {
250 if display <= 1.0 {
251 0.0
252 } else {
253 xp_base * xp_growth.powf(display - 1.0)
254 }
255 };
256 let b = baseline_display as f64;
257 let primary = bootstrap(b);
258 Self {
259 strength: primary,
260 dexterity: primary,
261 intelligence: primary,
262 stamina: primary,
263 vitality: primary,
264 wisdom: primary,
265 charisma: primary,
266 ..Self::default()
267 }
268 }
269
270 pub fn is_empty(&self) -> bool {
271 self.strength == 0.0
272 && self.dexterity == 0.0
273 && self.intelligence == 0.0
274 && self.stamina == 0.0
275 && self.vitality == 0.0
276 && self.wisdom == 0.0
277 && self.charisma == 0.0
278 && self.logging == 0.0
279 && self.mining == 0.0
280 && self.evocation == 0.0
281 && self.restoration == 0.0
282 && self.swords == 0.0
283 && self.archery == 0.0
284 && self.crafting == 0.0
285 && self.alchemy == 0.0
286 && self.cartography == 0.0
287 && self.ability.is_empty()
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub struct AbilityMasteryHud {
294 pub ability_id: String,
295 pub tier: u16,
297 pub level: u16,
299 pub xp: f64,
301 pub xp_to_next: f64,
303}
304
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307#[serde(default)]
308pub struct PlayerSkills {
309 pub logging: SkillProgress,
310 pub mining: SkillProgress,
311 pub evocation: SkillProgress,
312 #[serde(default)]
313 pub restoration: SkillProgress,
314 pub swords: SkillProgress,
315 #[serde(default)]
316 pub archery: SkillProgress,
317 pub crafting: SkillProgress,
318 #[serde(default)]
319 pub alchemy: SkillProgress,
320 pub cartography: SkillProgress,
321}
322
323impl Default for PlayerSkills {
324 fn default() -> Self {
325 Self {
326 logging: SkillProgress::default(),
327 mining: SkillProgress::default(),
328 evocation: SkillProgress::default(),
329 restoration: SkillProgress::default(),
330 swords: SkillProgress::default(),
331 archery: SkillProgress::default(),
332 crafting: SkillProgress::default(),
333 alchemy: SkillProgress::default(),
334 cartography: SkillProgress::default(),
335 }
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
341pub struct PlayerVitals {
342 pub health: f32,
343 pub health_max: f32,
344 pub mana: f32,
345 pub mana_max: f32,
346 pub stamina: f32,
347 pub stamina_max: f32,
348 #[serde(default = "default_survival_pool_max")]
349 pub hunger: f32,
350 #[serde(default = "default_survival_pool_max")]
351 pub hunger_max: f32,
352 #[serde(default = "default_survival_pool_max")]
353 pub thirst: f32,
354 #[serde(default = "default_survival_pool_max")]
355 pub thirst_max: f32,
356 #[serde(default)]
357 pub coins: u32,
358 #[serde(default)]
359 pub deaths: u32,
360 #[serde(default)]
361 pub life_state: LifeState,
362}
363
364fn default_survival_pool_max() -> f32 {
365 100.0
366}
367
368impl Default for PlayerVitals {
369 fn default() -> Self {
370 Self::from_attributes(PrimaryAttributes::default())
371 }
372}
373
374impl PlayerVitals {
375 pub fn from_attributes(attrs: PrimaryAttributes) -> Self {
382 let vit_d = PrimaryAttributes::display(attrs.vitality) as f32;
383 let int_d = PrimaryAttributes::display(attrs.intelligence) as f32;
384 let wis_d = PrimaryAttributes::display(attrs.wisdom) as f32;
385 let sta_d = PrimaryAttributes::display(attrs.stamina) as f32;
386
387 let health_max = 50.0 + vit_d * 2.0;
388 let stamina_max = 30.0 + sta_d * 1.4;
389 let mana_max = 25.0 + int_d * 1.1 + wis_d * 1.3;
390 let hunger_max = 100.0;
391 let thirst_max = 100.0;
392 Self {
393 health: health_max,
394 health_max,
395 mana: mana_max,
396 mana_max,
397 stamina: stamina_max,
398 stamina_max,
399 hunger: hunger_max,
400 hunger_max,
401 thirst: thirst_max,
402 thirst_max,
403 coins: 0,
404 deaths: 0,
405 life_state: LifeState::Alive,
406 }
407 }
408
409 pub fn legacy_maxima(attrs: PrimaryAttributes) -> (f32, f32, f32) {
411 (
412 attrs.vitality as f32 / 5.0,
413 attrs.stamina as f32 / 5.0,
414 (attrs.intelligence as f32 + attrs.wisdom as f32) / 20.0,
415 )
416 }
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
421#[serde(default)]
422pub struct StoredVitalsState {
423 pub health: f32,
424 pub mana: f32,
425 pub stamina: f32,
426 pub hunger: f32,
427 pub thirst: f32,
428 pub coins: u32,
429 pub deaths: u32,
430 pub life_state: LifeState,
431}
432
433impl StoredVitalsState {
434 pub fn from_live(v: &PlayerVitals) -> Self {
435 Self {
436 health: v.health,
437 mana: v.mana,
438 stamina: v.stamina,
439 hunger: v.hunger,
440 thirst: v.thirst,
441 coins: v.coins,
442 deaths: v.deaths,
443 life_state: v.life_state,
444 }
445 }
446
447 pub fn is_pristine(&self) -> bool {
449 self.health == 0.0
450 && self.mana == 0.0
451 && self.stamina == 0.0
452 && self.hunger == 0.0
453 && self.thirst == 0.0
454 && self.coins == 0
455 && self.deaths == 0
456 && self.life_state == LifeState::Alive
457 }
458
459 pub fn apply_to(self, attrs: PrimaryAttributes) -> PlayerVitals {
460 if self.is_pristine() {
461 return PlayerVitals::from_attributes(attrs);
462 }
463 let fresh = PlayerVitals::from_attributes(attrs);
464 let (legacy_hp, legacy_sta, legacy_mana) = PlayerVitals::legacy_maxima(attrs);
465
466 let scale = |current: f32, legacy_max: f32, new_max: f32| {
467 if legacy_max > 0.0 && new_max > legacy_max * 1.05 && current >= legacy_max * 0.95 {
468 let ratio = (current / legacy_max).clamp(0.0, 1.0);
469 (new_max * ratio).min(new_max)
470 } else {
471 current.min(new_max)
472 }
473 };
474
475 let mut v = fresh;
476 v.health = scale(self.health, legacy_hp, fresh.health_max);
477 v.mana = scale(self.mana, legacy_mana, fresh.mana_max);
478 v.stamina = scale(self.stamina, legacy_sta, fresh.stamina_max);
479 v.hunger = self.hunger.min(v.hunger_max);
480 v.thirst = self.thirst.min(v.thirst_max);
481 v.coins = self.coins;
482 v.deaths = self.deaths;
483 v.life_state = self.life_state;
484 v
485 }
486}
487
488impl Default for StoredVitalsState {
489 fn default() -> Self {
490 Self::from_live(&PlayerVitals::default())
491 }
492}
493
494pub fn humanize_snake_id(id: &str) -> String {
498 id.split('_')
499 .filter(|part| !part.is_empty())
500 .map(|part| {
501 let mut chars = part.chars();
502 match chars.next() {
503 None => String::new(),
504 Some(first) => first.to_uppercase().chain(chars).collect(),
505 }
506 })
507 .collect::<Vec<_>>()
508 .join(" ")
509}
510
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
513pub struct KnownAbility {
514 pub ability_id: String,
515 #[serde(default = "default_known_permanent")]
517 pub permanent: bool,
518 #[serde(default)]
520 pub expires_at_tick: Option<u64>,
521}
522
523fn default_known_permanent() -> bool {
524 true
525}
526
527impl KnownAbility {
528 pub fn permanent(ability_id: impl Into<String>) -> Self {
529 Self {
530 ability_id: ability_id.into(),
531 permanent: true,
532 expires_at_tick: None,
533 }
534 }
535
536 pub fn is_active(&self, tick: u64) -> bool {
537 if self.permanent {
538 return true;
539 }
540 match self.expires_at_tick {
541 Some(exp) => tick < exp,
542 None => false,
543 }
544 }
545}
546
547#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
549pub struct RotationPreset {
550 pub id: String,
551 pub label: String,
552 #[serde(default)]
553 pub abilities: Vec<String>,
554}
555
556impl RotationPreset {
557 pub fn melee_default(ability_id: impl Into<String>) -> Self {
558 let id = ability_id.into();
559 Self {
560 id: "melee".into(),
561 label: "Weapon".into(),
563 abilities: vec![id],
564 }
565 }
566
567 pub fn is_weapon_preset(&self) -> bool {
568 self.id == "melee"
569 }
570}
571
572#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
574pub struct StoredTargetSlot {
575 pub instance_id: Option<String>,
576 #[serde(default)]
577 pub preset_id: Option<String>,
578 #[serde(default)]
579 pub rotation_index: u32,
580 #[serde(default)]
581 pub auto_enabled: bool,
582}
583
584#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
585#[serde(default)]
586pub struct StoredCombatProfile {
587 pub combat_target_instance_id: Option<String>,
589 pub in_combat: bool,
590 pub last_combat_tick: u64,
591 pub last_attack_tick: u64,
592 pub cooldowns_until_tick: BTreeMap<String, u64>,
593 #[serde(default = "default_auto_attack")]
594 pub auto_attack_enabled: bool,
595 #[serde(default)]
597 pub mainhand_template_id: Option<String>,
598 #[serde(default)]
600 pub mainhand_instance_id: Option<Uuid>,
601 #[serde(default)]
603 pub offhand_template_id: Option<String>,
604 #[serde(default)]
606 pub offhand_instance_id: Option<Uuid>,
607 #[serde(default)]
610 pub worn: Vec<(BodySlot, ItemStack)>,
611 #[serde(default)]
613 pub rotation_presets: Vec<RotationPreset>,
614 #[serde(default)]
616 pub target_slots: Vec<StoredTargetSlot>,
617 #[serde(default)]
619 pub known_blueprint_ids: Vec<String>,
620 #[serde(default)]
622 pub keychain: Vec<ItemStack>,
623 #[serde(default)]
625 pub whisper_pouch: Vec<ItemStack>,
626 #[serde(default)]
628 pub known_abilities: Vec<KnownAbility>,
629 #[serde(default)]
631 pub hotbar: Vec<Option<String>>,
632 #[serde(default)]
634 pub abilities_schema_version: u32,
635 #[serde(default)]
637 pub bank_balance_copper: u64,
638}
639
640fn default_auto_attack() -> bool {
641 true
642}
643
644impl Default for StoredCombatProfile {
645 fn default() -> Self {
646 Self {
647 combat_target_instance_id: None,
648 in_combat: false,
649 last_combat_tick: 0,
650 last_attack_tick: 0,
651 cooldowns_until_tick: BTreeMap::new(),
652 auto_attack_enabled: true,
653 mainhand_template_id: None,
654 mainhand_instance_id: None,
655 offhand_template_id: None,
656 offhand_instance_id: None,
657 worn: Vec::new(),
658 rotation_presets: Vec::new(),
659 target_slots: Vec::new(),
660 known_blueprint_ids: Vec::new(),
661 keychain: Vec::new(),
662 whisper_pouch: Vec::new(),
663 known_abilities: Vec::new(),
664 hotbar: Vec::new(),
665 abilities_schema_version: 0,
666 bank_balance_copper: 0,
667 }
668 }
669}
670
671#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
673#[serde(rename_all = "snake_case")]
674pub enum CombatCueKind {
675 Dodge,
676 Block,
677 AttackTelegraph,
678}
679
680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
681pub struct CombatCueView {
682 pub kind: CombatCueKind,
683 pub until_tick: Tick,
685 #[serde(default)]
687 pub start_tick: Tick,
688 #[serde(default)]
690 pub ability_id: Option<String>,
691}
692
693#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
694pub struct EntityState {
695 pub id: EntityId,
696 pub transform: Transform,
697 #[serde(default)]
699 pub label: String,
700 #[serde(default)]
701 pub vitals: Option<PlayerVitals>,
702 #[serde(default)]
704 pub attributes: Option<PrimaryAttributes>,
705 #[serde(default)]
706 pub skills: Option<PlayerSkills>,
707 #[serde(default)]
709 pub inside_building: Option<String>,
710 #[serde(default)]
712 pub tile_id: Option<String>,
713 #[serde(default)]
715 pub paperdoll_ref: Option<String>,
716 #[serde(default)]
718 pub presentation_state: Option<String>,
719 #[serde(default)]
721 pub sprite_mode: Option<String>,
722 #[serde(default)]
724 pub progression_xp: Option<ProgressionXp>,
725 #[serde(default)]
727 pub combat_cues: Vec<CombatCueView>,
728}
729
730#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
732#[serde(rename_all = "snake_case")]
733pub enum ChatChannel {
734 Nearby,
736 Direct,
738 Whisper,
740 WhisperStone,
742}
743
744#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
746#[serde(rename_all = "snake_case")]
747pub enum ChatClarity {
748 #[default]
749 Clear,
750 Partial,
751 Heavy,
752}
753
754#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
755pub struct ChatMessage {
756 pub channel: ChatChannel,
757 pub from_entity: EntityId,
758 pub from_name: String,
759 pub text: String,
761 pub tick: Tick,
762 #[serde(default)]
764 pub to_entity: Option<EntityId>,
765 #[serde(default)]
766 pub clarity: ChatClarity,
767}
768
769#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
771pub enum Intent {
772 Move {
773 entity_id: EntityId,
774 forward: f32,
775 strafe: f32,
776 #[serde(default)]
778 vertical: f32,
779 #[serde(default)]
781 sprint: bool,
782 seq: Seq,
783 },
784 Stop {
785 entity_id: EntityId,
786 seq: Seq,
787 },
788 Harvest {
789 entity_id: EntityId,
790 node_id: String,
791 seq: Seq,
792 },
793 Use {
794 entity_id: EntityId,
795 template_id: String,
796 seq: Seq,
797 },
798 UseGrant {
800 entity_id: EntityId,
801 grant_instance_id: Uuid,
802 target_instance_id: Uuid,
803 seq: Seq,
804 },
805 Say {
806 entity_id: EntityId,
807 channel: ChatChannel,
808 text: String,
809 #[serde(default)]
811 to_entity: Option<EntityId>,
812 seq: Seq,
813 },
814 Craft {
816 entity_id: EntityId,
817 blueprint_id: String,
818 #[serde(default)]
820 count: Option<u32>,
821 seq: Seq,
822 },
823 Interact {
825 entity_id: EntityId,
826 target_id: String,
827 seq: Seq,
828 },
829 ShopBuy {
831 entity_id: EntityId,
832 npc_id: String,
833 offer_id: String,
834 #[serde(default = "default_one")]
835 quantity: u32,
836 seq: Seq,
837 },
838 ShopSell {
840 entity_id: EntityId,
841 npc_id: String,
842 template_id: String,
843 #[serde(default = "default_one")]
844 quantity: u32,
845 seq: Seq,
846 },
847 ShopClose {
849 entity_id: EntityId,
850 npc_id: String,
851 seq: Seq,
852 },
853 TestDamage {
855 entity_id: EntityId,
856 amount: f32,
857 seq: Seq,
858 },
859 SetTarget {
861 entity_id: EntityId,
862 target_id: EntityId,
863 seq: Seq,
864 },
865 SetTargetSlot {
867 entity_id: EntityId,
868 slot_index: u8,
869 target_id: EntityId,
870 seq: Seq,
871 },
872 ClearTarget {
873 entity_id: EntityId,
874 seq: Seq,
875 },
876 ClearTargetSlot {
877 entity_id: EntityId,
878 slot_index: u8,
879 seq: Seq,
880 },
881 SetAutoAttack {
883 entity_id: EntityId,
884 slot_index: u8,
885 enabled: bool,
886 seq: Seq,
887 },
888 Attack {
890 entity_id: EntityId,
891 #[serde(default)]
892 target_id: Option<EntityId>,
893 #[serde(default)]
894 weapon_slot: Option<u32>,
895 seq: Seq,
896 },
897 Pickup {
899 entity_id: EntityId,
900 #[serde(default)]
901 drop_id: Option<String>,
902 seq: Seq,
903 },
904 Cast {
907 entity_id: EntityId,
908 ability_id: String,
909 target_id: EntityId,
910 #[serde(default)]
911 target_point: Option<AimPoint>,
912 seq: Seq,
913 },
914 BindActionSlot {
916 entity_id: EntityId,
917 slot_index: u8,
918 ability_id: String,
919 #[serde(default = "default_auto_attack")]
920 auto_enabled: bool,
921 seq: Seq,
922 },
923 UseActionSlot {
925 entity_id: EntityId,
926 slot_index: u8,
927 seq: Seq,
928 },
929 Dodge {
931 entity_id: EntityId,
932 seq: Seq,
933 },
934 Lunge {
936 entity_id: EntityId,
937 #[serde(default)]
939 forward: f32,
940 #[serde(default)]
942 strafe: f32,
943 seq: Seq,
944 },
945 DirectionalJump {
947 entity_id: EntityId,
948 #[serde(default)]
950 forward: f32,
951 #[serde(default)]
953 strafe: f32,
954 seq: Seq,
955 },
956 Block {
958 entity_id: EntityId,
959 #[serde(default = "default_block_enabled")]
960 enabled: bool,
961 seq: Seq,
962 },
963 EquipMainhand {
967 entity_id: EntityId,
968 #[serde(default)]
969 template_id: Option<String>,
970 #[serde(default)]
971 instance_id: Option<Uuid>,
972 seq: Seq,
973 },
974 EquipOffhand {
976 entity_id: EntityId,
977 #[serde(default)]
978 template_id: Option<String>,
979 #[serde(default)]
980 instance_id: Option<Uuid>,
981 seq: Seq,
982 },
983 EquipWorn {
986 entity_id: EntityId,
987 slot: BodySlot,
988 #[serde(default)]
989 instance_id: Option<Uuid>,
990 seq: Seq,
991 },
992 MoveItem {
994 entity_id: EntityId,
995 item_instance_id: Uuid,
996 from: InventoryLocation,
997 to: InventoryLocation,
998 #[serde(default)]
1000 to_parent_instance_id: Option<Uuid>,
1001 #[serde(default)]
1003 quantity: Option<u32>,
1004 seq: Seq,
1005 },
1006 PlaceContainer {
1008 entity_id: EntityId,
1009 item_instance_id: Uuid,
1010 seq: Seq,
1011 },
1012 PickupContainer {
1014 entity_id: EntityId,
1015 container_id: String,
1016 seq: Seq,
1017 },
1018 MovePlacedContainer {
1020 entity_id: EntityId,
1021 container_id: String,
1022 x: f32,
1023 y: f32,
1024 seq: Seq,
1025 },
1026 SetContainerLocked {
1028 entity_id: EntityId,
1029 location: InventoryLocation,
1031 locked: bool,
1032 seq: Seq,
1033 },
1034 DropItem {
1036 entity_id: EntityId,
1037 item_instance_id: Uuid,
1038 from: InventoryLocation,
1039 seq: Seq,
1040 },
1041 DestroyItem {
1043 entity_id: EntityId,
1044 item_instance_id: Uuid,
1045 from: InventoryLocation,
1046 #[serde(default)]
1048 quantity: Option<u32>,
1049 seq: Seq,
1050 },
1051 RenameContainer {
1053 entity_id: EntityId,
1054 item_instance_id: Uuid,
1055 location: InventoryLocation,
1056 name: String,
1057 seq: Seq,
1058 },
1059 UpsertRotationPreset {
1061 entity_id: EntityId,
1062 preset: RotationPreset,
1063 seq: Seq,
1064 },
1065 DeleteRotationPreset {
1067 entity_id: EntityId,
1068 preset_id: String,
1069 seq: Seq,
1070 },
1071 AssignSlotPreset {
1073 entity_id: EntityId,
1074 slot_index: u8,
1075 preset_id: String,
1076 seq: Seq,
1077 },
1078 SetHotbarSlot {
1081 entity_id: EntityId,
1082 slot: u8,
1084 #[serde(default)]
1086 ability_id: Option<String>,
1087 seq: Seq,
1088 },
1089 AdvanceRotation {
1091 entity_id: EntityId,
1092 slot_index: u8,
1093 seq: Seq,
1094 },
1095 NpcTalkOpen {
1097 entity_id: EntityId,
1098 npc_id: String,
1099 seq: Seq,
1100 },
1101 NpcTalkSay {
1103 entity_id: EntityId,
1104 npc_id: String,
1105 message: String,
1106 seq: Seq,
1107 },
1108 NpcTalkClose {
1110 entity_id: EntityId,
1111 npc_id: String,
1112 seq: Seq,
1113 },
1114 AcceptQuest {
1116 entity_id: EntityId,
1117 quest_id: String,
1118 seq: Seq,
1119 },
1120 WithdrawQuest {
1122 entity_id: EntityId,
1123 quest_id: String,
1124 seq: Seq,
1125 },
1126 TrackQuest {
1128 entity_id: EntityId,
1129 quest_id: String,
1130 seq: Seq,
1131 },
1132 QuestGiveItem {
1134 entity_id: EntityId,
1135 npc_id: String,
1136 template_id: String,
1137 #[serde(default = "default_one")]
1138 quantity: u32,
1139 seq: Seq,
1140 },
1141 HireWorker {
1143 entity_id: EntityId,
1144 def_id: String,
1145 wage_copper_per_interval: u32,
1146 #[serde(default)]
1147 lodging_container_id: Option<String>,
1148 #[serde(default)]
1149 job_yaml: Option<String>,
1150 seq: Seq,
1151 },
1152 DismissWorker {
1154 entity_id: EntityId,
1155 worker_instance_id: String,
1156 seq: Seq,
1157 },
1158 SetWorkerJob {
1160 entity_id: EntityId,
1161 worker_instance_id: String,
1162 job_yaml: String,
1163 seq: Seq,
1164 },
1165 AssignWorkerLodging {
1167 entity_id: EntityId,
1168 worker_instance_id: String,
1169 lodging_container_id: String,
1170 seq: Seq,
1171 },
1172 SetWorkerMode {
1174 entity_id: EntityId,
1175 worker_instance_id: String,
1176 mode: String,
1177 seq: Seq,
1178 },
1179 GiveWorkerItem {
1182 entity_id: EntityId,
1183 worker_instance_id: String,
1184 item_instance_id: uuid::Uuid,
1185 #[serde(default)]
1186 quantity: Option<u32>,
1187 seq: Seq,
1188 },
1189 TakeWorkerItem {
1191 entity_id: EntityId,
1192 worker_instance_id: String,
1193 item_instance_id: uuid::Uuid,
1194 #[serde(default)]
1195 quantity: Option<u32>,
1196 seq: Seq,
1197 },
1198 RenameHiredWorker {
1200 entity_id: EntityId,
1201 worker_instance_id: String,
1202 name: String,
1203 seq: Seq,
1204 },
1205 TeachWorkerBlueprint {
1207 entity_id: EntityId,
1208 worker_instance_id: String,
1209 blueprint_id: String,
1210 seq: Seq,
1211 },
1212 AttendHiredWorker {
1214 entity_id: EntityId,
1215 worker_instance_id: String,
1216 attending: bool,
1217 seq: Seq,
1218 },
1219 BuyPlot {
1221 entity_id: EntityId,
1222 zone_id: String,
1223 x0: f32,
1224 y0: f32,
1225 x1: f32,
1226 y1: f32,
1227 seq: Seq,
1228 },
1229 BuyPlotAllFree {
1231 entity_id: EntityId,
1232 zone_id: String,
1233 seq: Seq,
1234 },
1235 SellPlotToCrown {
1237 entity_id: EntityId,
1238 plot_id: Uuid,
1239 seq: Seq,
1240 },
1241 Cultivate {
1243 entity_id: EntityId,
1244 x: f32,
1246 y: f32,
1247 seq: Seq,
1248 },
1249 PlantSeeds {
1251 entity_id: EntityId,
1252 seed_template_id: String,
1253 quantity: u32,
1254 seq: Seq,
1255 },
1256 SetPlotFarmPublic {
1258 entity_id: EntityId,
1259 plot_id: Uuid,
1260 public: bool,
1261 #[serde(default)]
1262 public_tax_discount_bps: u32,
1263 seq: Seq,
1264 },
1265 PlotFarmAllowUpsert {
1267 entity_id: EntityId,
1268 plot_id: Uuid,
1269 #[serde(default)]
1271 character_id: Option<Uuid>,
1272 #[serde(default)]
1274 character_name: String,
1275 #[serde(default)]
1276 tax_discount_bps: u32,
1277 seq: Seq,
1278 },
1279 PlotFarmAllowRemove {
1281 entity_id: EntityId,
1282 plot_id: Uuid,
1283 character_id: Uuid,
1284 seq: Seq,
1285 },
1286 BankDeposit {
1288 entity_id: EntityId,
1289 npc_id: String,
1290 #[serde(default)]
1292 amount_copper: u64,
1293 seq: Seq,
1294 },
1295 BankWithdraw {
1297 entity_id: EntityId,
1298 npc_id: String,
1299 #[serde(default)]
1301 amount_copper: u64,
1302 seq: Seq,
1303 },
1304 BankClose {
1306 entity_id: EntityId,
1307 npc_id: String,
1308 seq: Seq,
1309 },
1310 BankTransfer {
1312 entity_id: EntityId,
1313 npc_id: String,
1314 #[serde(default)]
1316 to_character_id: Option<Uuid>,
1317 #[serde(default)]
1319 to_name: String,
1320 amount_copper: u64,
1322 seq: Seq,
1323 },
1324 StorageStore {
1326 entity_id: EntityId,
1327 npc_id: String,
1328 item_instance_id: Uuid,
1329 #[serde(default)]
1330 quantity: Option<u32>,
1331 seq: Seq,
1332 },
1333 StorageTake {
1335 entity_id: EntityId,
1336 npc_id: String,
1337 item_instance_id: Uuid,
1338 #[serde(default)]
1339 quantity: Option<u32>,
1340 seq: Seq,
1341 },
1342 StorageShip {
1344 entity_id: EntityId,
1345 npc_id: String,
1346 dest_building_id: String,
1347 item_instance_id: Uuid,
1348 #[serde(default)]
1349 quantity: Option<u32>,
1350 seq: Seq,
1351 },
1352 StorageClose {
1354 entity_id: EntityId,
1355 npc_id: String,
1356 seq: Seq,
1357 },
1358 MarketList {
1361 entity_id: EntityId,
1362 npc_id: String,
1363 source: GoodsLocation,
1364 item_instance_id: Uuid,
1365 #[serde(default)]
1366 quantity: Option<u32>,
1367 unit_price_copper: u64,
1368 seq: Seq,
1369 },
1370 MarketReprice {
1372 entity_id: EntityId,
1373 npc_id: String,
1374 listing_id: Uuid,
1375 unit_price_copper: u64,
1376 seq: Seq,
1377 },
1378 MarketDelist {
1380 entity_id: EntityId,
1381 npc_id: String,
1382 listing_id: Uuid,
1383 dest: GoodsLocation,
1384 seq: Seq,
1385 },
1386 MarketBuy {
1388 entity_id: EntityId,
1389 npc_id: String,
1390 listing_id: Uuid,
1391 #[serde(default = "default_one")]
1392 quantity: u32,
1393 dest: GoodsLocation,
1394 seq: Seq,
1395 },
1396 MarketClose {
1398 entity_id: EntityId,
1399 npc_id: String,
1400 seq: Seq,
1401 },
1402 TradeRequest {
1404 entity_id: EntityId,
1405 peer_entity_id: EntityId,
1406 seq: Seq,
1407 },
1408 TradeRespond {
1410 entity_id: EntityId,
1411 peer_entity_id: EntityId,
1412 accept: bool,
1413 seq: Seq,
1414 },
1415 TradePresent {
1417 entity_id: EntityId,
1418 item_instance_id: Uuid,
1419 #[serde(default)]
1420 quantity: Option<u32>,
1421 seq: Seq,
1422 },
1423 TradeUnpresent {
1425 entity_id: EntityId,
1426 item_instance_id: Uuid,
1427 seq: Seq,
1428 },
1429 TradeSetReady {
1431 entity_id: EntityId,
1432 ready: bool,
1433 seq: Seq,
1434 },
1435 TradeCancel {
1437 entity_id: EntityId,
1438 seq: Seq,
1439 },
1440 DestroyWhisperStone {
1442 entity_id: EntityId,
1443 item_instance_id: Uuid,
1444 seq: Seq,
1445 },
1446 StowWhisperStone {
1448 entity_id: EntityId,
1449 item_instance_id: Uuid,
1450 seq: Seq,
1451 },
1452}
1453
1454fn default_block_enabled() -> bool {
1455 true
1456}
1457
1458#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1460pub struct StatusEffectHud {
1461 pub effect_id: String,
1462 pub label: String,
1463 #[serde(default)]
1464 pub polarity: String,
1465 #[serde(default)]
1466 pub icon_tile_id: Option<String>,
1467 #[serde(default)]
1469 pub remaining_sec: Option<f32>,
1470}
1471
1472#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1474pub struct CombatTargetHud {
1475 pub entity_id: EntityId,
1476 #[serde(default)]
1477 pub label: String,
1478 #[serde(default)]
1479 pub level: u32,
1480 pub health: f32,
1481 pub health_max: f32,
1482 #[serde(default)]
1483 pub life_state: LifeState,
1484 #[serde(default)]
1485 pub distance_m: f32,
1486 #[serde(default)]
1487 pub statuses: Vec<StatusEffectHud>,
1488}
1489
1490#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1492#[serde(rename_all = "snake_case")]
1493pub enum TimedChannelKind {
1494 #[default]
1495 Cultivate,
1496 Plant,
1497 Harvest,
1498}
1499
1500#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1502pub struct TimedChannelHud {
1503 #[serde(default)]
1504 pub label: String,
1505 #[serde(default)]
1506 pub channel: TimedChannelKind,
1507 #[serde(default)]
1508 pub cell_x: i32,
1509 #[serde(default)]
1510 pub cell_y: i32,
1511 #[serde(default)]
1512 pub ticks_remaining: u64,
1513 #[serde(default)]
1514 pub ticks_total: u64,
1515}
1516
1517#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1519pub struct CastProgressHud {
1520 #[serde(default)]
1521 pub ability_id: String,
1522 #[serde(default)]
1523 pub ability_label: String,
1524 #[serde(default)]
1525 pub ticks_remaining: u64,
1526 #[serde(default)]
1527 pub ticks_total: u64,
1528}
1529
1530#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1532pub struct AbilityCooldownHud {
1533 #[serde(default)]
1534 pub ability_id: String,
1535 #[serde(default)]
1536 pub label: String,
1537 #[serde(default)]
1538 pub cd_ticks: u64,
1539 #[serde(default)]
1540 pub cd_total_ticks: u64,
1541}
1542
1543#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1545pub struct CombatSlotHud {
1546 pub slot_index: u8,
1547 #[serde(default)]
1548 pub target_entity_id: Option<EntityId>,
1549 #[serde(default)]
1550 pub target_label: Option<String>,
1551 #[serde(default)]
1552 pub target: Option<CombatTargetHud>,
1553 #[serde(default)]
1554 pub preset_id: Option<String>,
1555 #[serde(default)]
1556 pub preset_label: Option<String>,
1557 #[serde(default)]
1558 pub rotation: Vec<String>,
1559 #[serde(default)]
1560 pub rotation_index: u32,
1561 #[serde(default)]
1562 pub next_ability_id: Option<String>,
1563 #[serde(default)]
1564 pub auto_enabled: bool,
1565}
1566
1567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1569pub struct DefensePieceHud {
1570 pub slot: BodySlot,
1571 pub label: String,
1572 pub template_id: String,
1573 #[serde(default)]
1574 pub armor_physical: f32,
1575 #[serde(default)]
1576 pub resists: Vec<(String, f32)>,
1577}
1578
1579impl Default for DefensePieceHud {
1580 fn default() -> Self {
1581 Self {
1582 slot: BodySlot::Head,
1583 label: String::new(),
1584 template_id: String::new(),
1585 armor_physical: 0.0,
1586 resists: Vec::new(),
1587 }
1588 }
1589}
1590
1591#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1593pub struct DefenseHud {
1594 pub armor_physical: f32,
1595 pub vitality_contribution: f32,
1596 pub total_mitigation_rating: f32,
1597 pub estimated_physical_dr: f32,
1599 #[serde(default)]
1600 pub resists: Vec<(String, f32)>,
1601 #[serde(default)]
1602 pub pieces: Vec<DefensePieceHud>,
1603}
1604
1605#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1607pub struct CombatHud {
1608 pub in_combat: bool,
1609 pub auto_attack: bool,
1611 pub has_los: bool,
1612 pub attack_cd_ticks: u64,
1613 #[serde(default)]
1614 pub ability_id: String,
1615 #[serde(default)]
1616 pub target_entity_id: Option<EntityId>,
1617 #[serde(default)]
1618 pub target_label: Option<String>,
1619 #[serde(default)]
1620 pub max_target_slots: u8,
1621 #[serde(default)]
1622 pub slots: Vec<CombatSlotHud>,
1623 #[serde(default)]
1624 pub rotation_presets: Vec<RotationPreset>,
1625 #[serde(default)]
1626 pub gcd_ticks: u64,
1627 #[serde(default)]
1628 pub mainhand_template_id: Option<String>,
1629 #[serde(default)]
1630 pub mainhand_label: Option<String>,
1631 #[serde(default)]
1632 pub offhand_template_id: Option<String>,
1633 #[serde(default)]
1634 pub offhand_label: Option<String>,
1635 #[serde(default)]
1637 pub mainhand_hand_slots: u8,
1638 #[serde(default)]
1640 pub worn: Vec<(BodySlot, ItemStack)>,
1641 #[serde(default)]
1643 pub defense: Option<DefenseHud>,
1644 #[serde(default)]
1645 pub carry_mass: f32,
1646 #[serde(default)]
1647 pub carry_mass_max: f32,
1648 #[serde(default)]
1649 pub encumbrance: EncumbranceState,
1650 #[serde(default)]
1652 pub keychain: Vec<ItemStack>,
1653 #[serde(default)]
1655 pub whisper_pouch: Vec<ItemStack>,
1656 #[serde(default)]
1657 pub target: Option<CombatTargetHud>,
1658 #[serde(default)]
1659 pub cast: Option<CastProgressHud>,
1660 #[serde(default)]
1662 pub timed_channel: Option<TimedChannelHud>,
1663 #[serde(default)]
1664 pub ability_cooldowns: Vec<AbilityCooldownHud>,
1665 #[serde(default)]
1666 pub blocking_active: bool,
1667 #[serde(default)]
1669 pub progression_xp: Option<ProgressionXp>,
1670 #[serde(default)]
1671 pub progression_baseline: u16,
1672 #[serde(default)]
1673 pub progression_xp_base: f64,
1674 #[serde(default)]
1675 pub progression_xp_growth: f64,
1676 #[serde(default)]
1677 pub attributes: Option<PrimaryAttributes>,
1678 #[serde(default)]
1679 pub skills: Option<PlayerSkills>,
1680 #[serde(default)]
1682 pub statuses: Vec<StatusEffectHud>,
1683 #[serde(default)]
1685 pub known_abilities: Vec<String>,
1686 #[serde(default)]
1688 pub ability_meta: Vec<AbilityMetaHud>,
1689 #[serde(default)]
1691 pub ability_mastery: Vec<AbilityMasteryHud>,
1692 #[serde(default)]
1695 pub hotbar: Vec<Option<String>>,
1696 #[serde(default)]
1698 pub max_abilities_per_rotation: u8,
1699}
1700
1701#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1703pub struct AbilityMetaHud {
1704 pub id: String,
1705 #[serde(default = "default_aim_mode_entity")]
1707 pub aim_mode: String,
1708 #[serde(default)]
1709 pub blast_radius_m: f32,
1710 #[serde(default)]
1711 pub allows_self: bool,
1712 #[serde(default)]
1713 pub is_heal: bool,
1714 #[serde(default = "default_auto_rotation_eligible")]
1717 pub auto_rotation_eligible: bool,
1718}
1719
1720fn default_auto_rotation_eligible() -> bool {
1721 true
1722}
1723
1724fn default_aim_mode_entity() -> String {
1725 "entity".into()
1726}
1727
1728pub const HOTBAR_ITEM_PREFIX: &str = "item:";
1730
1731pub fn hotbar_consumable_binding(template_id: &str) -> String {
1733 format!("{HOTBAR_ITEM_PREFIX}{}", template_id.trim())
1734}
1735
1736pub fn hotbar_consumable_template(binding: &str) -> Option<&str> {
1738 binding
1739 .strip_prefix(HOTBAR_ITEM_PREFIX)
1740 .map(str::trim)
1741 .filter(|id| !id.is_empty())
1742}
1743
1744pub fn hotbar_binding_is_consumable(binding: &str) -> bool {
1746 hotbar_consumable_template(binding).is_some()
1747}
1748
1749#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1751#[serde(rename_all = "snake_case")]
1752pub enum CombatFxKind {
1753 MeleeArc,
1754 Cone,
1755 Sphere,
1756 Beam,
1757 HitMarker,
1758}
1759
1760#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1762#[serde(rename_all = "snake_case")]
1763pub enum CombatFxHitOutcome {
1764 #[default]
1765 Hit,
1766 Blocked,
1767 Miss,
1768 Glance,
1769}
1770
1771#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1773pub struct CombatFxHit {
1774 pub entity_id: EntityId,
1775 pub x: f32,
1776 pub y: f32,
1777 pub z: f32,
1778 #[serde(default)]
1779 pub outcome: CombatFxHitOutcome,
1780}
1781
1782#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1784pub struct CombatFx {
1785 pub id: u64,
1786 pub kind: CombatFxKind,
1787 pub ability_id: String,
1788 pub caster_id: EntityId,
1789 pub origin_x: f32,
1790 pub origin_y: f32,
1791 pub origin_z: f32,
1792 #[serde(default)]
1793 pub end_x: Option<f32>,
1794 #[serde(default)]
1795 pub end_y: Option<f32>,
1796 #[serde(default)]
1797 pub end_z: Option<f32>,
1798 #[serde(default)]
1799 pub yaw: Option<f32>,
1800 #[serde(default)]
1801 pub reach_m: Option<f32>,
1802 #[serde(default)]
1803 pub arc_deg: Option<f32>,
1804 #[serde(default)]
1805 pub radius_m: Option<f32>,
1806 #[serde(default)]
1807 pub hits: Vec<CombatFxHit>,
1808 pub until_tick: u64,
1810 #[serde(default)]
1811 pub damage_type: String,
1812}
1813
1814#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1816#[serde(rename_all = "snake_case")]
1817pub enum WorkerModeView {
1818 Companion,
1819 JobLoop,
1820 Idle,
1823}
1824
1825#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1827#[serde(rename_all = "snake_case")]
1828pub enum WorkerStateView {
1829 Idle,
1830 Traveling,
1831 Working,
1832 Resting,
1833 Waiting,
1834 Strike,
1835 Dismissed,
1836}
1837
1838#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
1840pub struct WorkerVitalsSummary {
1841 pub health_pct: f32,
1842 pub stamina_pct: f32,
1843}
1844
1845#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1847#[serde(rename_all = "snake_case")]
1848pub enum WorkerRouteKindView {
1849 #[default]
1850 HarvestLoop,
1851 Ordered,
1852}
1853
1854#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1856pub struct WorkerRouteView {
1857 #[serde(default)]
1858 pub kind: WorkerRouteKindView,
1859 #[serde(default)]
1860 pub lodging_container_id: Option<String>,
1861 #[serde(default)]
1863 pub outbound_waypoints: Vec<WorkerRouteWaypointView>,
1864 #[serde(default)]
1866 pub harvest_nodes: Vec<String>,
1867 #[serde(default = "default_route_carry_ratio")]
1868 pub carry_return_ratio: f32,
1869 #[serde(default)]
1871 pub stops: Vec<WorkerRouteStopView>,
1872}
1873
1874fn default_route_carry_ratio() -> f32 {
1875 0.90
1876}
1877
1878fn default_true_view() -> bool {
1879 true
1880}
1881
1882#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1884pub struct WorkerWithdrawItemView {
1885 pub template: String,
1886 #[serde(default)]
1888 pub qty: u32,
1889 #[serde(default)]
1891 pub all: bool,
1892}
1893
1894#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1895pub struct WorkerRouteWaypointView {
1896 pub x: f32,
1897 pub y: f32,
1898 pub z: f32,
1899}
1900
1901#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1910#[serde(rename_all = "snake_case")]
1911pub enum WorkerRouteStopView {
1912 Waypoint {
1913 x: f32,
1914 y: f32,
1915 #[serde(default)]
1916 z: f32,
1917 },
1918 HarvestNode {
1919 node_id: String,
1920 },
1921 DepositAt {
1922 container_id: String,
1923 #[serde(default)]
1924 filter: Option<Vec<String>>,
1925 },
1926 TradeWith {
1927 #[serde(default)]
1928 npc_id: Option<String>,
1929 template: String,
1930 #[serde(default = "default_true_view")]
1931 sell_all: bool,
1932 },
1933 WithdrawFrom {
1934 container_id: String,
1935 items: Vec<WorkerWithdrawItemView>,
1936 },
1937 CraftAt {
1938 device: String,
1939 blueprint: String,
1940 #[serde(default)]
1941 qty: Option<u32>,
1942 },
1943 CultivatePlot {
1944 plot_id: uuid::Uuid,
1945 },
1946 PlantPlot {
1947 plot_id: uuid::Uuid,
1948 seed_template: String,
1949 },
1950 HarvestPlot {
1951 plot_id: uuid::Uuid,
1952 },
1953 RestIfNeeded,
1954 Wait {
1955 #[serde(default)]
1956 wait_ticks: u64,
1957 },
1958}
1959
1960#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1962#[serde(rename_all = "snake_case")]
1963pub enum LedgerCategory {
1964 Workers,
1965 Hire,
1966 Train,
1967 ShopBuy,
1968 Taxes,
1969 WorkerSales,
1970 TraderSales,
1971 BankDeposit,
1972 BankWithdraw,
1973 BankTransferOut,
1974 BankTransferIn,
1975 BankTransferFee,
1976 StorageShipFee,
1977 PropertyBuy,
1979 PropertySell,
1981 TaxShare,
1983 MarketBuy,
1985 MarketSell,
1987 Other,
1988}
1989
1990impl LedgerCategory {
1991 pub fn as_str(self) -> &'static str {
1992 match self {
1993 Self::Workers => "workers",
1994 Self::Hire => "hire",
1995 Self::Train => "train",
1996 Self::ShopBuy => "shop_buy",
1997 Self::Taxes => "taxes",
1998 Self::WorkerSales => "worker_sales",
1999 Self::TraderSales => "trader_sales",
2000 Self::BankDeposit => "bank_deposit",
2001 Self::BankWithdraw => "bank_withdraw",
2002 Self::BankTransferOut => "bank_transfer_out",
2003 Self::BankTransferIn => "bank_transfer_in",
2004 Self::BankTransferFee => "bank_transfer_fee",
2005 Self::StorageShipFee => "storage_ship_fee",
2006 Self::PropertyBuy => "property_buy",
2007 Self::PropertySell => "property_sell",
2008 Self::TaxShare => "tax_share",
2009 Self::MarketBuy => "market_buy",
2010 Self::MarketSell => "market_sell",
2011 Self::Other => "other",
2012 }
2013 }
2014}
2015
2016#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2017pub struct LedgerEntryView {
2018 pub id: uuid::Uuid,
2019 pub game_day: u64,
2020 pub signed_copper: i64,
2021 pub category: LedgerCategory,
2022 #[serde(default)]
2023 pub label: String,
2024}
2025
2026#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2027pub struct LedgerPeriodTotals {
2028 #[serde(default)]
2030 pub expenses: std::collections::HashMap<String, u64>,
2031 #[serde(default)]
2033 pub income: std::collections::HashMap<String, u64>,
2034 pub expense_copper: u64,
2035 pub income_copper: u64,
2036 pub cash_flow_copper: i64,
2038}
2039
2040#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2041pub struct PlayerLedgerView {
2042 pub current_game_day: u64,
2043 #[serde(default)]
2044 pub period_day: LedgerPeriodTotals,
2045 #[serde(default)]
2046 pub period_week: LedgerPeriodTotals,
2047 #[serde(default)]
2048 pub period_month: LedgerPeriodTotals,
2049 #[serde(default)]
2050 pub period_lifetime: LedgerPeriodTotals,
2051 #[serde(default)]
2052 pub recent: Vec<LedgerEntryView>,
2053 #[serde(default)]
2055 pub wealth_on_person_copper: u64,
2056 #[serde(default)]
2058 pub wealth_in_storage_copper: u64,
2059 #[serde(default)]
2061 pub wealth_in_bank_copper: u64,
2062 #[serde(default)]
2064 pub wealth_total_copper: u64,
2065 #[serde(default)]
2067 pub wealth_in_property_copper: u64,
2068 #[serde(default)]
2070 pub wealth_net_worth_copper: u64,
2071 #[serde(default)]
2073 pub property_assets: Vec<PropertyAssetView>,
2074 #[serde(default)]
2076 pub property_market_nearby: Vec<PropertyMarketCompView>,
2077 #[serde(default)]
2079 pub live_expense_per_interval_copper: u64,
2080 #[serde(default)]
2082 pub live_income_route_est_per_loop_copper: u64,
2083 #[serde(default)]
2085 pub live_income_avg_per_interval_copper: u64,
2086 #[serde(default)]
2088 pub live_income_avg_window_intervals: u32,
2089 #[serde(default)]
2091 pub live_net_avg_per_interval_copper: i64,
2092}
2093
2094#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2096pub struct PropertyAssetView {
2097 pub plot_id: Uuid,
2098 pub label: String,
2100 pub zone_id: String,
2101 #[serde(default)]
2102 pub zone_label: Option<String>,
2103 pub area_m2: f32,
2104 pub purchase_basis_copper: u64,
2106 pub upkeep_copper_per_day: u64,
2107}
2108
2109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2111pub struct PropertyMarketCompView {
2112 pub day: u64,
2113 pub zone_id: String,
2114 #[serde(default)]
2115 pub zone_label: Option<String>,
2116 pub area_m2: f32,
2117 pub price_copper: u64,
2118 pub price_per_m2_copper: u64,
2120 pub kind: String,
2122 pub distance_m: f32,
2124}
2125
2126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2128#[serde(rename_all = "snake_case")]
2129pub enum AnalyticsMetric {
2130 NpcKill,
2131 WildlifeKill,
2132 Harvest,
2133 QuestComplete,
2134 QuestAccept,
2135 QuestAbandon,
2136 PlayerDeath,
2137 Craft,
2138 WorkerHire,
2139 WorkerDismiss,
2140 WorkerTeach,
2141 NpcTalk,
2142 ShopBuy,
2143 ShopSell,
2144 PlaceContainer,
2145 PickupContainer,
2146 PickupDrop,
2147 ConsumableUse,
2148 AbilityUse,
2149 DistanceWalkedM,
2150 DoorUse,
2151 BuildingEnter,
2152}
2153
2154impl AnalyticsMetric {
2155 pub fn as_str(self) -> &'static str {
2156 match self {
2157 Self::NpcKill => "npc_kill",
2158 Self::WildlifeKill => "wildlife_kill",
2159 Self::Harvest => "harvest",
2160 Self::QuestComplete => "quest_complete",
2161 Self::QuestAccept => "quest_accept",
2162 Self::QuestAbandon => "quest_abandon",
2163 Self::PlayerDeath => "player_death",
2164 Self::Craft => "craft",
2165 Self::WorkerHire => "worker_hire",
2166 Self::WorkerDismiss => "worker_dismiss",
2167 Self::WorkerTeach => "worker_teach",
2168 Self::NpcTalk => "npc_talk",
2169 Self::ShopBuy => "shop_buy",
2170 Self::ShopSell => "shop_sell",
2171 Self::PlaceContainer => "place_container",
2172 Self::PickupContainer => "pickup_container",
2173 Self::PickupDrop => "pickup_drop",
2174 Self::ConsumableUse => "consumable_use",
2175 Self::AbilityUse => "ability_use",
2176 Self::DistanceWalkedM => "distance_walked_m",
2177 Self::DoorUse => "door_use",
2178 Self::BuildingEnter => "building_enter",
2179 }
2180 }
2181
2182 pub fn from_str_key(s: &str) -> Option<Self> {
2183 Some(match s {
2184 "npc_kill" => Self::NpcKill,
2185 "wildlife_kill" => Self::WildlifeKill,
2186 "harvest" => Self::Harvest,
2187 "quest_complete" => Self::QuestComplete,
2188 "quest_accept" => Self::QuestAccept,
2189 "quest_abandon" => Self::QuestAbandon,
2190 "player_death" => Self::PlayerDeath,
2191 "craft" => Self::Craft,
2192 "worker_hire" => Self::WorkerHire,
2193 "worker_dismiss" => Self::WorkerDismiss,
2194 "worker_teach" => Self::WorkerTeach,
2195 "npc_talk" => Self::NpcTalk,
2196 "shop_buy" => Self::ShopBuy,
2197 "shop_sell" => Self::ShopSell,
2198 "place_container" => Self::PlaceContainer,
2199 "pickup_container" => Self::PickupContainer,
2200 "pickup_drop" => Self::PickupDrop,
2201 "consumable_use" => Self::ConsumableUse,
2202 "ability_use" => Self::AbilityUse,
2203 "distance_walked_m" => Self::DistanceWalkedM,
2204 "door_use" => Self::DoorUse,
2205 "building_enter" => Self::BuildingEnter,
2206 _ => return None,
2207 })
2208 }
2209}
2210
2211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2212pub struct CareerMetricRow {
2213 pub subject_id: String,
2214 pub amount: u64,
2215}
2216
2217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2219pub struct PlayerCareerView {
2220 pub current_game_day: u64,
2221 #[serde(default)]
2222 pub kills: Vec<CareerMetricRow>,
2223 #[serde(default)]
2224 pub harvests: Vec<CareerMetricRow>,
2225 pub quests_completed: u64,
2226 #[serde(default)]
2227 pub crafts: Vec<CareerMetricRow>,
2228 pub deaths: u64,
2229 pub npc_talks: u64,
2230 pub shop_buys: u64,
2231 pub shop_sells: u64,
2232 pub distance_m: u64,
2233 #[serde(default)]
2234 pub other: Vec<CareerMetricRow>,
2235}
2236
2237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2239pub struct HiredWorkerView {
2240 pub instance_id: String,
2241 pub entity_id: EntityId,
2242 pub def_id: String,
2243 pub label: String,
2245 pub x: f32,
2246 pub y: f32,
2247 pub z: f32,
2248 pub mode: WorkerModeView,
2249 pub state: WorkerStateView,
2250 #[serde(default)]
2251 pub step_label: String,
2252 pub vitals: WorkerVitalsSummary,
2253 #[serde(default)]
2254 pub carry_pct: f32,
2255 #[serde(default)]
2256 pub last_error: Option<String>,
2257 pub wage_copper_per_interval: u32,
2258 #[serde(default)]
2260 pub effective_wage_copper: u32,
2261 #[serde(default)]
2263 pub wage_meters_walked: f32,
2264 #[serde(default)]
2266 pub lodging_container_id: Option<String>,
2267 #[serde(default)]
2269 pub route: Option<WorkerRouteView>,
2270 #[serde(default)]
2273 pub route_stop_index: Option<u32>,
2274 #[serde(default)]
2276 pub known_blueprint_ids: Vec<String>,
2277 #[serde(default = "default_worker_view_level")]
2279 pub level: u32,
2280 #[serde(default)]
2282 pub worker_xp: f64,
2283 #[serde(default)]
2285 pub inventory: Vec<ItemStack>,
2286}
2287
2288fn default_worker_view_level() -> u32 {
2289 1
2290}
2291
2292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2294pub struct TickDelta {
2295 pub tick: Tick,
2296 pub entities: Vec<EntityState>,
2297 #[serde(default)]
2298 pub resource_nodes: Vec<ResourceNodeView>,
2299 #[serde(default)]
2300 pub buildings: Vec<BuildingView>,
2301 #[serde(default)]
2302 pub doors: Vec<DoorView>,
2303 #[serde(default)]
2304 pub npcs: Vec<NpcView>,
2305 #[serde(default)]
2307 pub inventory: Vec<ItemStack>,
2308 #[serde(default)]
2309 pub blueprints: Vec<BlueprintView>,
2310 #[serde(default)]
2311 pub world_clock: WorldClock,
2312 #[serde(default)]
2313 pub ground_drops: Vec<GroundDropView>,
2314 #[serde(default)]
2315 pub placed_containers: Vec<PlacedContainerView>,
2316 #[serde(default)]
2317 pub combat: Option<CombatHud>,
2318 #[serde(default)]
2319 pub interior_map: Option<InteriorMapView>,
2320 #[serde(default)]
2321 pub quest_log: Vec<QuestLogEntry>,
2322 #[serde(default)]
2323 pub hired_workers: Vec<HiredWorkerView>,
2324 #[serde(default)]
2325 pub interactables: Vec<InteractableView>,
2326 #[serde(default)]
2327 pub ledger: Option<PlayerLedgerView>,
2328 #[serde(default)]
2329 pub career: Option<PlayerCareerView>,
2330 #[serde(default)]
2332 pub combat_fx: Vec<CombatFx>,
2333 #[serde(default)]
2335 pub property_plots: Vec<PropertyPlotView>,
2336 #[serde(default)]
2338 pub terrain_overlays: Vec<TerrainZoneView>,
2339}
2340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2341pub struct GroundDropView {
2342 pub id: String,
2343 pub template_id: String,
2344 pub quantity: u32,
2345 pub x: f32,
2346 pub y: f32,
2347 pub z: f32,
2348 #[serde(default)]
2350 pub tile_id: Option<String>,
2351 #[serde(default)]
2353 pub display_name: Option<String>,
2354 #[serde(default)]
2356 pub yaw: f32,
2357 #[serde(default)]
2359 pub pitch: f32,
2360 #[serde(default)]
2362 pub roll: f32,
2363 #[serde(default = "default_draw_scale")]
2365 pub draw_scale: f32,
2366}
2367
2368fn default_draw_scale() -> f32 {
2369 1.0
2370}
2371
2372#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2374pub struct Snapshot {
2375 pub tick: Tick,
2376 pub chunk_rev: u64,
2377 #[serde(default)]
2379 pub content_rev: u64,
2380 #[serde(default)]
2382 pub publish_rev: u64,
2383 pub entities: Vec<EntityState>,
2384 #[serde(default)]
2385 pub resource_nodes: Vec<ResourceNodeView>,
2386 #[serde(default)]
2388 pub world_x0: f32,
2389 #[serde(default)]
2390 pub world_y0: f32,
2391 #[serde(default)]
2393 pub world_width_m: f32,
2394 #[serde(default)]
2395 pub world_height_m: f32,
2396 #[serde(default)]
2397 pub buildings: Vec<BuildingView>,
2398 #[serde(default)]
2399 pub doors: Vec<DoorView>,
2400 #[serde(default)]
2401 pub npcs: Vec<NpcView>,
2402 #[serde(default)]
2403 pub inventory: Vec<ItemStack>,
2404 #[serde(default)]
2405 pub blueprints: Vec<BlueprintView>,
2406 #[serde(default)]
2407 pub world_clock: WorldClock,
2408 #[serde(default)]
2409 pub terrain_zones: Vec<TerrainZoneView>,
2410 #[serde(default)]
2411 pub z_platforms: Vec<ZPlatformView>,
2412 #[serde(default)]
2413 pub z_transitions: Vec<ZTransitionView>,
2414 #[serde(default)]
2415 pub ground_drops: Vec<GroundDropView>,
2416 #[serde(default)]
2417 pub placed_containers: Vec<PlacedContainerView>,
2418 #[serde(default)]
2419 pub combat: Option<CombatHud>,
2420 #[serde(default)]
2421 pub interior_map: Option<InteriorMapView>,
2422 #[serde(default)]
2423 pub quest_log: Vec<QuestLogEntry>,
2424 #[serde(default)]
2425 pub hired_workers: Vec<HiredWorkerView>,
2426 #[serde(default)]
2427 pub interactables: Vec<InteractableView>,
2428 #[serde(default)]
2429 pub ledger: Option<PlayerLedgerView>,
2430 #[serde(default)]
2431 pub career: Option<PlayerCareerView>,
2432 #[serde(default)]
2434 pub combat_fx: Vec<CombatFx>,
2435 #[serde(default)]
2437 pub property_zones: Vec<PropertyZoneView>,
2438 #[serde(default)]
2440 pub tax_zones: Vec<TaxZoneView>,
2441 #[serde(default)]
2443 pub boundary_zones: Vec<BoundaryZoneView>,
2444 #[serde(default)]
2446 pub growth_zones: Vec<GrowthZoneView>,
2447 #[serde(default)]
2449 pub biome_zones: Vec<BiomeZoneView>,
2450 #[serde(default)]
2452 pub property_plots: Vec<PropertyPlotView>,
2453 #[serde(default)]
2455 pub property_plot_settings: Option<PropertyPlotSettingsView>,
2456}
2457
2458#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2460pub struct ResourceNodeView {
2461 pub id: String,
2462 pub label: String,
2463 pub x: f32,
2464 pub y: f32,
2465 pub z: f32,
2466 pub item_template: String,
2467 #[serde(default = "default_node_state")]
2468 pub state: ResourceNodeState,
2469 #[serde(default = "default_blocking_view")]
2471 pub blocking: bool,
2472 #[serde(default = "default_blocking_radius_view")]
2474 pub blocking_radius_m: f32,
2475 #[serde(default)]
2477 pub tile_id: Option<String>,
2478 #[serde(default)]
2480 pub yaw: f32,
2481 #[serde(default)]
2483 pub pitch: f32,
2484 #[serde(default)]
2486 pub roll: f32,
2487 #[serde(default = "default_draw_scale")]
2489 pub draw_scale: f32,
2490 #[serde(default)]
2492 pub sprite_mode: Option<String>,
2493 #[serde(default)]
2495 pub presentation_state: Option<String>,
2496 #[serde(default)]
2499 pub growth_progress: Option<f32>,
2500 #[serde(default)]
2502 pub channel_start_tick: Option<Tick>,
2503 #[serde(default)]
2504 pub channel_end_tick: Option<Tick>,
2505 #[serde(default)]
2507 pub harvest_drop_templates: Vec<String>,
2508}
2509
2510fn default_blocking_radius_view() -> f32 {
2511 0.8
2512}
2513
2514fn default_blocking_view() -> bool {
2515 true
2516}
2517
2518#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2519#[serde(rename_all = "snake_case")]
2520pub enum ResourceNodeState {
2521 Available,
2522 Harvesting,
2523 Cooldown,
2524}
2525fn default_node_state() -> ResourceNodeState {
2526 ResourceNodeState::Available
2527}
2528
2529#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2531#[serde(rename_all = "snake_case")]
2532pub enum ItemSpawnStateView {
2533 Spawned,
2534 PickedUp {
2535 respawn_at_tick: u64,
2536 },
2537 Consumed,
2538}
2539
2540#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2542pub struct ItemSpawnView {
2543 pub id: String,
2544 pub label: String,
2545 pub item_template: String,
2546 pub quantity: u32,
2547 pub x: f32,
2548 pub y: f32,
2549 pub z: f32,
2550 pub respawn_ticks: u32,
2551 #[serde(default)]
2552 pub building_id: Option<String>,
2553 pub state: ItemSpawnStateView,
2554}
2555
2556#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2557#[serde(rename_all = "snake_case")]
2558pub enum ItemStatusBindingMode {
2559 OnHit,
2560 WhileEquipped,
2561}
2562
2563impl Default for ItemStatusBindingMode {
2564 fn default() -> Self {
2565 Self::OnHit
2566 }
2567}
2568
2569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2571pub struct ItemStatusBinding {
2572 pub effect_id: String,
2573 #[serde(default)]
2574 pub mode: ItemStatusBindingMode,
2575 #[serde(default)]
2577 pub source: String,
2578 #[serde(default)]
2579 pub applied_at_tick: u64,
2580 #[serde(default, skip_serializing_if = "Option::is_none")]
2582 pub expires_at_tick: Option<u64>,
2583}
2584
2585#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2586pub struct ItemStack {
2587 pub template_id: String,
2588 pub quantity: u32,
2589 #[serde(default)]
2591 pub item_instance_id: Option<Uuid>,
2592 #[serde(default)]
2594 pub props: BTreeMap<String, String>,
2595 #[serde(default)]
2597 pub status_bindings: Vec<ItemStatusBinding>,
2598 #[serde(default)]
2600 pub contents: Vec<ItemStack>,
2601 #[serde(default)]
2603 pub display_name: Option<String>,
2604 #[serde(default)]
2606 pub category: Option<String>,
2607 #[serde(default)]
2609 pub base_mass: Option<f32>,
2610 #[serde(default)]
2612 pub base_volume: Option<f32>,
2613 #[serde(default)]
2615 pub capacity_volume: Option<f32>,
2616 #[serde(default)]
2618 pub stackable: Option<bool>,
2619 #[serde(default)]
2621 pub world_placeable: Option<bool>,
2622 #[serde(default)]
2624 pub worker_lodging_capacity: Option<u32>,
2625 #[serde(default)]
2627 pub equip_slot: Option<BodySlot>,
2628 #[serde(default)]
2630 pub armor_physical: Option<f32>,
2631 #[serde(default)]
2633 pub resists: Vec<(String, f32)>,
2634 #[serde(default)]
2636 pub hand_slots: Option<u8>,
2637 #[serde(default)]
2639 pub listable: Option<bool>,
2640}
2641
2642impl ItemStack {
2643 pub fn simple(template_id: impl Into<String>, quantity: u32) -> Self {
2644 Self {
2645 template_id: template_id.into(),
2646 quantity,
2647 ..Default::default()
2648 }
2649 }
2650}
2651
2652impl Default for ItemStack {
2653 fn default() -> Self {
2654 Self {
2655 template_id: String::new(),
2656 quantity: 0,
2657 item_instance_id: None,
2658 props: BTreeMap::new(),
2659 status_bindings: Vec::new(),
2660 contents: Vec::new(),
2661 display_name: None,
2662 category: None,
2663 base_mass: None,
2664 base_volume: None,
2665 capacity_volume: None,
2666 stackable: None,
2667 world_placeable: None,
2668 worker_lodging_capacity: None,
2669 equip_slot: None,
2670 armor_physical: None,
2671 resists: Vec::new(),
2672 hand_slots: None,
2673 listable: None,
2674 }
2675 }
2676}
2677
2678#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2680#[serde(rename_all = "snake_case")]
2681pub enum EncumbranceState {
2682 #[default]
2683 Light,
2684 Heavy,
2685 Over,
2686}
2687
2688#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
2692#[serde(rename_all = "snake_case")]
2693pub enum BodySlot {
2694 Head,
2695 #[serde(alias = "body")]
2697 Chest,
2698 #[serde(alias = "arms")]
2700 Forearms,
2701 Legs,
2702 Feet,
2703 Cloak,
2704 Back,
2705 Waist,
2706 Earrings,
2707 Necklace,
2708 Eyeglasses,
2709 #[serde(rename = "ring_left_1", alias = "ring_left1")]
2711 RingLeft1,
2712 #[serde(rename = "ring_left_2", alias = "ring_left2")]
2713 RingLeft2,
2714 #[serde(rename = "ring_right_1", alias = "ring_right1")]
2715 RingRight1,
2716 #[serde(rename = "ring_right_2", alias = "ring_right2")]
2717 RingRight2,
2718}
2719
2720impl BodySlot {
2721 pub const ALL: [BodySlot; 15] = [
2723 BodySlot::Head,
2724 BodySlot::Chest,
2725 BodySlot::Forearms,
2726 BodySlot::Legs,
2727 BodySlot::Feet,
2728 BodySlot::Cloak,
2729 BodySlot::Back,
2730 BodySlot::Waist,
2731 BodySlot::Earrings,
2732 BodySlot::Necklace,
2733 BodySlot::Eyeglasses,
2734 BodySlot::RingLeft1,
2735 BodySlot::RingLeft2,
2736 BodySlot::RingRight1,
2737 BodySlot::RingRight2,
2738 ];
2739
2740 pub fn as_str(self) -> &'static str {
2741 match self {
2742 BodySlot::Head => "head",
2743 BodySlot::Chest => "chest",
2744 BodySlot::Forearms => "forearms",
2745 BodySlot::Legs => "legs",
2746 BodySlot::Feet => "feet",
2747 BodySlot::Cloak => "cloak",
2748 BodySlot::Back => "back",
2749 BodySlot::Waist => "waist",
2750 BodySlot::Earrings => "earrings",
2751 BodySlot::Necklace => "necklace",
2752 BodySlot::Eyeglasses => "eyeglasses",
2753 BodySlot::RingLeft1 => "ring_left_1",
2754 BodySlot::RingLeft2 => "ring_left_2",
2755 BodySlot::RingRight1 => "ring_right_1",
2756 BodySlot::RingRight2 => "ring_right_2",
2757 }
2758 }
2759}
2760
2761#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2763#[serde(rename_all = "snake_case")]
2764pub enum InventoryLocation {
2765 Root,
2767 Worn { slot: BodySlot },
2769 Placed { container_id: String },
2771 Keychain,
2773 WhisperPouch,
2775}
2776
2777#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2779pub struct PlacedContainerView {
2780 pub id: String,
2781 pub template_id: String,
2782 pub display_name: String,
2783 pub x: f32,
2784 pub y: f32,
2785 pub z: f32,
2786 pub locked: bool,
2787 #[serde(default)]
2789 pub accessible: bool,
2790 #[serde(default)]
2791 pub owner_character_id: Option<Uuid>,
2792 #[serde(default)]
2794 pub contents: Vec<ItemStack>,
2795 #[serde(default)]
2797 pub lock_id: Option<String>,
2798 #[serde(default)]
2800 pub capacity_volume: Option<f32>,
2801 #[serde(default)]
2803 pub item_instance_id: Option<Uuid>,
2804 #[serde(default)]
2806 pub tile_id: Option<String>,
2807 #[serde(default)]
2809 pub worker_lodging_capacity: Option<u32>,
2810 #[serde(default)]
2812 pub blocking: bool,
2813 #[serde(default)]
2815 pub blocking_radius_m: f32,
2816}
2817
2818#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2819pub struct BlueprintIngredientView {
2820 pub template_id: String,
2821 pub quantity: u32,
2822 pub consumed: bool,
2824}
2825
2826#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2827pub struct ToolRequirementView {
2828 pub item: String,
2829 pub consumed: bool,
2831}
2832
2833#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2834pub struct SkillRequirementView {
2835 pub skill: String,
2836 pub level: u32,
2837}
2838
2839#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2840pub struct BlueprintView {
2841 pub id: String,
2842 pub label: String,
2843 pub output: String,
2844 pub output_qty: u32,
2845 pub craft_ticks: u32,
2846 pub inputs: Vec<BlueprintIngredientView>,
2847 #[serde(default)]
2849 pub station: Option<String>,
2850 #[serde(default)]
2851 pub category: Option<String>,
2852 #[serde(default)]
2853 pub required_tools: Vec<ToolRequirementView>,
2854 #[serde(default)]
2855 pub skill: Option<SkillRequirementView>,
2856 #[serde(default)]
2857 pub failure_chance: f32,
2858 #[serde(default)]
2860 pub worker_train_copper: u64,
2861}
2862
2863#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2865#[serde(rename_all = "snake_case")]
2866pub enum TerrainKindView {
2867 #[default]
2868 Grass,
2869 Dirt,
2870 Tilled,
2871 Desert,
2872 Hill,
2873 Bog,
2874 Beach,
2875 ShallowWater,
2876 DeepWater,
2877 Trail,
2878 Road,
2879 Rock,
2880}
2881
2882#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2883pub struct TerrainZoneView {
2884 pub id: String,
2885 pub x0: f32,
2886 pub y0: f32,
2887 pub x1: f32,
2888 pub y1: f32,
2889 #[serde(default)]
2890 pub kind: TerrainKindView,
2891 #[serde(default)]
2893 pub elevation: f32,
2894 #[serde(default)]
2897 pub glyph: Option<String>,
2898 #[serde(default)]
2900 pub color: Option<String>,
2901 #[serde(default)]
2903 pub tile_id: Option<String>,
2904 #[serde(default)]
2906 pub z_order: i32,
2907 #[serde(default)]
2909 pub channel_start_tick: Option<Tick>,
2910 #[serde(default)]
2911 pub channel_end_tick: Option<Tick>,
2912}
2913
2914#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2916pub struct ZoneRectView {
2917 pub x0: f32,
2918 pub y0: f32,
2919 pub x1: f32,
2920 pub y1: f32,
2921}
2922
2923#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2925pub struct PropertyZoneView {
2926 pub id: String,
2927 #[serde(default)]
2929 pub label: Option<String>,
2930 pub rects: Vec<ZoneRectView>,
2931 #[serde(default)]
2932 pub z_order: i32,
2933 pub crown_price_copper: u64,
2934 pub upkeep_copper_per_day: u64,
2935 #[serde(default)]
2936 pub max_area_m2: Option<f32>,
2937 #[serde(default)]
2938 pub owner_tax_discount_bps: u32,
2939}
2940
2941#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2943pub struct TaxZoneView {
2944 pub id: String,
2945 #[serde(default)]
2946 pub label: Option<String>,
2947 pub rects: Vec<ZoneRectView>,
2948 #[serde(default)]
2949 pub z_order: i32,
2950 pub rate_bps: u32,
2951 #[serde(default)]
2952 pub flat_copper: u64,
2953 #[serde(default)]
2955 pub market_sales_tax_bps: u32,
2956 #[serde(default)]
2958 pub market_sales_flat_copper: u32,
2959}
2960
2961#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2963pub struct BoundaryZoneView {
2964 pub id: String,
2965 #[serde(default)]
2966 pub label: Option<String>,
2967 pub rects: Vec<ZoneRectView>,
2968 #[serde(default)]
2969 pub z_order: i32,
2970 #[serde(default, skip_serializing_if = "Option::is_none")]
2971 pub jurisdiction_id: Option<String>,
2972 #[serde(default = "default_true")]
2973 pub worker_logistics: bool,
2974 #[serde(default)]
2975 pub security_tier: String,
2976 #[serde(default)]
2977 pub pvp_mode: String,
2978 #[serde(default = "default_true")]
2979 pub crime_enabled: bool,
2980 #[serde(default)]
2981 pub guard_response: bool,
2982}
2983
2984fn default_true() -> bool {
2985 true
2986}
2987
2988#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2990pub struct GrowthZoneView {
2991 pub id: String,
2992 #[serde(default)]
2993 pub label: Option<String>,
2994 pub rects: Vec<ZoneRectView>,
2995 #[serde(default)]
2996 pub z_order: i32,
2997 #[serde(default = "default_one_f32")]
2998 pub fertility: f32,
2999}
3000
3001fn default_one_f32() -> f32 {
3002 1.0
3003}
3004
3005#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3007pub struct BiomeZoneView {
3008 pub id: String,
3009 #[serde(default)]
3010 pub label: Option<String>,
3011 pub rects: Vec<ZoneRectView>,
3012 #[serde(default)]
3013 pub z_order: i32,
3014 pub biome_id: String,
3015}
3016
3017#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3019pub struct FarmGrantView {
3020 pub character_id: Uuid,
3021 #[serde(default)]
3023 pub character_label: String,
3024 pub tax_discount_bps: u32,
3025}
3026
3027#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3029pub struct PropertyPlotView {
3030 pub plot_id: Uuid,
3031 pub property_zone_id: String,
3032 #[serde(default)]
3033 pub zone_label: Option<String>,
3034 pub deed_instance_id: Uuid,
3035 pub x0: f32,
3036 pub y0: f32,
3037 pub x1: f32,
3038 pub y1: f32,
3039 pub upkeep_copper_per_day: u64,
3040 pub arrears_days: u32,
3041 #[serde(default)]
3043 pub is_mine: bool,
3044 #[serde(default)]
3046 pub may_farm: bool,
3047 #[serde(default)]
3049 pub purchase_basis_copper: u64,
3050 #[serde(default)]
3051 pub farm_public: bool,
3052 #[serde(default)]
3053 pub public_tax_discount_bps: u32,
3054 #[serde(default)]
3055 pub farm_allow: Vec<FarmGrantView>,
3056 #[serde(default)]
3058 pub owner_character_id: Option<Uuid>,
3059 #[serde(default)]
3060 pub owner_label: Option<String>,
3061}
3062
3063#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3065pub struct PropertyPlotSettingsView {
3066 pub min_plot_area_m2: f32,
3067 pub tax_premium_weight: f32,
3068 pub sellback_bps: u32,
3069}
3070
3071#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3073pub struct ZPlatformView {
3074 pub id: String,
3075 pub z: f32,
3076 pub x0: f32,
3077 pub y0: f32,
3078 pub x1: f32,
3079 pub y1: f32,
3080}
3081
3082#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3084pub struct ZTransitionView {
3085 pub id: String,
3086 pub z_from: f32,
3087 pub z_to: f32,
3088 pub x0: f32,
3089 pub y0: f32,
3090 pub x1: f32,
3091 pub y1: f32,
3092}
3093
3094#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3095pub struct BuildingView {
3096 pub id: String,
3097 pub label: String,
3098 pub x: f32,
3099 pub y: f32,
3100 pub width_m: f32,
3101 pub depth_m: f32,
3102 #[serde(default)]
3103 pub interior_blueprint: Option<String>,
3104 #[serde(default)]
3105 pub tags: Vec<String>,
3106 #[serde(default)]
3108 pub market_boundary_zone_ids: Vec<String>,
3109 #[serde(default)]
3111 pub market_max_volume: Option<f32>,
3112 #[serde(default)]
3115 pub wall_set: Option<String>,
3116 #[serde(default)]
3118 pub roof_set: Option<String>,
3119}
3120
3121pub const DEFAULT_BUILDING_ART_SET: &str = "classic_stone";
3124
3125impl BuildingView {
3126 pub fn effective_wall_set(&self) -> &str {
3127 self.wall_set
3128 .as_deref()
3129 .filter(|s| !s.is_empty())
3130 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3131 }
3132
3133 pub fn effective_roof_set(&self) -> &str {
3134 self.roof_set
3135 .as_deref()
3136 .filter(|s| !s.is_empty())
3137 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3138 }
3139}
3140
3141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3142pub struct DoorView {
3143 pub id: String,
3144 pub building_id: String,
3145 pub x: f32,
3146 pub y: f32,
3147 #[serde(default)]
3148 pub open: bool,
3149 #[serde(default)]
3150 pub portal: Option<String>,
3151}
3152
3153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3154pub struct InteriorRoomView {
3155 pub id: String,
3156 pub label: String,
3157 pub floor: i32,
3158 pub x0: f32,
3159 pub y0: f32,
3160 pub x1: f32,
3161 pub y1: f32,
3162 #[serde(default)]
3163 pub floor_color: Option<String>,
3164 #[serde(default)]
3165 pub floor_glyph: Option<String>,
3166}
3167
3168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3169pub struct InteriorDoorView {
3170 pub id: String,
3171 pub room_a: String,
3172 pub room_b: String,
3173 pub x: f32,
3174 pub y: f32,
3175 pub kind: String,
3176 #[serde(default)]
3177 pub x_a: Option<f32>,
3178 #[serde(default)]
3179 pub y_a: Option<f32>,
3180 #[serde(default)]
3181 pub x_b: Option<f32>,
3182 #[serde(default)]
3183 pub y_b: Option<f32>,
3184}
3185
3186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3187pub struct InteriorMapView {
3188 pub building_id: String,
3189 pub blueprint_id: String,
3190 pub background_color: String,
3191 #[serde(default)]
3192 pub default_floor_color: Option<String>,
3193 #[serde(default = "default_floor_height_view")]
3194 pub floor_height_m: f32,
3195 #[serde(default)]
3197 pub z_platforms: Vec<ZPlatformView>,
3198 #[serde(default)]
3199 pub z_transitions: Vec<ZTransitionView>,
3200 pub rooms: Vec<InteriorRoomView>,
3201 #[serde(default)]
3202 pub room_doors: Vec<InteriorDoorView>,
3203}
3204
3205fn default_floor_height_view() -> f32 {
3206 3.0
3207}
3208
3209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3210pub struct NpcView {
3211 pub id: String,
3212 pub label: String,
3213 pub role: String,
3214 pub x: f32,
3215 pub y: f32,
3216 #[serde(default)]
3218 pub building_id: Option<String>,
3219 #[serde(default)]
3221 pub entity_id: Option<EntityId>,
3222 #[serde(default)]
3223 pub life_state: Option<LifeState>,
3224 #[serde(default)]
3225 pub hp_pct: Option<f32>,
3226 #[serde(default)]
3228 pub can_trade: bool,
3229 #[serde(default)]
3231 pub tile_id: Option<String>,
3232 #[serde(default)]
3234 pub behavior_state: Option<String>,
3235 #[serde(default)]
3237 pub presentation_state: Option<String>,
3238 #[serde(default)]
3240 pub sprite_mode: Option<String>,
3241 #[serde(default)]
3243 pub paperdoll_ref: Option<String>,
3244}
3245
3246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3247pub struct UseResult {
3248 pub template_id: String,
3249 pub hunger_restored: f32,
3250 pub thirst_restored: f32,
3251 #[serde(default)]
3252 pub health_restored: f32,
3253 #[serde(default)]
3254 pub mana_restored: f32,
3255 #[serde(default)]
3256 pub cleared_dot_ids: Vec<String>,
3257}
3258
3259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3260pub struct CraftResult {
3261 pub blueprint_id: String,
3262 pub outputs: Vec<ItemStack>,
3263 pub consumed: Vec<ItemStack>,
3264 #[serde(default = "default_one")]
3266 pub batch_index: u32,
3267 #[serde(default = "default_one")]
3269 pub batch_total: u32,
3270}
3271
3272fn default_one() -> u32 {
3273 1
3274}
3275
3276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3277pub struct DeathNotice {
3278 pub entity_id: EntityId,
3279 pub respawn_x: f32,
3280 pub respawn_y: f32,
3281 pub message: String,
3282}
3283
3284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3285pub struct InteractionNotice {
3286 pub target_id: String,
3287 pub message: String,
3288 #[serde(default)]
3289 pub coins_delta: i32,
3290 #[serde(default)]
3291 pub inventory_delta: Vec<ItemStack>,
3292}
3293
3294#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3295#[serde(rename_all = "snake_case")]
3296pub enum NpcTalkTrustFlag {
3297 Stranger,
3298 Acquainted,
3299 Trusted,
3300}
3301
3302#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3303#[serde(rename_all = "snake_case")]
3304pub enum NpcTalkDepth {
3305 #[default]
3306 Full,
3307 Brief,
3308 Unavailable,
3309}
3310
3311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3312pub struct NpcTalkOpened {
3313 pub npc_id: String,
3314 pub npc_label: String,
3315 pub greeting: String,
3316 pub trust_flag: NpcTalkTrustFlag,
3317 #[serde(default)]
3318 pub talk_depth: NpcTalkDepth,
3319 #[serde(default = "default_true")]
3320 pub trade_allowed: bool,
3321}
3322
3323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3324pub struct NpcTalkPending {
3325 pub npc_id: String,
3326}
3327
3328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3329pub struct NpcTalkReply {
3330 pub npc_id: String,
3331 pub line: String,
3332 pub trust_flag: NpcTalkTrustFlag,
3333 #[serde(default)]
3334 pub wind_down: bool,
3335 #[serde(default)]
3336 pub trade_disabled: bool,
3337}
3338
3339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3340pub struct NpcTalkClosed {
3341 pub npc_id: String,
3342}
3343
3344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3345pub struct NpcTalkError {
3346 pub npc_id: String,
3347 pub reason: String,
3348}
3349
3350#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3351#[serde(rename_all = "snake_case")]
3352pub enum QuestStatusView {
3353 Available,
3354 Active,
3355 Completed,
3356}
3357
3358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3359pub struct QuestObjectiveProgress {
3360 pub label: String,
3361 pub current: u32,
3362 pub required: u32,
3363 pub done: bool,
3364}
3365
3366#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3367pub struct QuestLogEntry {
3368 pub quest_id: String,
3369 pub title: String,
3370 pub description: String,
3371 pub status: QuestStatusView,
3372 #[serde(default)]
3373 pub current_step_id: Option<String>,
3374 #[serde(default)]
3375 pub current_step_title: String,
3376 #[serde(default)]
3377 pub objectives: Vec<QuestObjectiveProgress>,
3378 #[serde(default)]
3379 pub is_tracked: bool,
3380 #[serde(default)]
3381 pub can_withdraw: bool,
3382}
3383
3384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3385pub struct InteractableView {
3386 pub id: String,
3387 pub kind: String,
3388 pub label: String,
3389 pub x: f32,
3390 pub y: f32,
3391 pub z: f32,
3392 #[serde(default)]
3393 pub board_id: Option<String>,
3394}
3395
3396#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3397pub struct QuestOffer {
3398 pub quest_id: String,
3399 pub title: String,
3400 pub description: String,
3401 #[serde(default)]
3402 pub step_count: u32,
3403}
3404
3405#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3406pub struct QuestNotice {
3407 pub quest_id: String,
3408 pub title: String,
3409 pub message: String,
3410}
3411
3412#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3413#[serde(rename_all = "snake_case")]
3414pub enum ShopOfferKind {
3415 Item,
3416 Blueprint,
3417}
3418
3419#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3420pub struct ShopOffer {
3421 pub offer_id: String,
3422 pub kind: ShopOfferKind,
3423 pub label: String,
3424 #[serde(default)]
3425 pub template_id: Option<String>,
3426 #[serde(default)]
3427 pub blueprint_id: Option<String>,
3428 pub price_copper: u32,
3429 #[serde(default)]
3430 pub affordable: bool,
3431 #[serde(default)]
3432 pub already_owned: bool,
3433}
3434
3435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3436pub struct ShopBuyLine {
3437 pub template_id: String,
3438 pub label: String,
3439 pub quantity: u32,
3440 pub price_copper: u32,
3441}
3442
3443#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3445pub struct BankPanel {
3446 pub npc_id: String,
3447 pub npc_label: String,
3448 pub bank_balance_copper: u64,
3449 pub on_person_copper: u64,
3450 #[serde(default)]
3452 pub pending_outgoing_copper: u64,
3453 #[serde(default)]
3454 pub transfer_fee_bps: u32,
3455 #[serde(default)]
3456 pub transfer_clear_ticks: u64,
3457}
3458
3459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3461pub struct StoragePanel {
3462 pub npc_id: String,
3463 pub npc_label: String,
3464 pub building_id: String,
3465 pub building_label: String,
3466 pub used_volume: f32,
3467 pub max_volume: f32,
3468 #[serde(default)]
3469 pub contents: Vec<ItemStack>,
3470 #[serde(default)]
3472 pub ship_destinations: Vec<StorageShipDest>,
3473}
3474
3475#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3476pub struct StorageShipDest {
3477 pub building_id: String,
3478 pub label: String,
3479 pub distance_m: f32,
3480 pub fee_copper: u64,
3481 pub travel_ticks: u64,
3482}
3483
3484#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3487pub enum GoodsLocation {
3488 Person,
3490 TownStorage { building_id: String },
3493}
3494
3495#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3498pub struct MarketListingView {
3499 pub listing_id: Uuid,
3500 pub seller_character_id: Uuid,
3501 pub seller_label: String,
3503 pub hall_building_id: String,
3504 pub hall_label: String,
3505 pub template_id: String,
3506 pub display_name: String,
3507 #[serde(default)]
3509 pub category: String,
3510 pub quantity: u32,
3511 pub unit_price_copper: u64,
3512 pub line_total_copper: u64,
3514 pub mine: bool,
3516}
3517
3518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3520pub struct MarketListVault {
3521 pub building_id: String,
3522 pub building_label: String,
3524 #[serde(default)]
3525 pub contents: Vec<ItemStack>,
3526}
3527
3528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3531pub struct MarketPanel {
3532 pub npc_id: String,
3533 pub npc_label: String,
3534 pub building_id: String,
3535 pub building_label: String,
3536 pub used_volume: f32,
3538 pub max_volume: f32,
3539 #[serde(default)]
3542 pub listings: Vec<MarketListingView>,
3543 #[serde(default)]
3545 pub tax_bps: u32,
3546 #[serde(default)]
3547 pub tax_flat_copper: u32,
3548 #[serde(default)]
3550 pub list_vaults: Vec<MarketListVault>,
3551}
3552
3553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3554pub struct ShopCatalog {
3555 pub npc_id: String,
3556 pub npc_label: String,
3557 #[serde(default)]
3558 pub sells: Vec<ShopOffer>,
3559 #[serde(default)]
3560 pub buys: Vec<ShopBuyLine>,
3561}
3562
3563#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3564pub struct HarvestResult {
3565 pub node_id: String,
3566 pub quantity: u32,
3568 pub item_template: String,
3569 #[serde(default)]
3572 pub item_instance_id: Option<Uuid>,
3573}
3574
3575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3577pub struct Envelope<T> {
3578 pub protocol_version: u16,
3579 pub payload: T,
3580}
3581
3582impl<T> Envelope<T> {
3583 pub fn new(payload: T) -> Self {
3584 Self {
3585 protocol_version: crate::PROTOCOL_VERSION,
3586 payload,
3587 }
3588 }
3589}
3590
3591#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3593pub struct Hello {
3594 pub client_name: String,
3595 pub protocol_version: u16,
3596 #[serde(default)]
3597 pub auth: AuthCredential,
3598 #[serde(default)]
3600 pub character_id: Option<Uuid>,
3601}
3602
3603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3606#[serde(rename_all = "snake_case")]
3607pub enum AuthCredential {
3608 DevLocal,
3609 Session { token: String },
3610 ApiToken { token: String, character_id: Uuid },
3611}
3612
3613impl Default for AuthCredential {
3614 fn default() -> Self {
3615 Self::DevLocal
3616 }
3617}
3618
3619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3620pub struct Welcome {
3621 pub session_id: SessionId,
3622 pub entity_id: EntityId,
3623 pub snapshot: Snapshot,
3624}
3625
3626#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3627pub enum ServerMessage {
3628 Welcome(Welcome),
3629 ContentUpdated(Snapshot),
3631 Tick(TickDelta),
3632 IntentAck {
3633 entity_id: EntityId,
3634 seq: Seq,
3635 tick: Tick,
3636 },
3637 Chat(ChatMessage),
3638 HarvestResult(HarvestResult),
3639 UseResult(UseResult),
3640 CraftResult(CraftResult),
3641 Death(DeathNotice),
3642 Interaction(InteractionNotice),
3643 ShopOpened(ShopCatalog),
3644 NpcTalkOpened(NpcTalkOpened),
3645 NpcTalkPending(NpcTalkPending),
3646 NpcTalkReply(NpcTalkReply),
3647 NpcTalkClosed(NpcTalkClosed),
3648 NpcTalkError(NpcTalkError),
3649 QuestOffer(QuestOffer),
3650 QuestAccepted(QuestNotice),
3651 QuestWithdrawn(QuestNotice),
3652 QuestStepCompleted(QuestNotice),
3653 QuestCompleted(QuestNotice),
3654 BankOpened(BankPanel),
3656 StorageOpened(StoragePanel),
3658 MarketOpened(MarketPanel),
3660 TradeOpened(TradePanel),
3662 TradeClosed {
3664 reason: String,
3665 },
3666 ConnectRejected {
3669 reason: String,
3670 },
3671}
3672
3673#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3675pub struct TradePanel {
3676 pub peer_entity_id: EntityId,
3677 pub peer_name: String,
3678 pub my_presented: Vec<ItemStack>,
3679 pub their_presented: Vec<ItemStack>,
3680 pub i_ready: bool,
3681 pub they_ready: bool,
3682 pub my_mass_after: f32,
3684 pub my_mass_max: f32,
3685 pub my_encumbrance_after: EncumbranceState,
3686 pub overburden_warning: bool,
3688}
3689
3690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3691pub enum ClientMessage {
3692 Hello(Hello),
3693 Intent(Intent),
3694 Disconnect,
3695}
3696
3697#[cfg(test)]
3698mod tests {
3699 use super::*;
3700
3701 #[test]
3702 fn pristine_vitals_state_yields_full_pools() {
3703 let attrs = PrimaryAttributes::default();
3704 let vitals = StoredVitalsState::default().apply_to(attrs);
3705 assert!(vitals.health > 0.0);
3706 assert_eq!(vitals.health, vitals.health_max);
3707 assert!((vitals.mana_max - 61.0).abs() < 0.01);
3708 }
3709
3710 #[test]
3711 fn humanize_snake_id_title_cases_parts() {
3712 assert_eq!(humanize_snake_id("heal_touch"), "Heal Touch");
3713 assert_eq!(humanize_snake_id("fireball"), "Fireball");
3714 assert_eq!(humanize_snake_id("cone_frost"), "Cone Frost");
3715 }
3716
3717 #[test]
3718 fn saved_vitals_scale_when_pool_max_increases() {
3719 let mut attrs = PrimaryAttributes::default();
3720 attrs.intelligence = 140;
3721 attrs.wisdom = 140;
3722 let saved = StoredVitalsState {
3723 health: 100.0,
3724 mana: 14.0,
3725 stamina: 100.0,
3726 ..StoredVitalsState::default()
3727 };
3728 let vitals = saved.apply_to(attrs);
3729 assert!(vitals.mana_max > 55.0);
3730 assert!(
3731 (vitals.mana - vitals.mana_max).abs() < 0.01,
3732 "full legacy mana bar migrates to full new bar"
3733 );
3734 }
3735
3736 #[test]
3737 fn empty_vitals_state_is_pristine() {
3738 let pristine = StoredVitalsState {
3739 health: 0.0,
3740 mana: 0.0,
3741 stamina: 0.0,
3742 hunger: 0.0,
3743 thirst: 0.0,
3744 coins: 0,
3745 deaths: 0,
3746 life_state: LifeState::Alive,
3747 };
3748 assert!(pristine.is_pristine());
3749 let vitals = pristine.apply_to(PrimaryAttributes::default());
3750 assert!(vitals.health > 0.0);
3751 }
3752
3753 #[test]
3754 fn stored_vitals_roundtrip_preserves_partial_pools() {
3755 let attrs = PrimaryAttributes::default();
3756 let mut live = PlayerVitals::from_attributes(attrs);
3757 live.health = 25.0;
3758 live.hunger = 77.0;
3759 live.deaths = 2;
3760 let stored = StoredVitalsState::from_live(&live);
3761 let restored = stored.apply_to(attrs);
3762 assert!(
3763 (restored.health - 25.0).abs() < 0.01,
3764 "partial HP below cap stays absolute"
3765 );
3766 assert_eq!(restored.hunger, 77.0);
3767 assert_eq!(restored.deaths, 2);
3768 }
3769
3770 #[test]
3771 fn skill_tiers_start_at_zero() {
3772 let skill = SkillProgress::default();
3773 assert_eq!(skill.level, 0);
3774 assert_eq!(skill.display_tier(), 0);
3775 let trained = SkillProgress {
3776 level: 250,
3777 last_trained_tick: 1,
3778 };
3779 assert_eq!(trained.display_tier(), 2);
3780 }
3781
3782 #[test]
3783 fn quest_server_messages_roundtrip_json() {
3784 use crate::codec::{Codec, PostcardCodec};
3785
3786 let offer = ServerMessage::QuestOffer(QuestOffer {
3787 quest_id: "ada_goblin_hunt".into(),
3788 title: "Goblin Trouble".into(),
3789 description: "Help Ada".into(),
3790 step_count: 3,
3791 });
3792 let notice = ServerMessage::QuestAccepted(QuestNotice {
3793 quest_id: "ada_goblin_hunt".into(),
3794 title: "Goblin Trouble".into(),
3795 message: "Quest accepted".into(),
3796 });
3797 for msg in [offer, notice] {
3798 let bytes = PostcardCodec.encode(&msg).unwrap();
3799 let decoded: ServerMessage = PostcardCodec.decode(&bytes).unwrap();
3800 assert_eq!(decoded, msg);
3801 }
3802 }
3803
3804 #[test]
3805 fn hotbar_consumable_binding_roundtrips() {
3806 let binding = hotbar_consumable_binding("bottle_of_water");
3807 assert_eq!(binding, "item:bottle_of_water");
3808 assert!(hotbar_binding_is_consumable(&binding));
3809 assert_eq!(
3810 hotbar_consumable_template(&binding),
3811 Some("bottle_of_water")
3812 );
3813 assert!(!hotbar_binding_is_consumable("fireball"));
3814 assert_eq!(hotbar_consumable_template("fireball"), None);
3815 }
3816}