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}
242
243impl ProgressionXp {
244 pub fn bootstrap_new(baseline_display: u16, xp_base: f64, xp_growth: f64) -> Self {
246 let bootstrap = |display: f64| {
247 if display <= 1.0 {
248 0.0
249 } else {
250 xp_base * xp_growth.powf(display - 1.0)
251 }
252 };
253 let b = baseline_display as f64;
254 let primary = bootstrap(b);
255 Self {
256 strength: primary,
257 dexterity: primary,
258 intelligence: primary,
259 stamina: primary,
260 vitality: primary,
261 wisdom: primary,
262 charisma: primary,
263 ..Self::default()
264 }
265 }
266
267 pub fn is_empty(&self) -> bool {
268 self.strength == 0.0
269 && self.dexterity == 0.0
270 && self.intelligence == 0.0
271 && self.stamina == 0.0
272 && self.vitality == 0.0
273 && self.wisdom == 0.0
274 && self.charisma == 0.0
275 && self.logging == 0.0
276 && self.mining == 0.0
277 && self.evocation == 0.0
278 && self.restoration == 0.0
279 && self.swords == 0.0
280 && self.archery == 0.0
281 && self.crafting == 0.0
282 && self.alchemy == 0.0
283 && self.cartography == 0.0
284 }
285}
286
287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289#[serde(default)]
290pub struct PlayerSkills {
291 pub logging: SkillProgress,
292 pub mining: SkillProgress,
293 pub evocation: SkillProgress,
294 #[serde(default)]
295 pub restoration: SkillProgress,
296 pub swords: SkillProgress,
297 #[serde(default)]
298 pub archery: SkillProgress,
299 pub crafting: SkillProgress,
300 #[serde(default)]
301 pub alchemy: SkillProgress,
302 pub cartography: SkillProgress,
303}
304
305impl Default for PlayerSkills {
306 fn default() -> Self {
307 Self {
308 logging: SkillProgress::default(),
309 mining: SkillProgress::default(),
310 evocation: SkillProgress::default(),
311 restoration: SkillProgress::default(),
312 swords: SkillProgress::default(),
313 archery: SkillProgress::default(),
314 crafting: SkillProgress::default(),
315 alchemy: SkillProgress::default(),
316 cartography: SkillProgress::default(),
317 }
318 }
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
323pub struct PlayerVitals {
324 pub health: f32,
325 pub health_max: f32,
326 pub mana: f32,
327 pub mana_max: f32,
328 pub stamina: f32,
329 pub stamina_max: f32,
330 #[serde(default = "default_survival_pool_max")]
331 pub hunger: f32,
332 #[serde(default = "default_survival_pool_max")]
333 pub hunger_max: f32,
334 #[serde(default = "default_survival_pool_max")]
335 pub thirst: f32,
336 #[serde(default = "default_survival_pool_max")]
337 pub thirst_max: f32,
338 #[serde(default)]
339 pub coins: u32,
340 #[serde(default)]
341 pub deaths: u32,
342 #[serde(default)]
343 pub life_state: LifeState,
344}
345
346fn default_survival_pool_max() -> f32 {
347 100.0
348}
349
350impl Default for PlayerVitals {
351 fn default() -> Self {
352 Self::from_attributes(PrimaryAttributes::default())
353 }
354}
355
356impl PlayerVitals {
357 pub fn from_attributes(attrs: PrimaryAttributes) -> Self {
364 let vit_d = PrimaryAttributes::display(attrs.vitality) as f32;
365 let int_d = PrimaryAttributes::display(attrs.intelligence) as f32;
366 let wis_d = PrimaryAttributes::display(attrs.wisdom) as f32;
367 let sta_d = PrimaryAttributes::display(attrs.stamina) as f32;
368
369 let health_max = 50.0 + vit_d * 2.0;
370 let stamina_max = 30.0 + sta_d * 1.4;
371 let mana_max = 25.0 + int_d * 1.1 + wis_d * 1.3;
372 let hunger_max = 100.0;
373 let thirst_max = 100.0;
374 Self {
375 health: health_max,
376 health_max,
377 mana: mana_max,
378 mana_max,
379 stamina: stamina_max,
380 stamina_max,
381 hunger: hunger_max,
382 hunger_max,
383 thirst: thirst_max,
384 thirst_max,
385 coins: 0,
386 deaths: 0,
387 life_state: LifeState::Alive,
388 }
389 }
390
391 pub fn legacy_maxima(attrs: PrimaryAttributes) -> (f32, f32, f32) {
393 (
394 attrs.vitality as f32 / 5.0,
395 attrs.stamina as f32 / 5.0,
396 (attrs.intelligence as f32 + attrs.wisdom as f32) / 20.0,
397 )
398 }
399}
400
401#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
403#[serde(default)]
404pub struct StoredVitalsState {
405 pub health: f32,
406 pub mana: f32,
407 pub stamina: f32,
408 pub hunger: f32,
409 pub thirst: f32,
410 pub coins: u32,
411 pub deaths: u32,
412 pub life_state: LifeState,
413}
414
415impl StoredVitalsState {
416 pub fn from_live(v: &PlayerVitals) -> Self {
417 Self {
418 health: v.health,
419 mana: v.mana,
420 stamina: v.stamina,
421 hunger: v.hunger,
422 thirst: v.thirst,
423 coins: v.coins,
424 deaths: v.deaths,
425 life_state: v.life_state,
426 }
427 }
428
429 pub fn is_pristine(&self) -> bool {
431 self.health == 0.0
432 && self.mana == 0.0
433 && self.stamina == 0.0
434 && self.hunger == 0.0
435 && self.thirst == 0.0
436 && self.coins == 0
437 && self.deaths == 0
438 && self.life_state == LifeState::Alive
439 }
440
441 pub fn apply_to(self, attrs: PrimaryAttributes) -> PlayerVitals {
442 if self.is_pristine() {
443 return PlayerVitals::from_attributes(attrs);
444 }
445 let fresh = PlayerVitals::from_attributes(attrs);
446 let (legacy_hp, legacy_sta, legacy_mana) = PlayerVitals::legacy_maxima(attrs);
447
448 let scale = |current: f32, legacy_max: f32, new_max: f32| {
449 if legacy_max > 0.0 && new_max > legacy_max * 1.05 && current >= legacy_max * 0.95 {
450 let ratio = (current / legacy_max).clamp(0.0, 1.0);
451 (new_max * ratio).min(new_max)
452 } else {
453 current.min(new_max)
454 }
455 };
456
457 let mut v = fresh;
458 v.health = scale(self.health, legacy_hp, fresh.health_max);
459 v.mana = scale(self.mana, legacy_mana, fresh.mana_max);
460 v.stamina = scale(self.stamina, legacy_sta, fresh.stamina_max);
461 v.hunger = self.hunger.min(v.hunger_max);
462 v.thirst = self.thirst.min(v.thirst_max);
463 v.coins = self.coins;
464 v.deaths = self.deaths;
465 v.life_state = self.life_state;
466 v
467 }
468}
469
470impl Default for StoredVitalsState {
471 fn default() -> Self {
472 Self::from_live(&PlayerVitals::default())
473 }
474}
475
476pub fn humanize_snake_id(id: &str) -> String {
480 id.split('_')
481 .filter(|part| !part.is_empty())
482 .map(|part| {
483 let mut chars = part.chars();
484 match chars.next() {
485 None => String::new(),
486 Some(first) => first.to_uppercase().chain(chars).collect(),
487 }
488 })
489 .collect::<Vec<_>>()
490 .join(" ")
491}
492
493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
495pub struct KnownAbility {
496 pub ability_id: String,
497 #[serde(default = "default_known_permanent")]
499 pub permanent: bool,
500 #[serde(default)]
502 pub expires_at_tick: Option<u64>,
503}
504
505fn default_known_permanent() -> bool {
506 true
507}
508
509impl KnownAbility {
510 pub fn permanent(ability_id: impl Into<String>) -> Self {
511 Self {
512 ability_id: ability_id.into(),
513 permanent: true,
514 expires_at_tick: None,
515 }
516 }
517
518 pub fn is_active(&self, tick: u64) -> bool {
519 if self.permanent {
520 return true;
521 }
522 match self.expires_at_tick {
523 Some(exp) => tick < exp,
524 None => false,
525 }
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
531pub struct RotationPreset {
532 pub id: String,
533 pub label: String,
534 #[serde(default)]
535 pub abilities: Vec<String>,
536}
537
538impl RotationPreset {
539 pub fn melee_default(ability_id: impl Into<String>) -> Self {
540 let id = ability_id.into();
541 Self {
542 id: "melee".into(),
543 label: "Weapon".into(),
545 abilities: vec![id],
546 }
547 }
548
549 pub fn is_weapon_preset(&self) -> bool {
550 self.id == "melee"
551 }
552}
553
554#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
556pub struct StoredTargetSlot {
557 pub instance_id: Option<String>,
558 #[serde(default)]
559 pub preset_id: Option<String>,
560 #[serde(default)]
561 pub rotation_index: u32,
562 #[serde(default)]
563 pub auto_enabled: bool,
564}
565
566#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
567#[serde(default)]
568pub struct StoredCombatProfile {
569 pub combat_target_instance_id: Option<String>,
571 pub in_combat: bool,
572 pub last_combat_tick: u64,
573 pub last_attack_tick: u64,
574 pub cooldowns_until_tick: BTreeMap<String, u64>,
575 #[serde(default = "default_auto_attack")]
576 pub auto_attack_enabled: bool,
577 #[serde(default)]
579 pub mainhand_template_id: Option<String>,
580 #[serde(default)]
582 pub mainhand_instance_id: Option<Uuid>,
583 #[serde(default)]
585 pub offhand_template_id: Option<String>,
586 #[serde(default)]
588 pub offhand_instance_id: Option<Uuid>,
589 #[serde(default)]
592 pub worn: Vec<(BodySlot, ItemStack)>,
593 #[serde(default)]
595 pub rotation_presets: Vec<RotationPreset>,
596 #[serde(default)]
598 pub target_slots: Vec<StoredTargetSlot>,
599 #[serde(default)]
601 pub known_blueprint_ids: Vec<String>,
602 #[serde(default)]
604 pub keychain: Vec<ItemStack>,
605 #[serde(default)]
607 pub whisper_pouch: Vec<ItemStack>,
608 #[serde(default)]
610 pub known_abilities: Vec<KnownAbility>,
611 #[serde(default)]
613 pub hotbar: Vec<Option<String>>,
614 #[serde(default)]
616 pub abilities_schema_version: u32,
617 #[serde(default)]
619 pub bank_balance_copper: u64,
620}
621
622fn default_auto_attack() -> bool {
623 true
624}
625
626impl Default for StoredCombatProfile {
627 fn default() -> Self {
628 Self {
629 combat_target_instance_id: None,
630 in_combat: false,
631 last_combat_tick: 0,
632 last_attack_tick: 0,
633 cooldowns_until_tick: BTreeMap::new(),
634 auto_attack_enabled: true,
635 mainhand_template_id: None,
636 mainhand_instance_id: None,
637 offhand_template_id: None,
638 offhand_instance_id: None,
639 worn: Vec::new(),
640 rotation_presets: Vec::new(),
641 target_slots: Vec::new(),
642 known_blueprint_ids: Vec::new(),
643 keychain: Vec::new(),
644 whisper_pouch: Vec::new(),
645 known_abilities: Vec::new(),
646 hotbar: Vec::new(),
647 abilities_schema_version: 0,
648 bank_balance_copper: 0,
649 }
650 }
651}
652
653#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
655#[serde(rename_all = "snake_case")]
656pub enum CombatCueKind {
657 Dodge,
658 Block,
659 AttackTelegraph,
660}
661
662#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
663pub struct CombatCueView {
664 pub kind: CombatCueKind,
665 pub until_tick: Tick,
667 #[serde(default)]
669 pub start_tick: Tick,
670 #[serde(default)]
672 pub ability_id: Option<String>,
673}
674
675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
676pub struct EntityState {
677 pub id: EntityId,
678 pub transform: Transform,
679 #[serde(default)]
681 pub label: String,
682 #[serde(default)]
683 pub vitals: Option<PlayerVitals>,
684 #[serde(default)]
686 pub attributes: Option<PrimaryAttributes>,
687 #[serde(default)]
688 pub skills: Option<PlayerSkills>,
689 #[serde(default)]
691 pub inside_building: Option<String>,
692 #[serde(default)]
694 pub tile_id: Option<String>,
695 #[serde(default)]
697 pub paperdoll_ref: Option<String>,
698 #[serde(default)]
700 pub presentation_state: Option<String>,
701 #[serde(default)]
703 pub sprite_mode: Option<String>,
704 #[serde(default)]
706 pub progression_xp: Option<ProgressionXp>,
707 #[serde(default)]
709 pub combat_cues: Vec<CombatCueView>,
710}
711
712#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
714#[serde(rename_all = "snake_case")]
715pub enum ChatChannel {
716 Nearby,
718 Direct,
720 Whisper,
722 WhisperStone,
724}
725
726#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
728#[serde(rename_all = "snake_case")]
729pub enum ChatClarity {
730 #[default]
731 Clear,
732 Partial,
733 Heavy,
734}
735
736#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
737pub struct ChatMessage {
738 pub channel: ChatChannel,
739 pub from_entity: EntityId,
740 pub from_name: String,
741 pub text: String,
743 pub tick: Tick,
744 #[serde(default)]
746 pub to_entity: Option<EntityId>,
747 #[serde(default)]
748 pub clarity: ChatClarity,
749}
750
751#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
753pub enum Intent {
754 Move {
755 entity_id: EntityId,
756 forward: f32,
757 strafe: f32,
758 #[serde(default)]
760 vertical: f32,
761 #[serde(default)]
763 sprint: bool,
764 seq: Seq,
765 },
766 Stop {
767 entity_id: EntityId,
768 seq: Seq,
769 },
770 Harvest {
771 entity_id: EntityId,
772 node_id: String,
773 seq: Seq,
774 },
775 Use {
776 entity_id: EntityId,
777 template_id: String,
778 seq: Seq,
779 },
780 UseGrant {
782 entity_id: EntityId,
783 grant_instance_id: Uuid,
784 target_instance_id: Uuid,
785 seq: Seq,
786 },
787 Say {
788 entity_id: EntityId,
789 channel: ChatChannel,
790 text: String,
791 #[serde(default)]
793 to_entity: Option<EntityId>,
794 seq: Seq,
795 },
796 Craft {
798 entity_id: EntityId,
799 blueprint_id: String,
800 #[serde(default)]
802 count: Option<u32>,
803 seq: Seq,
804 },
805 Interact {
807 entity_id: EntityId,
808 target_id: String,
809 seq: Seq,
810 },
811 ShopBuy {
813 entity_id: EntityId,
814 npc_id: String,
815 offer_id: String,
816 #[serde(default = "default_one")]
817 quantity: u32,
818 seq: Seq,
819 },
820 ShopSell {
822 entity_id: EntityId,
823 npc_id: String,
824 template_id: String,
825 #[serde(default = "default_one")]
826 quantity: u32,
827 seq: Seq,
828 },
829 ShopClose {
831 entity_id: EntityId,
832 npc_id: String,
833 seq: Seq,
834 },
835 TestDamage {
837 entity_id: EntityId,
838 amount: f32,
839 seq: Seq,
840 },
841 SetTarget {
843 entity_id: EntityId,
844 target_id: EntityId,
845 seq: Seq,
846 },
847 SetTargetSlot {
849 entity_id: EntityId,
850 slot_index: u8,
851 target_id: EntityId,
852 seq: Seq,
853 },
854 ClearTarget {
855 entity_id: EntityId,
856 seq: Seq,
857 },
858 ClearTargetSlot {
859 entity_id: EntityId,
860 slot_index: u8,
861 seq: Seq,
862 },
863 SetAutoAttack {
865 entity_id: EntityId,
866 slot_index: u8,
867 enabled: bool,
868 seq: Seq,
869 },
870 Attack {
872 entity_id: EntityId,
873 #[serde(default)]
874 target_id: Option<EntityId>,
875 #[serde(default)]
876 weapon_slot: Option<u32>,
877 seq: Seq,
878 },
879 Pickup {
881 entity_id: EntityId,
882 #[serde(default)]
883 drop_id: Option<String>,
884 seq: Seq,
885 },
886 Cast {
889 entity_id: EntityId,
890 ability_id: String,
891 target_id: EntityId,
892 #[serde(default)]
893 target_point: Option<AimPoint>,
894 seq: Seq,
895 },
896 BindActionSlot {
898 entity_id: EntityId,
899 slot_index: u8,
900 ability_id: String,
901 #[serde(default = "default_auto_attack")]
902 auto_enabled: bool,
903 seq: Seq,
904 },
905 UseActionSlot {
907 entity_id: EntityId,
908 slot_index: u8,
909 seq: Seq,
910 },
911 Dodge {
913 entity_id: EntityId,
914 seq: Seq,
915 },
916 Lunge {
918 entity_id: EntityId,
919 #[serde(default)]
921 forward: f32,
922 #[serde(default)]
924 strafe: f32,
925 seq: Seq,
926 },
927 DirectionalJump {
929 entity_id: EntityId,
930 #[serde(default)]
932 forward: f32,
933 #[serde(default)]
935 strafe: f32,
936 seq: Seq,
937 },
938 Block {
940 entity_id: EntityId,
941 #[serde(default = "default_block_enabled")]
942 enabled: bool,
943 seq: Seq,
944 },
945 EquipMainhand {
949 entity_id: EntityId,
950 #[serde(default)]
951 template_id: Option<String>,
952 #[serde(default)]
953 instance_id: Option<Uuid>,
954 seq: Seq,
955 },
956 EquipOffhand {
958 entity_id: EntityId,
959 #[serde(default)]
960 template_id: Option<String>,
961 #[serde(default)]
962 instance_id: Option<Uuid>,
963 seq: Seq,
964 },
965 EquipWorn {
968 entity_id: EntityId,
969 slot: BodySlot,
970 #[serde(default)]
971 instance_id: Option<Uuid>,
972 seq: Seq,
973 },
974 MoveItem {
976 entity_id: EntityId,
977 item_instance_id: Uuid,
978 from: InventoryLocation,
979 to: InventoryLocation,
980 #[serde(default)]
982 to_parent_instance_id: Option<Uuid>,
983 #[serde(default)]
985 quantity: Option<u32>,
986 seq: Seq,
987 },
988 PlaceContainer {
990 entity_id: EntityId,
991 item_instance_id: Uuid,
992 seq: Seq,
993 },
994 PickupContainer {
996 entity_id: EntityId,
997 container_id: String,
998 seq: Seq,
999 },
1000 MovePlacedContainer {
1002 entity_id: EntityId,
1003 container_id: String,
1004 x: f32,
1005 y: f32,
1006 seq: Seq,
1007 },
1008 SetContainerLocked {
1010 entity_id: EntityId,
1011 location: InventoryLocation,
1013 locked: bool,
1014 seq: Seq,
1015 },
1016 DropItem {
1018 entity_id: EntityId,
1019 item_instance_id: Uuid,
1020 from: InventoryLocation,
1021 seq: Seq,
1022 },
1023 DestroyItem {
1025 entity_id: EntityId,
1026 item_instance_id: Uuid,
1027 from: InventoryLocation,
1028 #[serde(default)]
1030 quantity: Option<u32>,
1031 seq: Seq,
1032 },
1033 RenameContainer {
1035 entity_id: EntityId,
1036 item_instance_id: Uuid,
1037 location: InventoryLocation,
1038 name: String,
1039 seq: Seq,
1040 },
1041 UpsertRotationPreset {
1043 entity_id: EntityId,
1044 preset: RotationPreset,
1045 seq: Seq,
1046 },
1047 DeleteRotationPreset {
1049 entity_id: EntityId,
1050 preset_id: String,
1051 seq: Seq,
1052 },
1053 AssignSlotPreset {
1055 entity_id: EntityId,
1056 slot_index: u8,
1057 preset_id: String,
1058 seq: Seq,
1059 },
1060 SetHotbarSlot {
1063 entity_id: EntityId,
1064 slot: u8,
1066 #[serde(default)]
1068 ability_id: Option<String>,
1069 seq: Seq,
1070 },
1071 AdvanceRotation {
1073 entity_id: EntityId,
1074 slot_index: u8,
1075 seq: Seq,
1076 },
1077 NpcTalkOpen {
1079 entity_id: EntityId,
1080 npc_id: String,
1081 seq: Seq,
1082 },
1083 NpcTalkSay {
1085 entity_id: EntityId,
1086 npc_id: String,
1087 message: String,
1088 seq: Seq,
1089 },
1090 NpcTalkClose {
1092 entity_id: EntityId,
1093 npc_id: String,
1094 seq: Seq,
1095 },
1096 AcceptQuest {
1098 entity_id: EntityId,
1099 quest_id: String,
1100 seq: Seq,
1101 },
1102 WithdrawQuest {
1104 entity_id: EntityId,
1105 quest_id: String,
1106 seq: Seq,
1107 },
1108 TrackQuest {
1110 entity_id: EntityId,
1111 quest_id: String,
1112 seq: Seq,
1113 },
1114 QuestGiveItem {
1116 entity_id: EntityId,
1117 npc_id: String,
1118 template_id: String,
1119 #[serde(default = "default_one")]
1120 quantity: u32,
1121 seq: Seq,
1122 },
1123 HireWorker {
1125 entity_id: EntityId,
1126 def_id: String,
1127 wage_copper_per_interval: u32,
1128 #[serde(default)]
1129 lodging_container_id: Option<String>,
1130 #[serde(default)]
1131 job_yaml: Option<String>,
1132 seq: Seq,
1133 },
1134 DismissWorker {
1136 entity_id: EntityId,
1137 worker_instance_id: String,
1138 seq: Seq,
1139 },
1140 SetWorkerJob {
1142 entity_id: EntityId,
1143 worker_instance_id: String,
1144 job_yaml: String,
1145 seq: Seq,
1146 },
1147 AssignWorkerLodging {
1149 entity_id: EntityId,
1150 worker_instance_id: String,
1151 lodging_container_id: String,
1152 seq: Seq,
1153 },
1154 SetWorkerMode {
1156 entity_id: EntityId,
1157 worker_instance_id: String,
1158 mode: String,
1159 seq: Seq,
1160 },
1161 GiveWorkerItem {
1164 entity_id: EntityId,
1165 worker_instance_id: String,
1166 item_instance_id: uuid::Uuid,
1167 #[serde(default)]
1168 quantity: Option<u32>,
1169 seq: Seq,
1170 },
1171 TakeWorkerItem {
1173 entity_id: EntityId,
1174 worker_instance_id: String,
1175 item_instance_id: uuid::Uuid,
1176 #[serde(default)]
1177 quantity: Option<u32>,
1178 seq: Seq,
1179 },
1180 RenameHiredWorker {
1182 entity_id: EntityId,
1183 worker_instance_id: String,
1184 name: String,
1185 seq: Seq,
1186 },
1187 TeachWorkerBlueprint {
1189 entity_id: EntityId,
1190 worker_instance_id: String,
1191 blueprint_id: String,
1192 seq: Seq,
1193 },
1194 AttendHiredWorker {
1196 entity_id: EntityId,
1197 worker_instance_id: String,
1198 attending: bool,
1199 seq: Seq,
1200 },
1201 BuyPlot {
1203 entity_id: EntityId,
1204 zone_id: String,
1205 x0: f32,
1206 y0: f32,
1207 x1: f32,
1208 y1: f32,
1209 seq: Seq,
1210 },
1211 BuyPlotAllFree {
1213 entity_id: EntityId,
1214 zone_id: String,
1215 seq: Seq,
1216 },
1217 SellPlotToCrown {
1219 entity_id: EntityId,
1220 plot_id: Uuid,
1221 seq: Seq,
1222 },
1223 Cultivate {
1225 entity_id: EntityId,
1226 x: f32,
1228 y: f32,
1229 seq: Seq,
1230 },
1231 PlantSeeds {
1233 entity_id: EntityId,
1234 seed_template_id: String,
1235 quantity: u32,
1236 seq: Seq,
1237 },
1238 SetPlotFarmPublic {
1240 entity_id: EntityId,
1241 plot_id: Uuid,
1242 public: bool,
1243 #[serde(default)]
1244 public_tax_discount_bps: u32,
1245 seq: Seq,
1246 },
1247 PlotFarmAllowUpsert {
1249 entity_id: EntityId,
1250 plot_id: Uuid,
1251 #[serde(default)]
1253 character_id: Option<Uuid>,
1254 #[serde(default)]
1256 character_name: String,
1257 #[serde(default)]
1258 tax_discount_bps: u32,
1259 seq: Seq,
1260 },
1261 PlotFarmAllowRemove {
1263 entity_id: EntityId,
1264 plot_id: Uuid,
1265 character_id: Uuid,
1266 seq: Seq,
1267 },
1268 BankDeposit {
1270 entity_id: EntityId,
1271 npc_id: String,
1272 #[serde(default)]
1274 amount_copper: u64,
1275 seq: Seq,
1276 },
1277 BankWithdraw {
1279 entity_id: EntityId,
1280 npc_id: String,
1281 #[serde(default)]
1283 amount_copper: u64,
1284 seq: Seq,
1285 },
1286 BankClose {
1288 entity_id: EntityId,
1289 npc_id: String,
1290 seq: Seq,
1291 },
1292 BankTransfer {
1294 entity_id: EntityId,
1295 npc_id: String,
1296 #[serde(default)]
1298 to_character_id: Option<Uuid>,
1299 #[serde(default)]
1301 to_name: String,
1302 amount_copper: u64,
1304 seq: Seq,
1305 },
1306 StorageStore {
1308 entity_id: EntityId,
1309 npc_id: String,
1310 item_instance_id: Uuid,
1311 #[serde(default)]
1312 quantity: Option<u32>,
1313 seq: Seq,
1314 },
1315 StorageTake {
1317 entity_id: EntityId,
1318 npc_id: String,
1319 item_instance_id: Uuid,
1320 #[serde(default)]
1321 quantity: Option<u32>,
1322 seq: Seq,
1323 },
1324 StorageShip {
1326 entity_id: EntityId,
1327 npc_id: String,
1328 dest_building_id: String,
1329 item_instance_id: Uuid,
1330 #[serde(default)]
1331 quantity: Option<u32>,
1332 seq: Seq,
1333 },
1334 StorageClose {
1336 entity_id: EntityId,
1337 npc_id: String,
1338 seq: Seq,
1339 },
1340 MarketList {
1343 entity_id: EntityId,
1344 npc_id: String,
1345 source: GoodsLocation,
1346 item_instance_id: Uuid,
1347 #[serde(default)]
1348 quantity: Option<u32>,
1349 unit_price_copper: u64,
1350 seq: Seq,
1351 },
1352 MarketReprice {
1354 entity_id: EntityId,
1355 npc_id: String,
1356 listing_id: Uuid,
1357 unit_price_copper: u64,
1358 seq: Seq,
1359 },
1360 MarketDelist {
1362 entity_id: EntityId,
1363 npc_id: String,
1364 listing_id: Uuid,
1365 dest: GoodsLocation,
1366 seq: Seq,
1367 },
1368 MarketBuy {
1370 entity_id: EntityId,
1371 npc_id: String,
1372 listing_id: Uuid,
1373 #[serde(default = "default_one")]
1374 quantity: u32,
1375 dest: GoodsLocation,
1376 seq: Seq,
1377 },
1378 MarketClose {
1380 entity_id: EntityId,
1381 npc_id: String,
1382 seq: Seq,
1383 },
1384 TradeRequest {
1386 entity_id: EntityId,
1387 peer_entity_id: EntityId,
1388 seq: Seq,
1389 },
1390 TradeRespond {
1392 entity_id: EntityId,
1393 peer_entity_id: EntityId,
1394 accept: bool,
1395 seq: Seq,
1396 },
1397 TradePresent {
1399 entity_id: EntityId,
1400 item_instance_id: Uuid,
1401 #[serde(default)]
1402 quantity: Option<u32>,
1403 seq: Seq,
1404 },
1405 TradeUnpresent {
1407 entity_id: EntityId,
1408 item_instance_id: Uuid,
1409 seq: Seq,
1410 },
1411 TradeSetReady {
1413 entity_id: EntityId,
1414 ready: bool,
1415 seq: Seq,
1416 },
1417 TradeCancel {
1419 entity_id: EntityId,
1420 seq: Seq,
1421 },
1422 DestroyWhisperStone {
1424 entity_id: EntityId,
1425 item_instance_id: Uuid,
1426 seq: Seq,
1427 },
1428 StowWhisperStone {
1430 entity_id: EntityId,
1431 item_instance_id: Uuid,
1432 seq: Seq,
1433 },
1434}
1435
1436fn default_block_enabled() -> bool {
1437 true
1438}
1439
1440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1442pub struct StatusEffectHud {
1443 pub effect_id: String,
1444 pub label: String,
1445 #[serde(default)]
1446 pub polarity: String,
1447 #[serde(default)]
1448 pub icon_tile_id: Option<String>,
1449 #[serde(default)]
1451 pub remaining_sec: Option<f32>,
1452}
1453
1454#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1456pub struct CombatTargetHud {
1457 pub entity_id: EntityId,
1458 #[serde(default)]
1459 pub label: String,
1460 #[serde(default)]
1461 pub level: u32,
1462 pub health: f32,
1463 pub health_max: f32,
1464 #[serde(default)]
1465 pub life_state: LifeState,
1466 #[serde(default)]
1467 pub distance_m: f32,
1468 #[serde(default)]
1469 pub statuses: Vec<StatusEffectHud>,
1470}
1471
1472#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1474#[serde(rename_all = "snake_case")]
1475pub enum TimedChannelKind {
1476 #[default]
1477 Cultivate,
1478 Plant,
1479 Harvest,
1480}
1481
1482#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1484pub struct TimedChannelHud {
1485 #[serde(default)]
1486 pub label: String,
1487 #[serde(default)]
1488 pub channel: TimedChannelKind,
1489 #[serde(default)]
1490 pub cell_x: i32,
1491 #[serde(default)]
1492 pub cell_y: i32,
1493 #[serde(default)]
1494 pub ticks_remaining: u64,
1495 #[serde(default)]
1496 pub ticks_total: u64,
1497}
1498
1499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1501pub struct CastProgressHud {
1502 #[serde(default)]
1503 pub ability_id: String,
1504 #[serde(default)]
1505 pub ability_label: String,
1506 #[serde(default)]
1507 pub ticks_remaining: u64,
1508 #[serde(default)]
1509 pub ticks_total: u64,
1510}
1511
1512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1514pub struct AbilityCooldownHud {
1515 #[serde(default)]
1516 pub ability_id: String,
1517 #[serde(default)]
1518 pub label: String,
1519 #[serde(default)]
1520 pub cd_ticks: u64,
1521 #[serde(default)]
1522 pub cd_total_ticks: u64,
1523}
1524
1525#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1527pub struct CombatSlotHud {
1528 pub slot_index: u8,
1529 #[serde(default)]
1530 pub target_entity_id: Option<EntityId>,
1531 #[serde(default)]
1532 pub target_label: Option<String>,
1533 #[serde(default)]
1534 pub target: Option<CombatTargetHud>,
1535 #[serde(default)]
1536 pub preset_id: Option<String>,
1537 #[serde(default)]
1538 pub preset_label: Option<String>,
1539 #[serde(default)]
1540 pub rotation: Vec<String>,
1541 #[serde(default)]
1542 pub rotation_index: u32,
1543 #[serde(default)]
1544 pub next_ability_id: Option<String>,
1545 #[serde(default)]
1546 pub auto_enabled: bool,
1547}
1548
1549#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1551pub struct DefensePieceHud {
1552 pub slot: BodySlot,
1553 pub label: String,
1554 pub template_id: String,
1555 #[serde(default)]
1556 pub armor_physical: f32,
1557 #[serde(default)]
1558 pub resists: Vec<(String, f32)>,
1559}
1560
1561impl Default for DefensePieceHud {
1562 fn default() -> Self {
1563 Self {
1564 slot: BodySlot::Head,
1565 label: String::new(),
1566 template_id: String::new(),
1567 armor_physical: 0.0,
1568 resists: Vec::new(),
1569 }
1570 }
1571}
1572
1573#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1575pub struct DefenseHud {
1576 pub armor_physical: f32,
1577 pub vitality_contribution: f32,
1578 pub total_mitigation_rating: f32,
1579 pub estimated_physical_dr: f32,
1581 #[serde(default)]
1582 pub resists: Vec<(String, f32)>,
1583 #[serde(default)]
1584 pub pieces: Vec<DefensePieceHud>,
1585}
1586
1587#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1589pub struct CombatHud {
1590 pub in_combat: bool,
1591 pub auto_attack: bool,
1593 pub has_los: bool,
1594 pub attack_cd_ticks: u64,
1595 #[serde(default)]
1596 pub ability_id: String,
1597 #[serde(default)]
1598 pub target_entity_id: Option<EntityId>,
1599 #[serde(default)]
1600 pub target_label: Option<String>,
1601 #[serde(default)]
1602 pub max_target_slots: u8,
1603 #[serde(default)]
1604 pub slots: Vec<CombatSlotHud>,
1605 #[serde(default)]
1606 pub rotation_presets: Vec<RotationPreset>,
1607 #[serde(default)]
1608 pub gcd_ticks: u64,
1609 #[serde(default)]
1610 pub mainhand_template_id: Option<String>,
1611 #[serde(default)]
1612 pub mainhand_label: Option<String>,
1613 #[serde(default)]
1614 pub offhand_template_id: Option<String>,
1615 #[serde(default)]
1616 pub offhand_label: Option<String>,
1617 #[serde(default)]
1619 pub mainhand_hand_slots: u8,
1620 #[serde(default)]
1622 pub worn: Vec<(BodySlot, ItemStack)>,
1623 #[serde(default)]
1625 pub defense: Option<DefenseHud>,
1626 #[serde(default)]
1627 pub carry_mass: f32,
1628 #[serde(default)]
1629 pub carry_mass_max: f32,
1630 #[serde(default)]
1631 pub encumbrance: EncumbranceState,
1632 #[serde(default)]
1634 pub keychain: Vec<ItemStack>,
1635 #[serde(default)]
1637 pub whisper_pouch: Vec<ItemStack>,
1638 #[serde(default)]
1639 pub target: Option<CombatTargetHud>,
1640 #[serde(default)]
1641 pub cast: Option<CastProgressHud>,
1642 #[serde(default)]
1644 pub timed_channel: Option<TimedChannelHud>,
1645 #[serde(default)]
1646 pub ability_cooldowns: Vec<AbilityCooldownHud>,
1647 #[serde(default)]
1648 pub blocking_active: bool,
1649 #[serde(default)]
1651 pub progression_xp: Option<ProgressionXp>,
1652 #[serde(default)]
1653 pub progression_baseline: u16,
1654 #[serde(default)]
1655 pub progression_xp_base: f64,
1656 #[serde(default)]
1657 pub progression_xp_growth: f64,
1658 #[serde(default)]
1659 pub attributes: Option<PrimaryAttributes>,
1660 #[serde(default)]
1661 pub skills: Option<PlayerSkills>,
1662 #[serde(default)]
1664 pub statuses: Vec<StatusEffectHud>,
1665 #[serde(default)]
1667 pub known_abilities: Vec<String>,
1668 #[serde(default)]
1670 pub ability_meta: Vec<AbilityMetaHud>,
1671 #[serde(default)]
1674 pub hotbar: Vec<Option<String>>,
1675 #[serde(default)]
1677 pub max_abilities_per_rotation: u8,
1678}
1679
1680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1682pub struct AbilityMetaHud {
1683 pub id: String,
1684 #[serde(default = "default_aim_mode_entity")]
1686 pub aim_mode: String,
1687 #[serde(default)]
1688 pub blast_radius_m: f32,
1689 #[serde(default)]
1690 pub allows_self: bool,
1691 #[serde(default)]
1692 pub is_heal: bool,
1693}
1694
1695fn default_aim_mode_entity() -> String {
1696 "entity".into()
1697}
1698
1699pub const HOTBAR_ITEM_PREFIX: &str = "item:";
1701
1702pub fn hotbar_consumable_binding(template_id: &str) -> String {
1704 format!("{HOTBAR_ITEM_PREFIX}{}", template_id.trim())
1705}
1706
1707pub fn hotbar_consumable_template(binding: &str) -> Option<&str> {
1709 binding
1710 .strip_prefix(HOTBAR_ITEM_PREFIX)
1711 .map(str::trim)
1712 .filter(|id| !id.is_empty())
1713}
1714
1715pub fn hotbar_binding_is_consumable(binding: &str) -> bool {
1717 hotbar_consumable_template(binding).is_some()
1718}
1719
1720#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1722#[serde(rename_all = "snake_case")]
1723pub enum CombatFxKind {
1724 MeleeArc,
1725 Cone,
1726 Sphere,
1727 Beam,
1728 HitMarker,
1729}
1730
1731#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1733#[serde(rename_all = "snake_case")]
1734pub enum CombatFxHitOutcome {
1735 #[default]
1736 Hit,
1737 Blocked,
1738 Miss,
1739 Glance,
1740}
1741
1742#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1744pub struct CombatFxHit {
1745 pub entity_id: EntityId,
1746 pub x: f32,
1747 pub y: f32,
1748 pub z: f32,
1749 #[serde(default)]
1750 pub outcome: CombatFxHitOutcome,
1751}
1752
1753#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1755pub struct CombatFx {
1756 pub id: u64,
1757 pub kind: CombatFxKind,
1758 pub ability_id: String,
1759 pub caster_id: EntityId,
1760 pub origin_x: f32,
1761 pub origin_y: f32,
1762 pub origin_z: f32,
1763 #[serde(default)]
1764 pub end_x: Option<f32>,
1765 #[serde(default)]
1766 pub end_y: Option<f32>,
1767 #[serde(default)]
1768 pub end_z: Option<f32>,
1769 #[serde(default)]
1770 pub yaw: Option<f32>,
1771 #[serde(default)]
1772 pub reach_m: Option<f32>,
1773 #[serde(default)]
1774 pub arc_deg: Option<f32>,
1775 #[serde(default)]
1776 pub radius_m: Option<f32>,
1777 #[serde(default)]
1778 pub hits: Vec<CombatFxHit>,
1779 pub until_tick: u64,
1781 #[serde(default)]
1782 pub damage_type: String,
1783}
1784
1785#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1787#[serde(rename_all = "snake_case")]
1788pub enum WorkerModeView {
1789 Companion,
1790 JobLoop,
1791 Idle,
1794}
1795
1796#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1798#[serde(rename_all = "snake_case")]
1799pub enum WorkerStateView {
1800 Idle,
1801 Traveling,
1802 Working,
1803 Resting,
1804 Waiting,
1805 Strike,
1806 Dismissed,
1807}
1808
1809#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
1811pub struct WorkerVitalsSummary {
1812 pub health_pct: f32,
1813 pub stamina_pct: f32,
1814}
1815
1816#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1818#[serde(rename_all = "snake_case")]
1819pub enum WorkerRouteKindView {
1820 #[default]
1821 HarvestLoop,
1822 Ordered,
1823}
1824
1825#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1827pub struct WorkerRouteView {
1828 #[serde(default)]
1829 pub kind: WorkerRouteKindView,
1830 #[serde(default)]
1831 pub lodging_container_id: Option<String>,
1832 #[serde(default)]
1834 pub outbound_waypoints: Vec<WorkerRouteWaypointView>,
1835 #[serde(default)]
1837 pub harvest_nodes: Vec<String>,
1838 #[serde(default = "default_route_carry_ratio")]
1839 pub carry_return_ratio: f32,
1840 #[serde(default)]
1842 pub stops: Vec<WorkerRouteStopView>,
1843}
1844
1845fn default_route_carry_ratio() -> f32 {
1846 0.90
1847}
1848
1849fn default_true_view() -> bool {
1850 true
1851}
1852
1853#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1855pub struct WorkerWithdrawItemView {
1856 pub template: String,
1857 #[serde(default)]
1859 pub qty: u32,
1860 #[serde(default)]
1862 pub all: bool,
1863}
1864
1865#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1866pub struct WorkerRouteWaypointView {
1867 pub x: f32,
1868 pub y: f32,
1869 pub z: f32,
1870}
1871
1872#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1881#[serde(rename_all = "snake_case")]
1882pub enum WorkerRouteStopView {
1883 Waypoint {
1884 x: f32,
1885 y: f32,
1886 #[serde(default)]
1887 z: f32,
1888 },
1889 HarvestNode {
1890 node_id: String,
1891 },
1892 DepositAt {
1893 container_id: String,
1894 #[serde(default)]
1895 filter: Option<Vec<String>>,
1896 },
1897 TradeWith {
1898 #[serde(default)]
1899 npc_id: Option<String>,
1900 template: String,
1901 #[serde(default = "default_true_view")]
1902 sell_all: bool,
1903 },
1904 WithdrawFrom {
1905 container_id: String,
1906 items: Vec<WorkerWithdrawItemView>,
1907 },
1908 CraftAt {
1909 device: String,
1910 blueprint: String,
1911 #[serde(default)]
1912 qty: Option<u32>,
1913 },
1914 CultivatePlot {
1915 plot_id: uuid::Uuid,
1916 },
1917 PlantPlot {
1918 plot_id: uuid::Uuid,
1919 seed_template: String,
1920 },
1921 HarvestPlot {
1922 plot_id: uuid::Uuid,
1923 },
1924 RestIfNeeded,
1925 Wait {
1926 #[serde(default)]
1927 wait_ticks: u64,
1928 },
1929}
1930
1931#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1933#[serde(rename_all = "snake_case")]
1934pub enum LedgerCategory {
1935 Workers,
1936 Hire,
1937 Train,
1938 ShopBuy,
1939 Taxes,
1940 WorkerSales,
1941 TraderSales,
1942 BankDeposit,
1943 BankWithdraw,
1944 BankTransferOut,
1945 BankTransferIn,
1946 BankTransferFee,
1947 StorageShipFee,
1948 PropertyBuy,
1950 PropertySell,
1952 TaxShare,
1954 MarketBuy,
1956 MarketSell,
1958 Other,
1959}
1960
1961impl LedgerCategory {
1962 pub fn as_str(self) -> &'static str {
1963 match self {
1964 Self::Workers => "workers",
1965 Self::Hire => "hire",
1966 Self::Train => "train",
1967 Self::ShopBuy => "shop_buy",
1968 Self::Taxes => "taxes",
1969 Self::WorkerSales => "worker_sales",
1970 Self::TraderSales => "trader_sales",
1971 Self::BankDeposit => "bank_deposit",
1972 Self::BankWithdraw => "bank_withdraw",
1973 Self::BankTransferOut => "bank_transfer_out",
1974 Self::BankTransferIn => "bank_transfer_in",
1975 Self::BankTransferFee => "bank_transfer_fee",
1976 Self::StorageShipFee => "storage_ship_fee",
1977 Self::PropertyBuy => "property_buy",
1978 Self::PropertySell => "property_sell",
1979 Self::TaxShare => "tax_share",
1980 Self::MarketBuy => "market_buy",
1981 Self::MarketSell => "market_sell",
1982 Self::Other => "other",
1983 }
1984 }
1985}
1986
1987#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1988pub struct LedgerEntryView {
1989 pub id: uuid::Uuid,
1990 pub game_day: u64,
1991 pub signed_copper: i64,
1992 pub category: LedgerCategory,
1993 #[serde(default)]
1994 pub label: String,
1995}
1996
1997#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1998pub struct LedgerPeriodTotals {
1999 #[serde(default)]
2001 pub expenses: std::collections::HashMap<String, u64>,
2002 #[serde(default)]
2004 pub income: std::collections::HashMap<String, u64>,
2005 pub expense_copper: u64,
2006 pub income_copper: u64,
2007 pub cash_flow_copper: i64,
2009}
2010
2011#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2012pub struct PlayerLedgerView {
2013 pub current_game_day: u64,
2014 #[serde(default)]
2015 pub period_day: LedgerPeriodTotals,
2016 #[serde(default)]
2017 pub period_week: LedgerPeriodTotals,
2018 #[serde(default)]
2019 pub period_month: LedgerPeriodTotals,
2020 #[serde(default)]
2021 pub period_lifetime: LedgerPeriodTotals,
2022 #[serde(default)]
2023 pub recent: Vec<LedgerEntryView>,
2024 #[serde(default)]
2026 pub wealth_on_person_copper: u64,
2027 #[serde(default)]
2029 pub wealth_in_storage_copper: u64,
2030 #[serde(default)]
2032 pub wealth_in_bank_copper: u64,
2033 #[serde(default)]
2035 pub wealth_total_copper: u64,
2036 #[serde(default)]
2038 pub wealth_in_property_copper: u64,
2039 #[serde(default)]
2041 pub wealth_net_worth_copper: u64,
2042 #[serde(default)]
2044 pub property_assets: Vec<PropertyAssetView>,
2045 #[serde(default)]
2047 pub property_market_nearby: Vec<PropertyMarketCompView>,
2048 #[serde(default)]
2050 pub live_expense_per_interval_copper: u64,
2051 #[serde(default)]
2053 pub live_income_route_est_per_loop_copper: u64,
2054 #[serde(default)]
2056 pub live_income_avg_per_interval_copper: u64,
2057 #[serde(default)]
2059 pub live_income_avg_window_intervals: u32,
2060 #[serde(default)]
2062 pub live_net_avg_per_interval_copper: i64,
2063}
2064
2065#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2067pub struct PropertyAssetView {
2068 pub plot_id: Uuid,
2069 pub label: String,
2071 pub zone_id: String,
2072 #[serde(default)]
2073 pub zone_label: Option<String>,
2074 pub area_m2: f32,
2075 pub purchase_basis_copper: u64,
2077 pub upkeep_copper_per_day: u64,
2078}
2079
2080#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2082pub struct PropertyMarketCompView {
2083 pub day: u64,
2084 pub zone_id: String,
2085 #[serde(default)]
2086 pub zone_label: Option<String>,
2087 pub area_m2: f32,
2088 pub price_copper: u64,
2089 pub price_per_m2_copper: u64,
2091 pub kind: String,
2093 pub distance_m: f32,
2095}
2096
2097#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2099#[serde(rename_all = "snake_case")]
2100pub enum AnalyticsMetric {
2101 NpcKill,
2102 WildlifeKill,
2103 Harvest,
2104 QuestComplete,
2105 QuestAccept,
2106 QuestAbandon,
2107 PlayerDeath,
2108 Craft,
2109 WorkerHire,
2110 WorkerDismiss,
2111 WorkerTeach,
2112 NpcTalk,
2113 ShopBuy,
2114 ShopSell,
2115 PlaceContainer,
2116 PickupContainer,
2117 PickupDrop,
2118 ConsumableUse,
2119 AbilityUse,
2120 DistanceWalkedM,
2121 DoorUse,
2122 BuildingEnter,
2123}
2124
2125impl AnalyticsMetric {
2126 pub fn as_str(self) -> &'static str {
2127 match self {
2128 Self::NpcKill => "npc_kill",
2129 Self::WildlifeKill => "wildlife_kill",
2130 Self::Harvest => "harvest",
2131 Self::QuestComplete => "quest_complete",
2132 Self::QuestAccept => "quest_accept",
2133 Self::QuestAbandon => "quest_abandon",
2134 Self::PlayerDeath => "player_death",
2135 Self::Craft => "craft",
2136 Self::WorkerHire => "worker_hire",
2137 Self::WorkerDismiss => "worker_dismiss",
2138 Self::WorkerTeach => "worker_teach",
2139 Self::NpcTalk => "npc_talk",
2140 Self::ShopBuy => "shop_buy",
2141 Self::ShopSell => "shop_sell",
2142 Self::PlaceContainer => "place_container",
2143 Self::PickupContainer => "pickup_container",
2144 Self::PickupDrop => "pickup_drop",
2145 Self::ConsumableUse => "consumable_use",
2146 Self::AbilityUse => "ability_use",
2147 Self::DistanceWalkedM => "distance_walked_m",
2148 Self::DoorUse => "door_use",
2149 Self::BuildingEnter => "building_enter",
2150 }
2151 }
2152
2153 pub fn from_str_key(s: &str) -> Option<Self> {
2154 Some(match s {
2155 "npc_kill" => Self::NpcKill,
2156 "wildlife_kill" => Self::WildlifeKill,
2157 "harvest" => Self::Harvest,
2158 "quest_complete" => Self::QuestComplete,
2159 "quest_accept" => Self::QuestAccept,
2160 "quest_abandon" => Self::QuestAbandon,
2161 "player_death" => Self::PlayerDeath,
2162 "craft" => Self::Craft,
2163 "worker_hire" => Self::WorkerHire,
2164 "worker_dismiss" => Self::WorkerDismiss,
2165 "worker_teach" => Self::WorkerTeach,
2166 "npc_talk" => Self::NpcTalk,
2167 "shop_buy" => Self::ShopBuy,
2168 "shop_sell" => Self::ShopSell,
2169 "place_container" => Self::PlaceContainer,
2170 "pickup_container" => Self::PickupContainer,
2171 "pickup_drop" => Self::PickupDrop,
2172 "consumable_use" => Self::ConsumableUse,
2173 "ability_use" => Self::AbilityUse,
2174 "distance_walked_m" => Self::DistanceWalkedM,
2175 "door_use" => Self::DoorUse,
2176 "building_enter" => Self::BuildingEnter,
2177 _ => return None,
2178 })
2179 }
2180}
2181
2182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2183pub struct CareerMetricRow {
2184 pub subject_id: String,
2185 pub amount: u64,
2186}
2187
2188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2190pub struct PlayerCareerView {
2191 pub current_game_day: u64,
2192 #[serde(default)]
2193 pub kills: Vec<CareerMetricRow>,
2194 #[serde(default)]
2195 pub harvests: Vec<CareerMetricRow>,
2196 pub quests_completed: u64,
2197 #[serde(default)]
2198 pub crafts: Vec<CareerMetricRow>,
2199 pub deaths: u64,
2200 pub npc_talks: u64,
2201 pub shop_buys: u64,
2202 pub shop_sells: u64,
2203 pub distance_m: u64,
2204 #[serde(default)]
2205 pub other: Vec<CareerMetricRow>,
2206}
2207
2208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2210pub struct HiredWorkerView {
2211 pub instance_id: String,
2212 pub entity_id: EntityId,
2213 pub def_id: String,
2214 pub label: String,
2216 pub x: f32,
2217 pub y: f32,
2218 pub z: f32,
2219 pub mode: WorkerModeView,
2220 pub state: WorkerStateView,
2221 #[serde(default)]
2222 pub step_label: String,
2223 pub vitals: WorkerVitalsSummary,
2224 #[serde(default)]
2225 pub carry_pct: f32,
2226 #[serde(default)]
2227 pub last_error: Option<String>,
2228 pub wage_copper_per_interval: u32,
2229 #[serde(default)]
2231 pub effective_wage_copper: u32,
2232 #[serde(default)]
2234 pub wage_meters_walked: f32,
2235 #[serde(default)]
2237 pub lodging_container_id: Option<String>,
2238 #[serde(default)]
2240 pub route: Option<WorkerRouteView>,
2241 #[serde(default)]
2244 pub route_stop_index: Option<u32>,
2245 #[serde(default)]
2247 pub known_blueprint_ids: Vec<String>,
2248 #[serde(default = "default_worker_view_level")]
2250 pub level: u32,
2251 #[serde(default)]
2253 pub worker_xp: f64,
2254 #[serde(default)]
2256 pub inventory: Vec<ItemStack>,
2257}
2258
2259fn default_worker_view_level() -> u32 {
2260 1
2261}
2262
2263#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2265pub struct TickDelta {
2266 pub tick: Tick,
2267 pub entities: Vec<EntityState>,
2268 #[serde(default)]
2269 pub resource_nodes: Vec<ResourceNodeView>,
2270 #[serde(default)]
2271 pub buildings: Vec<BuildingView>,
2272 #[serde(default)]
2273 pub doors: Vec<DoorView>,
2274 #[serde(default)]
2275 pub npcs: Vec<NpcView>,
2276 #[serde(default)]
2278 pub inventory: Vec<ItemStack>,
2279 #[serde(default)]
2280 pub blueprints: Vec<BlueprintView>,
2281 #[serde(default)]
2282 pub world_clock: WorldClock,
2283 #[serde(default)]
2284 pub ground_drops: Vec<GroundDropView>,
2285 #[serde(default)]
2286 pub placed_containers: Vec<PlacedContainerView>,
2287 #[serde(default)]
2288 pub combat: Option<CombatHud>,
2289 #[serde(default)]
2290 pub interior_map: Option<InteriorMapView>,
2291 #[serde(default)]
2292 pub quest_log: Vec<QuestLogEntry>,
2293 #[serde(default)]
2294 pub hired_workers: Vec<HiredWorkerView>,
2295 #[serde(default)]
2296 pub interactables: Vec<InteractableView>,
2297 #[serde(default)]
2298 pub ledger: Option<PlayerLedgerView>,
2299 #[serde(default)]
2300 pub career: Option<PlayerCareerView>,
2301 #[serde(default)]
2303 pub combat_fx: Vec<CombatFx>,
2304 #[serde(default)]
2306 pub property_plots: Vec<PropertyPlotView>,
2307 #[serde(default)]
2309 pub terrain_overlays: Vec<TerrainZoneView>,
2310}
2311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2312pub struct GroundDropView {
2313 pub id: String,
2314 pub template_id: String,
2315 pub quantity: u32,
2316 pub x: f32,
2317 pub y: f32,
2318 pub z: f32,
2319 #[serde(default)]
2321 pub tile_id: Option<String>,
2322 #[serde(default)]
2324 pub display_name: Option<String>,
2325 #[serde(default)]
2327 pub yaw: f32,
2328 #[serde(default)]
2330 pub pitch: f32,
2331 #[serde(default)]
2333 pub roll: f32,
2334 #[serde(default = "default_draw_scale")]
2336 pub draw_scale: f32,
2337}
2338
2339fn default_draw_scale() -> f32 {
2340 1.0
2341}
2342
2343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2345pub struct Snapshot {
2346 pub tick: Tick,
2347 pub chunk_rev: u64,
2348 #[serde(default)]
2350 pub content_rev: u64,
2351 #[serde(default)]
2353 pub publish_rev: u64,
2354 pub entities: Vec<EntityState>,
2355 #[serde(default)]
2356 pub resource_nodes: Vec<ResourceNodeView>,
2357 #[serde(default)]
2359 pub world_x0: f32,
2360 #[serde(default)]
2361 pub world_y0: f32,
2362 #[serde(default)]
2364 pub world_width_m: f32,
2365 #[serde(default)]
2366 pub world_height_m: f32,
2367 #[serde(default)]
2368 pub buildings: Vec<BuildingView>,
2369 #[serde(default)]
2370 pub doors: Vec<DoorView>,
2371 #[serde(default)]
2372 pub npcs: Vec<NpcView>,
2373 #[serde(default)]
2374 pub inventory: Vec<ItemStack>,
2375 #[serde(default)]
2376 pub blueprints: Vec<BlueprintView>,
2377 #[serde(default)]
2378 pub world_clock: WorldClock,
2379 #[serde(default)]
2380 pub terrain_zones: Vec<TerrainZoneView>,
2381 #[serde(default)]
2382 pub z_platforms: Vec<ZPlatformView>,
2383 #[serde(default)]
2384 pub z_transitions: Vec<ZTransitionView>,
2385 #[serde(default)]
2386 pub ground_drops: Vec<GroundDropView>,
2387 #[serde(default)]
2388 pub placed_containers: Vec<PlacedContainerView>,
2389 #[serde(default)]
2390 pub combat: Option<CombatHud>,
2391 #[serde(default)]
2392 pub interior_map: Option<InteriorMapView>,
2393 #[serde(default)]
2394 pub quest_log: Vec<QuestLogEntry>,
2395 #[serde(default)]
2396 pub hired_workers: Vec<HiredWorkerView>,
2397 #[serde(default)]
2398 pub interactables: Vec<InteractableView>,
2399 #[serde(default)]
2400 pub ledger: Option<PlayerLedgerView>,
2401 #[serde(default)]
2402 pub career: Option<PlayerCareerView>,
2403 #[serde(default)]
2405 pub combat_fx: Vec<CombatFx>,
2406 #[serde(default)]
2408 pub property_zones: Vec<PropertyZoneView>,
2409 #[serde(default)]
2411 pub tax_zones: Vec<TaxZoneView>,
2412 #[serde(default)]
2414 pub boundary_zones: Vec<BoundaryZoneView>,
2415 #[serde(default)]
2417 pub growth_zones: Vec<GrowthZoneView>,
2418 #[serde(default)]
2420 pub biome_zones: Vec<BiomeZoneView>,
2421 #[serde(default)]
2423 pub property_plots: Vec<PropertyPlotView>,
2424 #[serde(default)]
2426 pub property_plot_settings: Option<PropertyPlotSettingsView>,
2427}
2428
2429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2431pub struct ResourceNodeView {
2432 pub id: String,
2433 pub label: String,
2434 pub x: f32,
2435 pub y: f32,
2436 pub z: f32,
2437 pub item_template: String,
2438 #[serde(default = "default_node_state")]
2439 pub state: ResourceNodeState,
2440 #[serde(default = "default_blocking_view")]
2442 pub blocking: bool,
2443 #[serde(default = "default_blocking_radius_view")]
2445 pub blocking_radius_m: f32,
2446 #[serde(default)]
2448 pub tile_id: Option<String>,
2449 #[serde(default)]
2451 pub yaw: f32,
2452 #[serde(default)]
2454 pub pitch: f32,
2455 #[serde(default)]
2457 pub roll: f32,
2458 #[serde(default = "default_draw_scale")]
2460 pub draw_scale: f32,
2461 #[serde(default)]
2463 pub sprite_mode: Option<String>,
2464 #[serde(default)]
2466 pub presentation_state: Option<String>,
2467 #[serde(default)]
2470 pub growth_progress: Option<f32>,
2471 #[serde(default)]
2473 pub channel_start_tick: Option<Tick>,
2474 #[serde(default)]
2475 pub channel_end_tick: Option<Tick>,
2476 #[serde(default)]
2478 pub harvest_drop_templates: Vec<String>,
2479}
2480
2481fn default_blocking_radius_view() -> f32 {
2482 0.8
2483}
2484
2485fn default_blocking_view() -> bool {
2486 true
2487}
2488
2489#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2490#[serde(rename_all = "snake_case")]
2491pub enum ResourceNodeState {
2492 Available,
2493 Harvesting,
2494 Cooldown,
2495}
2496fn default_node_state() -> ResourceNodeState {
2497 ResourceNodeState::Available
2498}
2499
2500#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2502#[serde(rename_all = "snake_case")]
2503pub enum ItemSpawnStateView {
2504 Spawned,
2505 PickedUp {
2506 respawn_at_tick: u64,
2507 },
2508 Consumed,
2509}
2510
2511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2513pub struct ItemSpawnView {
2514 pub id: String,
2515 pub label: String,
2516 pub item_template: String,
2517 pub quantity: u32,
2518 pub x: f32,
2519 pub y: f32,
2520 pub z: f32,
2521 pub respawn_ticks: u32,
2522 #[serde(default)]
2523 pub building_id: Option<String>,
2524 pub state: ItemSpawnStateView,
2525}
2526
2527#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2528#[serde(rename_all = "snake_case")]
2529pub enum ItemStatusBindingMode {
2530 OnHit,
2531 WhileEquipped,
2532}
2533
2534impl Default for ItemStatusBindingMode {
2535 fn default() -> Self {
2536 Self::OnHit
2537 }
2538}
2539
2540#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2542pub struct ItemStatusBinding {
2543 pub effect_id: String,
2544 #[serde(default)]
2545 pub mode: ItemStatusBindingMode,
2546 #[serde(default)]
2548 pub source: String,
2549 #[serde(default)]
2550 pub applied_at_tick: u64,
2551 #[serde(default, skip_serializing_if = "Option::is_none")]
2553 pub expires_at_tick: Option<u64>,
2554}
2555
2556#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2557pub struct ItemStack {
2558 pub template_id: String,
2559 pub quantity: u32,
2560 #[serde(default)]
2562 pub item_instance_id: Option<Uuid>,
2563 #[serde(default)]
2565 pub props: BTreeMap<String, String>,
2566 #[serde(default)]
2568 pub status_bindings: Vec<ItemStatusBinding>,
2569 #[serde(default)]
2571 pub contents: Vec<ItemStack>,
2572 #[serde(default)]
2574 pub display_name: Option<String>,
2575 #[serde(default)]
2577 pub category: Option<String>,
2578 #[serde(default)]
2580 pub base_mass: Option<f32>,
2581 #[serde(default)]
2583 pub base_volume: Option<f32>,
2584 #[serde(default)]
2586 pub capacity_volume: Option<f32>,
2587 #[serde(default)]
2589 pub stackable: Option<bool>,
2590 #[serde(default)]
2592 pub world_placeable: Option<bool>,
2593 #[serde(default)]
2595 pub worker_lodging_capacity: Option<u32>,
2596 #[serde(default)]
2598 pub equip_slot: Option<BodySlot>,
2599 #[serde(default)]
2601 pub armor_physical: Option<f32>,
2602 #[serde(default)]
2604 pub resists: Vec<(String, f32)>,
2605 #[serde(default)]
2607 pub hand_slots: Option<u8>,
2608 #[serde(default)]
2610 pub listable: Option<bool>,
2611}
2612
2613impl ItemStack {
2614 pub fn simple(template_id: impl Into<String>, quantity: u32) -> Self {
2615 Self {
2616 template_id: template_id.into(),
2617 quantity,
2618 ..Default::default()
2619 }
2620 }
2621}
2622
2623impl Default for ItemStack {
2624 fn default() -> Self {
2625 Self {
2626 template_id: String::new(),
2627 quantity: 0,
2628 item_instance_id: None,
2629 props: BTreeMap::new(),
2630 status_bindings: Vec::new(),
2631 contents: Vec::new(),
2632 display_name: None,
2633 category: None,
2634 base_mass: None,
2635 base_volume: None,
2636 capacity_volume: None,
2637 stackable: None,
2638 world_placeable: None,
2639 worker_lodging_capacity: None,
2640 equip_slot: None,
2641 armor_physical: None,
2642 resists: Vec::new(),
2643 hand_slots: None,
2644 listable: None,
2645 }
2646 }
2647}
2648
2649#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2651#[serde(rename_all = "snake_case")]
2652pub enum EncumbranceState {
2653 #[default]
2654 Light,
2655 Heavy,
2656 Over,
2657}
2658
2659#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
2663#[serde(rename_all = "snake_case")]
2664pub enum BodySlot {
2665 Head,
2666 #[serde(alias = "body")]
2668 Chest,
2669 #[serde(alias = "arms")]
2671 Forearms,
2672 Legs,
2673 Feet,
2674 Cloak,
2675 Back,
2676 Waist,
2677 Earrings,
2678 Necklace,
2679 Eyeglasses,
2680 #[serde(rename = "ring_left_1", alias = "ring_left1")]
2682 RingLeft1,
2683 #[serde(rename = "ring_left_2", alias = "ring_left2")]
2684 RingLeft2,
2685 #[serde(rename = "ring_right_1", alias = "ring_right1")]
2686 RingRight1,
2687 #[serde(rename = "ring_right_2", alias = "ring_right2")]
2688 RingRight2,
2689}
2690
2691impl BodySlot {
2692 pub const ALL: [BodySlot; 15] = [
2694 BodySlot::Head,
2695 BodySlot::Chest,
2696 BodySlot::Forearms,
2697 BodySlot::Legs,
2698 BodySlot::Feet,
2699 BodySlot::Cloak,
2700 BodySlot::Back,
2701 BodySlot::Waist,
2702 BodySlot::Earrings,
2703 BodySlot::Necklace,
2704 BodySlot::Eyeglasses,
2705 BodySlot::RingLeft1,
2706 BodySlot::RingLeft2,
2707 BodySlot::RingRight1,
2708 BodySlot::RingRight2,
2709 ];
2710
2711 pub fn as_str(self) -> &'static str {
2712 match self {
2713 BodySlot::Head => "head",
2714 BodySlot::Chest => "chest",
2715 BodySlot::Forearms => "forearms",
2716 BodySlot::Legs => "legs",
2717 BodySlot::Feet => "feet",
2718 BodySlot::Cloak => "cloak",
2719 BodySlot::Back => "back",
2720 BodySlot::Waist => "waist",
2721 BodySlot::Earrings => "earrings",
2722 BodySlot::Necklace => "necklace",
2723 BodySlot::Eyeglasses => "eyeglasses",
2724 BodySlot::RingLeft1 => "ring_left_1",
2725 BodySlot::RingLeft2 => "ring_left_2",
2726 BodySlot::RingRight1 => "ring_right_1",
2727 BodySlot::RingRight2 => "ring_right_2",
2728 }
2729 }
2730}
2731
2732#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2734#[serde(rename_all = "snake_case")]
2735pub enum InventoryLocation {
2736 Root,
2738 Worn { slot: BodySlot },
2740 Placed { container_id: String },
2742 Keychain,
2744 WhisperPouch,
2746}
2747
2748#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2750pub struct PlacedContainerView {
2751 pub id: String,
2752 pub template_id: String,
2753 pub display_name: String,
2754 pub x: f32,
2755 pub y: f32,
2756 pub z: f32,
2757 pub locked: bool,
2758 #[serde(default)]
2760 pub accessible: bool,
2761 #[serde(default)]
2762 pub owner_character_id: Option<Uuid>,
2763 #[serde(default)]
2765 pub contents: Vec<ItemStack>,
2766 #[serde(default)]
2768 pub lock_id: Option<String>,
2769 #[serde(default)]
2771 pub capacity_volume: Option<f32>,
2772 #[serde(default)]
2774 pub item_instance_id: Option<Uuid>,
2775 #[serde(default)]
2777 pub tile_id: Option<String>,
2778 #[serde(default)]
2780 pub worker_lodging_capacity: Option<u32>,
2781 #[serde(default)]
2783 pub blocking: bool,
2784 #[serde(default)]
2786 pub blocking_radius_m: f32,
2787}
2788
2789#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2790pub struct BlueprintIngredientView {
2791 pub template_id: String,
2792 pub quantity: u32,
2793 pub consumed: bool,
2795}
2796
2797#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2798pub struct ToolRequirementView {
2799 pub item: String,
2800 pub consumed: bool,
2802}
2803
2804#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2805pub struct SkillRequirementView {
2806 pub skill: String,
2807 pub level: u32,
2808}
2809
2810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2811pub struct BlueprintView {
2812 pub id: String,
2813 pub label: String,
2814 pub output: String,
2815 pub output_qty: u32,
2816 pub craft_ticks: u32,
2817 pub inputs: Vec<BlueprintIngredientView>,
2818 #[serde(default)]
2820 pub station: Option<String>,
2821 #[serde(default)]
2822 pub category: Option<String>,
2823 #[serde(default)]
2824 pub required_tools: Vec<ToolRequirementView>,
2825 #[serde(default)]
2826 pub skill: Option<SkillRequirementView>,
2827 #[serde(default)]
2828 pub failure_chance: f32,
2829 #[serde(default)]
2831 pub worker_train_copper: u64,
2832}
2833
2834#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2836#[serde(rename_all = "snake_case")]
2837pub enum TerrainKindView {
2838 #[default]
2839 Grass,
2840 Dirt,
2841 Tilled,
2842 Desert,
2843 Hill,
2844 Bog,
2845 Beach,
2846 ShallowWater,
2847 DeepWater,
2848 Trail,
2849 Road,
2850 Rock,
2851}
2852
2853#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2854pub struct TerrainZoneView {
2855 pub id: String,
2856 pub x0: f32,
2857 pub y0: f32,
2858 pub x1: f32,
2859 pub y1: f32,
2860 #[serde(default)]
2861 pub kind: TerrainKindView,
2862 #[serde(default)]
2864 pub elevation: f32,
2865 #[serde(default)]
2868 pub glyph: Option<String>,
2869 #[serde(default)]
2871 pub color: Option<String>,
2872 #[serde(default)]
2874 pub tile_id: Option<String>,
2875 #[serde(default)]
2877 pub z_order: i32,
2878 #[serde(default)]
2880 pub channel_start_tick: Option<Tick>,
2881 #[serde(default)]
2882 pub channel_end_tick: Option<Tick>,
2883}
2884
2885#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2887pub struct ZoneRectView {
2888 pub x0: f32,
2889 pub y0: f32,
2890 pub x1: f32,
2891 pub y1: f32,
2892}
2893
2894#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2896pub struct PropertyZoneView {
2897 pub id: String,
2898 #[serde(default)]
2900 pub label: Option<String>,
2901 pub rects: Vec<ZoneRectView>,
2902 #[serde(default)]
2903 pub z_order: i32,
2904 pub crown_price_copper: u64,
2905 pub upkeep_copper_per_day: u64,
2906 #[serde(default)]
2907 pub max_area_m2: Option<f32>,
2908 #[serde(default)]
2909 pub owner_tax_discount_bps: u32,
2910}
2911
2912#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2914pub struct TaxZoneView {
2915 pub id: String,
2916 #[serde(default)]
2917 pub label: Option<String>,
2918 pub rects: Vec<ZoneRectView>,
2919 #[serde(default)]
2920 pub z_order: i32,
2921 pub rate_bps: u32,
2922 #[serde(default)]
2923 pub flat_copper: u64,
2924 #[serde(default)]
2926 pub market_sales_tax_bps: u32,
2927 #[serde(default)]
2929 pub market_sales_flat_copper: u32,
2930}
2931
2932#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2934pub struct BoundaryZoneView {
2935 pub id: String,
2936 #[serde(default)]
2937 pub label: Option<String>,
2938 pub rects: Vec<ZoneRectView>,
2939 #[serde(default)]
2940 pub z_order: i32,
2941 #[serde(default, skip_serializing_if = "Option::is_none")]
2942 pub jurisdiction_id: Option<String>,
2943 #[serde(default = "default_true")]
2944 pub worker_logistics: bool,
2945 #[serde(default)]
2946 pub security_tier: String,
2947 #[serde(default)]
2948 pub pvp_mode: String,
2949 #[serde(default = "default_true")]
2950 pub crime_enabled: bool,
2951 #[serde(default)]
2952 pub guard_response: bool,
2953}
2954
2955fn default_true() -> bool {
2956 true
2957}
2958
2959#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2961pub struct GrowthZoneView {
2962 pub id: String,
2963 #[serde(default)]
2964 pub label: Option<String>,
2965 pub rects: Vec<ZoneRectView>,
2966 #[serde(default)]
2967 pub z_order: i32,
2968 #[serde(default = "default_one_f32")]
2969 pub fertility: f32,
2970}
2971
2972fn default_one_f32() -> f32 {
2973 1.0
2974}
2975
2976#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2978pub struct BiomeZoneView {
2979 pub id: String,
2980 #[serde(default)]
2981 pub label: Option<String>,
2982 pub rects: Vec<ZoneRectView>,
2983 #[serde(default)]
2984 pub z_order: i32,
2985 pub biome_id: String,
2986}
2987
2988#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2990pub struct FarmGrantView {
2991 pub character_id: Uuid,
2992 #[serde(default)]
2994 pub character_label: String,
2995 pub tax_discount_bps: u32,
2996}
2997
2998#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3000pub struct PropertyPlotView {
3001 pub plot_id: Uuid,
3002 pub property_zone_id: String,
3003 #[serde(default)]
3004 pub zone_label: Option<String>,
3005 pub deed_instance_id: Uuid,
3006 pub x0: f32,
3007 pub y0: f32,
3008 pub x1: f32,
3009 pub y1: f32,
3010 pub upkeep_copper_per_day: u64,
3011 pub arrears_days: u32,
3012 #[serde(default)]
3014 pub is_mine: bool,
3015 #[serde(default)]
3017 pub may_farm: bool,
3018 #[serde(default)]
3020 pub purchase_basis_copper: u64,
3021 #[serde(default)]
3022 pub farm_public: bool,
3023 #[serde(default)]
3024 pub public_tax_discount_bps: u32,
3025 #[serde(default)]
3026 pub farm_allow: Vec<FarmGrantView>,
3027 #[serde(default)]
3029 pub owner_character_id: Option<Uuid>,
3030 #[serde(default)]
3031 pub owner_label: Option<String>,
3032}
3033
3034#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3036pub struct PropertyPlotSettingsView {
3037 pub min_plot_area_m2: f32,
3038 pub tax_premium_weight: f32,
3039 pub sellback_bps: u32,
3040}
3041
3042#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3044pub struct ZPlatformView {
3045 pub id: String,
3046 pub z: f32,
3047 pub x0: f32,
3048 pub y0: f32,
3049 pub x1: f32,
3050 pub y1: f32,
3051}
3052
3053#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3055pub struct ZTransitionView {
3056 pub id: String,
3057 pub z_from: f32,
3058 pub z_to: f32,
3059 pub x0: f32,
3060 pub y0: f32,
3061 pub x1: f32,
3062 pub y1: f32,
3063}
3064
3065#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3066pub struct BuildingView {
3067 pub id: String,
3068 pub label: String,
3069 pub x: f32,
3070 pub y: f32,
3071 pub width_m: f32,
3072 pub depth_m: f32,
3073 #[serde(default)]
3074 pub interior_blueprint: Option<String>,
3075 #[serde(default)]
3076 pub tags: Vec<String>,
3077 #[serde(default)]
3079 pub market_boundary_zone_ids: Vec<String>,
3080 #[serde(default)]
3082 pub market_max_volume: Option<f32>,
3083 #[serde(default)]
3086 pub wall_set: Option<String>,
3087 #[serde(default)]
3089 pub roof_set: Option<String>,
3090}
3091
3092pub const DEFAULT_BUILDING_ART_SET: &str = "classic_stone";
3095
3096impl BuildingView {
3097 pub fn effective_wall_set(&self) -> &str {
3098 self.wall_set
3099 .as_deref()
3100 .filter(|s| !s.is_empty())
3101 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3102 }
3103
3104 pub fn effective_roof_set(&self) -> &str {
3105 self.roof_set
3106 .as_deref()
3107 .filter(|s| !s.is_empty())
3108 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3109 }
3110}
3111
3112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3113pub struct DoorView {
3114 pub id: String,
3115 pub building_id: String,
3116 pub x: f32,
3117 pub y: f32,
3118 #[serde(default)]
3119 pub open: bool,
3120 #[serde(default)]
3121 pub portal: Option<String>,
3122}
3123
3124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3125pub struct InteriorRoomView {
3126 pub id: String,
3127 pub label: String,
3128 pub floor: i32,
3129 pub x0: f32,
3130 pub y0: f32,
3131 pub x1: f32,
3132 pub y1: f32,
3133 #[serde(default)]
3134 pub floor_color: Option<String>,
3135 #[serde(default)]
3136 pub floor_glyph: Option<String>,
3137}
3138
3139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3140pub struct InteriorDoorView {
3141 pub id: String,
3142 pub room_a: String,
3143 pub room_b: String,
3144 pub x: f32,
3145 pub y: f32,
3146 pub kind: String,
3147 #[serde(default)]
3148 pub x_a: Option<f32>,
3149 #[serde(default)]
3150 pub y_a: Option<f32>,
3151 #[serde(default)]
3152 pub x_b: Option<f32>,
3153 #[serde(default)]
3154 pub y_b: Option<f32>,
3155}
3156
3157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3158pub struct InteriorMapView {
3159 pub building_id: String,
3160 pub blueprint_id: String,
3161 pub background_color: String,
3162 #[serde(default)]
3163 pub default_floor_color: Option<String>,
3164 #[serde(default = "default_floor_height_view")]
3165 pub floor_height_m: f32,
3166 #[serde(default)]
3168 pub z_platforms: Vec<ZPlatformView>,
3169 #[serde(default)]
3170 pub z_transitions: Vec<ZTransitionView>,
3171 pub rooms: Vec<InteriorRoomView>,
3172 #[serde(default)]
3173 pub room_doors: Vec<InteriorDoorView>,
3174}
3175
3176fn default_floor_height_view() -> f32 {
3177 3.0
3178}
3179
3180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3181pub struct NpcView {
3182 pub id: String,
3183 pub label: String,
3184 pub role: String,
3185 pub x: f32,
3186 pub y: f32,
3187 #[serde(default)]
3189 pub building_id: Option<String>,
3190 #[serde(default)]
3192 pub entity_id: Option<EntityId>,
3193 #[serde(default)]
3194 pub life_state: Option<LifeState>,
3195 #[serde(default)]
3196 pub hp_pct: Option<f32>,
3197 #[serde(default)]
3199 pub can_trade: bool,
3200 #[serde(default)]
3202 pub tile_id: Option<String>,
3203 #[serde(default)]
3205 pub behavior_state: Option<String>,
3206 #[serde(default)]
3208 pub presentation_state: Option<String>,
3209 #[serde(default)]
3211 pub sprite_mode: Option<String>,
3212 #[serde(default)]
3214 pub paperdoll_ref: Option<String>,
3215}
3216
3217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3218pub struct UseResult {
3219 pub template_id: String,
3220 pub hunger_restored: f32,
3221 pub thirst_restored: f32,
3222 #[serde(default)]
3223 pub health_restored: f32,
3224 #[serde(default)]
3225 pub mana_restored: f32,
3226 #[serde(default)]
3227 pub cleared_dot_ids: Vec<String>,
3228}
3229
3230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3231pub struct CraftResult {
3232 pub blueprint_id: String,
3233 pub outputs: Vec<ItemStack>,
3234 pub consumed: Vec<ItemStack>,
3235 #[serde(default = "default_one")]
3237 pub batch_index: u32,
3238 #[serde(default = "default_one")]
3240 pub batch_total: u32,
3241}
3242
3243fn default_one() -> u32 {
3244 1
3245}
3246
3247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3248pub struct DeathNotice {
3249 pub entity_id: EntityId,
3250 pub respawn_x: f32,
3251 pub respawn_y: f32,
3252 pub message: String,
3253}
3254
3255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3256pub struct InteractionNotice {
3257 pub target_id: String,
3258 pub message: String,
3259 #[serde(default)]
3260 pub coins_delta: i32,
3261 #[serde(default)]
3262 pub inventory_delta: Vec<ItemStack>,
3263}
3264
3265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3266#[serde(rename_all = "snake_case")]
3267pub enum NpcTalkTrustFlag {
3268 Stranger,
3269 Acquainted,
3270 Trusted,
3271}
3272
3273#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3274#[serde(rename_all = "snake_case")]
3275pub enum NpcTalkDepth {
3276 #[default]
3277 Full,
3278 Brief,
3279 Unavailable,
3280}
3281
3282#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3283pub struct NpcTalkOpened {
3284 pub npc_id: String,
3285 pub npc_label: String,
3286 pub greeting: String,
3287 pub trust_flag: NpcTalkTrustFlag,
3288 #[serde(default)]
3289 pub talk_depth: NpcTalkDepth,
3290 #[serde(default = "default_true")]
3291 pub trade_allowed: bool,
3292}
3293
3294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3295pub struct NpcTalkPending {
3296 pub npc_id: String,
3297}
3298
3299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3300pub struct NpcTalkReply {
3301 pub npc_id: String,
3302 pub line: String,
3303 pub trust_flag: NpcTalkTrustFlag,
3304 #[serde(default)]
3305 pub wind_down: bool,
3306 #[serde(default)]
3307 pub trade_disabled: bool,
3308}
3309
3310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3311pub struct NpcTalkClosed {
3312 pub npc_id: String,
3313}
3314
3315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3316pub struct NpcTalkError {
3317 pub npc_id: String,
3318 pub reason: String,
3319}
3320
3321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3322#[serde(rename_all = "snake_case")]
3323pub enum QuestStatusView {
3324 Available,
3325 Active,
3326 Completed,
3327}
3328
3329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3330pub struct QuestObjectiveProgress {
3331 pub label: String,
3332 pub current: u32,
3333 pub required: u32,
3334 pub done: bool,
3335}
3336
3337#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3338pub struct QuestLogEntry {
3339 pub quest_id: String,
3340 pub title: String,
3341 pub description: String,
3342 pub status: QuestStatusView,
3343 #[serde(default)]
3344 pub current_step_id: Option<String>,
3345 #[serde(default)]
3346 pub current_step_title: String,
3347 #[serde(default)]
3348 pub objectives: Vec<QuestObjectiveProgress>,
3349 #[serde(default)]
3350 pub is_tracked: bool,
3351 #[serde(default)]
3352 pub can_withdraw: bool,
3353}
3354
3355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3356pub struct InteractableView {
3357 pub id: String,
3358 pub kind: String,
3359 pub label: String,
3360 pub x: f32,
3361 pub y: f32,
3362 pub z: f32,
3363 #[serde(default)]
3364 pub board_id: Option<String>,
3365}
3366
3367#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3368pub struct QuestOffer {
3369 pub quest_id: String,
3370 pub title: String,
3371 pub description: String,
3372 #[serde(default)]
3373 pub step_count: u32,
3374}
3375
3376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3377pub struct QuestNotice {
3378 pub quest_id: String,
3379 pub title: String,
3380 pub message: String,
3381}
3382
3383#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3384#[serde(rename_all = "snake_case")]
3385pub enum ShopOfferKind {
3386 Item,
3387 Blueprint,
3388}
3389
3390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3391pub struct ShopOffer {
3392 pub offer_id: String,
3393 pub kind: ShopOfferKind,
3394 pub label: String,
3395 #[serde(default)]
3396 pub template_id: Option<String>,
3397 #[serde(default)]
3398 pub blueprint_id: Option<String>,
3399 pub price_copper: u32,
3400 #[serde(default)]
3401 pub affordable: bool,
3402 #[serde(default)]
3403 pub already_owned: bool,
3404}
3405
3406#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3407pub struct ShopBuyLine {
3408 pub template_id: String,
3409 pub label: String,
3410 pub quantity: u32,
3411 pub price_copper: u32,
3412}
3413
3414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3416pub struct BankPanel {
3417 pub npc_id: String,
3418 pub npc_label: String,
3419 pub bank_balance_copper: u64,
3420 pub on_person_copper: u64,
3421 #[serde(default)]
3423 pub pending_outgoing_copper: u64,
3424 #[serde(default)]
3425 pub transfer_fee_bps: u32,
3426 #[serde(default)]
3427 pub transfer_clear_ticks: u64,
3428}
3429
3430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3432pub struct StoragePanel {
3433 pub npc_id: String,
3434 pub npc_label: String,
3435 pub building_id: String,
3436 pub building_label: String,
3437 pub used_volume: f32,
3438 pub max_volume: f32,
3439 #[serde(default)]
3440 pub contents: Vec<ItemStack>,
3441 #[serde(default)]
3443 pub ship_destinations: Vec<StorageShipDest>,
3444}
3445
3446#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3447pub struct StorageShipDest {
3448 pub building_id: String,
3449 pub label: String,
3450 pub distance_m: f32,
3451 pub fee_copper: u64,
3452 pub travel_ticks: u64,
3453}
3454
3455#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3458pub enum GoodsLocation {
3459 Person,
3461 TownStorage { building_id: String },
3464}
3465
3466#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3469pub struct MarketListingView {
3470 pub listing_id: Uuid,
3471 pub seller_character_id: Uuid,
3472 pub seller_label: String,
3474 pub hall_building_id: String,
3475 pub hall_label: String,
3476 pub template_id: String,
3477 pub display_name: String,
3478 #[serde(default)]
3480 pub category: String,
3481 pub quantity: u32,
3482 pub unit_price_copper: u64,
3483 pub line_total_copper: u64,
3485 pub mine: bool,
3487}
3488
3489#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3491pub struct MarketListVault {
3492 pub building_id: String,
3493 pub building_label: String,
3495 #[serde(default)]
3496 pub contents: Vec<ItemStack>,
3497}
3498
3499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3502pub struct MarketPanel {
3503 pub npc_id: String,
3504 pub npc_label: String,
3505 pub building_id: String,
3506 pub building_label: String,
3507 pub used_volume: f32,
3509 pub max_volume: f32,
3510 #[serde(default)]
3513 pub listings: Vec<MarketListingView>,
3514 #[serde(default)]
3516 pub tax_bps: u32,
3517 #[serde(default)]
3518 pub tax_flat_copper: u32,
3519 #[serde(default)]
3521 pub list_vaults: Vec<MarketListVault>,
3522}
3523
3524#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3525pub struct ShopCatalog {
3526 pub npc_id: String,
3527 pub npc_label: String,
3528 #[serde(default)]
3529 pub sells: Vec<ShopOffer>,
3530 #[serde(default)]
3531 pub buys: Vec<ShopBuyLine>,
3532}
3533
3534#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3535pub struct HarvestResult {
3536 pub node_id: String,
3537 pub quantity: u32,
3539 pub item_template: String,
3540 #[serde(default)]
3543 pub item_instance_id: Option<Uuid>,
3544}
3545
3546#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3548pub struct Envelope<T> {
3549 pub protocol_version: u16,
3550 pub payload: T,
3551}
3552
3553impl<T> Envelope<T> {
3554 pub fn new(payload: T) -> Self {
3555 Self {
3556 protocol_version: crate::PROTOCOL_VERSION,
3557 payload,
3558 }
3559 }
3560}
3561
3562#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3564pub struct Hello {
3565 pub client_name: String,
3566 pub protocol_version: u16,
3567 #[serde(default)]
3568 pub auth: AuthCredential,
3569 #[serde(default)]
3571 pub character_id: Option<Uuid>,
3572}
3573
3574#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3577#[serde(rename_all = "snake_case")]
3578pub enum AuthCredential {
3579 DevLocal,
3580 Session { token: String },
3581 ApiToken { token: String, character_id: Uuid },
3582}
3583
3584impl Default for AuthCredential {
3585 fn default() -> Self {
3586 Self::DevLocal
3587 }
3588}
3589
3590#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3591pub struct Welcome {
3592 pub session_id: SessionId,
3593 pub entity_id: EntityId,
3594 pub snapshot: Snapshot,
3595}
3596
3597#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3598pub enum ServerMessage {
3599 Welcome(Welcome),
3600 ContentUpdated(Snapshot),
3602 Tick(TickDelta),
3603 IntentAck {
3604 entity_id: EntityId,
3605 seq: Seq,
3606 tick: Tick,
3607 },
3608 Chat(ChatMessage),
3609 HarvestResult(HarvestResult),
3610 UseResult(UseResult),
3611 CraftResult(CraftResult),
3612 Death(DeathNotice),
3613 Interaction(InteractionNotice),
3614 ShopOpened(ShopCatalog),
3615 NpcTalkOpened(NpcTalkOpened),
3616 NpcTalkPending(NpcTalkPending),
3617 NpcTalkReply(NpcTalkReply),
3618 NpcTalkClosed(NpcTalkClosed),
3619 NpcTalkError(NpcTalkError),
3620 QuestOffer(QuestOffer),
3621 QuestAccepted(QuestNotice),
3622 QuestWithdrawn(QuestNotice),
3623 QuestStepCompleted(QuestNotice),
3624 QuestCompleted(QuestNotice),
3625 BankOpened(BankPanel),
3627 StorageOpened(StoragePanel),
3629 MarketOpened(MarketPanel),
3631 TradeOpened(TradePanel),
3633 TradeClosed {
3635 reason: String,
3636 },
3637 ConnectRejected {
3640 reason: String,
3641 },
3642}
3643
3644#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3646pub struct TradePanel {
3647 pub peer_entity_id: EntityId,
3648 pub peer_name: String,
3649 pub my_presented: Vec<ItemStack>,
3650 pub their_presented: Vec<ItemStack>,
3651 pub i_ready: bool,
3652 pub they_ready: bool,
3653 pub my_mass_after: f32,
3655 pub my_mass_max: f32,
3656 pub my_encumbrance_after: EncumbranceState,
3657 pub overburden_warning: bool,
3659}
3660
3661#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3662pub enum ClientMessage {
3663 Hello(Hello),
3664 Intent(Intent),
3665 Disconnect,
3666}
3667
3668#[cfg(test)]
3669mod tests {
3670 use super::*;
3671
3672 #[test]
3673 fn pristine_vitals_state_yields_full_pools() {
3674 let attrs = PrimaryAttributes::default();
3675 let vitals = StoredVitalsState::default().apply_to(attrs);
3676 assert!(vitals.health > 0.0);
3677 assert_eq!(vitals.health, vitals.health_max);
3678 assert!((vitals.mana_max - 61.0).abs() < 0.01);
3679 }
3680
3681 #[test]
3682 fn humanize_snake_id_title_cases_parts() {
3683 assert_eq!(humanize_snake_id("heal_touch"), "Heal Touch");
3684 assert_eq!(humanize_snake_id("fireball"), "Fireball");
3685 assert_eq!(humanize_snake_id("cone_frost"), "Cone Frost");
3686 }
3687
3688 #[test]
3689 fn saved_vitals_scale_when_pool_max_increases() {
3690 let mut attrs = PrimaryAttributes::default();
3691 attrs.intelligence = 140;
3692 attrs.wisdom = 140;
3693 let saved = StoredVitalsState {
3694 health: 100.0,
3695 mana: 14.0,
3696 stamina: 100.0,
3697 ..StoredVitalsState::default()
3698 };
3699 let vitals = saved.apply_to(attrs);
3700 assert!(vitals.mana_max > 55.0);
3701 assert!(
3702 (vitals.mana - vitals.mana_max).abs() < 0.01,
3703 "full legacy mana bar migrates to full new bar"
3704 );
3705 }
3706
3707 #[test]
3708 fn empty_vitals_state_is_pristine() {
3709 let pristine = StoredVitalsState {
3710 health: 0.0,
3711 mana: 0.0,
3712 stamina: 0.0,
3713 hunger: 0.0,
3714 thirst: 0.0,
3715 coins: 0,
3716 deaths: 0,
3717 life_state: LifeState::Alive,
3718 };
3719 assert!(pristine.is_pristine());
3720 let vitals = pristine.apply_to(PrimaryAttributes::default());
3721 assert!(vitals.health > 0.0);
3722 }
3723
3724 #[test]
3725 fn stored_vitals_roundtrip_preserves_partial_pools() {
3726 let attrs = PrimaryAttributes::default();
3727 let mut live = PlayerVitals::from_attributes(attrs);
3728 live.health = 25.0;
3729 live.hunger = 77.0;
3730 live.deaths = 2;
3731 let stored = StoredVitalsState::from_live(&live);
3732 let restored = stored.apply_to(attrs);
3733 assert!(
3734 (restored.health - 25.0).abs() < 0.01,
3735 "partial HP below cap stays absolute"
3736 );
3737 assert_eq!(restored.hunger, 77.0);
3738 assert_eq!(restored.deaths, 2);
3739 }
3740
3741 #[test]
3742 fn skill_tiers_start_at_zero() {
3743 let skill = SkillProgress::default();
3744 assert_eq!(skill.level, 0);
3745 assert_eq!(skill.display_tier(), 0);
3746 let trained = SkillProgress {
3747 level: 250,
3748 last_trained_tick: 1,
3749 };
3750 assert_eq!(trained.display_tier(), 2);
3751 }
3752
3753 #[test]
3754 fn quest_server_messages_roundtrip_json() {
3755 use crate::codec::{Codec, PostcardCodec};
3756
3757 let offer = ServerMessage::QuestOffer(QuestOffer {
3758 quest_id: "ada_goblin_hunt".into(),
3759 title: "Goblin Trouble".into(),
3760 description: "Help Ada".into(),
3761 step_count: 3,
3762 });
3763 let notice = ServerMessage::QuestAccepted(QuestNotice {
3764 quest_id: "ada_goblin_hunt".into(),
3765 title: "Goblin Trouble".into(),
3766 message: "Quest accepted".into(),
3767 });
3768 for msg in [offer, notice] {
3769 let bytes = PostcardCodec.encode(&msg).unwrap();
3770 let decoded: ServerMessage = PostcardCodec.decode(&bytes).unwrap();
3771 assert_eq!(decoded, msg);
3772 }
3773 }
3774
3775 #[test]
3776 fn hotbar_consumable_binding_roundtrips() {
3777 let binding = hotbar_consumable_binding("bottle_of_water");
3778 assert_eq!(binding, "item:bottle_of_water");
3779 assert!(hotbar_binding_is_consumable(&binding));
3780 assert_eq!(
3781 hotbar_consumable_template(&binding),
3782 Some("bottle_of_water")
3783 );
3784 assert!(!hotbar_binding_is_consumable("fireball"));
3785 assert_eq!(hotbar_consumable_template("fireball"), None);
3786 }
3787}