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 hearing_range_m: 6.0
183 + wis_d * 0.08
184 + (PrimaryAttributes::display(self.stamina) as f32) * 0.04,
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq)]
191pub struct DerivedPreview {
192 pub attack_power: f32,
193 pub spell_power: f32,
194 pub evasion: f32,
195 pub carry_mass_max: f32,
196 pub sight_range_m: f32,
197 pub fov_deg: f32,
198 pub hearing_range_m: f32,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203pub struct SkillProgress {
204 pub level: u16,
205 #[serde(default)]
206 pub last_trained_tick: u64,
207}
208
209impl Default for SkillProgress {
210 fn default() -> Self {
211 Self {
212 level: 0,
213 last_trained_tick: 0,
214 }
215 }
216}
217
218impl SkillProgress {
219 pub fn display_tier(&self) -> u16 {
221 (self.level / 100).min(10)
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
227#[serde(default)]
228pub struct ProgressionXp {
229 pub strength: f64,
230 pub dexterity: f64,
231 pub intelligence: f64,
232 pub stamina: f64,
233 pub vitality: f64,
234 pub wisdom: f64,
235 pub charisma: f64,
236 pub logging: f64,
237 pub mining: f64,
238 pub evocation: f64,
239 pub restoration: f64,
240 pub swords: f64,
241 pub archery: f64,
242 pub crafting: f64,
243 pub alchemy: f64,
244 pub cartography: f64,
245 #[serde(default)]
247 pub ability: std::collections::BTreeMap<String, f64>,
248}
249
250impl ProgressionXp {
251 pub fn bootstrap_new(baseline_display: u16, xp_base: f64, xp_growth: f64) -> Self {
253 let bootstrap = |display: f64| {
254 if display <= 1.0 {
255 0.0
256 } else {
257 xp_base * xp_growth.powf(display - 1.0)
258 }
259 };
260 let b = baseline_display as f64;
261 let primary = bootstrap(b);
262 Self {
263 strength: primary,
264 dexterity: primary,
265 intelligence: primary,
266 stamina: primary,
267 vitality: primary,
268 wisdom: primary,
269 charisma: primary,
270 ..Self::default()
271 }
272 }
273
274 pub fn is_empty(&self) -> bool {
275 self.strength == 0.0
276 && self.dexterity == 0.0
277 && self.intelligence == 0.0
278 && self.stamina == 0.0
279 && self.vitality == 0.0
280 && self.wisdom == 0.0
281 && self.charisma == 0.0
282 && self.logging == 0.0
283 && self.mining == 0.0
284 && self.evocation == 0.0
285 && self.restoration == 0.0
286 && self.swords == 0.0
287 && self.archery == 0.0
288 && self.crafting == 0.0
289 && self.alchemy == 0.0
290 && self.cartography == 0.0
291 && self.ability.is_empty()
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297pub struct AbilityMasteryHud {
298 pub ability_id: String,
299 pub tier: u16,
301 pub level: u16,
303 pub xp: f64,
305 pub xp_to_next: f64,
307}
308
309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311#[serde(default)]
312pub struct PlayerSkills {
313 pub logging: SkillProgress,
314 pub mining: SkillProgress,
315 pub evocation: SkillProgress,
316 #[serde(default)]
317 pub restoration: SkillProgress,
318 pub swords: SkillProgress,
319 #[serde(default)]
320 pub archery: SkillProgress,
321 pub crafting: SkillProgress,
322 #[serde(default)]
323 pub alchemy: SkillProgress,
324 pub cartography: SkillProgress,
325}
326
327impl Default for PlayerSkills {
328 fn default() -> Self {
329 Self {
330 logging: SkillProgress::default(),
331 mining: SkillProgress::default(),
332 evocation: SkillProgress::default(),
333 restoration: SkillProgress::default(),
334 swords: SkillProgress::default(),
335 archery: SkillProgress::default(),
336 crafting: SkillProgress::default(),
337 alchemy: SkillProgress::default(),
338 cartography: SkillProgress::default(),
339 }
340 }
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
345pub struct PlayerVitals {
346 pub health: f32,
347 pub health_max: f32,
348 pub mana: f32,
349 pub mana_max: f32,
350 pub stamina: f32,
351 pub stamina_max: f32,
352 #[serde(default = "default_survival_pool_max")]
353 pub hunger: f32,
354 #[serde(default = "default_survival_pool_max")]
355 pub hunger_max: f32,
356 #[serde(default = "default_survival_pool_max")]
357 pub thirst: f32,
358 #[serde(default = "default_survival_pool_max")]
359 pub thirst_max: f32,
360 #[serde(default)]
361 pub coins: u32,
362 #[serde(default)]
363 pub deaths: u32,
364 #[serde(default)]
365 pub life_state: LifeState,
366}
367
368fn default_survival_pool_max() -> f32 {
369 100.0
370}
371
372impl Default for PlayerVitals {
373 fn default() -> Self {
374 Self::from_attributes(PrimaryAttributes::default())
375 }
376}
377
378impl PlayerVitals {
379 pub fn from_attributes(attrs: PrimaryAttributes) -> Self {
386 let vit_d = PrimaryAttributes::display(attrs.vitality) as f32;
387 let int_d = PrimaryAttributes::display(attrs.intelligence) as f32;
388 let wis_d = PrimaryAttributes::display(attrs.wisdom) as f32;
389 let sta_d = PrimaryAttributes::display(attrs.stamina) as f32;
390
391 let health_max = 50.0 + vit_d * 2.0;
392 let stamina_max = 30.0 + sta_d * 1.4;
393 let mana_max = 25.0 + int_d * 1.1 + wis_d * 1.3;
394 let hunger_max = 100.0;
395 let thirst_max = 100.0;
396 Self {
397 health: health_max,
398 health_max,
399 mana: mana_max,
400 mana_max,
401 stamina: stamina_max,
402 stamina_max,
403 hunger: hunger_max,
404 hunger_max,
405 thirst: thirst_max,
406 thirst_max,
407 coins: 0,
408 deaths: 0,
409 life_state: LifeState::Alive,
410 }
411 }
412
413 pub fn legacy_maxima(attrs: PrimaryAttributes) -> (f32, f32, f32) {
415 (
416 attrs.vitality as f32 / 5.0,
417 attrs.stamina as f32 / 5.0,
418 (attrs.intelligence as f32 + attrs.wisdom as f32) / 20.0,
419 )
420 }
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
425#[serde(default)]
426pub struct StoredVitalsState {
427 pub health: f32,
428 pub mana: f32,
429 pub stamina: f32,
430 pub hunger: f32,
431 pub thirst: f32,
432 pub coins: u32,
433 pub deaths: u32,
434 pub life_state: LifeState,
435}
436
437impl StoredVitalsState {
438 pub fn from_live(v: &PlayerVitals) -> Self {
439 Self {
440 health: v.health,
441 mana: v.mana,
442 stamina: v.stamina,
443 hunger: v.hunger,
444 thirst: v.thirst,
445 coins: v.coins,
446 deaths: v.deaths,
447 life_state: v.life_state,
448 }
449 }
450
451 pub fn is_pristine(&self) -> bool {
453 self.health == 0.0
454 && self.mana == 0.0
455 && self.stamina == 0.0
456 && self.hunger == 0.0
457 && self.thirst == 0.0
458 && self.coins == 0
459 && self.deaths == 0
460 && self.life_state == LifeState::Alive
461 }
462
463 pub fn apply_to(self, attrs: PrimaryAttributes) -> PlayerVitals {
464 if self.is_pristine() {
465 return PlayerVitals::from_attributes(attrs);
466 }
467 let fresh = PlayerVitals::from_attributes(attrs);
468 let (legacy_hp, legacy_sta, legacy_mana) = PlayerVitals::legacy_maxima(attrs);
469
470 let scale = |current: f32, legacy_max: f32, new_max: f32| {
471 if legacy_max > 0.0 && new_max > legacy_max * 1.05 && current >= legacy_max * 0.95 {
472 let ratio = (current / legacy_max).clamp(0.0, 1.0);
473 (new_max * ratio).min(new_max)
474 } else {
475 current.min(new_max)
476 }
477 };
478
479 let mut v = fresh;
480 v.health = scale(self.health, legacy_hp, fresh.health_max);
481 v.mana = scale(self.mana, legacy_mana, fresh.mana_max);
482 v.stamina = scale(self.stamina, legacy_sta, fresh.stamina_max);
483 v.hunger = self.hunger.min(v.hunger_max);
484 v.thirst = self.thirst.min(v.thirst_max);
485 v.coins = self.coins;
486 v.deaths = self.deaths;
487 v.life_state = self.life_state;
488 v
489 }
490}
491
492impl Default for StoredVitalsState {
493 fn default() -> Self {
494 Self::from_live(&PlayerVitals::default())
495 }
496}
497
498pub fn humanize_snake_id(id: &str) -> String {
502 id.split('_')
503 .filter(|part| !part.is_empty())
504 .map(|part| {
505 let mut chars = part.chars();
506 match chars.next() {
507 None => String::new(),
508 Some(first) => first.to_uppercase().chain(chars).collect(),
509 }
510 })
511 .collect::<Vec<_>>()
512 .join(" ")
513}
514
515#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
517pub struct KnownAbility {
518 pub ability_id: String,
519 #[serde(default = "default_known_permanent")]
521 pub permanent: bool,
522 #[serde(default)]
524 pub expires_at_tick: Option<u64>,
525}
526
527fn default_known_permanent() -> bool {
528 true
529}
530
531impl KnownAbility {
532 pub fn permanent(ability_id: impl Into<String>) -> Self {
533 Self {
534 ability_id: ability_id.into(),
535 permanent: true,
536 expires_at_tick: None,
537 }
538 }
539
540 pub fn is_active(&self, tick: u64) -> bool {
541 if self.permanent {
542 return true;
543 }
544 match self.expires_at_tick {
545 Some(exp) => tick < exp,
546 None => false,
547 }
548 }
549}
550
551#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
553pub struct RotationPreset {
554 pub id: String,
555 pub label: String,
556 #[serde(default)]
557 pub abilities: Vec<String>,
558}
559
560impl RotationPreset {
561 pub fn melee_default(ability_id: impl Into<String>) -> Self {
562 let id = ability_id.into();
563 Self {
564 id: "melee".into(),
565 label: "Weapon".into(),
567 abilities: vec![id],
568 }
569 }
570
571 pub fn is_weapon_preset(&self) -> bool {
572 self.id == "melee"
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
578pub struct StoredTargetSlot {
579 pub instance_id: Option<String>,
580 #[serde(default)]
581 pub preset_id: Option<String>,
582 #[serde(default)]
583 pub rotation_index: u32,
584 #[serde(default)]
585 pub auto_enabled: bool,
586}
587
588#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
589#[serde(default)]
590pub struct StoredCombatProfile {
591 pub combat_target_instance_id: Option<String>,
593 pub in_combat: bool,
594 pub last_combat_tick: u64,
595 pub last_attack_tick: u64,
596 pub cooldowns_until_tick: BTreeMap<String, u64>,
597 #[serde(default = "default_auto_attack")]
598 pub auto_attack_enabled: bool,
599 #[serde(default)]
601 pub mainhand_template_id: Option<String>,
602 #[serde(default)]
604 pub mainhand_instance_id: Option<Uuid>,
605 #[serde(default)]
607 pub offhand_template_id: Option<String>,
608 #[serde(default)]
610 pub offhand_instance_id: Option<Uuid>,
611 #[serde(default)]
614 pub worn: Vec<(BodySlot, ItemStack)>,
615 #[serde(default)]
617 pub rotation_presets: Vec<RotationPreset>,
618 #[serde(default)]
620 pub target_slots: Vec<StoredTargetSlot>,
621 #[serde(default)]
623 pub known_blueprint_ids: Vec<String>,
624 #[serde(default)]
626 pub keychain: Vec<ItemStack>,
627 #[serde(default)]
629 pub whisper_pouch: Vec<ItemStack>,
630 #[serde(default)]
632 pub known_abilities: Vec<KnownAbility>,
633 #[serde(default)]
635 pub hotbar: Vec<Option<String>>,
636 #[serde(default)]
638 pub abilities_schema_version: u32,
639 #[serde(default)]
641 pub bank_balance_copper: u64,
642}
643
644fn default_auto_attack() -> bool {
645 true
646}
647
648impl Default for StoredCombatProfile {
649 fn default() -> Self {
650 Self {
651 combat_target_instance_id: None,
652 in_combat: false,
653 last_combat_tick: 0,
654 last_attack_tick: 0,
655 cooldowns_until_tick: BTreeMap::new(),
656 auto_attack_enabled: true,
657 mainhand_template_id: None,
658 mainhand_instance_id: None,
659 offhand_template_id: None,
660 offhand_instance_id: None,
661 worn: Vec::new(),
662 rotation_presets: Vec::new(),
663 target_slots: Vec::new(),
664 known_blueprint_ids: Vec::new(),
665 keychain: Vec::new(),
666 whisper_pouch: Vec::new(),
667 known_abilities: Vec::new(),
668 hotbar: Vec::new(),
669 abilities_schema_version: 0,
670 bank_balance_copper: 0,
671 }
672 }
673}
674
675#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
677#[serde(rename_all = "snake_case")]
678pub enum CombatCueKind {
679 Dodge,
680 Block,
681 AttackTelegraph,
682}
683
684#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
685pub struct CombatCueView {
686 pub kind: CombatCueKind,
687 pub until_tick: Tick,
689 #[serde(default)]
691 pub start_tick: Tick,
692 #[serde(default)]
694 pub ability_id: Option<String>,
695 #[serde(default)]
697 pub telegraph_kind: Option<CombatFxKind>,
698 #[serde(default)]
699 pub origin_x: Option<f32>,
700 #[serde(default)]
701 pub origin_y: Option<f32>,
702 #[serde(default)]
703 pub origin_z: Option<f32>,
704 #[serde(default)]
705 pub end_x: Option<f32>,
706 #[serde(default)]
707 pub end_y: Option<f32>,
708 #[serde(default)]
709 pub end_z: Option<f32>,
710 #[serde(default)]
711 pub yaw: Option<f32>,
712 #[serde(default)]
713 pub reach_m: Option<f32>,
714 #[serde(default)]
715 pub arc_deg: Option<f32>,
716 #[serde(default)]
717 pub radius_m: Option<f32>,
718}
719
720impl CombatCueView {
721 pub fn timing(kind: CombatCueKind, until_tick: Tick, start_tick: Tick) -> Self {
723 Self {
724 kind,
725 until_tick,
726 start_tick,
727 ability_id: None,
728 telegraph_kind: None,
729 origin_x: None,
730 origin_y: None,
731 origin_z: None,
732 end_x: None,
733 end_y: None,
734 end_z: None,
735 yaw: None,
736 reach_m: None,
737 arc_deg: None,
738 radius_m: None,
739 }
740 }
741}
742
743#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
744pub struct EntityState {
745 pub id: EntityId,
746 pub transform: Transform,
747 #[serde(default)]
749 pub label: String,
750 #[serde(default)]
751 pub vitals: Option<PlayerVitals>,
752 #[serde(default)]
754 pub attributes: Option<PrimaryAttributes>,
755 #[serde(default)]
756 pub skills: Option<PlayerSkills>,
757 #[serde(default)]
759 pub inside_building: Option<String>,
760 #[serde(default)]
762 pub tile_id: Option<String>,
763 #[serde(default)]
765 pub paperdoll_ref: Option<String>,
766 #[serde(default = "default_draw_scale")]
768 pub draw_scale: f32,
769 #[serde(default)]
771 pub presentation_state: Option<String>,
772 #[serde(default)]
774 pub sprite_mode: Option<String>,
775 #[serde(default)]
777 pub progression_xp: Option<ProgressionXp>,
778 #[serde(default)]
780 pub combat_cues: Vec<CombatCueView>,
781 #[serde(default)]
783 pub statuses: Vec<StatusEffectHud>,
784}
785
786#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
788#[serde(rename_all = "snake_case")]
789pub enum ChatChannel {
790 Nearby,
792 Direct,
794 Whisper,
796 WhisperStone,
798}
799
800#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
802#[serde(rename_all = "snake_case")]
803pub enum ChatClarity {
804 #[default]
805 Clear,
806 Partial,
807 Heavy,
808}
809
810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
811pub struct ChatMessage {
812 pub channel: ChatChannel,
813 pub from_entity: EntityId,
814 pub from_name: String,
815 pub text: String,
817 pub tick: Tick,
818 #[serde(default)]
820 pub to_entity: Option<EntityId>,
821 #[serde(default)]
822 pub clarity: ChatClarity,
823}
824
825#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
827pub enum Intent {
828 Move {
829 entity_id: EntityId,
830 forward: f32,
831 strafe: f32,
832 #[serde(default)]
834 vertical: f32,
835 #[serde(default)]
837 sprint: bool,
838 #[serde(default)]
840 sneak: bool,
841 seq: Seq,
842 },
843 Stop {
844 entity_id: EntityId,
845 seq: Seq,
846 },
847 Harvest {
848 entity_id: EntityId,
849 node_id: String,
850 seq: Seq,
851 },
852 Use {
853 entity_id: EntityId,
854 template_id: String,
855 seq: Seq,
856 },
857 UseGrant {
859 entity_id: EntityId,
860 grant_instance_id: Uuid,
861 target_instance_id: Uuid,
862 seq: Seq,
863 },
864 Say {
865 entity_id: EntityId,
866 channel: ChatChannel,
867 text: String,
868 #[serde(default)]
870 to_entity: Option<EntityId>,
871 seq: Seq,
872 },
873 Craft {
875 entity_id: EntityId,
876 blueprint_id: String,
877 #[serde(default)]
879 count: Option<u32>,
880 seq: Seq,
881 },
882 Interact {
884 entity_id: EntityId,
885 target_id: String,
886 seq: Seq,
887 },
888 ShopBuy {
890 entity_id: EntityId,
891 npc_id: String,
892 offer_id: String,
893 #[serde(default = "default_one")]
894 quantity: u32,
895 seq: Seq,
896 },
897 ShopSell {
899 entity_id: EntityId,
900 npc_id: String,
901 template_id: String,
902 #[serde(default = "default_one")]
903 quantity: u32,
904 seq: Seq,
905 },
906 ShopClose {
908 entity_id: EntityId,
909 npc_id: String,
910 seq: Seq,
911 },
912 TestDamage {
914 entity_id: EntityId,
915 amount: f32,
916 seq: Seq,
917 },
918 SetTarget {
920 entity_id: EntityId,
921 target_id: EntityId,
922 seq: Seq,
923 },
924 SetTargetSlot {
926 entity_id: EntityId,
927 slot_index: u8,
928 target_id: EntityId,
929 seq: Seq,
930 },
931 ClearTarget {
932 entity_id: EntityId,
933 seq: Seq,
934 },
935 ClearTargetSlot {
936 entity_id: EntityId,
937 slot_index: u8,
938 seq: Seq,
939 },
940 SetAutoAttack {
942 entity_id: EntityId,
943 slot_index: u8,
944 enabled: bool,
945 seq: Seq,
946 },
947 Attack {
949 entity_id: EntityId,
950 #[serde(default)]
951 target_id: Option<EntityId>,
952 #[serde(default)]
953 weapon_slot: Option<u32>,
954 seq: Seq,
955 },
956 Pickup {
958 entity_id: EntityId,
959 #[serde(default)]
960 drop_id: Option<String>,
961 seq: Seq,
962 },
963 Cast {
966 entity_id: EntityId,
967 ability_id: String,
968 target_id: EntityId,
969 #[serde(default)]
970 target_point: Option<AimPoint>,
971 seq: Seq,
972 },
973 BindActionSlot {
975 entity_id: EntityId,
976 slot_index: u8,
977 ability_id: String,
978 #[serde(default = "default_auto_attack")]
979 auto_enabled: bool,
980 seq: Seq,
981 },
982 UseActionSlot {
984 entity_id: EntityId,
985 slot_index: u8,
986 seq: Seq,
987 },
988 Dodge {
992 entity_id: EntityId,
993 #[serde(default)]
995 forward: f32,
996 #[serde(default)]
998 strafe: f32,
999 seq: Seq,
1000 },
1001 Lunge {
1003 entity_id: EntityId,
1004 #[serde(default)]
1006 forward: f32,
1007 #[serde(default)]
1009 strafe: f32,
1010 seq: Seq,
1011 },
1012 DirectionalJump {
1014 entity_id: EntityId,
1015 #[serde(default)]
1017 forward: f32,
1018 #[serde(default)]
1020 strafe: f32,
1021 seq: Seq,
1022 },
1023 Block {
1025 entity_id: EntityId,
1026 #[serde(default = "default_block_enabled")]
1027 enabled: bool,
1028 seq: Seq,
1029 },
1030 EquipMainhand {
1034 entity_id: EntityId,
1035 #[serde(default)]
1036 template_id: Option<String>,
1037 #[serde(default)]
1038 instance_id: Option<Uuid>,
1039 seq: Seq,
1040 },
1041 EquipOffhand {
1043 entity_id: EntityId,
1044 #[serde(default)]
1045 template_id: Option<String>,
1046 #[serde(default)]
1047 instance_id: Option<Uuid>,
1048 seq: Seq,
1049 },
1050 EquipWorn {
1053 entity_id: EntityId,
1054 slot: BodySlot,
1055 #[serde(default)]
1056 instance_id: Option<Uuid>,
1057 seq: Seq,
1058 },
1059 MoveItem {
1061 entity_id: EntityId,
1062 item_instance_id: Uuid,
1063 from: InventoryLocation,
1064 to: InventoryLocation,
1065 #[serde(default)]
1067 to_parent_instance_id: Option<Uuid>,
1068 #[serde(default)]
1070 quantity: Option<u32>,
1071 seq: Seq,
1072 },
1073 PlaceContainer {
1075 entity_id: EntityId,
1076 item_instance_id: Uuid,
1077 seq: Seq,
1078 },
1079 PickupContainer {
1081 entity_id: EntityId,
1082 container_id: String,
1083 seq: Seq,
1084 },
1085 MovePlacedContainer {
1087 entity_id: EntityId,
1088 container_id: String,
1089 x: f32,
1090 y: f32,
1091 seq: Seq,
1092 },
1093 SetContainerLocked {
1095 entity_id: EntityId,
1096 location: InventoryLocation,
1098 locked: bool,
1099 seq: Seq,
1100 },
1101 DropItem {
1103 entity_id: EntityId,
1104 item_instance_id: Uuid,
1105 from: InventoryLocation,
1106 seq: Seq,
1107 },
1108 DestroyItem {
1110 entity_id: EntityId,
1111 item_instance_id: Uuid,
1112 from: InventoryLocation,
1113 #[serde(default)]
1115 quantity: Option<u32>,
1116 seq: Seq,
1117 },
1118 RenameContainer {
1120 entity_id: EntityId,
1121 item_instance_id: Uuid,
1122 location: InventoryLocation,
1123 name: String,
1124 seq: Seq,
1125 },
1126 UpsertRotationPreset {
1128 entity_id: EntityId,
1129 preset: RotationPreset,
1130 seq: Seq,
1131 },
1132 DeleteRotationPreset {
1134 entity_id: EntityId,
1135 preset_id: String,
1136 seq: Seq,
1137 },
1138 AssignSlotPreset {
1140 entity_id: EntityId,
1141 slot_index: u8,
1142 preset_id: String,
1143 seq: Seq,
1144 },
1145 SetHotbarSlot {
1148 entity_id: EntityId,
1149 slot: u8,
1151 #[serde(default)]
1153 ability_id: Option<String>,
1154 seq: Seq,
1155 },
1156 AdvanceRotation {
1158 entity_id: EntityId,
1159 slot_index: u8,
1160 seq: Seq,
1161 },
1162 NpcTalkOpen {
1164 entity_id: EntityId,
1165 npc_id: String,
1166 seq: Seq,
1167 },
1168 NpcTalkSay {
1170 entity_id: EntityId,
1171 npc_id: String,
1172 message: String,
1173 seq: Seq,
1174 },
1175 NpcTalkClose {
1177 entity_id: EntityId,
1178 npc_id: String,
1179 seq: Seq,
1180 },
1181 AcceptQuest {
1183 entity_id: EntityId,
1184 quest_id: String,
1185 seq: Seq,
1186 },
1187 WithdrawQuest {
1189 entity_id: EntityId,
1190 quest_id: String,
1191 seq: Seq,
1192 },
1193 TrackQuest {
1195 entity_id: EntityId,
1196 quest_id: String,
1197 seq: Seq,
1198 },
1199 QuestGiveItem {
1201 entity_id: EntityId,
1202 npc_id: String,
1203 template_id: String,
1204 #[serde(default = "default_one")]
1205 quantity: u32,
1206 seq: Seq,
1207 },
1208 HireWorker {
1210 entity_id: EntityId,
1211 def_id: String,
1212 wage_copper_per_interval: u32,
1213 #[serde(default)]
1214 lodging_container_id: Option<String>,
1215 #[serde(default)]
1216 job_yaml: Option<String>,
1217 seq: Seq,
1218 },
1219 DismissWorker {
1221 entity_id: EntityId,
1222 worker_instance_id: String,
1223 seq: Seq,
1224 },
1225 SetWorkerJob {
1227 entity_id: EntityId,
1228 worker_instance_id: String,
1229 job_yaml: String,
1230 seq: Seq,
1231 },
1232 AssignWorkerLodging {
1234 entity_id: EntityId,
1235 worker_instance_id: String,
1236 lodging_container_id: String,
1237 seq: Seq,
1238 },
1239 SetWorkerMode {
1241 entity_id: EntityId,
1242 worker_instance_id: String,
1243 mode: String,
1244 seq: Seq,
1245 },
1246 EquipWorkerItem {
1252 entity_id: EntityId,
1253 worker_instance_id: String,
1254 item_instance_id: uuid::Uuid,
1255 slot: String,
1256 seq: Seq,
1257 },
1258 GiveWorkerItem {
1261 entity_id: EntityId,
1262 worker_instance_id: String,
1263 item_instance_id: uuid::Uuid,
1264 #[serde(default)]
1265 quantity: Option<u32>,
1266 seq: Seq,
1267 },
1268 TakeWorkerItem {
1270 entity_id: EntityId,
1271 worker_instance_id: String,
1272 item_instance_id: uuid::Uuid,
1273 #[serde(default)]
1274 quantity: Option<u32>,
1275 seq: Seq,
1276 },
1277 RenameHiredWorker {
1279 entity_id: EntityId,
1280 worker_instance_id: String,
1281 name: String,
1282 seq: Seq,
1283 },
1284 RenamePropertyPlot {
1286 entity_id: EntityId,
1287 plot_id: Uuid,
1288 label: String,
1289 seq: Seq,
1290 },
1291 TeachWorkerBlueprint {
1293 entity_id: EntityId,
1294 worker_instance_id: String,
1295 blueprint_id: String,
1296 seq: Seq,
1297 },
1298 AttendHiredWorker {
1300 entity_id: EntityId,
1301 worker_instance_id: String,
1302 attending: bool,
1303 seq: Seq,
1304 },
1305 BuyPlot {
1307 entity_id: EntityId,
1308 zone_id: String,
1309 x0: f32,
1310 y0: f32,
1311 x1: f32,
1312 y1: f32,
1313 seq: Seq,
1314 },
1315 BuyPlotAllFree {
1317 entity_id: EntityId,
1318 zone_id: String,
1319 seq: Seq,
1320 },
1321 SellPlotToCrown {
1323 entity_id: EntityId,
1324 plot_id: Uuid,
1325 seq: Seq,
1326 },
1327 Cultivate {
1329 entity_id: EntityId,
1330 x: f32,
1332 y: f32,
1333 seq: Seq,
1334 },
1335 PlantSeeds {
1337 entity_id: EntityId,
1338 seed_template_id: String,
1339 quantity: u32,
1340 seq: Seq,
1341 },
1342 SetPlotFarmPublic {
1344 entity_id: EntityId,
1345 plot_id: Uuid,
1346 public: bool,
1347 #[serde(default)]
1348 public_tax_discount_bps: u32,
1349 seq: Seq,
1350 },
1351 PlotFarmAllowUpsert {
1353 entity_id: EntityId,
1354 plot_id: Uuid,
1355 #[serde(default)]
1357 character_id: Option<Uuid>,
1358 #[serde(default)]
1360 character_name: String,
1361 #[serde(default)]
1362 tax_discount_bps: u32,
1363 seq: Seq,
1364 },
1365 PlotFarmAllowRemove {
1367 entity_id: EntityId,
1368 plot_id: Uuid,
1369 character_id: Uuid,
1370 seq: Seq,
1371 },
1372 StartPlotBuild {
1375 entity_id: EntityId,
1376 plot_id: Uuid,
1377 wall_material_id: String,
1378 roof_material_id: String,
1379 seq: Seq,
1380 },
1381 CancelPlotBuild {
1382 entity_id: EntityId,
1383 seq: Seq,
1384 },
1385 SetDoorLocked {
1388 entity_id: EntityId,
1389 door_id: String,
1390 locked: bool,
1391 seq: Seq,
1392 },
1393 EnterBuildingDoor {
1396 entity_id: EntityId,
1397 door_id: String,
1398 seq: Seq,
1399 },
1400 ExitBuildingDoor {
1403 entity_id: EntityId,
1404 door_id: String,
1405 seq: Seq,
1406 },
1407 ConfirmInteriorEdit {
1409 entity_id: EntityId,
1410 building_id: String,
1411 rooms: Vec<InteriorRoomEdit>,
1412 room_doors: Vec<InteriorRoomDoorEdit>,
1413 seq: Seq,
1414 },
1415 CancelInteriorEdit {
1416 entity_id: EntityId,
1417 building_id: String,
1418 seq: Seq,
1419 },
1420 BankDeposit {
1422 entity_id: EntityId,
1423 npc_id: String,
1424 #[serde(default)]
1426 amount_copper: u64,
1427 seq: Seq,
1428 },
1429 BankWithdraw {
1431 entity_id: EntityId,
1432 npc_id: String,
1433 #[serde(default)]
1435 amount_copper: u64,
1436 seq: Seq,
1437 },
1438 BankClose {
1440 entity_id: EntityId,
1441 npc_id: String,
1442 seq: Seq,
1443 },
1444 BankTransfer {
1446 entity_id: EntityId,
1447 npc_id: String,
1448 #[serde(default)]
1450 to_character_id: Option<Uuid>,
1451 #[serde(default)]
1453 to_name: String,
1454 amount_copper: u64,
1456 seq: Seq,
1457 },
1458 StorageStore {
1460 entity_id: EntityId,
1461 npc_id: String,
1462 item_instance_id: Uuid,
1463 #[serde(default)]
1464 quantity: Option<u32>,
1465 seq: Seq,
1466 },
1467 StorageTake {
1469 entity_id: EntityId,
1470 npc_id: String,
1471 item_instance_id: Uuid,
1472 #[serde(default)]
1473 quantity: Option<u32>,
1474 seq: Seq,
1475 },
1476 StorageShip {
1478 entity_id: EntityId,
1479 npc_id: String,
1480 dest_building_id: String,
1481 item_instance_id: Uuid,
1482 #[serde(default)]
1483 quantity: Option<u32>,
1484 seq: Seq,
1485 },
1486 StorageClose {
1488 entity_id: EntityId,
1489 npc_id: String,
1490 seq: Seq,
1491 },
1492 MarketList {
1495 entity_id: EntityId,
1496 npc_id: String,
1497 source: GoodsLocation,
1498 item_instance_id: Uuid,
1499 #[serde(default)]
1500 quantity: Option<u32>,
1501 unit_price_copper: u64,
1502 #[serde(default)]
1504 npc_price: bool,
1505 seq: Seq,
1506 },
1507 MarketReprice {
1509 entity_id: EntityId,
1510 npc_id: String,
1511 listing_id: Uuid,
1512 unit_price_copper: u64,
1513 seq: Seq,
1514 },
1515 MarketDelist {
1517 entity_id: EntityId,
1518 npc_id: String,
1519 listing_id: Uuid,
1520 dest: GoodsLocation,
1521 seq: Seq,
1522 },
1523 MarketBuy {
1525 entity_id: EntityId,
1526 npc_id: String,
1527 listing_id: Uuid,
1528 #[serde(default = "default_one")]
1529 quantity: u32,
1530 dest: GoodsLocation,
1531 seq: Seq,
1532 },
1533 MarketClose {
1535 entity_id: EntityId,
1536 npc_id: String,
1537 seq: Seq,
1538 },
1539 TradeRequest {
1541 entity_id: EntityId,
1542 peer_entity_id: EntityId,
1543 seq: Seq,
1544 },
1545 TradeRespond {
1547 entity_id: EntityId,
1548 peer_entity_id: EntityId,
1549 accept: bool,
1550 seq: Seq,
1551 },
1552 TradePresent {
1554 entity_id: EntityId,
1555 item_instance_id: Uuid,
1556 #[serde(default)]
1557 quantity: Option<u32>,
1558 seq: Seq,
1559 },
1560 TradeUnpresent {
1562 entity_id: EntityId,
1563 item_instance_id: Uuid,
1564 seq: Seq,
1565 },
1566 TradeSetReady {
1568 entity_id: EntityId,
1569 ready: bool,
1570 seq: Seq,
1571 },
1572 TradeCancel {
1574 entity_id: EntityId,
1575 seq: Seq,
1576 },
1577 DestroyWhisperStone {
1579 entity_id: EntityId,
1580 item_instance_id: Uuid,
1581 seq: Seq,
1582 },
1583 StowWhisperStone {
1585 entity_id: EntityId,
1586 item_instance_id: Uuid,
1587 seq: Seq,
1588 },
1589 DeliverWorkerToNearestStorage {
1592 entity_id: EntityId,
1593 worker_instance_id: String,
1594 seq: Seq,
1595 },
1596 CancelWorkerDelivery {
1598 entity_id: EntityId,
1599 worker_instance_id: String,
1600 seq: Seq,
1601 },
1602}
1603
1604fn default_block_enabled() -> bool {
1605 true
1606}
1607
1608#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1610pub struct StatusEffectHud {
1611 pub effect_id: String,
1612 pub label: String,
1613 #[serde(default)]
1614 pub polarity: String,
1615 #[serde(default)]
1616 pub icon_tile_id: Option<String>,
1617 #[serde(default)]
1619 pub dot_color: Option<String>,
1620 #[serde(default)]
1622 pub remaining_sec: Option<f32>,
1623 #[serde(default = "default_stack_count")]
1625 pub stack_count: u8,
1626}
1627
1628fn default_stack_count() -> u8 {
1629 1
1630}
1631
1632#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1634pub struct CombatTargetHud {
1635 pub entity_id: EntityId,
1636 #[serde(default)]
1637 pub label: String,
1638 #[serde(default)]
1639 pub level: u32,
1640 pub health: f32,
1641 pub health_max: f32,
1642 #[serde(default)]
1643 pub life_state: LifeState,
1644 #[serde(default)]
1645 pub distance_m: f32,
1646 #[serde(default)]
1647 pub statuses: Vec<StatusEffectHud>,
1648}
1649
1650#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1652#[serde(rename_all = "snake_case")]
1653pub enum TimedChannelKind {
1654 #[default]
1655 Cultivate,
1656 Plant,
1657 Harvest,
1658 Build,
1660}
1661
1662#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1664pub struct TimedChannelHud {
1665 #[serde(default)]
1666 pub label: String,
1667 #[serde(default)]
1668 pub channel: TimedChannelKind,
1669 #[serde(default)]
1670 pub cell_x: i32,
1671 #[serde(default)]
1672 pub cell_y: i32,
1673 #[serde(default)]
1675 pub x0: f32,
1676 #[serde(default)]
1677 pub y0: f32,
1678 #[serde(default)]
1679 pub x1: f32,
1680 #[serde(default)]
1681 pub y1: f32,
1682 #[serde(default)]
1683 pub ticks_remaining: u64,
1684 #[serde(default)]
1685 pub ticks_total: u64,
1686}
1687
1688impl TimedChannelHud {
1689 pub fn has_footprint(&self) -> bool {
1691 self.x1 > self.x0 && self.y1 > self.y0
1692 }
1693}
1694
1695#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1697#[serde(rename_all = "snake_case")]
1698pub enum PlotBuildMaterialSource {
1699 #[default]
1700 None,
1701 TownStorage,
1702 NearbyContainer,
1703}
1704
1705#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1707pub struct BuildingMaterialView {
1708 pub id: String,
1709 pub display_name: String,
1710 #[serde(default)]
1711 pub can_wall: bool,
1712 #[serde(default)]
1713 pub can_roof: bool,
1714 #[serde(default)]
1715 pub wall_set: String,
1716 #[serde(default)]
1717 pub roof_set: String,
1718 #[serde(default = "default_material_tick_mult")]
1719 pub tick_mult: f32,
1720 #[serde(default)]
1721 pub wall_bom: Vec<BuildingBomLineView>,
1722 #[serde(default)]
1723 pub roof_bom: Vec<BuildingBomLineView>,
1724}
1725
1726fn default_material_tick_mult() -> f32 {
1727 1.0
1728}
1729
1730#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1731pub struct BuildingBomLineView {
1732 pub template_id: String,
1733 #[serde(default)]
1734 pub display_name: String,
1735 pub per_m2: f32,
1736}
1737
1738#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1740pub struct PlotBuildStockView {
1741 pub template_id: String,
1742 #[serde(default)]
1743 pub display_name: String,
1744 pub quantity: u32,
1745}
1746
1747#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1749pub struct PlotBuildOfferHud {
1750 pub plot_id: Uuid,
1751 #[serde(default)]
1752 pub pad_width_m: f32,
1753 #[serde(default)]
1754 pub pad_depth_m: f32,
1755 #[serde(default)]
1756 pub pad_ok: bool,
1757 #[serde(default)]
1758 pub pad_error: String,
1759 #[serde(default)]
1760 pub source: PlotBuildMaterialSource,
1761 #[serde(default)]
1762 pub source_label: String,
1763 #[serde(default)]
1764 pub available: Vec<PlotBuildStockView>,
1765 #[serde(default)]
1766 pub base_ticks: u32,
1767 #[serde(default)]
1768 pub tick_per_m2: u32,
1769}
1770
1771#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1773pub struct CastProgressHud {
1774 #[serde(default)]
1775 pub ability_id: String,
1776 #[serde(default)]
1777 pub ability_label: String,
1778 #[serde(default)]
1779 pub ticks_remaining: u64,
1780 #[serde(default)]
1781 pub ticks_total: u64,
1782}
1783
1784#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1786pub struct AbilityCooldownHud {
1787 #[serde(default)]
1788 pub ability_id: String,
1789 #[serde(default)]
1790 pub label: String,
1791 #[serde(default)]
1792 pub cd_ticks: u64,
1793 #[serde(default)]
1794 pub cd_total_ticks: u64,
1795}
1796
1797#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1799pub struct CombatSlotHud {
1800 pub slot_index: u8,
1801 #[serde(default)]
1802 pub target_entity_id: Option<EntityId>,
1803 #[serde(default)]
1804 pub target_label: Option<String>,
1805 #[serde(default)]
1806 pub target: Option<CombatTargetHud>,
1807 #[serde(default)]
1808 pub preset_id: Option<String>,
1809 #[serde(default)]
1810 pub preset_label: Option<String>,
1811 #[serde(default)]
1812 pub rotation: Vec<String>,
1813 #[serde(default)]
1814 pub rotation_index: u32,
1815 #[serde(default)]
1816 pub next_ability_id: Option<String>,
1817 #[serde(default)]
1818 pub auto_enabled: bool,
1819}
1820
1821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1823pub struct DefensePieceHud {
1824 pub slot: BodySlot,
1825 pub label: String,
1826 pub template_id: String,
1827 #[serde(default)]
1828 pub armor_physical: f32,
1829 #[serde(default)]
1830 pub resists: Vec<(String, f32)>,
1831}
1832
1833impl Default for DefensePieceHud {
1834 fn default() -> Self {
1835 Self {
1836 slot: BodySlot::Head,
1837 label: String::new(),
1838 template_id: String::new(),
1839 armor_physical: 0.0,
1840 resists: Vec::new(),
1841 }
1842 }
1843}
1844
1845#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1847pub struct DefenseHud {
1848 pub armor_physical: f32,
1849 pub vitality_contribution: f32,
1850 pub total_mitigation_rating: f32,
1851 pub estimated_physical_dr: f32,
1853 #[serde(default)]
1854 pub resists: Vec<(String, f32)>,
1855 #[serde(default)]
1856 pub pieces: Vec<DefensePieceHud>,
1857}
1858
1859#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1861pub struct CombatHud {
1862 pub in_combat: bool,
1863 pub auto_attack: bool,
1865 pub has_los: bool,
1866 pub attack_cd_ticks: u64,
1867 #[serde(default)]
1868 pub ability_id: String,
1869 #[serde(default)]
1870 pub target_entity_id: Option<EntityId>,
1871 #[serde(default)]
1872 pub target_label: Option<String>,
1873 #[serde(default)]
1874 pub max_target_slots: u8,
1875 #[serde(default)]
1876 pub slots: Vec<CombatSlotHud>,
1877 #[serde(default)]
1878 pub rotation_presets: Vec<RotationPreset>,
1879 #[serde(default)]
1880 pub gcd_ticks: u64,
1881 #[serde(default)]
1882 pub mainhand_template_id: Option<String>,
1883 #[serde(default)]
1884 pub mainhand_label: Option<String>,
1885 #[serde(default)]
1887 pub mainhand_instance_id: Option<Uuid>,
1888 #[serde(default)]
1889 pub offhand_template_id: Option<String>,
1890 #[serde(default)]
1891 pub offhand_label: Option<String>,
1892 #[serde(default)]
1894 pub offhand_instance_id: Option<Uuid>,
1895 #[serde(default)]
1897 pub mainhand_hand_slots: u8,
1898 #[serde(default)]
1900 pub worn: Vec<(BodySlot, ItemStack)>,
1901 #[serde(default)]
1903 pub defense: Option<DefenseHud>,
1904 #[serde(default)]
1905 pub carry_mass: f32,
1906 #[serde(default)]
1907 pub carry_mass_max: f32,
1908 #[serde(default)]
1909 pub encumbrance: EncumbranceState,
1910 #[serde(default)]
1912 pub keychain: Vec<ItemStack>,
1913 #[serde(default)]
1915 pub whisper_pouch: Vec<ItemStack>,
1916 #[serde(default)]
1917 pub target: Option<CombatTargetHud>,
1918 #[serde(default)]
1919 pub cast: Option<CastProgressHud>,
1920 #[serde(default)]
1922 pub timed_channel: Option<TimedChannelHud>,
1923 #[serde(default)]
1925 pub plot_build: Option<PlotBuildOfferHud>,
1926 #[serde(default)]
1927 pub ability_cooldowns: Vec<AbilityCooldownHud>,
1928 #[serde(default)]
1929 pub blocking_active: bool,
1930 #[serde(default)]
1932 pub progression_xp: Option<ProgressionXp>,
1933 #[serde(default)]
1934 pub progression_baseline: u16,
1935 #[serde(default)]
1936 pub progression_xp_base: f64,
1937 #[serde(default)]
1938 pub progression_xp_growth: f64,
1939 #[serde(default)]
1940 pub attributes: Option<PrimaryAttributes>,
1941 #[serde(default)]
1942 pub skills: Option<PlayerSkills>,
1943 #[serde(default)]
1945 pub statuses: Vec<StatusEffectHud>,
1946 #[serde(default)]
1948 pub known_abilities: Vec<String>,
1949 #[serde(default)]
1951 pub ability_meta: Vec<AbilityMetaHud>,
1952 #[serde(default)]
1954 pub ability_mastery: Vec<AbilityMasteryHud>,
1955 #[serde(default)]
1958 pub hotbar: Vec<Option<String>>,
1959 #[serde(default)]
1961 pub max_abilities_per_rotation: u8,
1962}
1963
1964#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1966pub struct AbilityMetaHud {
1967 pub id: String,
1968 #[serde(default = "default_aim_mode_entity")]
1970 pub aim_mode: String,
1971 #[serde(default)]
1972 pub blast_radius_m: f32,
1973 #[serde(default)]
1974 pub allows_self: bool,
1975 #[serde(default)]
1976 pub is_heal: bool,
1977 #[serde(default = "default_auto_rotation_eligible")]
1980 pub auto_rotation_eligible: bool,
1981}
1982
1983fn default_auto_rotation_eligible() -> bool {
1984 true
1985}
1986
1987fn default_aim_mode_entity() -> String {
1988 "entity".into()
1989}
1990
1991pub const HOTBAR_ITEM_PREFIX: &str = "item:";
1993
1994pub fn hotbar_consumable_binding(template_id: &str) -> String {
1996 format!("{HOTBAR_ITEM_PREFIX}{}", template_id.trim())
1997}
1998
1999pub fn hotbar_consumable_template(binding: &str) -> Option<&str> {
2001 binding
2002 .strip_prefix(HOTBAR_ITEM_PREFIX)
2003 .map(str::trim)
2004 .filter(|id| !id.is_empty())
2005}
2006
2007pub fn hotbar_binding_is_consumable(binding: &str) -> bool {
2009 hotbar_consumable_template(binding).is_some()
2010}
2011
2012#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2014#[serde(rename_all = "snake_case")]
2015pub enum CombatFxKind {
2016 MeleeArc,
2017 Cone,
2018 Sphere,
2019 Beam,
2020 HitMarker,
2021}
2022
2023#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2025#[serde(rename_all = "snake_case")]
2026pub enum CombatFxHitOutcome {
2027 #[default]
2028 Hit,
2029 Blocked,
2030 Miss,
2031 Glance,
2032}
2033
2034#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2036pub struct CombatFxHit {
2037 pub entity_id: EntityId,
2038 pub x: f32,
2039 pub y: f32,
2040 pub z: f32,
2041 #[serde(default)]
2042 pub outcome: CombatFxHitOutcome,
2043}
2044
2045#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2047pub struct CombatFx {
2048 pub id: u64,
2049 pub kind: CombatFxKind,
2050 pub ability_id: String,
2051 pub caster_id: EntityId,
2052 pub origin_x: f32,
2053 pub origin_y: f32,
2054 pub origin_z: f32,
2055 #[serde(default)]
2056 pub end_x: Option<f32>,
2057 #[serde(default)]
2058 pub end_y: Option<f32>,
2059 #[serde(default)]
2060 pub end_z: Option<f32>,
2061 #[serde(default)]
2062 pub yaw: Option<f32>,
2063 #[serde(default)]
2064 pub reach_m: Option<f32>,
2065 #[serde(default)]
2066 pub arc_deg: Option<f32>,
2067 #[serde(default)]
2068 pub radius_m: Option<f32>,
2069 #[serde(default)]
2070 pub hits: Vec<CombatFxHit>,
2071 pub until_tick: u64,
2073 #[serde(default)]
2074 pub damage_type: String,
2075}
2076
2077#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2079pub struct GroundHazardView {
2080 pub x: f32,
2081 pub y: f32,
2082 pub z: f32,
2083 pub radius_m: f32,
2084 pub expires_at_tick: u64,
2085 #[serde(default)]
2086 pub damage_type: String,
2087}
2088
2089#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2091#[serde(rename_all = "snake_case")]
2092pub enum WorkerModeView {
2093 Companion,
2094 Defender,
2095 JobLoop,
2096 Idle,
2099}
2100
2101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2103#[serde(rename_all = "snake_case")]
2104pub enum WorkerStateView {
2105 Idle,
2106 Traveling,
2107 Working,
2108 Resting,
2109 Waiting,
2110 Strike,
2111 Dismissed,
2112}
2113
2114#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
2116pub struct WorkerVitalsSummary {
2117 pub health_pct: f32,
2118 pub stamina_pct: f32,
2119 #[serde(default)]
2120 pub mana_pct: f32,
2121 #[serde(default)]
2122 pub hunger_pct: f32,
2123 #[serde(default)]
2124 pub thirst_pct: f32,
2125}
2126
2127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2129#[serde(rename_all = "snake_case")]
2130pub enum WorkerRouteKindView {
2131 #[default]
2132 HarvestLoop,
2133 Ordered,
2134}
2135
2136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2138pub struct WorkerRouteView {
2139 #[serde(default)]
2140 pub kind: WorkerRouteKindView,
2141 #[serde(default)]
2142 pub lodging_container_id: Option<String>,
2143 #[serde(default)]
2145 pub outbound_waypoints: Vec<WorkerRouteWaypointView>,
2146 #[serde(default)]
2148 pub harvest_nodes: Vec<String>,
2149 #[serde(default = "default_route_carry_ratio")]
2150 pub carry_return_ratio: f32,
2151 #[serde(default)]
2153 pub stops: Vec<WorkerRouteStopView>,
2154}
2155
2156fn default_route_carry_ratio() -> f32 {
2157 0.90
2158}
2159
2160fn default_true_view() -> bool {
2161 true
2162}
2163
2164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2166pub struct WorkerWithdrawItemView {
2167 pub template: String,
2168 #[serde(default)]
2170 pub qty: u32,
2171 #[serde(default)]
2173 pub all: bool,
2174}
2175
2176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2177pub struct WorkerRouteWaypointView {
2178 pub x: f32,
2179 pub y: f32,
2180 pub z: f32,
2181}
2182
2183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2192#[serde(rename_all = "snake_case")]
2193pub enum WorkerRouteStopView {
2194 Waypoint {
2195 x: f32,
2196 y: f32,
2197 #[serde(default)]
2198 z: f32,
2199 },
2200 HarvestNode {
2201 node_id: String,
2202 },
2203 DepositAt {
2204 container_id: String,
2205 #[serde(default)]
2206 filter: Option<Vec<String>>,
2207 },
2208 TradeWith {
2209 #[serde(default)]
2210 npc_id: Option<String>,
2211 template: String,
2212 #[serde(default = "default_true_view")]
2213 sell_all: bool,
2214 },
2215 WithdrawFrom {
2216 container_id: String,
2217 items: Vec<WorkerWithdrawItemView>,
2218 },
2219 CraftAt {
2220 device: String,
2221 blueprint: String,
2222 #[serde(default)]
2223 qty: Option<u32>,
2224 },
2225 CultivatePlot {
2226 plot_id: uuid::Uuid,
2227 },
2228 PlantPlot {
2229 plot_id: uuid::Uuid,
2230 seed_template: String,
2231 },
2232 HarvestPlot {
2233 plot_id: uuid::Uuid,
2234 },
2235 RestIfNeeded,
2236 Wait {
2237 #[serde(default)]
2238 wait_ticks: u64,
2239 },
2240}
2241
2242#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2244#[serde(rename_all = "snake_case")]
2245pub enum LedgerCategory {
2246 Workers,
2247 Hire,
2248 Train,
2249 ShopBuy,
2250 Taxes,
2251 WorkerSales,
2252 TraderSales,
2253 BankDeposit,
2254 BankWithdraw,
2255 BankTransferOut,
2256 BankTransferIn,
2257 BankTransferFee,
2258 StorageShipFee,
2259 PropertyBuy,
2261 PropertySell,
2263 TaxShare,
2265 MarketBuy,
2267 MarketSell,
2269 Other,
2270}
2271
2272impl LedgerCategory {
2273 pub fn as_str(self) -> &'static str {
2274 match self {
2275 Self::Workers => "workers",
2276 Self::Hire => "hire",
2277 Self::Train => "train",
2278 Self::ShopBuy => "shop_buy",
2279 Self::Taxes => "taxes",
2280 Self::WorkerSales => "worker_sales",
2281 Self::TraderSales => "trader_sales",
2282 Self::BankDeposit => "bank_deposit",
2283 Self::BankWithdraw => "bank_withdraw",
2284 Self::BankTransferOut => "bank_transfer_out",
2285 Self::BankTransferIn => "bank_transfer_in",
2286 Self::BankTransferFee => "bank_transfer_fee",
2287 Self::StorageShipFee => "storage_ship_fee",
2288 Self::PropertyBuy => "property_buy",
2289 Self::PropertySell => "property_sell",
2290 Self::TaxShare => "tax_share",
2291 Self::MarketBuy => "market_buy",
2292 Self::MarketSell => "market_sell",
2293 Self::Other => "other",
2294 }
2295 }
2296}
2297
2298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2299pub struct LedgerEntryView {
2300 pub id: uuid::Uuid,
2301 pub game_day: u64,
2302 pub signed_copper: i64,
2303 pub category: LedgerCategory,
2304 #[serde(default)]
2305 pub label: String,
2306}
2307
2308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2309pub struct LedgerPeriodTotals {
2310 #[serde(default)]
2312 pub expenses: std::collections::HashMap<String, u64>,
2313 #[serde(default)]
2315 pub income: std::collections::HashMap<String, u64>,
2316 pub expense_copper: u64,
2317 pub income_copper: u64,
2318 pub cash_flow_copper: i64,
2320}
2321
2322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2323pub struct PlayerLedgerView {
2324 pub current_game_day: u64,
2325 #[serde(default)]
2326 pub period_day: LedgerPeriodTotals,
2327 #[serde(default)]
2328 pub period_week: LedgerPeriodTotals,
2329 #[serde(default)]
2330 pub period_month: LedgerPeriodTotals,
2331 #[serde(default)]
2332 pub period_lifetime: LedgerPeriodTotals,
2333 #[serde(default)]
2334 pub recent: Vec<LedgerEntryView>,
2335 #[serde(default)]
2337 pub wealth_on_person_copper: u64,
2338 #[serde(default)]
2340 pub wealth_in_storage_copper: u64,
2341 #[serde(default)]
2343 pub wealth_in_bank_copper: u64,
2344 #[serde(default)]
2346 pub wealth_total_copper: u64,
2347 #[serde(default)]
2349 pub wealth_in_property_copper: u64,
2350 #[serde(default)]
2352 pub wealth_net_worth_copper: u64,
2353 #[serde(default)]
2355 pub property_assets: Vec<PropertyAssetView>,
2356 #[serde(default)]
2358 pub property_market_nearby: Vec<PropertyMarketCompView>,
2359 #[serde(default)]
2361 pub live_expense_per_interval_copper: u64,
2362 #[serde(default)]
2364 pub live_income_route_est_per_loop_copper: u64,
2365 #[serde(default)]
2367 pub live_income_avg_per_interval_copper: u64,
2368 #[serde(default)]
2370 pub live_income_avg_window_intervals: u32,
2371 #[serde(default)]
2373 pub live_net_avg_per_interval_copper: i64,
2374}
2375
2376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2378pub struct PropertyAssetView {
2379 pub plot_id: Uuid,
2380 pub label: String,
2382 pub zone_id: String,
2383 #[serde(default)]
2384 pub zone_label: Option<String>,
2385 pub area_m2: f32,
2386 pub purchase_basis_copper: u64,
2388 pub upkeep_copper_per_day: u64,
2389}
2390
2391#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2393pub struct PropertyMarketCompView {
2394 pub day: u64,
2395 pub zone_id: String,
2396 #[serde(default)]
2397 pub zone_label: Option<String>,
2398 pub area_m2: f32,
2399 pub price_copper: u64,
2400 pub price_per_m2_copper: u64,
2402 pub kind: String,
2404 pub distance_m: f32,
2406}
2407
2408#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2410#[serde(rename_all = "snake_case")]
2411pub enum AnalyticsMetric {
2412 NpcKill,
2413 WildlifeKill,
2414 Harvest,
2415 QuestComplete,
2416 QuestAccept,
2417 QuestAbandon,
2418 PlayerDeath,
2419 Craft,
2420 WorkerHire,
2421 WorkerDismiss,
2422 WorkerTeach,
2423 NpcTalk,
2424 ShopBuy,
2425 ShopSell,
2426 PlaceContainer,
2427 PickupContainer,
2428 PickupDrop,
2429 ConsumableUse,
2430 AbilityUse,
2431 DistanceWalkedM,
2432 DoorUse,
2433 BuildingEnter,
2434}
2435
2436impl AnalyticsMetric {
2437 pub fn as_str(self) -> &'static str {
2438 match self {
2439 Self::NpcKill => "npc_kill",
2440 Self::WildlifeKill => "wildlife_kill",
2441 Self::Harvest => "harvest",
2442 Self::QuestComplete => "quest_complete",
2443 Self::QuestAccept => "quest_accept",
2444 Self::QuestAbandon => "quest_abandon",
2445 Self::PlayerDeath => "player_death",
2446 Self::Craft => "craft",
2447 Self::WorkerHire => "worker_hire",
2448 Self::WorkerDismiss => "worker_dismiss",
2449 Self::WorkerTeach => "worker_teach",
2450 Self::NpcTalk => "npc_talk",
2451 Self::ShopBuy => "shop_buy",
2452 Self::ShopSell => "shop_sell",
2453 Self::PlaceContainer => "place_container",
2454 Self::PickupContainer => "pickup_container",
2455 Self::PickupDrop => "pickup_drop",
2456 Self::ConsumableUse => "consumable_use",
2457 Self::AbilityUse => "ability_use",
2458 Self::DistanceWalkedM => "distance_walked_m",
2459 Self::DoorUse => "door_use",
2460 Self::BuildingEnter => "building_enter",
2461 }
2462 }
2463
2464 pub fn from_str_key(s: &str) -> Option<Self> {
2465 Some(match s {
2466 "npc_kill" => Self::NpcKill,
2467 "wildlife_kill" => Self::WildlifeKill,
2468 "harvest" => Self::Harvest,
2469 "quest_complete" => Self::QuestComplete,
2470 "quest_accept" => Self::QuestAccept,
2471 "quest_abandon" => Self::QuestAbandon,
2472 "player_death" => Self::PlayerDeath,
2473 "craft" => Self::Craft,
2474 "worker_hire" => Self::WorkerHire,
2475 "worker_dismiss" => Self::WorkerDismiss,
2476 "worker_teach" => Self::WorkerTeach,
2477 "npc_talk" => Self::NpcTalk,
2478 "shop_buy" => Self::ShopBuy,
2479 "shop_sell" => Self::ShopSell,
2480 "place_container" => Self::PlaceContainer,
2481 "pickup_container" => Self::PickupContainer,
2482 "pickup_drop" => Self::PickupDrop,
2483 "consumable_use" => Self::ConsumableUse,
2484 "ability_use" => Self::AbilityUse,
2485 "distance_walked_m" => Self::DistanceWalkedM,
2486 "door_use" => Self::DoorUse,
2487 "building_enter" => Self::BuildingEnter,
2488 _ => return None,
2489 })
2490 }
2491}
2492
2493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2494pub struct CareerMetricRow {
2495 pub subject_id: String,
2496 pub amount: u64,
2497}
2498
2499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2501pub struct PlayerCareerView {
2502 pub current_game_day: u64,
2503 #[serde(default)]
2504 pub kills: Vec<CareerMetricRow>,
2505 #[serde(default)]
2506 pub harvests: Vec<CareerMetricRow>,
2507 pub quests_completed: u64,
2508 #[serde(default)]
2509 pub crafts: Vec<CareerMetricRow>,
2510 pub deaths: u64,
2511 pub npc_talks: u64,
2512 pub shop_buys: u64,
2513 pub shop_sells: u64,
2514 pub distance_m: u64,
2515 #[serde(default)]
2516 pub other: Vec<CareerMetricRow>,
2517}
2518
2519#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2524pub struct WorkerEquipmentView {
2525 #[serde(default)]
2526 pub mainhand: Option<ItemStack>,
2527 #[serde(default)]
2528 pub offhand: Option<ItemStack>,
2529 #[serde(default)]
2530 pub worn: Vec<(BodySlot, ItemStack)>,
2531}
2532
2533#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2535pub struct HiredWorkerView {
2536 pub instance_id: String,
2537 pub entity_id: EntityId,
2538 pub def_id: String,
2539 pub label: String,
2541 pub x: f32,
2542 pub y: f32,
2543 pub z: f32,
2544 pub mode: WorkerModeView,
2545 pub state: WorkerStateView,
2546 #[serde(default)]
2547 pub step_label: String,
2548 pub vitals: WorkerVitalsSummary,
2549 #[serde(default)]
2550 pub carry_pct: f32,
2551 #[serde(default)]
2552 pub last_error: Option<String>,
2553 pub wage_copper_per_interval: u32,
2554 #[serde(default)]
2556 pub effective_wage_copper: u32,
2557 #[serde(default)]
2559 pub wage_meters_walked: f32,
2560 #[serde(default)]
2562 pub lodging_container_id: Option<String>,
2563 #[serde(default)]
2565 pub route: Option<WorkerRouteView>,
2566 #[serde(default)]
2569 pub route_stop_index: Option<u32>,
2570 #[serde(default)]
2572 pub known_blueprint_ids: Vec<String>,
2573 #[serde(default = "default_worker_view_level")]
2575 pub level: u32,
2576 #[serde(default)]
2578 pub worker_xp: f64,
2579 #[serde(default)]
2581 pub inventory: Vec<ItemStack>,
2582 #[serde(default)]
2584 pub equipment: WorkerEquipmentView,
2585 #[serde(default)]
2588 pub issue_hint: Option<String>,
2589}
2590
2591fn default_worker_view_level() -> u32 {
2592 1
2593}
2594
2595#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2597pub struct TickDelta {
2598 pub tick: Tick,
2599 pub entities: Vec<EntityState>,
2600 #[serde(default)]
2601 pub resource_nodes: Vec<ResourceNodeView>,
2602 #[serde(default)]
2603 pub buildings: Vec<BuildingView>,
2604 #[serde(default)]
2605 pub doors: Vec<DoorView>,
2606 #[serde(default)]
2607 pub npcs: Vec<NpcView>,
2608 #[serde(default)]
2610 pub inventory: Vec<ItemStack>,
2611 #[serde(default)]
2612 pub blueprints: Vec<BlueprintView>,
2613 #[serde(default)]
2615 pub building_materials: Vec<BuildingMaterialView>,
2616 #[serde(default)]
2617 pub world_clock: WorldClock,
2618 #[serde(default)]
2619 pub ground_drops: Vec<GroundDropView>,
2620 #[serde(default)]
2621 pub placed_containers: Vec<PlacedContainerView>,
2622 #[serde(default)]
2623 pub combat: Option<CombatHud>,
2624 #[serde(default)]
2625 pub interior_map: Option<InteriorMapView>,
2626 #[serde(default)]
2627 pub quest_log: Vec<QuestLogEntry>,
2628 #[serde(default)]
2629 pub hired_workers: Vec<HiredWorkerView>,
2630 #[serde(default)]
2631 pub interactables: Vec<InteractableView>,
2632 #[serde(default)]
2633 pub ledger: Option<PlayerLedgerView>,
2634 #[serde(default)]
2635 pub career: Option<PlayerCareerView>,
2636 #[serde(default)]
2638 pub combat_fx: Vec<CombatFx>,
2639 #[serde(default)]
2641 pub ground_hazards: Vec<GroundHazardView>,
2642 #[serde(default)]
2644 pub property_plots: Vec<PropertyPlotView>,
2645 #[serde(default)]
2647 pub terrain_overlays: Vec<TerrainZoneView>,
2648}
2649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2650pub struct GroundDropView {
2651 pub id: String,
2652 pub template_id: String,
2653 pub quantity: u32,
2654 pub x: f32,
2655 pub y: f32,
2656 pub z: f32,
2657 #[serde(default)]
2659 pub tile_id: Option<String>,
2660 #[serde(default)]
2662 pub display_name: Option<String>,
2663 #[serde(default)]
2665 pub yaw: f32,
2666 #[serde(default)]
2668 pub pitch: f32,
2669 #[serde(default)]
2671 pub roll: f32,
2672 #[serde(default = "default_draw_scale")]
2674 pub draw_scale: f32,
2675}
2676
2677fn default_draw_scale() -> f32 {
2678 1.0
2679}
2680
2681#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2683pub struct Snapshot {
2684 pub tick: Tick,
2685 pub chunk_rev: u64,
2686 #[serde(default)]
2688 pub content_rev: u64,
2689 #[serde(default)]
2691 pub publish_rev: u64,
2692 pub entities: Vec<EntityState>,
2693 #[serde(default)]
2694 pub resource_nodes: Vec<ResourceNodeView>,
2695 #[serde(default)]
2697 pub world_x0: f32,
2698 #[serde(default)]
2699 pub world_y0: f32,
2700 #[serde(default)]
2702 pub world_width_m: f32,
2703 #[serde(default)]
2704 pub world_height_m: f32,
2705 #[serde(default)]
2706 pub buildings: Vec<BuildingView>,
2707 #[serde(default)]
2708 pub doors: Vec<DoorView>,
2709 #[serde(default)]
2710 pub npcs: Vec<NpcView>,
2711 #[serde(default)]
2712 pub inventory: Vec<ItemStack>,
2713 #[serde(default)]
2714 pub blueprints: Vec<BlueprintView>,
2715 #[serde(default)]
2717 pub building_materials: Vec<BuildingMaterialView>,
2718 #[serde(default)]
2719 pub world_clock: WorldClock,
2720 #[serde(default)]
2721 pub terrain_zones: Vec<TerrainZoneView>,
2722 #[serde(default)]
2723 pub z_platforms: Vec<ZPlatformView>,
2724 #[serde(default)]
2725 pub z_transitions: Vec<ZTransitionView>,
2726 #[serde(default)]
2727 pub ground_drops: Vec<GroundDropView>,
2728 #[serde(default)]
2729 pub placed_containers: Vec<PlacedContainerView>,
2730 #[serde(default)]
2731 pub combat: Option<CombatHud>,
2732 #[serde(default)]
2733 pub interior_map: Option<InteriorMapView>,
2734 #[serde(default)]
2735 pub quest_log: Vec<QuestLogEntry>,
2736 #[serde(default)]
2737 pub hired_workers: Vec<HiredWorkerView>,
2738 #[serde(default)]
2739 pub interactables: Vec<InteractableView>,
2740 #[serde(default)]
2741 pub ledger: Option<PlayerLedgerView>,
2742 #[serde(default)]
2743 pub career: Option<PlayerCareerView>,
2744 #[serde(default)]
2746 pub combat_fx: Vec<CombatFx>,
2747 #[serde(default)]
2749 pub ground_hazards: Vec<GroundHazardView>,
2750 #[serde(default)]
2752 pub property_zones: Vec<PropertyZoneView>,
2753 #[serde(default)]
2755 pub tax_zones: Vec<TaxZoneView>,
2756 #[serde(default)]
2758 pub boundary_zones: Vec<BoundaryZoneView>,
2759 #[serde(default)]
2761 pub encounter_zones: Vec<EncounterZoneView>,
2762 #[serde(default)]
2764 pub growth_zones: Vec<GrowthZoneView>,
2765 #[serde(default)]
2767 pub biome_zones: Vec<BiomeZoneView>,
2768 #[serde(default)]
2770 pub terrain_kind_nav: Vec<TerrainKindNavView>,
2771 #[serde(default)]
2773 pub property_plots: Vec<PropertyPlotView>,
2774 #[serde(default)]
2776 pub property_plot_settings: Option<PropertyPlotSettingsView>,
2777}
2778
2779#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2781pub struct ResourceNodeView {
2782 pub id: String,
2783 pub label: String,
2784 pub x: f32,
2785 pub y: f32,
2786 pub z: f32,
2787 pub item_template: String,
2788 #[serde(default = "default_node_state")]
2789 pub state: ResourceNodeState,
2790 #[serde(default = "default_blocking_view")]
2792 pub blocking: bool,
2793 #[serde(default = "default_blocking_radius_view")]
2795 pub blocking_radius_m: f32,
2796 #[serde(default)]
2798 pub harvest_off: bool,
2799 #[serde(default)]
2801 pub tile_id: Option<String>,
2802 #[serde(default)]
2804 pub yaw: f32,
2805 #[serde(default)]
2807 pub pitch: f32,
2808 #[serde(default)]
2810 pub roll: f32,
2811 #[serde(default = "default_draw_scale")]
2813 pub draw_scale: f32,
2814 #[serde(default)]
2816 pub sprite_mode: Option<String>,
2817 #[serde(default)]
2819 pub presentation_state: Option<String>,
2820 #[serde(default)]
2823 pub growth_progress: Option<f32>,
2824 #[serde(default)]
2826 pub channel_start_tick: Option<Tick>,
2827 #[serde(default)]
2828 pub channel_end_tick: Option<Tick>,
2829 #[serde(default)]
2831 pub harvest_drop_templates: Vec<String>,
2832}
2833
2834fn default_blocking_radius_view() -> f32 {
2835 0.8
2836}
2837
2838fn default_blocking_view() -> bool {
2839 true
2840}
2841
2842#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2843#[serde(rename_all = "snake_case")]
2844pub enum ResourceNodeState {
2845 Available,
2846 Harvesting,
2847 Cooldown,
2848}
2849fn default_node_state() -> ResourceNodeState {
2850 ResourceNodeState::Available
2851}
2852
2853#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2855#[serde(rename_all = "snake_case")]
2856pub enum ItemSpawnStateView {
2857 Spawned,
2858 PickedUp { respawn_at_tick: u64 },
2859 Consumed,
2860}
2861
2862#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2864pub struct ItemSpawnView {
2865 pub id: String,
2866 pub label: String,
2867 pub item_template: String,
2868 pub quantity: u32,
2869 pub x: f32,
2870 pub y: f32,
2871 pub z: f32,
2872 pub respawn_ticks: u32,
2873 #[serde(default)]
2874 pub building_id: Option<String>,
2875 pub state: ItemSpawnStateView,
2876}
2877
2878#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2879#[serde(rename_all = "snake_case")]
2880pub enum ItemStatusBindingMode {
2881 OnHit,
2882 WhileEquipped,
2883}
2884
2885impl Default for ItemStatusBindingMode {
2886 fn default() -> Self {
2887 Self::OnHit
2888 }
2889}
2890
2891#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2893pub struct ItemStatusBinding {
2894 pub effect_id: String,
2895 #[serde(default)]
2896 pub mode: ItemStatusBindingMode,
2897 #[serde(default)]
2899 pub source: String,
2900 #[serde(default)]
2901 pub applied_at_tick: u64,
2902 #[serde(default)]
2905 pub expires_at_tick: Option<u64>,
2906}
2907
2908#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2909pub struct ItemStack {
2910 pub template_id: String,
2911 pub quantity: u32,
2912 #[serde(default)]
2914 pub item_instance_id: Option<Uuid>,
2915 #[serde(default)]
2917 pub props: BTreeMap<String, String>,
2918 #[serde(default)]
2920 pub status_bindings: Vec<ItemStatusBinding>,
2921 #[serde(default)]
2923 pub contents: Vec<ItemStack>,
2924 #[serde(default)]
2926 pub display_name: Option<String>,
2927 #[serde(default)]
2929 pub category: Option<String>,
2930 #[serde(default)]
2932 pub base_mass: Option<f32>,
2933 #[serde(default)]
2935 pub base_volume: Option<f32>,
2936 #[serde(default)]
2938 pub capacity_volume: Option<f32>,
2939 #[serde(default)]
2941 pub stackable: Option<bool>,
2942 #[serde(default)]
2944 pub world_placeable: Option<bool>,
2945 #[serde(default)]
2947 pub worker_lodging_capacity: Option<u32>,
2948 #[serde(default)]
2950 pub equip_slot: Option<BodySlot>,
2951 #[serde(default)]
2953 pub armor_physical: Option<f32>,
2954 #[serde(default)]
2956 pub resists: Vec<(String, f32)>,
2957 #[serde(default)]
2959 pub hand_slots: Option<u8>,
2960 #[serde(default)]
2962 pub listable: Option<bool>,
2963 #[serde(default)]
2965 pub base_value_copper: Option<u32>,
2966}
2967
2968impl ItemStack {
2969 pub fn simple(template_id: impl Into<String>, quantity: u32) -> Self {
2970 Self {
2971 template_id: template_id.into(),
2972 quantity,
2973 ..Default::default()
2974 }
2975 }
2976}
2977
2978impl Default for ItemStack {
2979 fn default() -> Self {
2980 Self {
2981 template_id: String::new(),
2982 quantity: 0,
2983 item_instance_id: None,
2984 props: BTreeMap::new(),
2985 status_bindings: Vec::new(),
2986 contents: Vec::new(),
2987 display_name: None,
2988 category: None,
2989 base_mass: None,
2990 base_volume: None,
2991 capacity_volume: None,
2992 stackable: None,
2993 world_placeable: None,
2994 worker_lodging_capacity: None,
2995 equip_slot: None,
2996 armor_physical: None,
2997 resists: Vec::new(),
2998 hand_slots: None,
2999 listable: None,
3000 base_value_copper: None,
3001 }
3002 }
3003}
3004
3005#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3007#[serde(rename_all = "snake_case")]
3008pub enum EncumbranceState {
3009 #[default]
3010 Light,
3011 Heavy,
3012 Over,
3013}
3014
3015#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
3019#[serde(rename_all = "snake_case")]
3020pub enum BodySlot {
3021 Head,
3022 #[serde(alias = "body")]
3024 Chest,
3025 #[serde(alias = "arms")]
3027 Forearms,
3028 Legs,
3029 Feet,
3030 Cloak,
3031 Back,
3032 Waist,
3033 Earrings,
3034 Necklace,
3035 Eyeglasses,
3036 #[serde(rename = "ring_left_1", alias = "ring_left1")]
3038 RingLeft1,
3039 #[serde(rename = "ring_left_2", alias = "ring_left2")]
3040 RingLeft2,
3041 #[serde(rename = "ring_right_1", alias = "ring_right1")]
3042 RingRight1,
3043 #[serde(rename = "ring_right_2", alias = "ring_right2")]
3044 RingRight2,
3045}
3046
3047impl BodySlot {
3048 pub const ALL: [BodySlot; 15] = [
3050 BodySlot::Head,
3051 BodySlot::Chest,
3052 BodySlot::Forearms,
3053 BodySlot::Legs,
3054 BodySlot::Feet,
3055 BodySlot::Cloak,
3056 BodySlot::Back,
3057 BodySlot::Waist,
3058 BodySlot::Earrings,
3059 BodySlot::Necklace,
3060 BodySlot::Eyeglasses,
3061 BodySlot::RingLeft1,
3062 BodySlot::RingLeft2,
3063 BodySlot::RingRight1,
3064 BodySlot::RingRight2,
3065 ];
3066
3067 pub fn as_str(self) -> &'static str {
3068 match self {
3069 BodySlot::Head => "head",
3070 BodySlot::Chest => "chest",
3071 BodySlot::Forearms => "forearms",
3072 BodySlot::Legs => "legs",
3073 BodySlot::Feet => "feet",
3074 BodySlot::Cloak => "cloak",
3075 BodySlot::Back => "back",
3076 BodySlot::Waist => "waist",
3077 BodySlot::Earrings => "earrings",
3078 BodySlot::Necklace => "necklace",
3079 BodySlot::Eyeglasses => "eyeglasses",
3080 BodySlot::RingLeft1 => "ring_left_1",
3081 BodySlot::RingLeft2 => "ring_left_2",
3082 BodySlot::RingRight1 => "ring_right_1",
3083 BodySlot::RingRight2 => "ring_right_2",
3084 }
3085 }
3086}
3087
3088#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3090#[serde(rename_all = "snake_case")]
3091pub enum InventoryLocation {
3092 Root,
3094 Worn { slot: BodySlot },
3096 Placed { container_id: String },
3098 Keychain,
3100 WhisperPouch,
3102}
3103
3104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3106pub struct PlacedContainerView {
3107 pub id: String,
3108 pub template_id: String,
3109 pub display_name: String,
3110 pub x: f32,
3111 pub y: f32,
3112 pub z: f32,
3113 pub locked: bool,
3114 #[serde(default)]
3116 pub accessible: bool,
3117 #[serde(default)]
3118 pub owner_character_id: Option<Uuid>,
3119 #[serde(default)]
3121 pub contents: Vec<ItemStack>,
3122 #[serde(default)]
3124 pub lock_id: Option<String>,
3125 #[serde(default)]
3127 pub capacity_volume: Option<f32>,
3128 #[serde(default)]
3130 pub item_instance_id: Option<Uuid>,
3131 #[serde(default)]
3133 pub tile_id: Option<String>,
3134 #[serde(default)]
3136 pub worker_lodging_capacity: Option<u32>,
3137 #[serde(default)]
3139 pub blocking: bool,
3140 #[serde(default)]
3142 pub blocking_radius_m: f32,
3143 #[serde(default)]
3146 pub building_id: Option<String>,
3147}
3148
3149#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3150pub struct BlueprintIngredientView {
3151 pub template_id: String,
3152 pub quantity: u32,
3153 pub consumed: bool,
3155 #[serde(default)]
3157 pub display_name: String,
3158}
3159
3160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3161pub struct ToolRequirementView {
3162 pub item: String,
3163 pub consumed: bool,
3165 #[serde(default)]
3167 pub display_name: String,
3168}
3169
3170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3171pub struct SkillRequirementView {
3172 pub skill: String,
3173 pub level: u32,
3174}
3175
3176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3177pub struct BlueprintView {
3178 pub id: String,
3179 pub label: String,
3180 pub output: String,
3181 pub output_qty: u32,
3182 pub craft_ticks: u32,
3183 pub inputs: Vec<BlueprintIngredientView>,
3184 #[serde(default)]
3186 pub station: Option<String>,
3187 #[serde(default)]
3188 pub category: Option<String>,
3189 #[serde(default)]
3190 pub required_tools: Vec<ToolRequirementView>,
3191 #[serde(default)]
3192 pub skill: Option<SkillRequirementView>,
3193 #[serde(default)]
3194 pub failure_chance: f32,
3195 #[serde(default)]
3197 pub worker_train_copper: u64,
3198 #[serde(default)]
3200 pub output_display_name: String,
3201}
3202
3203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3205pub struct TerrainKindNavView {
3206 pub kind: TerrainKindView,
3207 #[serde(default = "default_move_speed_mult_one")]
3208 pub move_speed_mult: f32,
3209 #[serde(default)]
3210 pub impassable: bool,
3211}
3212
3213fn default_move_speed_mult_one() -> f32 {
3214 1.0
3215}
3216
3217#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3219#[serde(rename_all = "snake_case")]
3220pub enum TerrainKindView {
3221 #[default]
3222 Grass,
3223 Dirt,
3224 Tilled,
3225 Desert,
3226 Hill,
3227 Bog,
3228 Beach,
3229 ShallowWater,
3230 DeepWater,
3231 Trail,
3232 Road,
3233 Rock,
3234}
3235
3236#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3237pub struct TerrainZoneView {
3238 pub id: String,
3239 pub x0: f32,
3240 pub y0: f32,
3241 pub x1: f32,
3242 pub y1: f32,
3243 #[serde(default)]
3244 pub kind: TerrainKindView,
3245 #[serde(default)]
3247 pub elevation: f32,
3248 #[serde(default)]
3251 pub glyph: Option<String>,
3252 #[serde(default)]
3254 pub color: Option<String>,
3255 #[serde(default)]
3257 pub tile_id: Option<String>,
3258 #[serde(default)]
3260 pub z_order: i32,
3261 #[serde(default)]
3263 pub channel_start_tick: Option<Tick>,
3264 #[serde(default)]
3265 pub channel_end_tick: Option<Tick>,
3266}
3267
3268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3270pub struct ZoneRectView {
3271 pub x0: f32,
3272 pub y0: f32,
3273 pub x1: f32,
3274 pub y1: f32,
3275}
3276
3277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3279pub struct PropertyZoneView {
3280 pub id: String,
3281 #[serde(default)]
3283 pub label: Option<String>,
3284 pub rects: Vec<ZoneRectView>,
3285 #[serde(default)]
3286 pub z_order: i32,
3287 pub crown_price_copper: u64,
3288 pub upkeep_copper_per_day: u64,
3289 #[serde(default)]
3290 pub max_area_m2: Option<f32>,
3291 #[serde(default)]
3292 pub owner_tax_discount_bps: u32,
3293}
3294
3295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3297pub struct TaxZoneView {
3298 pub id: String,
3299 #[serde(default)]
3300 pub label: Option<String>,
3301 pub rects: Vec<ZoneRectView>,
3302 #[serde(default)]
3303 pub z_order: i32,
3304 pub rate_bps: u32,
3305 #[serde(default)]
3306 pub flat_copper: u64,
3307 #[serde(default)]
3309 pub market_sales_tax_bps: u32,
3310 #[serde(default)]
3312 pub market_sales_flat_copper: u32,
3313}
3314
3315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3317pub struct BoundaryZoneView {
3318 pub id: String,
3319 #[serde(default)]
3320 pub label: Option<String>,
3321 pub rects: Vec<ZoneRectView>,
3322 #[serde(default)]
3323 pub z_order: i32,
3324 #[serde(default, skip_serializing_if = "Option::is_none")]
3325 pub jurisdiction_id: Option<String>,
3326 #[serde(default = "default_true")]
3327 pub worker_logistics: bool,
3328 #[serde(default)]
3329 pub security_tier: String,
3330 #[serde(default)]
3331 pub pvp_mode: String,
3332 #[serde(default = "default_true")]
3333 pub crime_enabled: bool,
3334 #[serde(default)]
3335 pub guard_response: bool,
3336 #[serde(default)]
3338 pub presence_mode: String,
3339}
3340
3341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3343pub struct EncounterZoneView {
3344 pub id: String,
3345 #[serde(default)]
3346 pub label: Option<String>,
3347 pub rects: Vec<ZoneRectView>,
3348 #[serde(default)]
3349 pub z_order: i32,
3350}
3351
3352fn default_true() -> bool {
3353 true
3354}
3355
3356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3358pub struct GrowthZoneView {
3359 pub id: String,
3360 #[serde(default)]
3361 pub label: Option<String>,
3362 pub rects: Vec<ZoneRectView>,
3363 #[serde(default)]
3364 pub z_order: i32,
3365 #[serde(default = "default_one_f32")]
3366 pub fertility: f32,
3367}
3368
3369fn default_one_f32() -> f32 {
3370 1.0
3371}
3372
3373#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3375pub struct BiomeZoneView {
3376 pub id: String,
3377 #[serde(default)]
3378 pub label: Option<String>,
3379 pub rects: Vec<ZoneRectView>,
3380 #[serde(default)]
3381 pub z_order: i32,
3382 pub biome_id: String,
3383}
3384
3385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3387pub struct FarmGrantView {
3388 pub character_id: Uuid,
3389 #[serde(default)]
3391 pub character_label: String,
3392 pub tax_discount_bps: u32,
3393}
3394
3395#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3397pub struct PropertyPlotView {
3398 pub plot_id: Uuid,
3399 pub property_zone_id: String,
3400 #[serde(default)]
3401 pub zone_label: Option<String>,
3402 pub deed_instance_id: Uuid,
3403 pub x0: f32,
3404 pub y0: f32,
3405 pub x1: f32,
3406 pub y1: f32,
3407 pub upkeep_copper_per_day: u64,
3408 pub arrears_days: u32,
3409 #[serde(default)]
3411 pub is_mine: bool,
3412 #[serde(default)]
3414 pub may_farm: bool,
3415 #[serde(default)]
3417 pub purchase_basis_copper: u64,
3418 #[serde(default)]
3419 pub farm_public: bool,
3420 #[serde(default)]
3421 pub public_tax_discount_bps: u32,
3422 #[serde(default)]
3423 pub farm_allow: Vec<FarmGrantView>,
3424 #[serde(default)]
3426 pub owner_character_id: Option<Uuid>,
3427 #[serde(default)]
3428 pub owner_label: Option<String>,
3429 #[serde(default)]
3431 pub building_id: Option<String>,
3432 #[serde(default)]
3434 pub plot_code: String,
3435 #[serde(default)]
3437 pub label: String,
3438}
3439
3440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3442pub struct PropertyPlotSettingsView {
3443 pub min_plot_area_m2: f32,
3444 pub tax_premium_weight: f32,
3445 pub sellback_bps: u32,
3446}
3447
3448#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3450pub struct ZPlatformView {
3451 pub id: String,
3452 pub z: f32,
3453 pub x0: f32,
3454 pub y0: f32,
3455 pub x1: f32,
3456 pub y1: f32,
3457}
3458
3459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3461pub struct ZTransitionView {
3462 pub id: String,
3463 pub z_from: f32,
3464 pub z_to: f32,
3465 pub x0: f32,
3466 pub y0: f32,
3467 pub x1: f32,
3468 pub y1: f32,
3469}
3470
3471#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3472pub struct BuildingView {
3473 pub id: String,
3474 pub label: String,
3475 pub x: f32,
3476 pub y: f32,
3477 pub width_m: f32,
3478 pub depth_m: f32,
3479 #[serde(default)]
3480 pub interior_blueprint: Option<String>,
3481 #[serde(default)]
3482 pub tags: Vec<String>,
3483 #[serde(default)]
3485 pub market_boundary_zone_ids: Vec<String>,
3486 #[serde(default)]
3488 pub market_max_volume: Option<f32>,
3489 #[serde(default)]
3492 pub wall_set: Option<String>,
3493 #[serde(default)]
3495 pub roof_set: Option<String>,
3496}
3497
3498pub const DEFAULT_BUILDING_ART_SET: &str = "classic_stone";
3501
3502impl BuildingView {
3503 pub fn effective_wall_set(&self) -> &str {
3504 self.wall_set
3505 .as_deref()
3506 .filter(|s| !s.is_empty())
3507 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3508 }
3509
3510 pub fn effective_roof_set(&self) -> &str {
3511 self.roof_set
3512 .as_deref()
3513 .filter(|s| !s.is_empty())
3514 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3515 }
3516}
3517
3518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3519pub struct DoorView {
3520 pub id: String,
3521 pub building_id: String,
3522 pub x: f32,
3523 pub y: f32,
3524 #[serde(default)]
3525 pub open: bool,
3526 #[serde(default)]
3527 pub portal: Option<String>,
3528 #[serde(default)]
3530 pub locked: bool,
3531 #[serde(default = "default_door_accessible")]
3533 pub accessible: bool,
3534 #[serde(default)]
3535 pub lock_id: Option<Uuid>,
3536}
3537
3538fn default_door_accessible() -> bool {
3539 true
3540}
3541
3542#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3544pub struct InteriorRoomEdit {
3545 pub id: String,
3546 pub label: String,
3547 pub x0: f32,
3548 pub y0: f32,
3549 pub x1: f32,
3550 pub y1: f32,
3551}
3552
3553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3554pub struct InteriorRoomDoorEdit {
3555 pub id: String,
3556 pub room_a: String,
3557 pub room_b: String,
3558 pub x: f32,
3559 pub y: f32,
3560}
3561
3562#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3563pub struct InteriorRoomView {
3564 pub id: String,
3565 pub label: String,
3566 pub floor: i32,
3567 pub x0: f32,
3568 pub y0: f32,
3569 pub x1: f32,
3570 pub y1: f32,
3571 #[serde(default)]
3572 pub floor_color: Option<String>,
3573 #[serde(default)]
3574 pub floor_glyph: Option<String>,
3575}
3576
3577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3578pub struct InteriorDoorView {
3579 pub id: String,
3580 pub room_a: String,
3581 pub room_b: String,
3582 pub x: f32,
3583 pub y: f32,
3584 pub kind: String,
3585 #[serde(default)]
3586 pub x_a: Option<f32>,
3587 #[serde(default)]
3588 pub y_a: Option<f32>,
3589 #[serde(default)]
3590 pub x_b: Option<f32>,
3591 #[serde(default)]
3592 pub y_b: Option<f32>,
3593}
3594
3595#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3596pub struct InteriorMapView {
3597 pub building_id: String,
3598 pub blueprint_id: String,
3599 pub background_color: String,
3600 #[serde(default)]
3601 pub default_floor_color: Option<String>,
3602 #[serde(default = "default_floor_height_view")]
3603 pub floor_height_m: f32,
3604 #[serde(default)]
3606 pub z_platforms: Vec<ZPlatformView>,
3607 #[serde(default)]
3608 pub z_transitions: Vec<ZTransitionView>,
3609 pub rooms: Vec<InteriorRoomView>,
3610 #[serde(default)]
3611 pub room_doors: Vec<InteriorDoorView>,
3612}
3613
3614fn default_floor_height_view() -> f32 {
3615 3.0
3616}
3617
3618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3619pub struct NpcView {
3620 pub id: String,
3621 pub label: String,
3622 pub role: String,
3623 pub x: f32,
3624 pub y: f32,
3625 #[serde(default)]
3627 pub building_id: Option<String>,
3628 #[serde(default)]
3630 pub entity_id: Option<EntityId>,
3631 #[serde(default)]
3632 pub life_state: Option<LifeState>,
3633 #[serde(default)]
3634 pub hp_pct: Option<f32>,
3635 #[serde(default)]
3637 pub can_trade: bool,
3638 #[serde(default)]
3640 pub buy_templates: Vec<String>,
3641 #[serde(default)]
3643 pub tile_id: Option<String>,
3644 #[serde(default)]
3646 pub behavior_state: Option<String>,
3647 #[serde(default)]
3649 pub presentation_state: Option<String>,
3650 #[serde(default)]
3652 pub sprite_mode: Option<String>,
3653 #[serde(default)]
3655 pub paperdoll_ref: Option<String>,
3656 #[serde(default = "default_draw_scale")]
3658 pub draw_scale: f32,
3659 #[serde(default)]
3661 pub yaw: Option<f32>,
3662 #[serde(default)]
3664 pub perception_fov_deg: Option<f32>,
3665 #[serde(default)]
3667 pub perception_sight_m: Option<f32>,
3668 #[serde(default)]
3670 pub perception_hear_m: Option<f32>,
3671}
3672
3673#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3674pub struct UseResult {
3675 pub template_id: String,
3676 pub hunger_restored: f32,
3677 pub thirst_restored: f32,
3678 #[serde(default)]
3679 pub health_restored: f32,
3680 #[serde(default)]
3681 pub mana_restored: f32,
3682 #[serde(default)]
3683 pub cleared_dot_ids: Vec<String>,
3684}
3685
3686#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3687pub struct CraftResult {
3688 pub blueprint_id: String,
3689 pub outputs: Vec<ItemStack>,
3690 pub consumed: Vec<ItemStack>,
3691 #[serde(default = "default_one")]
3693 pub batch_index: u32,
3694 #[serde(default = "default_one")]
3696 pub batch_total: u32,
3697}
3698
3699fn default_one() -> u32 {
3700 1
3701}
3702
3703#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3704pub struct DeathNotice {
3705 pub entity_id: EntityId,
3706 pub respawn_x: f32,
3707 pub respawn_y: f32,
3708 pub message: String,
3709}
3710
3711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3712pub struct InteractionNotice {
3713 pub target_id: String,
3714 pub message: String,
3715 #[serde(default)]
3716 pub coins_delta: i32,
3717 #[serde(default)]
3718 pub inventory_delta: Vec<ItemStack>,
3719}
3720
3721#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3722#[serde(rename_all = "snake_case")]
3723pub enum NpcTalkTrustFlag {
3724 Stranger,
3725 Acquainted,
3726 Trusted,
3727}
3728
3729#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3730#[serde(rename_all = "snake_case")]
3731pub enum NpcTalkDepth {
3732 #[default]
3733 Full,
3734 Brief,
3735 Unavailable,
3736}
3737
3738#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3739pub struct NpcTalkOpened {
3740 pub npc_id: String,
3741 pub npc_label: String,
3742 pub greeting: String,
3743 pub trust_flag: NpcTalkTrustFlag,
3744 #[serde(default)]
3745 pub talk_depth: NpcTalkDepth,
3746 #[serde(default = "default_true")]
3747 pub trade_allowed: bool,
3748 #[serde(default)]
3750 pub suggested_topics: Vec<String>,
3751}
3752
3753#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3754pub struct NpcTalkPending {
3755 pub npc_id: String,
3756}
3757
3758#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3759pub struct NpcTalkReply {
3760 pub npc_id: String,
3761 pub line: String,
3762 pub trust_flag: NpcTalkTrustFlag,
3763 #[serde(default)]
3764 pub wind_down: bool,
3765 #[serde(default)]
3766 pub trade_disabled: bool,
3767}
3768
3769#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3770pub struct NpcTalkClosed {
3771 pub npc_id: String,
3772}
3773
3774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3775pub struct NpcTalkError {
3776 pub npc_id: String,
3777 pub reason: String,
3778}
3779
3780#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3781#[serde(rename_all = "snake_case")]
3782pub enum QuestStatusView {
3783 Available,
3784 Active,
3785 Completed,
3786}
3787
3788#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3789pub struct QuestObjectiveProgress {
3790 pub label: String,
3791 pub current: u32,
3792 pub required: u32,
3793 pub done: bool,
3794 #[serde(default)]
3798 pub kind: String,
3799 #[serde(default)]
3800 pub npc_ref: Option<String>,
3801 #[serde(default)]
3802 pub item_template: Option<String>,
3803 #[serde(default)]
3804 pub blueprint_id: Option<String>,
3805 #[serde(default)]
3806 pub building_id: Option<String>,
3807}
3808
3809#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3810pub struct QuestLogEntry {
3811 pub quest_id: String,
3812 pub title: String,
3813 pub description: String,
3814 pub status: QuestStatusView,
3815 #[serde(default)]
3816 pub current_step_id: Option<String>,
3817 #[serde(default)]
3818 pub current_step_title: String,
3819 #[serde(default)]
3820 pub objectives: Vec<QuestObjectiveProgress>,
3821 #[serde(default)]
3822 pub is_tracked: bool,
3823 #[serde(default)]
3824 pub can_withdraw: bool,
3825}
3826
3827#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3828pub struct InteractableView {
3829 pub id: String,
3830 pub kind: String,
3831 pub label: String,
3832 pub x: f32,
3833 pub y: f32,
3834 pub z: f32,
3835 #[serde(default)]
3836 pub board_id: Option<String>,
3837}
3838
3839#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3840pub struct QuestOffer {
3841 pub quest_id: String,
3842 pub title: String,
3843 pub description: String,
3844 #[serde(default)]
3845 pub step_count: u32,
3846}
3847
3848#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3849pub struct QuestCatalogEntry {
3850 pub quest_id: String,
3851 pub title: String,
3852 pub description: String,
3853 pub step_count: u32,
3854 #[serde(default)]
3855 pub board_ids: Vec<String>,
3856}
3857
3858#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3859pub struct QuestCatalogUpdated {
3860 pub revision: u64,
3861 pub game_day: String,
3862 #[serde(default)]
3863 pub accepted: Vec<QuestCatalogEntry>,
3864 #[serde(default)]
3865 pub retired: Vec<String>,
3866}
3867
3868#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3869pub struct QuestNotice {
3870 pub quest_id: String,
3871 pub title: String,
3872 pub message: String,
3873}
3874
3875#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3876#[serde(rename_all = "snake_case")]
3877pub enum ShopOfferKind {
3878 Item,
3879 Blueprint,
3880}
3881
3882#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3883pub struct ShopOffer {
3884 pub offer_id: String,
3885 pub kind: ShopOfferKind,
3886 pub label: String,
3887 #[serde(default)]
3888 pub template_id: Option<String>,
3889 #[serde(default)]
3890 pub blueprint_id: Option<String>,
3891 pub price_copper: u32,
3892 #[serde(default)]
3893 pub affordable: bool,
3894 #[serde(default)]
3895 pub already_owned: bool,
3896}
3897
3898#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3899pub struct ShopBuyLine {
3900 pub template_id: String,
3901 pub label: String,
3902 pub quantity: u32,
3903 pub price_copper: u32,
3904}
3905
3906#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3908pub struct BankPanel {
3909 pub npc_id: String,
3910 pub npc_label: String,
3911 pub bank_balance_copper: u64,
3912 pub on_person_copper: u64,
3913 #[serde(default)]
3915 pub pending_outgoing_copper: u64,
3916 #[serde(default)]
3917 pub transfer_fee_bps: u32,
3918 #[serde(default)]
3919 pub transfer_clear_ticks: u64,
3920}
3921
3922#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3924pub struct StoragePanel {
3925 pub npc_id: String,
3926 pub npc_label: String,
3927 pub building_id: String,
3928 pub building_label: String,
3929 pub used_volume: f32,
3930 pub max_volume: f32,
3931 #[serde(default)]
3932 pub contents: Vec<ItemStack>,
3933 #[serde(default)]
3935 pub ship_destinations: Vec<StorageShipDest>,
3936}
3937
3938#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3939pub struct StorageShipDest {
3940 pub building_id: String,
3941 pub label: String,
3942 pub distance_m: f32,
3943 pub fee_copper: u64,
3944 pub travel_ticks: u64,
3945}
3946
3947#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3950pub enum GoodsLocation {
3951 Person,
3953 TownStorage { building_id: String },
3956}
3957
3958#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3961pub struct MarketListingView {
3962 pub listing_id: Uuid,
3963 pub seller_character_id: Uuid,
3964 pub seller_label: String,
3966 pub hall_building_id: String,
3967 pub hall_label: String,
3968 pub template_id: String,
3969 pub display_name: String,
3970 #[serde(default)]
3972 pub category: String,
3973 pub quantity: u32,
3974 pub unit_price_copper: u64,
3975 pub line_total_copper: u64,
3977 #[serde(default)]
3979 pub npc_price: bool,
3980 #[serde(default)]
3983 pub npc_dump_unit_copper: Option<u32>,
3984 pub mine: bool,
3986}
3987
3988#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3990pub struct MarketListVault {
3991 pub building_id: String,
3992 pub building_label: String,
3994 #[serde(default)]
3995 pub contents: Vec<ItemStack>,
3996}
3997
3998#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4001pub struct MarketPanel {
4002 pub npc_id: String,
4003 pub npc_label: String,
4004 pub building_id: String,
4005 pub building_label: String,
4006 pub used_volume: f32,
4008 pub max_volume: f32,
4009 #[serde(default)]
4012 pub listings: Vec<MarketListingView>,
4013 #[serde(default)]
4015 pub tax_bps: u32,
4016 #[serde(default)]
4017 pub tax_flat_copper: u32,
4018 #[serde(default)]
4020 pub list_vaults: Vec<MarketListVault>,
4021}
4022
4023#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4024pub struct ShopCatalog {
4025 pub npc_id: String,
4026 pub npc_label: String,
4027 #[serde(default)]
4028 pub sells: Vec<ShopOffer>,
4029 #[serde(default)]
4030 pub buys: Vec<ShopBuyLine>,
4031}
4032
4033#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4034pub struct HarvestResult {
4035 pub node_id: String,
4036 pub quantity: u32,
4038 pub item_template: String,
4039 #[serde(default)]
4042 pub item_instance_id: Option<Uuid>,
4043}
4044
4045#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4047pub struct Envelope<T> {
4048 pub protocol_version: u16,
4049 pub payload: T,
4050}
4051
4052impl<T> Envelope<T> {
4053 pub fn new(payload: T) -> Self {
4054 Self {
4055 protocol_version: crate::PROTOCOL_VERSION,
4056 payload,
4057 }
4058 }
4059}
4060
4061#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4063pub struct Hello {
4064 pub client_name: String,
4065 pub protocol_version: u16,
4066 #[serde(default)]
4067 pub auth: AuthCredential,
4068 #[serde(default)]
4070 pub character_id: Option<Uuid>,
4071}
4072
4073#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4076#[serde(rename_all = "snake_case")]
4077pub enum AuthCredential {
4078 DevLocal,
4079 Session { token: String },
4080 ApiToken { token: String, character_id: Uuid },
4081}
4082
4083impl Default for AuthCredential {
4084 fn default() -> Self {
4085 Self::DevLocal
4086 }
4087}
4088
4089#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4090pub struct Welcome {
4091 pub session_id: SessionId,
4092 pub entity_id: EntityId,
4093 pub snapshot: Snapshot,
4094}
4095
4096#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4097pub enum ServerMessage {
4098 Welcome(Welcome),
4099 ContentUpdated(Snapshot),
4101 Tick(TickDelta),
4102 IntentAck {
4103 entity_id: EntityId,
4104 seq: Seq,
4105 tick: Tick,
4106 },
4107 Chat(ChatMessage),
4108 HarvestResult(HarvestResult),
4109 UseResult(UseResult),
4110 CraftResult(CraftResult),
4111 Death(DeathNotice),
4112 Interaction(InteractionNotice),
4113 ShopOpened(ShopCatalog),
4114 NpcTalkOpened(NpcTalkOpened),
4115 NpcTalkPending(NpcTalkPending),
4116 NpcTalkReply(NpcTalkReply),
4117 NpcTalkClosed(NpcTalkClosed),
4118 NpcTalkError(NpcTalkError),
4119 QuestOffer(QuestOffer),
4120 QuestAccepted(QuestNotice),
4121 QuestWithdrawn(QuestNotice),
4122 QuestStepCompleted(QuestNotice),
4123 QuestCompleted(QuestNotice),
4124 QuestCatalogUpdated(QuestCatalogUpdated),
4125 BankOpened(BankPanel),
4127 StorageOpened(StoragePanel),
4129 MarketOpened(MarketPanel),
4131 TradeOpened(TradePanel),
4133 TradeClosed {
4135 reason: String,
4136 },
4137 ConnectRejected {
4140 reason: String,
4141 },
4142}
4143
4144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4146pub struct TradePanel {
4147 pub peer_entity_id: EntityId,
4148 pub peer_name: String,
4149 pub my_presented: Vec<ItemStack>,
4150 pub their_presented: Vec<ItemStack>,
4151 pub i_ready: bool,
4152 pub they_ready: bool,
4153 pub my_mass_after: f32,
4155 pub my_mass_max: f32,
4156 pub my_encumbrance_after: EncumbranceState,
4157 pub overburden_warning: bool,
4159}
4160
4161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4162pub enum ClientMessage {
4163 Hello(Hello),
4164 Intent(Intent),
4165 Disconnect,
4166}
4167
4168#[cfg(test)]
4169mod tests {
4170 use super::*;
4171
4172 #[test]
4173 fn pristine_vitals_state_yields_full_pools() {
4174 let attrs = PrimaryAttributes::default();
4175 let vitals = StoredVitalsState::default().apply_to(attrs);
4176 assert!(vitals.health > 0.0);
4177 assert_eq!(vitals.health, vitals.health_max);
4178 assert!((vitals.mana_max - 61.0).abs() < 0.01);
4179 }
4180
4181 #[test]
4182 fn humanize_snake_id_title_cases_parts() {
4183 assert_eq!(humanize_snake_id("heal_touch"), "Heal Touch");
4184 assert_eq!(humanize_snake_id("fireball"), "Fireball");
4185 assert_eq!(humanize_snake_id("cone_frost"), "Cone Frost");
4186 }
4187
4188 #[test]
4189 fn saved_vitals_scale_when_pool_max_increases() {
4190 let mut attrs = PrimaryAttributes::default();
4191 attrs.intelligence = 140;
4192 attrs.wisdom = 140;
4193 let saved = StoredVitalsState {
4194 health: 100.0,
4195 mana: 14.0,
4196 stamina: 100.0,
4197 ..StoredVitalsState::default()
4198 };
4199 let vitals = saved.apply_to(attrs);
4200 assert!(vitals.mana_max > 55.0);
4201 assert!(
4202 (vitals.mana - vitals.mana_max).abs() < 0.01,
4203 "full legacy mana bar migrates to full new bar"
4204 );
4205 }
4206
4207 #[test]
4208 fn empty_vitals_state_is_pristine() {
4209 let pristine = StoredVitalsState {
4210 health: 0.0,
4211 mana: 0.0,
4212 stamina: 0.0,
4213 hunger: 0.0,
4214 thirst: 0.0,
4215 coins: 0,
4216 deaths: 0,
4217 life_state: LifeState::Alive,
4218 };
4219 assert!(pristine.is_pristine());
4220 let vitals = pristine.apply_to(PrimaryAttributes::default());
4221 assert!(vitals.health > 0.0);
4222 }
4223
4224 #[test]
4225 fn stored_vitals_roundtrip_preserves_partial_pools() {
4226 let attrs = PrimaryAttributes::default();
4227 let mut live = PlayerVitals::from_attributes(attrs);
4228 live.health = 25.0;
4229 live.hunger = 77.0;
4230 live.deaths = 2;
4231 let stored = StoredVitalsState::from_live(&live);
4232 let restored = stored.apply_to(attrs);
4233 assert!(
4234 (restored.health - 25.0).abs() < 0.01,
4235 "partial HP below cap stays absolute"
4236 );
4237 assert_eq!(restored.hunger, 77.0);
4238 assert_eq!(restored.deaths, 2);
4239 }
4240
4241 #[test]
4242 fn skill_tiers_start_at_zero() {
4243 let skill = SkillProgress::default();
4244 assert_eq!(skill.level, 0);
4245 assert_eq!(skill.display_tier(), 0);
4246 let trained = SkillProgress {
4247 level: 250,
4248 last_trained_tick: 1,
4249 };
4250 assert_eq!(trained.display_tier(), 2);
4251 }
4252
4253 #[test]
4254 fn quest_server_messages_roundtrip_json() {
4255 use crate::codec::{Codec, PostcardCodec};
4256
4257 let offer = ServerMessage::QuestOffer(QuestOffer {
4258 quest_id: "ada_goblin_hunt".into(),
4259 title: "Goblin Trouble".into(),
4260 description: "Help Ada".into(),
4261 step_count: 3,
4262 });
4263 let notice = ServerMessage::QuestAccepted(QuestNotice {
4264 quest_id: "ada_goblin_hunt".into(),
4265 title: "Goblin Trouble".into(),
4266 message: "Quest accepted".into(),
4267 });
4268 for msg in [offer, notice] {
4269 let bytes = PostcardCodec.encode(&msg).unwrap();
4270 let decoded: ServerMessage = PostcardCodec.decode(&bytes).unwrap();
4271 assert_eq!(decoded, msg);
4272 }
4273 }
4274
4275 #[test]
4276 fn hotbar_consumable_binding_roundtrips() {
4277 let binding = hotbar_consumable_binding("bottle_of_water");
4278 assert_eq!(binding, "item:bottle_of_water");
4279 assert!(hotbar_binding_is_consumable(&binding));
4280 assert_eq!(
4281 hotbar_consumable_template(&binding),
4282 Some("bottle_of_water")
4283 );
4284 assert!(!hotbar_binding_is_consumable("fireball"));
4285 assert_eq!(hotbar_consumable_template("fireball"), None);
4286 }
4287}