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}
2120
2121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2123#[serde(rename_all = "snake_case")]
2124pub enum WorkerRouteKindView {
2125 #[default]
2126 HarvestLoop,
2127 Ordered,
2128}
2129
2130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2132pub struct WorkerRouteView {
2133 #[serde(default)]
2134 pub kind: WorkerRouteKindView,
2135 #[serde(default)]
2136 pub lodging_container_id: Option<String>,
2137 #[serde(default)]
2139 pub outbound_waypoints: Vec<WorkerRouteWaypointView>,
2140 #[serde(default)]
2142 pub harvest_nodes: Vec<String>,
2143 #[serde(default = "default_route_carry_ratio")]
2144 pub carry_return_ratio: f32,
2145 #[serde(default)]
2147 pub stops: Vec<WorkerRouteStopView>,
2148}
2149
2150fn default_route_carry_ratio() -> f32 {
2151 0.90
2152}
2153
2154fn default_true_view() -> bool {
2155 true
2156}
2157
2158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2160pub struct WorkerWithdrawItemView {
2161 pub template: String,
2162 #[serde(default)]
2164 pub qty: u32,
2165 #[serde(default)]
2167 pub all: bool,
2168}
2169
2170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2171pub struct WorkerRouteWaypointView {
2172 pub x: f32,
2173 pub y: f32,
2174 pub z: f32,
2175}
2176
2177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2186#[serde(rename_all = "snake_case")]
2187pub enum WorkerRouteStopView {
2188 Waypoint {
2189 x: f32,
2190 y: f32,
2191 #[serde(default)]
2192 z: f32,
2193 },
2194 HarvestNode {
2195 node_id: String,
2196 },
2197 DepositAt {
2198 container_id: String,
2199 #[serde(default)]
2200 filter: Option<Vec<String>>,
2201 },
2202 TradeWith {
2203 #[serde(default)]
2204 npc_id: Option<String>,
2205 template: String,
2206 #[serde(default = "default_true_view")]
2207 sell_all: bool,
2208 },
2209 WithdrawFrom {
2210 container_id: String,
2211 items: Vec<WorkerWithdrawItemView>,
2212 },
2213 CraftAt {
2214 device: String,
2215 blueprint: String,
2216 #[serde(default)]
2217 qty: Option<u32>,
2218 },
2219 CultivatePlot {
2220 plot_id: uuid::Uuid,
2221 },
2222 PlantPlot {
2223 plot_id: uuid::Uuid,
2224 seed_template: String,
2225 },
2226 HarvestPlot {
2227 plot_id: uuid::Uuid,
2228 },
2229 RestIfNeeded,
2230 Wait {
2231 #[serde(default)]
2232 wait_ticks: u64,
2233 },
2234}
2235
2236#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2238#[serde(rename_all = "snake_case")]
2239pub enum LedgerCategory {
2240 Workers,
2241 Hire,
2242 Train,
2243 ShopBuy,
2244 Taxes,
2245 WorkerSales,
2246 TraderSales,
2247 BankDeposit,
2248 BankWithdraw,
2249 BankTransferOut,
2250 BankTransferIn,
2251 BankTransferFee,
2252 StorageShipFee,
2253 PropertyBuy,
2255 PropertySell,
2257 TaxShare,
2259 MarketBuy,
2261 MarketSell,
2263 Other,
2264}
2265
2266impl LedgerCategory {
2267 pub fn as_str(self) -> &'static str {
2268 match self {
2269 Self::Workers => "workers",
2270 Self::Hire => "hire",
2271 Self::Train => "train",
2272 Self::ShopBuy => "shop_buy",
2273 Self::Taxes => "taxes",
2274 Self::WorkerSales => "worker_sales",
2275 Self::TraderSales => "trader_sales",
2276 Self::BankDeposit => "bank_deposit",
2277 Self::BankWithdraw => "bank_withdraw",
2278 Self::BankTransferOut => "bank_transfer_out",
2279 Self::BankTransferIn => "bank_transfer_in",
2280 Self::BankTransferFee => "bank_transfer_fee",
2281 Self::StorageShipFee => "storage_ship_fee",
2282 Self::PropertyBuy => "property_buy",
2283 Self::PropertySell => "property_sell",
2284 Self::TaxShare => "tax_share",
2285 Self::MarketBuy => "market_buy",
2286 Self::MarketSell => "market_sell",
2287 Self::Other => "other",
2288 }
2289 }
2290}
2291
2292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2293pub struct LedgerEntryView {
2294 pub id: uuid::Uuid,
2295 pub game_day: u64,
2296 pub signed_copper: i64,
2297 pub category: LedgerCategory,
2298 #[serde(default)]
2299 pub label: String,
2300}
2301
2302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2303pub struct LedgerPeriodTotals {
2304 #[serde(default)]
2306 pub expenses: std::collections::HashMap<String, u64>,
2307 #[serde(default)]
2309 pub income: std::collections::HashMap<String, u64>,
2310 pub expense_copper: u64,
2311 pub income_copper: u64,
2312 pub cash_flow_copper: i64,
2314}
2315
2316#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2317pub struct PlayerLedgerView {
2318 pub current_game_day: u64,
2319 #[serde(default)]
2320 pub period_day: LedgerPeriodTotals,
2321 #[serde(default)]
2322 pub period_week: LedgerPeriodTotals,
2323 #[serde(default)]
2324 pub period_month: LedgerPeriodTotals,
2325 #[serde(default)]
2326 pub period_lifetime: LedgerPeriodTotals,
2327 #[serde(default)]
2328 pub recent: Vec<LedgerEntryView>,
2329 #[serde(default)]
2331 pub wealth_on_person_copper: u64,
2332 #[serde(default)]
2334 pub wealth_in_storage_copper: u64,
2335 #[serde(default)]
2337 pub wealth_in_bank_copper: u64,
2338 #[serde(default)]
2340 pub wealth_total_copper: u64,
2341 #[serde(default)]
2343 pub wealth_in_property_copper: u64,
2344 #[serde(default)]
2346 pub wealth_net_worth_copper: u64,
2347 #[serde(default)]
2349 pub property_assets: Vec<PropertyAssetView>,
2350 #[serde(default)]
2352 pub property_market_nearby: Vec<PropertyMarketCompView>,
2353 #[serde(default)]
2355 pub live_expense_per_interval_copper: u64,
2356 #[serde(default)]
2358 pub live_income_route_est_per_loop_copper: u64,
2359 #[serde(default)]
2361 pub live_income_avg_per_interval_copper: u64,
2362 #[serde(default)]
2364 pub live_income_avg_window_intervals: u32,
2365 #[serde(default)]
2367 pub live_net_avg_per_interval_copper: i64,
2368}
2369
2370#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2372pub struct PropertyAssetView {
2373 pub plot_id: Uuid,
2374 pub label: String,
2376 pub zone_id: String,
2377 #[serde(default)]
2378 pub zone_label: Option<String>,
2379 pub area_m2: f32,
2380 pub purchase_basis_copper: u64,
2382 pub upkeep_copper_per_day: u64,
2383}
2384
2385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2387pub struct PropertyMarketCompView {
2388 pub day: u64,
2389 pub zone_id: String,
2390 #[serde(default)]
2391 pub zone_label: Option<String>,
2392 pub area_m2: f32,
2393 pub price_copper: u64,
2394 pub price_per_m2_copper: u64,
2396 pub kind: String,
2398 pub distance_m: f32,
2400}
2401
2402#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2404#[serde(rename_all = "snake_case")]
2405pub enum AnalyticsMetric {
2406 NpcKill,
2407 WildlifeKill,
2408 Harvest,
2409 QuestComplete,
2410 QuestAccept,
2411 QuestAbandon,
2412 PlayerDeath,
2413 Craft,
2414 WorkerHire,
2415 WorkerDismiss,
2416 WorkerTeach,
2417 NpcTalk,
2418 ShopBuy,
2419 ShopSell,
2420 PlaceContainer,
2421 PickupContainer,
2422 PickupDrop,
2423 ConsumableUse,
2424 AbilityUse,
2425 DistanceWalkedM,
2426 DoorUse,
2427 BuildingEnter,
2428}
2429
2430impl AnalyticsMetric {
2431 pub fn as_str(self) -> &'static str {
2432 match self {
2433 Self::NpcKill => "npc_kill",
2434 Self::WildlifeKill => "wildlife_kill",
2435 Self::Harvest => "harvest",
2436 Self::QuestComplete => "quest_complete",
2437 Self::QuestAccept => "quest_accept",
2438 Self::QuestAbandon => "quest_abandon",
2439 Self::PlayerDeath => "player_death",
2440 Self::Craft => "craft",
2441 Self::WorkerHire => "worker_hire",
2442 Self::WorkerDismiss => "worker_dismiss",
2443 Self::WorkerTeach => "worker_teach",
2444 Self::NpcTalk => "npc_talk",
2445 Self::ShopBuy => "shop_buy",
2446 Self::ShopSell => "shop_sell",
2447 Self::PlaceContainer => "place_container",
2448 Self::PickupContainer => "pickup_container",
2449 Self::PickupDrop => "pickup_drop",
2450 Self::ConsumableUse => "consumable_use",
2451 Self::AbilityUse => "ability_use",
2452 Self::DistanceWalkedM => "distance_walked_m",
2453 Self::DoorUse => "door_use",
2454 Self::BuildingEnter => "building_enter",
2455 }
2456 }
2457
2458 pub fn from_str_key(s: &str) -> Option<Self> {
2459 Some(match s {
2460 "npc_kill" => Self::NpcKill,
2461 "wildlife_kill" => Self::WildlifeKill,
2462 "harvest" => Self::Harvest,
2463 "quest_complete" => Self::QuestComplete,
2464 "quest_accept" => Self::QuestAccept,
2465 "quest_abandon" => Self::QuestAbandon,
2466 "player_death" => Self::PlayerDeath,
2467 "craft" => Self::Craft,
2468 "worker_hire" => Self::WorkerHire,
2469 "worker_dismiss" => Self::WorkerDismiss,
2470 "worker_teach" => Self::WorkerTeach,
2471 "npc_talk" => Self::NpcTalk,
2472 "shop_buy" => Self::ShopBuy,
2473 "shop_sell" => Self::ShopSell,
2474 "place_container" => Self::PlaceContainer,
2475 "pickup_container" => Self::PickupContainer,
2476 "pickup_drop" => Self::PickupDrop,
2477 "consumable_use" => Self::ConsumableUse,
2478 "ability_use" => Self::AbilityUse,
2479 "distance_walked_m" => Self::DistanceWalkedM,
2480 "door_use" => Self::DoorUse,
2481 "building_enter" => Self::BuildingEnter,
2482 _ => return None,
2483 })
2484 }
2485}
2486
2487#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2488pub struct CareerMetricRow {
2489 pub subject_id: String,
2490 pub amount: u64,
2491}
2492
2493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2495pub struct PlayerCareerView {
2496 pub current_game_day: u64,
2497 #[serde(default)]
2498 pub kills: Vec<CareerMetricRow>,
2499 #[serde(default)]
2500 pub harvests: Vec<CareerMetricRow>,
2501 pub quests_completed: u64,
2502 #[serde(default)]
2503 pub crafts: Vec<CareerMetricRow>,
2504 pub deaths: u64,
2505 pub npc_talks: u64,
2506 pub shop_buys: u64,
2507 pub shop_sells: u64,
2508 pub distance_m: u64,
2509 #[serde(default)]
2510 pub other: Vec<CareerMetricRow>,
2511}
2512
2513#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2518pub struct WorkerEquipmentView {
2519 #[serde(default)]
2520 pub mainhand: Option<ItemStack>,
2521 #[serde(default)]
2522 pub offhand: Option<ItemStack>,
2523 #[serde(default)]
2524 pub worn: Vec<(BodySlot, ItemStack)>,
2525}
2526
2527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2529pub struct HiredWorkerView {
2530 pub instance_id: String,
2531 pub entity_id: EntityId,
2532 pub def_id: String,
2533 pub label: String,
2535 pub x: f32,
2536 pub y: f32,
2537 pub z: f32,
2538 pub mode: WorkerModeView,
2539 pub state: WorkerStateView,
2540 #[serde(default)]
2541 pub step_label: String,
2542 pub vitals: WorkerVitalsSummary,
2543 #[serde(default)]
2544 pub carry_pct: f32,
2545 #[serde(default)]
2546 pub last_error: Option<String>,
2547 pub wage_copper_per_interval: u32,
2548 #[serde(default)]
2550 pub effective_wage_copper: u32,
2551 #[serde(default)]
2553 pub wage_meters_walked: f32,
2554 #[serde(default)]
2556 pub lodging_container_id: Option<String>,
2557 #[serde(default)]
2559 pub route: Option<WorkerRouteView>,
2560 #[serde(default)]
2563 pub route_stop_index: Option<u32>,
2564 #[serde(default)]
2566 pub known_blueprint_ids: Vec<String>,
2567 #[serde(default = "default_worker_view_level")]
2569 pub level: u32,
2570 #[serde(default)]
2572 pub worker_xp: f64,
2573 #[serde(default)]
2575 pub inventory: Vec<ItemStack>,
2576 #[serde(default)]
2578 pub equipment: WorkerEquipmentView,
2579 #[serde(default)]
2582 pub issue_hint: Option<String>,
2583}
2584
2585fn default_worker_view_level() -> u32 {
2586 1
2587}
2588
2589#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2591pub struct TickDelta {
2592 pub tick: Tick,
2593 pub entities: Vec<EntityState>,
2594 #[serde(default)]
2595 pub resource_nodes: Vec<ResourceNodeView>,
2596 #[serde(default)]
2597 pub buildings: Vec<BuildingView>,
2598 #[serde(default)]
2599 pub doors: Vec<DoorView>,
2600 #[serde(default)]
2601 pub npcs: Vec<NpcView>,
2602 #[serde(default)]
2604 pub inventory: Vec<ItemStack>,
2605 #[serde(default)]
2606 pub blueprints: Vec<BlueprintView>,
2607 #[serde(default)]
2609 pub building_materials: Vec<BuildingMaterialView>,
2610 #[serde(default)]
2611 pub world_clock: WorldClock,
2612 #[serde(default)]
2613 pub ground_drops: Vec<GroundDropView>,
2614 #[serde(default)]
2615 pub placed_containers: Vec<PlacedContainerView>,
2616 #[serde(default)]
2617 pub combat: Option<CombatHud>,
2618 #[serde(default)]
2619 pub interior_map: Option<InteriorMapView>,
2620 #[serde(default)]
2621 pub quest_log: Vec<QuestLogEntry>,
2622 #[serde(default)]
2623 pub hired_workers: Vec<HiredWorkerView>,
2624 #[serde(default)]
2625 pub interactables: Vec<InteractableView>,
2626 #[serde(default)]
2627 pub ledger: Option<PlayerLedgerView>,
2628 #[serde(default)]
2629 pub career: Option<PlayerCareerView>,
2630 #[serde(default)]
2632 pub combat_fx: Vec<CombatFx>,
2633 #[serde(default)]
2635 pub ground_hazards: Vec<GroundHazardView>,
2636 #[serde(default)]
2638 pub property_plots: Vec<PropertyPlotView>,
2639 #[serde(default)]
2641 pub terrain_overlays: Vec<TerrainZoneView>,
2642}
2643#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2644pub struct GroundDropView {
2645 pub id: String,
2646 pub template_id: String,
2647 pub quantity: u32,
2648 pub x: f32,
2649 pub y: f32,
2650 pub z: f32,
2651 #[serde(default)]
2653 pub tile_id: Option<String>,
2654 #[serde(default)]
2656 pub display_name: Option<String>,
2657 #[serde(default)]
2659 pub yaw: f32,
2660 #[serde(default)]
2662 pub pitch: f32,
2663 #[serde(default)]
2665 pub roll: f32,
2666 #[serde(default = "default_draw_scale")]
2668 pub draw_scale: f32,
2669}
2670
2671fn default_draw_scale() -> f32 {
2672 1.0
2673}
2674
2675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2677pub struct Snapshot {
2678 pub tick: Tick,
2679 pub chunk_rev: u64,
2680 #[serde(default)]
2682 pub content_rev: u64,
2683 #[serde(default)]
2685 pub publish_rev: u64,
2686 pub entities: Vec<EntityState>,
2687 #[serde(default)]
2688 pub resource_nodes: Vec<ResourceNodeView>,
2689 #[serde(default)]
2691 pub world_x0: f32,
2692 #[serde(default)]
2693 pub world_y0: f32,
2694 #[serde(default)]
2696 pub world_width_m: f32,
2697 #[serde(default)]
2698 pub world_height_m: f32,
2699 #[serde(default)]
2700 pub buildings: Vec<BuildingView>,
2701 #[serde(default)]
2702 pub doors: Vec<DoorView>,
2703 #[serde(default)]
2704 pub npcs: Vec<NpcView>,
2705 #[serde(default)]
2706 pub inventory: Vec<ItemStack>,
2707 #[serde(default)]
2708 pub blueprints: Vec<BlueprintView>,
2709 #[serde(default)]
2711 pub building_materials: Vec<BuildingMaterialView>,
2712 #[serde(default)]
2713 pub world_clock: WorldClock,
2714 #[serde(default)]
2715 pub terrain_zones: Vec<TerrainZoneView>,
2716 #[serde(default)]
2717 pub z_platforms: Vec<ZPlatformView>,
2718 #[serde(default)]
2719 pub z_transitions: Vec<ZTransitionView>,
2720 #[serde(default)]
2721 pub ground_drops: Vec<GroundDropView>,
2722 #[serde(default)]
2723 pub placed_containers: Vec<PlacedContainerView>,
2724 #[serde(default)]
2725 pub combat: Option<CombatHud>,
2726 #[serde(default)]
2727 pub interior_map: Option<InteriorMapView>,
2728 #[serde(default)]
2729 pub quest_log: Vec<QuestLogEntry>,
2730 #[serde(default)]
2731 pub hired_workers: Vec<HiredWorkerView>,
2732 #[serde(default)]
2733 pub interactables: Vec<InteractableView>,
2734 #[serde(default)]
2735 pub ledger: Option<PlayerLedgerView>,
2736 #[serde(default)]
2737 pub career: Option<PlayerCareerView>,
2738 #[serde(default)]
2740 pub combat_fx: Vec<CombatFx>,
2741 #[serde(default)]
2743 pub ground_hazards: Vec<GroundHazardView>,
2744 #[serde(default)]
2746 pub property_zones: Vec<PropertyZoneView>,
2747 #[serde(default)]
2749 pub tax_zones: Vec<TaxZoneView>,
2750 #[serde(default)]
2752 pub boundary_zones: Vec<BoundaryZoneView>,
2753 #[serde(default)]
2755 pub encounter_zones: Vec<EncounterZoneView>,
2756 #[serde(default)]
2758 pub growth_zones: Vec<GrowthZoneView>,
2759 #[serde(default)]
2761 pub biome_zones: Vec<BiomeZoneView>,
2762 #[serde(default)]
2764 pub terrain_kind_nav: Vec<TerrainKindNavView>,
2765 #[serde(default)]
2767 pub property_plots: Vec<PropertyPlotView>,
2768 #[serde(default)]
2770 pub property_plot_settings: Option<PropertyPlotSettingsView>,
2771}
2772
2773#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2775pub struct ResourceNodeView {
2776 pub id: String,
2777 pub label: String,
2778 pub x: f32,
2779 pub y: f32,
2780 pub z: f32,
2781 pub item_template: String,
2782 #[serde(default = "default_node_state")]
2783 pub state: ResourceNodeState,
2784 #[serde(default = "default_blocking_view")]
2786 pub blocking: bool,
2787 #[serde(default = "default_blocking_radius_view")]
2789 pub blocking_radius_m: f32,
2790 #[serde(default)]
2792 pub harvest_off: bool,
2793 #[serde(default)]
2795 pub tile_id: Option<String>,
2796 #[serde(default)]
2798 pub yaw: f32,
2799 #[serde(default)]
2801 pub pitch: f32,
2802 #[serde(default)]
2804 pub roll: f32,
2805 #[serde(default = "default_draw_scale")]
2807 pub draw_scale: f32,
2808 #[serde(default)]
2810 pub sprite_mode: Option<String>,
2811 #[serde(default)]
2813 pub presentation_state: Option<String>,
2814 #[serde(default)]
2817 pub growth_progress: Option<f32>,
2818 #[serde(default)]
2820 pub channel_start_tick: Option<Tick>,
2821 #[serde(default)]
2822 pub channel_end_tick: Option<Tick>,
2823 #[serde(default)]
2825 pub harvest_drop_templates: Vec<String>,
2826}
2827
2828fn default_blocking_radius_view() -> f32 {
2829 0.8
2830}
2831
2832fn default_blocking_view() -> bool {
2833 true
2834}
2835
2836#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2837#[serde(rename_all = "snake_case")]
2838pub enum ResourceNodeState {
2839 Available,
2840 Harvesting,
2841 Cooldown,
2842}
2843fn default_node_state() -> ResourceNodeState {
2844 ResourceNodeState::Available
2845}
2846
2847#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2849#[serde(rename_all = "snake_case")]
2850pub enum ItemSpawnStateView {
2851 Spawned,
2852 PickedUp { respawn_at_tick: u64 },
2853 Consumed,
2854}
2855
2856#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2858pub struct ItemSpawnView {
2859 pub id: String,
2860 pub label: String,
2861 pub item_template: String,
2862 pub quantity: u32,
2863 pub x: f32,
2864 pub y: f32,
2865 pub z: f32,
2866 pub respawn_ticks: u32,
2867 #[serde(default)]
2868 pub building_id: Option<String>,
2869 pub state: ItemSpawnStateView,
2870}
2871
2872#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2873#[serde(rename_all = "snake_case")]
2874pub enum ItemStatusBindingMode {
2875 OnHit,
2876 WhileEquipped,
2877}
2878
2879impl Default for ItemStatusBindingMode {
2880 fn default() -> Self {
2881 Self::OnHit
2882 }
2883}
2884
2885#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2887pub struct ItemStatusBinding {
2888 pub effect_id: String,
2889 #[serde(default)]
2890 pub mode: ItemStatusBindingMode,
2891 #[serde(default)]
2893 pub source: String,
2894 #[serde(default)]
2895 pub applied_at_tick: u64,
2896 #[serde(default)]
2899 pub expires_at_tick: Option<u64>,
2900}
2901
2902#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2903pub struct ItemStack {
2904 pub template_id: String,
2905 pub quantity: u32,
2906 #[serde(default)]
2908 pub item_instance_id: Option<Uuid>,
2909 #[serde(default)]
2911 pub props: BTreeMap<String, String>,
2912 #[serde(default)]
2914 pub status_bindings: Vec<ItemStatusBinding>,
2915 #[serde(default)]
2917 pub contents: Vec<ItemStack>,
2918 #[serde(default)]
2920 pub display_name: Option<String>,
2921 #[serde(default)]
2923 pub category: Option<String>,
2924 #[serde(default)]
2926 pub base_mass: Option<f32>,
2927 #[serde(default)]
2929 pub base_volume: Option<f32>,
2930 #[serde(default)]
2932 pub capacity_volume: Option<f32>,
2933 #[serde(default)]
2935 pub stackable: Option<bool>,
2936 #[serde(default)]
2938 pub world_placeable: Option<bool>,
2939 #[serde(default)]
2941 pub worker_lodging_capacity: Option<u32>,
2942 #[serde(default)]
2944 pub equip_slot: Option<BodySlot>,
2945 #[serde(default)]
2947 pub armor_physical: Option<f32>,
2948 #[serde(default)]
2950 pub resists: Vec<(String, f32)>,
2951 #[serde(default)]
2953 pub hand_slots: Option<u8>,
2954 #[serde(default)]
2956 pub listable: Option<bool>,
2957 #[serde(default)]
2959 pub base_value_copper: Option<u32>,
2960}
2961
2962impl ItemStack {
2963 pub fn simple(template_id: impl Into<String>, quantity: u32) -> Self {
2964 Self {
2965 template_id: template_id.into(),
2966 quantity,
2967 ..Default::default()
2968 }
2969 }
2970}
2971
2972impl Default for ItemStack {
2973 fn default() -> Self {
2974 Self {
2975 template_id: String::new(),
2976 quantity: 0,
2977 item_instance_id: None,
2978 props: BTreeMap::new(),
2979 status_bindings: Vec::new(),
2980 contents: Vec::new(),
2981 display_name: None,
2982 category: None,
2983 base_mass: None,
2984 base_volume: None,
2985 capacity_volume: None,
2986 stackable: None,
2987 world_placeable: None,
2988 worker_lodging_capacity: None,
2989 equip_slot: None,
2990 armor_physical: None,
2991 resists: Vec::new(),
2992 hand_slots: None,
2993 listable: None,
2994 base_value_copper: None,
2995 }
2996 }
2997}
2998
2999#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3001#[serde(rename_all = "snake_case")]
3002pub enum EncumbranceState {
3003 #[default]
3004 Light,
3005 Heavy,
3006 Over,
3007}
3008
3009#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
3013#[serde(rename_all = "snake_case")]
3014pub enum BodySlot {
3015 Head,
3016 #[serde(alias = "body")]
3018 Chest,
3019 #[serde(alias = "arms")]
3021 Forearms,
3022 Legs,
3023 Feet,
3024 Cloak,
3025 Back,
3026 Waist,
3027 Earrings,
3028 Necklace,
3029 Eyeglasses,
3030 #[serde(rename = "ring_left_1", alias = "ring_left1")]
3032 RingLeft1,
3033 #[serde(rename = "ring_left_2", alias = "ring_left2")]
3034 RingLeft2,
3035 #[serde(rename = "ring_right_1", alias = "ring_right1")]
3036 RingRight1,
3037 #[serde(rename = "ring_right_2", alias = "ring_right2")]
3038 RingRight2,
3039}
3040
3041impl BodySlot {
3042 pub const ALL: [BodySlot; 15] = [
3044 BodySlot::Head,
3045 BodySlot::Chest,
3046 BodySlot::Forearms,
3047 BodySlot::Legs,
3048 BodySlot::Feet,
3049 BodySlot::Cloak,
3050 BodySlot::Back,
3051 BodySlot::Waist,
3052 BodySlot::Earrings,
3053 BodySlot::Necklace,
3054 BodySlot::Eyeglasses,
3055 BodySlot::RingLeft1,
3056 BodySlot::RingLeft2,
3057 BodySlot::RingRight1,
3058 BodySlot::RingRight2,
3059 ];
3060
3061 pub fn as_str(self) -> &'static str {
3062 match self {
3063 BodySlot::Head => "head",
3064 BodySlot::Chest => "chest",
3065 BodySlot::Forearms => "forearms",
3066 BodySlot::Legs => "legs",
3067 BodySlot::Feet => "feet",
3068 BodySlot::Cloak => "cloak",
3069 BodySlot::Back => "back",
3070 BodySlot::Waist => "waist",
3071 BodySlot::Earrings => "earrings",
3072 BodySlot::Necklace => "necklace",
3073 BodySlot::Eyeglasses => "eyeglasses",
3074 BodySlot::RingLeft1 => "ring_left_1",
3075 BodySlot::RingLeft2 => "ring_left_2",
3076 BodySlot::RingRight1 => "ring_right_1",
3077 BodySlot::RingRight2 => "ring_right_2",
3078 }
3079 }
3080}
3081
3082#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3084#[serde(rename_all = "snake_case")]
3085pub enum InventoryLocation {
3086 Root,
3088 Worn { slot: BodySlot },
3090 Placed { container_id: String },
3092 Keychain,
3094 WhisperPouch,
3096}
3097
3098#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3100pub struct PlacedContainerView {
3101 pub id: String,
3102 pub template_id: String,
3103 pub display_name: String,
3104 pub x: f32,
3105 pub y: f32,
3106 pub z: f32,
3107 pub locked: bool,
3108 #[serde(default)]
3110 pub accessible: bool,
3111 #[serde(default)]
3112 pub owner_character_id: Option<Uuid>,
3113 #[serde(default)]
3115 pub contents: Vec<ItemStack>,
3116 #[serde(default)]
3118 pub lock_id: Option<String>,
3119 #[serde(default)]
3121 pub capacity_volume: Option<f32>,
3122 #[serde(default)]
3124 pub item_instance_id: Option<Uuid>,
3125 #[serde(default)]
3127 pub tile_id: Option<String>,
3128 #[serde(default)]
3130 pub worker_lodging_capacity: Option<u32>,
3131 #[serde(default)]
3133 pub blocking: bool,
3134 #[serde(default)]
3136 pub blocking_radius_m: f32,
3137 #[serde(default)]
3140 pub building_id: Option<String>,
3141}
3142
3143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3144pub struct BlueprintIngredientView {
3145 pub template_id: String,
3146 pub quantity: u32,
3147 pub consumed: bool,
3149 #[serde(default)]
3151 pub display_name: String,
3152}
3153
3154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3155pub struct ToolRequirementView {
3156 pub item: String,
3157 pub consumed: bool,
3159 #[serde(default)]
3161 pub display_name: String,
3162}
3163
3164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3165pub struct SkillRequirementView {
3166 pub skill: String,
3167 pub level: u32,
3168}
3169
3170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3171pub struct BlueprintView {
3172 pub id: String,
3173 pub label: String,
3174 pub output: String,
3175 pub output_qty: u32,
3176 pub craft_ticks: u32,
3177 pub inputs: Vec<BlueprintIngredientView>,
3178 #[serde(default)]
3180 pub station: Option<String>,
3181 #[serde(default)]
3182 pub category: Option<String>,
3183 #[serde(default)]
3184 pub required_tools: Vec<ToolRequirementView>,
3185 #[serde(default)]
3186 pub skill: Option<SkillRequirementView>,
3187 #[serde(default)]
3188 pub failure_chance: f32,
3189 #[serde(default)]
3191 pub worker_train_copper: u64,
3192 #[serde(default)]
3194 pub output_display_name: String,
3195}
3196
3197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3199pub struct TerrainKindNavView {
3200 pub kind: TerrainKindView,
3201 #[serde(default = "default_move_speed_mult_one")]
3202 pub move_speed_mult: f32,
3203 #[serde(default)]
3204 pub impassable: bool,
3205}
3206
3207fn default_move_speed_mult_one() -> f32 {
3208 1.0
3209}
3210
3211#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3213#[serde(rename_all = "snake_case")]
3214pub enum TerrainKindView {
3215 #[default]
3216 Grass,
3217 Dirt,
3218 Tilled,
3219 Desert,
3220 Hill,
3221 Bog,
3222 Beach,
3223 ShallowWater,
3224 DeepWater,
3225 Trail,
3226 Road,
3227 Rock,
3228}
3229
3230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3231pub struct TerrainZoneView {
3232 pub id: String,
3233 pub x0: f32,
3234 pub y0: f32,
3235 pub x1: f32,
3236 pub y1: f32,
3237 #[serde(default)]
3238 pub kind: TerrainKindView,
3239 #[serde(default)]
3241 pub elevation: f32,
3242 #[serde(default)]
3245 pub glyph: Option<String>,
3246 #[serde(default)]
3248 pub color: Option<String>,
3249 #[serde(default)]
3251 pub tile_id: Option<String>,
3252 #[serde(default)]
3254 pub z_order: i32,
3255 #[serde(default)]
3257 pub channel_start_tick: Option<Tick>,
3258 #[serde(default)]
3259 pub channel_end_tick: Option<Tick>,
3260}
3261
3262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3264pub struct ZoneRectView {
3265 pub x0: f32,
3266 pub y0: f32,
3267 pub x1: f32,
3268 pub y1: f32,
3269}
3270
3271#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3273pub struct PropertyZoneView {
3274 pub id: String,
3275 #[serde(default)]
3277 pub label: Option<String>,
3278 pub rects: Vec<ZoneRectView>,
3279 #[serde(default)]
3280 pub z_order: i32,
3281 pub crown_price_copper: u64,
3282 pub upkeep_copper_per_day: u64,
3283 #[serde(default)]
3284 pub max_area_m2: Option<f32>,
3285 #[serde(default)]
3286 pub owner_tax_discount_bps: u32,
3287}
3288
3289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3291pub struct TaxZoneView {
3292 pub id: String,
3293 #[serde(default)]
3294 pub label: Option<String>,
3295 pub rects: Vec<ZoneRectView>,
3296 #[serde(default)]
3297 pub z_order: i32,
3298 pub rate_bps: u32,
3299 #[serde(default)]
3300 pub flat_copper: u64,
3301 #[serde(default)]
3303 pub market_sales_tax_bps: u32,
3304 #[serde(default)]
3306 pub market_sales_flat_copper: u32,
3307}
3308
3309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3311pub struct BoundaryZoneView {
3312 pub id: String,
3313 #[serde(default)]
3314 pub label: Option<String>,
3315 pub rects: Vec<ZoneRectView>,
3316 #[serde(default)]
3317 pub z_order: i32,
3318 #[serde(default, skip_serializing_if = "Option::is_none")]
3319 pub jurisdiction_id: Option<String>,
3320 #[serde(default = "default_true")]
3321 pub worker_logistics: bool,
3322 #[serde(default)]
3323 pub security_tier: String,
3324 #[serde(default)]
3325 pub pvp_mode: String,
3326 #[serde(default = "default_true")]
3327 pub crime_enabled: bool,
3328 #[serde(default)]
3329 pub guard_response: bool,
3330 #[serde(default)]
3332 pub presence_mode: String,
3333}
3334
3335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3337pub struct EncounterZoneView {
3338 pub id: String,
3339 #[serde(default)]
3340 pub label: Option<String>,
3341 pub rects: Vec<ZoneRectView>,
3342 #[serde(default)]
3343 pub z_order: i32,
3344}
3345
3346fn default_true() -> bool {
3347 true
3348}
3349
3350#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3352pub struct GrowthZoneView {
3353 pub id: String,
3354 #[serde(default)]
3355 pub label: Option<String>,
3356 pub rects: Vec<ZoneRectView>,
3357 #[serde(default)]
3358 pub z_order: i32,
3359 #[serde(default = "default_one_f32")]
3360 pub fertility: f32,
3361}
3362
3363fn default_one_f32() -> f32 {
3364 1.0
3365}
3366
3367#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3369pub struct BiomeZoneView {
3370 pub id: String,
3371 #[serde(default)]
3372 pub label: Option<String>,
3373 pub rects: Vec<ZoneRectView>,
3374 #[serde(default)]
3375 pub z_order: i32,
3376 pub biome_id: String,
3377}
3378
3379#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3381pub struct FarmGrantView {
3382 pub character_id: Uuid,
3383 #[serde(default)]
3385 pub character_label: String,
3386 pub tax_discount_bps: u32,
3387}
3388
3389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3391pub struct PropertyPlotView {
3392 pub plot_id: Uuid,
3393 pub property_zone_id: String,
3394 #[serde(default)]
3395 pub zone_label: Option<String>,
3396 pub deed_instance_id: Uuid,
3397 pub x0: f32,
3398 pub y0: f32,
3399 pub x1: f32,
3400 pub y1: f32,
3401 pub upkeep_copper_per_day: u64,
3402 pub arrears_days: u32,
3403 #[serde(default)]
3405 pub is_mine: bool,
3406 #[serde(default)]
3408 pub may_farm: bool,
3409 #[serde(default)]
3411 pub purchase_basis_copper: u64,
3412 #[serde(default)]
3413 pub farm_public: bool,
3414 #[serde(default)]
3415 pub public_tax_discount_bps: u32,
3416 #[serde(default)]
3417 pub farm_allow: Vec<FarmGrantView>,
3418 #[serde(default)]
3420 pub owner_character_id: Option<Uuid>,
3421 #[serde(default)]
3422 pub owner_label: Option<String>,
3423 #[serde(default)]
3425 pub building_id: Option<String>,
3426 #[serde(default)]
3428 pub plot_code: String,
3429 #[serde(default)]
3431 pub label: String,
3432}
3433
3434#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3436pub struct PropertyPlotSettingsView {
3437 pub min_plot_area_m2: f32,
3438 pub tax_premium_weight: f32,
3439 pub sellback_bps: u32,
3440}
3441
3442#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3444pub struct ZPlatformView {
3445 pub id: String,
3446 pub z: f32,
3447 pub x0: f32,
3448 pub y0: f32,
3449 pub x1: f32,
3450 pub y1: f32,
3451}
3452
3453#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3455pub struct ZTransitionView {
3456 pub id: String,
3457 pub z_from: f32,
3458 pub z_to: f32,
3459 pub x0: f32,
3460 pub y0: f32,
3461 pub x1: f32,
3462 pub y1: f32,
3463}
3464
3465#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3466pub struct BuildingView {
3467 pub id: String,
3468 pub label: String,
3469 pub x: f32,
3470 pub y: f32,
3471 pub width_m: f32,
3472 pub depth_m: f32,
3473 #[serde(default)]
3474 pub interior_blueprint: Option<String>,
3475 #[serde(default)]
3476 pub tags: Vec<String>,
3477 #[serde(default)]
3479 pub market_boundary_zone_ids: Vec<String>,
3480 #[serde(default)]
3482 pub market_max_volume: Option<f32>,
3483 #[serde(default)]
3486 pub wall_set: Option<String>,
3487 #[serde(default)]
3489 pub roof_set: Option<String>,
3490}
3491
3492pub const DEFAULT_BUILDING_ART_SET: &str = "classic_stone";
3495
3496impl BuildingView {
3497 pub fn effective_wall_set(&self) -> &str {
3498 self.wall_set
3499 .as_deref()
3500 .filter(|s| !s.is_empty())
3501 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3502 }
3503
3504 pub fn effective_roof_set(&self) -> &str {
3505 self.roof_set
3506 .as_deref()
3507 .filter(|s| !s.is_empty())
3508 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3509 }
3510}
3511
3512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3513pub struct DoorView {
3514 pub id: String,
3515 pub building_id: String,
3516 pub x: f32,
3517 pub y: f32,
3518 #[serde(default)]
3519 pub open: bool,
3520 #[serde(default)]
3521 pub portal: Option<String>,
3522 #[serde(default)]
3524 pub locked: bool,
3525 #[serde(default = "default_door_accessible")]
3527 pub accessible: bool,
3528 #[serde(default)]
3529 pub lock_id: Option<Uuid>,
3530}
3531
3532fn default_door_accessible() -> bool {
3533 true
3534}
3535
3536#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3538pub struct InteriorRoomEdit {
3539 pub id: String,
3540 pub label: String,
3541 pub x0: f32,
3542 pub y0: f32,
3543 pub x1: f32,
3544 pub y1: f32,
3545}
3546
3547#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3548pub struct InteriorRoomDoorEdit {
3549 pub id: String,
3550 pub room_a: String,
3551 pub room_b: String,
3552 pub x: f32,
3553 pub y: f32,
3554}
3555
3556#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3557pub struct InteriorRoomView {
3558 pub id: String,
3559 pub label: String,
3560 pub floor: i32,
3561 pub x0: f32,
3562 pub y0: f32,
3563 pub x1: f32,
3564 pub y1: f32,
3565 #[serde(default)]
3566 pub floor_color: Option<String>,
3567 #[serde(default)]
3568 pub floor_glyph: Option<String>,
3569}
3570
3571#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3572pub struct InteriorDoorView {
3573 pub id: String,
3574 pub room_a: String,
3575 pub room_b: String,
3576 pub x: f32,
3577 pub y: f32,
3578 pub kind: String,
3579 #[serde(default)]
3580 pub x_a: Option<f32>,
3581 #[serde(default)]
3582 pub y_a: Option<f32>,
3583 #[serde(default)]
3584 pub x_b: Option<f32>,
3585 #[serde(default)]
3586 pub y_b: Option<f32>,
3587}
3588
3589#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3590pub struct InteriorMapView {
3591 pub building_id: String,
3592 pub blueprint_id: String,
3593 pub background_color: String,
3594 #[serde(default)]
3595 pub default_floor_color: Option<String>,
3596 #[serde(default = "default_floor_height_view")]
3597 pub floor_height_m: f32,
3598 #[serde(default)]
3600 pub z_platforms: Vec<ZPlatformView>,
3601 #[serde(default)]
3602 pub z_transitions: Vec<ZTransitionView>,
3603 pub rooms: Vec<InteriorRoomView>,
3604 #[serde(default)]
3605 pub room_doors: Vec<InteriorDoorView>,
3606}
3607
3608fn default_floor_height_view() -> f32 {
3609 3.0
3610}
3611
3612#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3613pub struct NpcView {
3614 pub id: String,
3615 pub label: String,
3616 pub role: String,
3617 pub x: f32,
3618 pub y: f32,
3619 #[serde(default)]
3621 pub building_id: Option<String>,
3622 #[serde(default)]
3624 pub entity_id: Option<EntityId>,
3625 #[serde(default)]
3626 pub life_state: Option<LifeState>,
3627 #[serde(default)]
3628 pub hp_pct: Option<f32>,
3629 #[serde(default)]
3631 pub can_trade: bool,
3632 #[serde(default)]
3634 pub buy_templates: Vec<String>,
3635 #[serde(default)]
3637 pub tile_id: Option<String>,
3638 #[serde(default)]
3640 pub behavior_state: Option<String>,
3641 #[serde(default)]
3643 pub presentation_state: Option<String>,
3644 #[serde(default)]
3646 pub sprite_mode: Option<String>,
3647 #[serde(default)]
3649 pub paperdoll_ref: Option<String>,
3650 #[serde(default = "default_draw_scale")]
3652 pub draw_scale: f32,
3653 #[serde(default)]
3655 pub yaw: Option<f32>,
3656 #[serde(default)]
3658 pub perception_fov_deg: Option<f32>,
3659 #[serde(default)]
3661 pub perception_sight_m: Option<f32>,
3662 #[serde(default)]
3664 pub perception_hear_m: Option<f32>,
3665}
3666
3667#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3668pub struct UseResult {
3669 pub template_id: String,
3670 pub hunger_restored: f32,
3671 pub thirst_restored: f32,
3672 #[serde(default)]
3673 pub health_restored: f32,
3674 #[serde(default)]
3675 pub mana_restored: f32,
3676 #[serde(default)]
3677 pub cleared_dot_ids: Vec<String>,
3678}
3679
3680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3681pub struct CraftResult {
3682 pub blueprint_id: String,
3683 pub outputs: Vec<ItemStack>,
3684 pub consumed: Vec<ItemStack>,
3685 #[serde(default = "default_one")]
3687 pub batch_index: u32,
3688 #[serde(default = "default_one")]
3690 pub batch_total: u32,
3691}
3692
3693fn default_one() -> u32 {
3694 1
3695}
3696
3697#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3698pub struct DeathNotice {
3699 pub entity_id: EntityId,
3700 pub respawn_x: f32,
3701 pub respawn_y: f32,
3702 pub message: String,
3703}
3704
3705#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3706pub struct InteractionNotice {
3707 pub target_id: String,
3708 pub message: String,
3709 #[serde(default)]
3710 pub coins_delta: i32,
3711 #[serde(default)]
3712 pub inventory_delta: Vec<ItemStack>,
3713}
3714
3715#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3716#[serde(rename_all = "snake_case")]
3717pub enum NpcTalkTrustFlag {
3718 Stranger,
3719 Acquainted,
3720 Trusted,
3721}
3722
3723#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3724#[serde(rename_all = "snake_case")]
3725pub enum NpcTalkDepth {
3726 #[default]
3727 Full,
3728 Brief,
3729 Unavailable,
3730}
3731
3732#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3733pub struct NpcTalkOpened {
3734 pub npc_id: String,
3735 pub npc_label: String,
3736 pub greeting: String,
3737 pub trust_flag: NpcTalkTrustFlag,
3738 #[serde(default)]
3739 pub talk_depth: NpcTalkDepth,
3740 #[serde(default = "default_true")]
3741 pub trade_allowed: bool,
3742 #[serde(default)]
3744 pub suggested_topics: Vec<String>,
3745}
3746
3747#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3748pub struct NpcTalkPending {
3749 pub npc_id: String,
3750}
3751
3752#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3753pub struct NpcTalkReply {
3754 pub npc_id: String,
3755 pub line: String,
3756 pub trust_flag: NpcTalkTrustFlag,
3757 #[serde(default)]
3758 pub wind_down: bool,
3759 #[serde(default)]
3760 pub trade_disabled: bool,
3761}
3762
3763#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3764pub struct NpcTalkClosed {
3765 pub npc_id: String,
3766}
3767
3768#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3769pub struct NpcTalkError {
3770 pub npc_id: String,
3771 pub reason: String,
3772}
3773
3774#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3775#[serde(rename_all = "snake_case")]
3776pub enum QuestStatusView {
3777 Available,
3778 Active,
3779 Completed,
3780}
3781
3782#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3783pub struct QuestObjectiveProgress {
3784 pub label: String,
3785 pub current: u32,
3786 pub required: u32,
3787 pub done: bool,
3788}
3789
3790#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3791pub struct QuestLogEntry {
3792 pub quest_id: String,
3793 pub title: String,
3794 pub description: String,
3795 pub status: QuestStatusView,
3796 #[serde(default)]
3797 pub current_step_id: Option<String>,
3798 #[serde(default)]
3799 pub current_step_title: String,
3800 #[serde(default)]
3801 pub objectives: Vec<QuestObjectiveProgress>,
3802 #[serde(default)]
3803 pub is_tracked: bool,
3804 #[serde(default)]
3805 pub can_withdraw: bool,
3806}
3807
3808#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3809pub struct InteractableView {
3810 pub id: String,
3811 pub kind: String,
3812 pub label: String,
3813 pub x: f32,
3814 pub y: f32,
3815 pub z: f32,
3816 #[serde(default)]
3817 pub board_id: Option<String>,
3818}
3819
3820#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3821pub struct QuestOffer {
3822 pub quest_id: String,
3823 pub title: String,
3824 pub description: String,
3825 #[serde(default)]
3826 pub step_count: u32,
3827}
3828
3829#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3830pub struct QuestCatalogEntry {
3831 pub quest_id: String,
3832 pub title: String,
3833 pub description: String,
3834 pub step_count: u32,
3835 #[serde(default)]
3836 pub board_ids: Vec<String>,
3837}
3838
3839#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3840pub struct QuestCatalogUpdated {
3841 pub revision: u64,
3842 pub game_day: String,
3843 #[serde(default)]
3844 pub accepted: Vec<QuestCatalogEntry>,
3845 #[serde(default)]
3846 pub retired: Vec<String>,
3847}
3848
3849#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3850pub struct QuestNotice {
3851 pub quest_id: String,
3852 pub title: String,
3853 pub message: String,
3854}
3855
3856#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3857#[serde(rename_all = "snake_case")]
3858pub enum ShopOfferKind {
3859 Item,
3860 Blueprint,
3861}
3862
3863#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3864pub struct ShopOffer {
3865 pub offer_id: String,
3866 pub kind: ShopOfferKind,
3867 pub label: String,
3868 #[serde(default)]
3869 pub template_id: Option<String>,
3870 #[serde(default)]
3871 pub blueprint_id: Option<String>,
3872 pub price_copper: u32,
3873 #[serde(default)]
3874 pub affordable: bool,
3875 #[serde(default)]
3876 pub already_owned: bool,
3877}
3878
3879#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3880pub struct ShopBuyLine {
3881 pub template_id: String,
3882 pub label: String,
3883 pub quantity: u32,
3884 pub price_copper: u32,
3885}
3886
3887#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3889pub struct BankPanel {
3890 pub npc_id: String,
3891 pub npc_label: String,
3892 pub bank_balance_copper: u64,
3893 pub on_person_copper: u64,
3894 #[serde(default)]
3896 pub pending_outgoing_copper: u64,
3897 #[serde(default)]
3898 pub transfer_fee_bps: u32,
3899 #[serde(default)]
3900 pub transfer_clear_ticks: u64,
3901}
3902
3903#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3905pub struct StoragePanel {
3906 pub npc_id: String,
3907 pub npc_label: String,
3908 pub building_id: String,
3909 pub building_label: String,
3910 pub used_volume: f32,
3911 pub max_volume: f32,
3912 #[serde(default)]
3913 pub contents: Vec<ItemStack>,
3914 #[serde(default)]
3916 pub ship_destinations: Vec<StorageShipDest>,
3917}
3918
3919#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3920pub struct StorageShipDest {
3921 pub building_id: String,
3922 pub label: String,
3923 pub distance_m: f32,
3924 pub fee_copper: u64,
3925 pub travel_ticks: u64,
3926}
3927
3928#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3931pub enum GoodsLocation {
3932 Person,
3934 TownStorage { building_id: String },
3937}
3938
3939#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3942pub struct MarketListingView {
3943 pub listing_id: Uuid,
3944 pub seller_character_id: Uuid,
3945 pub seller_label: String,
3947 pub hall_building_id: String,
3948 pub hall_label: String,
3949 pub template_id: String,
3950 pub display_name: String,
3951 #[serde(default)]
3953 pub category: String,
3954 pub quantity: u32,
3955 pub unit_price_copper: u64,
3956 pub line_total_copper: u64,
3958 #[serde(default)]
3960 pub npc_price: bool,
3961 #[serde(default)]
3964 pub npc_dump_unit_copper: Option<u32>,
3965 pub mine: bool,
3967}
3968
3969#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3971pub struct MarketListVault {
3972 pub building_id: String,
3973 pub building_label: String,
3975 #[serde(default)]
3976 pub contents: Vec<ItemStack>,
3977}
3978
3979#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3982pub struct MarketPanel {
3983 pub npc_id: String,
3984 pub npc_label: String,
3985 pub building_id: String,
3986 pub building_label: String,
3987 pub used_volume: f32,
3989 pub max_volume: f32,
3990 #[serde(default)]
3993 pub listings: Vec<MarketListingView>,
3994 #[serde(default)]
3996 pub tax_bps: u32,
3997 #[serde(default)]
3998 pub tax_flat_copper: u32,
3999 #[serde(default)]
4001 pub list_vaults: Vec<MarketListVault>,
4002}
4003
4004#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4005pub struct ShopCatalog {
4006 pub npc_id: String,
4007 pub npc_label: String,
4008 #[serde(default)]
4009 pub sells: Vec<ShopOffer>,
4010 #[serde(default)]
4011 pub buys: Vec<ShopBuyLine>,
4012}
4013
4014#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4015pub struct HarvestResult {
4016 pub node_id: String,
4017 pub quantity: u32,
4019 pub item_template: String,
4020 #[serde(default)]
4023 pub item_instance_id: Option<Uuid>,
4024}
4025
4026#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4028pub struct Envelope<T> {
4029 pub protocol_version: u16,
4030 pub payload: T,
4031}
4032
4033impl<T> Envelope<T> {
4034 pub fn new(payload: T) -> Self {
4035 Self {
4036 protocol_version: crate::PROTOCOL_VERSION,
4037 payload,
4038 }
4039 }
4040}
4041
4042#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4044pub struct Hello {
4045 pub client_name: String,
4046 pub protocol_version: u16,
4047 #[serde(default)]
4048 pub auth: AuthCredential,
4049 #[serde(default)]
4051 pub character_id: Option<Uuid>,
4052}
4053
4054#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4057#[serde(rename_all = "snake_case")]
4058pub enum AuthCredential {
4059 DevLocal,
4060 Session { token: String },
4061 ApiToken { token: String, character_id: Uuid },
4062}
4063
4064impl Default for AuthCredential {
4065 fn default() -> Self {
4066 Self::DevLocal
4067 }
4068}
4069
4070#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4071pub struct Welcome {
4072 pub session_id: SessionId,
4073 pub entity_id: EntityId,
4074 pub snapshot: Snapshot,
4075}
4076
4077#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4078pub enum ServerMessage {
4079 Welcome(Welcome),
4080 ContentUpdated(Snapshot),
4082 Tick(TickDelta),
4083 IntentAck {
4084 entity_id: EntityId,
4085 seq: Seq,
4086 tick: Tick,
4087 },
4088 Chat(ChatMessage),
4089 HarvestResult(HarvestResult),
4090 UseResult(UseResult),
4091 CraftResult(CraftResult),
4092 Death(DeathNotice),
4093 Interaction(InteractionNotice),
4094 ShopOpened(ShopCatalog),
4095 NpcTalkOpened(NpcTalkOpened),
4096 NpcTalkPending(NpcTalkPending),
4097 NpcTalkReply(NpcTalkReply),
4098 NpcTalkClosed(NpcTalkClosed),
4099 NpcTalkError(NpcTalkError),
4100 QuestOffer(QuestOffer),
4101 QuestAccepted(QuestNotice),
4102 QuestWithdrawn(QuestNotice),
4103 QuestStepCompleted(QuestNotice),
4104 QuestCompleted(QuestNotice),
4105 QuestCatalogUpdated(QuestCatalogUpdated),
4106 BankOpened(BankPanel),
4108 StorageOpened(StoragePanel),
4110 MarketOpened(MarketPanel),
4112 TradeOpened(TradePanel),
4114 TradeClosed {
4116 reason: String,
4117 },
4118 ConnectRejected {
4121 reason: String,
4122 },
4123}
4124
4125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4127pub struct TradePanel {
4128 pub peer_entity_id: EntityId,
4129 pub peer_name: String,
4130 pub my_presented: Vec<ItemStack>,
4131 pub their_presented: Vec<ItemStack>,
4132 pub i_ready: bool,
4133 pub they_ready: bool,
4134 pub my_mass_after: f32,
4136 pub my_mass_max: f32,
4137 pub my_encumbrance_after: EncumbranceState,
4138 pub overburden_warning: bool,
4140}
4141
4142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4143pub enum ClientMessage {
4144 Hello(Hello),
4145 Intent(Intent),
4146 Disconnect,
4147}
4148
4149#[cfg(test)]
4150mod tests {
4151 use super::*;
4152
4153 #[test]
4154 fn pristine_vitals_state_yields_full_pools() {
4155 let attrs = PrimaryAttributes::default();
4156 let vitals = StoredVitalsState::default().apply_to(attrs);
4157 assert!(vitals.health > 0.0);
4158 assert_eq!(vitals.health, vitals.health_max);
4159 assert!((vitals.mana_max - 61.0).abs() < 0.01);
4160 }
4161
4162 #[test]
4163 fn humanize_snake_id_title_cases_parts() {
4164 assert_eq!(humanize_snake_id("heal_touch"), "Heal Touch");
4165 assert_eq!(humanize_snake_id("fireball"), "Fireball");
4166 assert_eq!(humanize_snake_id("cone_frost"), "Cone Frost");
4167 }
4168
4169 #[test]
4170 fn saved_vitals_scale_when_pool_max_increases() {
4171 let mut attrs = PrimaryAttributes::default();
4172 attrs.intelligence = 140;
4173 attrs.wisdom = 140;
4174 let saved = StoredVitalsState {
4175 health: 100.0,
4176 mana: 14.0,
4177 stamina: 100.0,
4178 ..StoredVitalsState::default()
4179 };
4180 let vitals = saved.apply_to(attrs);
4181 assert!(vitals.mana_max > 55.0);
4182 assert!(
4183 (vitals.mana - vitals.mana_max).abs() < 0.01,
4184 "full legacy mana bar migrates to full new bar"
4185 );
4186 }
4187
4188 #[test]
4189 fn empty_vitals_state_is_pristine() {
4190 let pristine = StoredVitalsState {
4191 health: 0.0,
4192 mana: 0.0,
4193 stamina: 0.0,
4194 hunger: 0.0,
4195 thirst: 0.0,
4196 coins: 0,
4197 deaths: 0,
4198 life_state: LifeState::Alive,
4199 };
4200 assert!(pristine.is_pristine());
4201 let vitals = pristine.apply_to(PrimaryAttributes::default());
4202 assert!(vitals.health > 0.0);
4203 }
4204
4205 #[test]
4206 fn stored_vitals_roundtrip_preserves_partial_pools() {
4207 let attrs = PrimaryAttributes::default();
4208 let mut live = PlayerVitals::from_attributes(attrs);
4209 live.health = 25.0;
4210 live.hunger = 77.0;
4211 live.deaths = 2;
4212 let stored = StoredVitalsState::from_live(&live);
4213 let restored = stored.apply_to(attrs);
4214 assert!(
4215 (restored.health - 25.0).abs() < 0.01,
4216 "partial HP below cap stays absolute"
4217 );
4218 assert_eq!(restored.hunger, 77.0);
4219 assert_eq!(restored.deaths, 2);
4220 }
4221
4222 #[test]
4223 fn skill_tiers_start_at_zero() {
4224 let skill = SkillProgress::default();
4225 assert_eq!(skill.level, 0);
4226 assert_eq!(skill.display_tier(), 0);
4227 let trained = SkillProgress {
4228 level: 250,
4229 last_trained_tick: 1,
4230 };
4231 assert_eq!(trained.display_tier(), 2);
4232 }
4233
4234 #[test]
4235 fn quest_server_messages_roundtrip_json() {
4236 use crate::codec::{Codec, PostcardCodec};
4237
4238 let offer = ServerMessage::QuestOffer(QuestOffer {
4239 quest_id: "ada_goblin_hunt".into(),
4240 title: "Goblin Trouble".into(),
4241 description: "Help Ada".into(),
4242 step_count: 3,
4243 });
4244 let notice = ServerMessage::QuestAccepted(QuestNotice {
4245 quest_id: "ada_goblin_hunt".into(),
4246 title: "Goblin Trouble".into(),
4247 message: "Quest accepted".into(),
4248 });
4249 for msg in [offer, notice] {
4250 let bytes = PostcardCodec.encode(&msg).unwrap();
4251 let decoded: ServerMessage = PostcardCodec.decode(&bytes).unwrap();
4252 assert_eq!(decoded, msg);
4253 }
4254 }
4255
4256 #[test]
4257 fn hotbar_consumable_binding_roundtrips() {
4258 let binding = hotbar_consumable_binding("bottle_of_water");
4259 assert_eq!(binding, "item:bottle_of_water");
4260 assert!(hotbar_binding_is_consumable(&binding));
4261 assert_eq!(
4262 hotbar_consumable_template(&binding),
4263 Some("bottle_of_water")
4264 );
4265 assert!(!hotbar_binding_is_consumable("fireball"));
4266 assert_eq!(hotbar_consumable_template("fireball"), None);
4267 }
4268}