1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6pub type EntityId = u64;
7pub type Tick = u64;
8pub type Seq = u32;
9pub type SessionId = u64;
10
11#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct WorldCoord {
14 pub x: f32,
15 pub y: f32,
16 pub z: f32,
17 pub w: u32,
18 pub t: u32,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
23pub struct AimPoint {
24 pub x: f32,
25 pub y: f32,
26 #[serde(default)]
27 pub z: f32,
28}
29
30impl AimPoint {
31 pub fn xy(x: f32, y: f32) -> Self {
32 Self { x, y, z: 0.0 }
33 }
34}
35
36impl WorldCoord {
37 pub fn surface(x: f32, y: f32) -> Self {
38 Self {
39 x,
40 y,
41 z: 0.0,
42 w: 0,
43 t: 0,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
49pub struct Velocity2D {
50 pub vx: f32,
51 pub vy: f32,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
55pub struct Transform {
56 pub position: WorldCoord,
57 pub yaw: f32,
58 pub velocity: Velocity2D,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum LifeState {
64 Alive,
65 Dead,
66}
67
68impl Default for LifeState {
69 fn default() -> Self {
70 Self::Alive
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum TimeOfDayPhase {
78 Night,
79 Dawn,
80 Morning,
81 Midday,
82 Afternoon,
83 Evening,
84}
85
86impl TimeOfDayPhase {
87 pub fn label(self) -> &'static str {
88 match self {
89 Self::Night => "Night",
90 Self::Dawn => "Dawn",
91 Self::Morning => "Morning",
92 Self::Midday => "Midday",
93 Self::Afternoon => "Afternoon",
94 Self::Evening => "Evening",
95 }
96 }
97
98 pub fn from_name(name: &str) -> Self {
99 match name.to_ascii_lowercase().as_str() {
100 "dawn" => Self::Dawn,
101 "morning" => Self::Morning,
102 "midday" | "mid_day" | "noon" => Self::Midday,
103 "afternoon" => Self::Afternoon,
104 "evening" | "dusk" => Self::Evening,
105 _ => Self::Night,
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
112pub struct WorldClock {
113 pub day: u64,
115 pub hour: u8,
116 pub minute: u8,
117 pub phase: TimeOfDayPhase,
118}
119
120impl Default for WorldClock {
121 fn default() -> Self {
122 Self {
123 day: 0,
124 hour: 8,
125 minute: 0,
126 phase: TimeOfDayPhase::Morning,
127 }
128 }
129}
130
131impl WorldClock {
132 pub fn display_time(self) -> String {
133 format!("{:02}:{:02}", self.hour, self.minute)
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
139pub struct PrimaryAttributes {
140 pub strength: u16,
141 pub dexterity: u16,
142 pub intelligence: u16,
143 pub stamina: u16,
144 pub vitality: u16,
145 pub wisdom: u16,
146 pub charisma: u16,
147}
148
149impl Default for PrimaryAttributes {
150 fn default() -> Self {
151 Self {
152 strength: 150,
153 dexterity: 150,
154 intelligence: 150,
155 stamina: 150,
156 vitality: 150,
157 wisdom: 150,
158 charisma: 150,
159 }
160 }
161}
162
163impl PrimaryAttributes {
164 pub fn display(value: u16) -> u16 {
166 (value / 10).clamp(1, 100)
167 }
168
169 pub fn derived_preview(&self) -> DerivedPreview {
171 let str_d = Self::display(self.strength) as f32;
172 let dex_d = Self::display(self.dexterity) as f32;
173 let int_d = Self::display(self.intelligence) as f32;
174 let wis_d = Self::display(self.wisdom) as f32;
175 DerivedPreview {
176 attack_power: str_d * 1.2 + dex_d * 0.3,
177 spell_power: int_d * 1.1 + wis_d * 0.4,
178 evasion: dex_d * 0.8 + wis_d * 0.2,
179 carry_mass_max: str_d * 2.5,
180 sight_range_m: 12.0 + wis_d * 0.15 + dex_d * 0.05,
181 fov_deg: 120.0 + wis_d * 0.2,
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq)]
188pub struct DerivedPreview {
189 pub attack_power: f32,
190 pub spell_power: f32,
191 pub evasion: f32,
192 pub carry_mass_max: f32,
193 pub sight_range_m: f32,
194 pub fov_deg: f32,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
199pub struct SkillProgress {
200 pub level: u16,
201 #[serde(default)]
202 pub last_trained_tick: u64,
203}
204
205impl Default for SkillProgress {
206 fn default() -> Self {
207 Self {
208 level: 0,
209 last_trained_tick: 0,
210 }
211 }
212}
213
214impl SkillProgress {
215 pub fn display_tier(&self) -> u16 {
217 (self.level / 100).min(10)
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
223#[serde(default)]
224pub struct ProgressionXp {
225 pub strength: f64,
226 pub dexterity: f64,
227 pub intelligence: f64,
228 pub stamina: f64,
229 pub vitality: f64,
230 pub wisdom: f64,
231 pub charisma: f64,
232 pub logging: f64,
233 pub mining: f64,
234 pub evocation: f64,
235 pub restoration: f64,
236 pub swords: f64,
237 pub archery: f64,
238 pub crafting: f64,
239 pub alchemy: f64,
240 pub cartography: f64,
241 #[serde(default)]
243 pub ability: std::collections::BTreeMap<String, f64>,
244}
245
246impl ProgressionXp {
247 pub fn bootstrap_new(baseline_display: u16, xp_base: f64, xp_growth: f64) -> Self {
249 let bootstrap = |display: f64| {
250 if display <= 1.0 {
251 0.0
252 } else {
253 xp_base * xp_growth.powf(display - 1.0)
254 }
255 };
256 let b = baseline_display as f64;
257 let primary = bootstrap(b);
258 Self {
259 strength: primary,
260 dexterity: primary,
261 intelligence: primary,
262 stamina: primary,
263 vitality: primary,
264 wisdom: primary,
265 charisma: primary,
266 ..Self::default()
267 }
268 }
269
270 pub fn is_empty(&self) -> bool {
271 self.strength == 0.0
272 && self.dexterity == 0.0
273 && self.intelligence == 0.0
274 && self.stamina == 0.0
275 && self.vitality == 0.0
276 && self.wisdom == 0.0
277 && self.charisma == 0.0
278 && self.logging == 0.0
279 && self.mining == 0.0
280 && self.evocation == 0.0
281 && self.restoration == 0.0
282 && self.swords == 0.0
283 && self.archery == 0.0
284 && self.crafting == 0.0
285 && self.alchemy == 0.0
286 && self.cartography == 0.0
287 && self.ability.is_empty()
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub struct AbilityMasteryHud {
294 pub ability_id: String,
295 pub tier: u16,
297 pub level: u16,
299 pub xp: f64,
301 pub xp_to_next: f64,
303}
304
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307#[serde(default)]
308pub struct PlayerSkills {
309 pub logging: SkillProgress,
310 pub mining: SkillProgress,
311 pub evocation: SkillProgress,
312 #[serde(default)]
313 pub restoration: SkillProgress,
314 pub swords: SkillProgress,
315 #[serde(default)]
316 pub archery: SkillProgress,
317 pub crafting: SkillProgress,
318 #[serde(default)]
319 pub alchemy: SkillProgress,
320 pub cartography: SkillProgress,
321}
322
323impl Default for PlayerSkills {
324 fn default() -> Self {
325 Self {
326 logging: SkillProgress::default(),
327 mining: SkillProgress::default(),
328 evocation: SkillProgress::default(),
329 restoration: SkillProgress::default(),
330 swords: SkillProgress::default(),
331 archery: SkillProgress::default(),
332 crafting: SkillProgress::default(),
333 alchemy: SkillProgress::default(),
334 cartography: SkillProgress::default(),
335 }
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
341pub struct PlayerVitals {
342 pub health: f32,
343 pub health_max: f32,
344 pub mana: f32,
345 pub mana_max: f32,
346 pub stamina: f32,
347 pub stamina_max: f32,
348 #[serde(default = "default_survival_pool_max")]
349 pub hunger: f32,
350 #[serde(default = "default_survival_pool_max")]
351 pub hunger_max: f32,
352 #[serde(default = "default_survival_pool_max")]
353 pub thirst: f32,
354 #[serde(default = "default_survival_pool_max")]
355 pub thirst_max: f32,
356 #[serde(default)]
357 pub coins: u32,
358 #[serde(default)]
359 pub deaths: u32,
360 #[serde(default)]
361 pub life_state: LifeState,
362}
363
364fn default_survival_pool_max() -> f32 {
365 100.0
366}
367
368impl Default for PlayerVitals {
369 fn default() -> Self {
370 Self::from_attributes(PrimaryAttributes::default())
371 }
372}
373
374impl PlayerVitals {
375 pub fn from_attributes(attrs: PrimaryAttributes) -> Self {
382 let vit_d = PrimaryAttributes::display(attrs.vitality) as f32;
383 let int_d = PrimaryAttributes::display(attrs.intelligence) as f32;
384 let wis_d = PrimaryAttributes::display(attrs.wisdom) as f32;
385 let sta_d = PrimaryAttributes::display(attrs.stamina) as f32;
386
387 let health_max = 50.0 + vit_d * 2.0;
388 let stamina_max = 30.0 + sta_d * 1.4;
389 let mana_max = 25.0 + int_d * 1.1 + wis_d * 1.3;
390 let hunger_max = 100.0;
391 let thirst_max = 100.0;
392 Self {
393 health: health_max,
394 health_max,
395 mana: mana_max,
396 mana_max,
397 stamina: stamina_max,
398 stamina_max,
399 hunger: hunger_max,
400 hunger_max,
401 thirst: thirst_max,
402 thirst_max,
403 coins: 0,
404 deaths: 0,
405 life_state: LifeState::Alive,
406 }
407 }
408
409 pub fn legacy_maxima(attrs: PrimaryAttributes) -> (f32, f32, f32) {
411 (
412 attrs.vitality as f32 / 5.0,
413 attrs.stamina as f32 / 5.0,
414 (attrs.intelligence as f32 + attrs.wisdom as f32) / 20.0,
415 )
416 }
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
421#[serde(default)]
422pub struct StoredVitalsState {
423 pub health: f32,
424 pub mana: f32,
425 pub stamina: f32,
426 pub hunger: f32,
427 pub thirst: f32,
428 pub coins: u32,
429 pub deaths: u32,
430 pub life_state: LifeState,
431}
432
433impl StoredVitalsState {
434 pub fn from_live(v: &PlayerVitals) -> Self {
435 Self {
436 health: v.health,
437 mana: v.mana,
438 stamina: v.stamina,
439 hunger: v.hunger,
440 thirst: v.thirst,
441 coins: v.coins,
442 deaths: v.deaths,
443 life_state: v.life_state,
444 }
445 }
446
447 pub fn is_pristine(&self) -> bool {
449 self.health == 0.0
450 && self.mana == 0.0
451 && self.stamina == 0.0
452 && self.hunger == 0.0
453 && self.thirst == 0.0
454 && self.coins == 0
455 && self.deaths == 0
456 && self.life_state == LifeState::Alive
457 }
458
459 pub fn apply_to(self, attrs: PrimaryAttributes) -> PlayerVitals {
460 if self.is_pristine() {
461 return PlayerVitals::from_attributes(attrs);
462 }
463 let fresh = PlayerVitals::from_attributes(attrs);
464 let (legacy_hp, legacy_sta, legacy_mana) = PlayerVitals::legacy_maxima(attrs);
465
466 let scale = |current: f32, legacy_max: f32, new_max: f32| {
467 if legacy_max > 0.0 && new_max > legacy_max * 1.05 && current >= legacy_max * 0.95 {
468 let ratio = (current / legacy_max).clamp(0.0, 1.0);
469 (new_max * ratio).min(new_max)
470 } else {
471 current.min(new_max)
472 }
473 };
474
475 let mut v = fresh;
476 v.health = scale(self.health, legacy_hp, fresh.health_max);
477 v.mana = scale(self.mana, legacy_mana, fresh.mana_max);
478 v.stamina = scale(self.stamina, legacy_sta, fresh.stamina_max);
479 v.hunger = self.hunger.min(v.hunger_max);
480 v.thirst = self.thirst.min(v.thirst_max);
481 v.coins = self.coins;
482 v.deaths = self.deaths;
483 v.life_state = self.life_state;
484 v
485 }
486}
487
488impl Default for StoredVitalsState {
489 fn default() -> Self {
490 Self::from_live(&PlayerVitals::default())
491 }
492}
493
494pub fn humanize_snake_id(id: &str) -> String {
498 id.split('_')
499 .filter(|part| !part.is_empty())
500 .map(|part| {
501 let mut chars = part.chars();
502 match chars.next() {
503 None => String::new(),
504 Some(first) => first.to_uppercase().chain(chars).collect(),
505 }
506 })
507 .collect::<Vec<_>>()
508 .join(" ")
509}
510
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
513pub struct KnownAbility {
514 pub ability_id: String,
515 #[serde(default = "default_known_permanent")]
517 pub permanent: bool,
518 #[serde(default)]
520 pub expires_at_tick: Option<u64>,
521}
522
523fn default_known_permanent() -> bool {
524 true
525}
526
527impl KnownAbility {
528 pub fn permanent(ability_id: impl Into<String>) -> Self {
529 Self {
530 ability_id: ability_id.into(),
531 permanent: true,
532 expires_at_tick: None,
533 }
534 }
535
536 pub fn is_active(&self, tick: u64) -> bool {
537 if self.permanent {
538 return true;
539 }
540 match self.expires_at_tick {
541 Some(exp) => tick < exp,
542 None => false,
543 }
544 }
545}
546
547#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
549pub struct RotationPreset {
550 pub id: String,
551 pub label: String,
552 #[serde(default)]
553 pub abilities: Vec<String>,
554}
555
556impl RotationPreset {
557 pub fn melee_default(ability_id: impl Into<String>) -> Self {
558 let id = ability_id.into();
559 Self {
560 id: "melee".into(),
561 label: "Weapon".into(),
563 abilities: vec![id],
564 }
565 }
566
567 pub fn is_weapon_preset(&self) -> bool {
568 self.id == "melee"
569 }
570}
571
572#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
574pub struct StoredTargetSlot {
575 pub instance_id: Option<String>,
576 #[serde(default)]
577 pub preset_id: Option<String>,
578 #[serde(default)]
579 pub rotation_index: u32,
580 #[serde(default)]
581 pub auto_enabled: bool,
582}
583
584#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
585#[serde(default)]
586pub struct StoredCombatProfile {
587 pub combat_target_instance_id: Option<String>,
589 pub in_combat: bool,
590 pub last_combat_tick: u64,
591 pub last_attack_tick: u64,
592 pub cooldowns_until_tick: BTreeMap<String, u64>,
593 #[serde(default = "default_auto_attack")]
594 pub auto_attack_enabled: bool,
595 #[serde(default)]
597 pub mainhand_template_id: Option<String>,
598 #[serde(default)]
600 pub mainhand_instance_id: Option<Uuid>,
601 #[serde(default)]
603 pub offhand_template_id: Option<String>,
604 #[serde(default)]
606 pub offhand_instance_id: Option<Uuid>,
607 #[serde(default)]
610 pub worn: Vec<(BodySlot, ItemStack)>,
611 #[serde(default)]
613 pub rotation_presets: Vec<RotationPreset>,
614 #[serde(default)]
616 pub target_slots: Vec<StoredTargetSlot>,
617 #[serde(default)]
619 pub known_blueprint_ids: Vec<String>,
620 #[serde(default)]
622 pub keychain: Vec<ItemStack>,
623 #[serde(default)]
625 pub whisper_pouch: Vec<ItemStack>,
626 #[serde(default)]
628 pub known_abilities: Vec<KnownAbility>,
629 #[serde(default)]
631 pub hotbar: Vec<Option<String>>,
632 #[serde(default)]
634 pub abilities_schema_version: u32,
635 #[serde(default)]
637 pub bank_balance_copper: u64,
638}
639
640fn default_auto_attack() -> bool {
641 true
642}
643
644impl Default for StoredCombatProfile {
645 fn default() -> Self {
646 Self {
647 combat_target_instance_id: None,
648 in_combat: false,
649 last_combat_tick: 0,
650 last_attack_tick: 0,
651 cooldowns_until_tick: BTreeMap::new(),
652 auto_attack_enabled: true,
653 mainhand_template_id: None,
654 mainhand_instance_id: None,
655 offhand_template_id: None,
656 offhand_instance_id: None,
657 worn: Vec::new(),
658 rotation_presets: Vec::new(),
659 target_slots: Vec::new(),
660 known_blueprint_ids: Vec::new(),
661 keychain: Vec::new(),
662 whisper_pouch: Vec::new(),
663 known_abilities: Vec::new(),
664 hotbar: Vec::new(),
665 abilities_schema_version: 0,
666 bank_balance_copper: 0,
667 }
668 }
669}
670
671#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
673#[serde(rename_all = "snake_case")]
674pub enum CombatCueKind {
675 Dodge,
676 Block,
677 AttackTelegraph,
678}
679
680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
681pub struct CombatCueView {
682 pub kind: CombatCueKind,
683 pub until_tick: Tick,
685 #[serde(default)]
687 pub start_tick: Tick,
688 #[serde(default)]
690 pub ability_id: Option<String>,
691 #[serde(default)]
693 pub telegraph_kind: Option<CombatFxKind>,
694 #[serde(default)]
695 pub origin_x: Option<f32>,
696 #[serde(default)]
697 pub origin_y: Option<f32>,
698 #[serde(default)]
699 pub origin_z: Option<f32>,
700 #[serde(default)]
701 pub end_x: Option<f32>,
702 #[serde(default)]
703 pub end_y: Option<f32>,
704 #[serde(default)]
705 pub end_z: Option<f32>,
706 #[serde(default)]
707 pub yaw: Option<f32>,
708 #[serde(default)]
709 pub reach_m: Option<f32>,
710 #[serde(default)]
711 pub arc_deg: Option<f32>,
712 #[serde(default)]
713 pub radius_m: Option<f32>,
714}
715
716impl CombatCueView {
717 pub fn timing(kind: CombatCueKind, until_tick: Tick, start_tick: Tick) -> Self {
719 Self {
720 kind,
721 until_tick,
722 start_tick,
723 ability_id: None,
724 telegraph_kind: None,
725 origin_x: None,
726 origin_y: None,
727 origin_z: None,
728 end_x: None,
729 end_y: None,
730 end_z: None,
731 yaw: None,
732 reach_m: None,
733 arc_deg: None,
734 radius_m: None,
735 }
736 }
737}
738
739#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
740pub struct EntityState {
741 pub id: EntityId,
742 pub transform: Transform,
743 #[serde(default)]
745 pub label: String,
746 #[serde(default)]
747 pub vitals: Option<PlayerVitals>,
748 #[serde(default)]
750 pub attributes: Option<PrimaryAttributes>,
751 #[serde(default)]
752 pub skills: Option<PlayerSkills>,
753 #[serde(default)]
755 pub inside_building: Option<String>,
756 #[serde(default)]
758 pub tile_id: Option<String>,
759 #[serde(default)]
761 pub paperdoll_ref: Option<String>,
762 #[serde(default = "default_draw_scale")]
764 pub draw_scale: f32,
765 #[serde(default)]
767 pub presentation_state: Option<String>,
768 #[serde(default)]
770 pub sprite_mode: Option<String>,
771 #[serde(default)]
773 pub progression_xp: Option<ProgressionXp>,
774 #[serde(default)]
776 pub combat_cues: Vec<CombatCueView>,
777 #[serde(default)]
779 pub statuses: Vec<StatusEffectHud>,
780}
781
782#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
784#[serde(rename_all = "snake_case")]
785pub enum ChatChannel {
786 Nearby,
788 Direct,
790 Whisper,
792 WhisperStone,
794}
795
796#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
798#[serde(rename_all = "snake_case")]
799pub enum ChatClarity {
800 #[default]
801 Clear,
802 Partial,
803 Heavy,
804}
805
806#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
807pub struct ChatMessage {
808 pub channel: ChatChannel,
809 pub from_entity: EntityId,
810 pub from_name: String,
811 pub text: String,
813 pub tick: Tick,
814 #[serde(default)]
816 pub to_entity: Option<EntityId>,
817 #[serde(default)]
818 pub clarity: ChatClarity,
819}
820
821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
823pub enum Intent {
824 Move {
825 entity_id: EntityId,
826 forward: f32,
827 strafe: f32,
828 #[serde(default)]
830 vertical: f32,
831 #[serde(default)]
833 sprint: bool,
834 seq: Seq,
835 },
836 Stop {
837 entity_id: EntityId,
838 seq: Seq,
839 },
840 Harvest {
841 entity_id: EntityId,
842 node_id: String,
843 seq: Seq,
844 },
845 Use {
846 entity_id: EntityId,
847 template_id: String,
848 seq: Seq,
849 },
850 UseGrant {
852 entity_id: EntityId,
853 grant_instance_id: Uuid,
854 target_instance_id: Uuid,
855 seq: Seq,
856 },
857 Say {
858 entity_id: EntityId,
859 channel: ChatChannel,
860 text: String,
861 #[serde(default)]
863 to_entity: Option<EntityId>,
864 seq: Seq,
865 },
866 Craft {
868 entity_id: EntityId,
869 blueprint_id: String,
870 #[serde(default)]
872 count: Option<u32>,
873 seq: Seq,
874 },
875 Interact {
877 entity_id: EntityId,
878 target_id: String,
879 seq: Seq,
880 },
881 ShopBuy {
883 entity_id: EntityId,
884 npc_id: String,
885 offer_id: String,
886 #[serde(default = "default_one")]
887 quantity: u32,
888 seq: Seq,
889 },
890 ShopSell {
892 entity_id: EntityId,
893 npc_id: String,
894 template_id: String,
895 #[serde(default = "default_one")]
896 quantity: u32,
897 seq: Seq,
898 },
899 ShopClose {
901 entity_id: EntityId,
902 npc_id: String,
903 seq: Seq,
904 },
905 TestDamage {
907 entity_id: EntityId,
908 amount: f32,
909 seq: Seq,
910 },
911 SetTarget {
913 entity_id: EntityId,
914 target_id: EntityId,
915 seq: Seq,
916 },
917 SetTargetSlot {
919 entity_id: EntityId,
920 slot_index: u8,
921 target_id: EntityId,
922 seq: Seq,
923 },
924 ClearTarget {
925 entity_id: EntityId,
926 seq: Seq,
927 },
928 ClearTargetSlot {
929 entity_id: EntityId,
930 slot_index: u8,
931 seq: Seq,
932 },
933 SetAutoAttack {
935 entity_id: EntityId,
936 slot_index: u8,
937 enabled: bool,
938 seq: Seq,
939 },
940 Attack {
942 entity_id: EntityId,
943 #[serde(default)]
944 target_id: Option<EntityId>,
945 #[serde(default)]
946 weapon_slot: Option<u32>,
947 seq: Seq,
948 },
949 Pickup {
951 entity_id: EntityId,
952 #[serde(default)]
953 drop_id: Option<String>,
954 seq: Seq,
955 },
956 Cast {
959 entity_id: EntityId,
960 ability_id: String,
961 target_id: EntityId,
962 #[serde(default)]
963 target_point: Option<AimPoint>,
964 seq: Seq,
965 },
966 BindActionSlot {
968 entity_id: EntityId,
969 slot_index: u8,
970 ability_id: String,
971 #[serde(default = "default_auto_attack")]
972 auto_enabled: bool,
973 seq: Seq,
974 },
975 UseActionSlot {
977 entity_id: EntityId,
978 slot_index: u8,
979 seq: Seq,
980 },
981 Dodge {
985 entity_id: EntityId,
986 #[serde(default)]
988 forward: f32,
989 #[serde(default)]
991 strafe: f32,
992 seq: Seq,
993 },
994 Lunge {
996 entity_id: EntityId,
997 #[serde(default)]
999 forward: f32,
1000 #[serde(default)]
1002 strafe: f32,
1003 seq: Seq,
1004 },
1005 DirectionalJump {
1007 entity_id: EntityId,
1008 #[serde(default)]
1010 forward: f32,
1011 #[serde(default)]
1013 strafe: f32,
1014 seq: Seq,
1015 },
1016 Block {
1018 entity_id: EntityId,
1019 #[serde(default = "default_block_enabled")]
1020 enabled: bool,
1021 seq: Seq,
1022 },
1023 EquipMainhand {
1027 entity_id: EntityId,
1028 #[serde(default)]
1029 template_id: Option<String>,
1030 #[serde(default)]
1031 instance_id: Option<Uuid>,
1032 seq: Seq,
1033 },
1034 EquipOffhand {
1036 entity_id: EntityId,
1037 #[serde(default)]
1038 template_id: Option<String>,
1039 #[serde(default)]
1040 instance_id: Option<Uuid>,
1041 seq: Seq,
1042 },
1043 EquipWorn {
1046 entity_id: EntityId,
1047 slot: BodySlot,
1048 #[serde(default)]
1049 instance_id: Option<Uuid>,
1050 seq: Seq,
1051 },
1052 MoveItem {
1054 entity_id: EntityId,
1055 item_instance_id: Uuid,
1056 from: InventoryLocation,
1057 to: InventoryLocation,
1058 #[serde(default)]
1060 to_parent_instance_id: Option<Uuid>,
1061 #[serde(default)]
1063 quantity: Option<u32>,
1064 seq: Seq,
1065 },
1066 PlaceContainer {
1068 entity_id: EntityId,
1069 item_instance_id: Uuid,
1070 seq: Seq,
1071 },
1072 PickupContainer {
1074 entity_id: EntityId,
1075 container_id: String,
1076 seq: Seq,
1077 },
1078 MovePlacedContainer {
1080 entity_id: EntityId,
1081 container_id: String,
1082 x: f32,
1083 y: f32,
1084 seq: Seq,
1085 },
1086 SetContainerLocked {
1088 entity_id: EntityId,
1089 location: InventoryLocation,
1091 locked: bool,
1092 seq: Seq,
1093 },
1094 DropItem {
1096 entity_id: EntityId,
1097 item_instance_id: Uuid,
1098 from: InventoryLocation,
1099 seq: Seq,
1100 },
1101 DestroyItem {
1103 entity_id: EntityId,
1104 item_instance_id: Uuid,
1105 from: InventoryLocation,
1106 #[serde(default)]
1108 quantity: Option<u32>,
1109 seq: Seq,
1110 },
1111 RenameContainer {
1113 entity_id: EntityId,
1114 item_instance_id: Uuid,
1115 location: InventoryLocation,
1116 name: String,
1117 seq: Seq,
1118 },
1119 UpsertRotationPreset {
1121 entity_id: EntityId,
1122 preset: RotationPreset,
1123 seq: Seq,
1124 },
1125 DeleteRotationPreset {
1127 entity_id: EntityId,
1128 preset_id: String,
1129 seq: Seq,
1130 },
1131 AssignSlotPreset {
1133 entity_id: EntityId,
1134 slot_index: u8,
1135 preset_id: String,
1136 seq: Seq,
1137 },
1138 SetHotbarSlot {
1141 entity_id: EntityId,
1142 slot: u8,
1144 #[serde(default)]
1146 ability_id: Option<String>,
1147 seq: Seq,
1148 },
1149 AdvanceRotation {
1151 entity_id: EntityId,
1152 slot_index: u8,
1153 seq: Seq,
1154 },
1155 NpcTalkOpen {
1157 entity_id: EntityId,
1158 npc_id: String,
1159 seq: Seq,
1160 },
1161 NpcTalkSay {
1163 entity_id: EntityId,
1164 npc_id: String,
1165 message: String,
1166 seq: Seq,
1167 },
1168 NpcTalkClose {
1170 entity_id: EntityId,
1171 npc_id: String,
1172 seq: Seq,
1173 },
1174 AcceptQuest {
1176 entity_id: EntityId,
1177 quest_id: String,
1178 seq: Seq,
1179 },
1180 WithdrawQuest {
1182 entity_id: EntityId,
1183 quest_id: String,
1184 seq: Seq,
1185 },
1186 TrackQuest {
1188 entity_id: EntityId,
1189 quest_id: String,
1190 seq: Seq,
1191 },
1192 QuestGiveItem {
1194 entity_id: EntityId,
1195 npc_id: String,
1196 template_id: String,
1197 #[serde(default = "default_one")]
1198 quantity: u32,
1199 seq: Seq,
1200 },
1201 HireWorker {
1203 entity_id: EntityId,
1204 def_id: String,
1205 wage_copper_per_interval: u32,
1206 #[serde(default)]
1207 lodging_container_id: Option<String>,
1208 #[serde(default)]
1209 job_yaml: Option<String>,
1210 seq: Seq,
1211 },
1212 DismissWorker {
1214 entity_id: EntityId,
1215 worker_instance_id: String,
1216 seq: Seq,
1217 },
1218 SetWorkerJob {
1220 entity_id: EntityId,
1221 worker_instance_id: String,
1222 job_yaml: String,
1223 seq: Seq,
1224 },
1225 AssignWorkerLodging {
1227 entity_id: EntityId,
1228 worker_instance_id: String,
1229 lodging_container_id: String,
1230 seq: Seq,
1231 },
1232 SetWorkerMode {
1234 entity_id: EntityId,
1235 worker_instance_id: String,
1236 mode: String,
1237 seq: Seq,
1238 },
1239 GiveWorkerItem {
1242 entity_id: EntityId,
1243 worker_instance_id: String,
1244 item_instance_id: uuid::Uuid,
1245 #[serde(default)]
1246 quantity: Option<u32>,
1247 seq: Seq,
1248 },
1249 TakeWorkerItem {
1251 entity_id: EntityId,
1252 worker_instance_id: String,
1253 item_instance_id: uuid::Uuid,
1254 #[serde(default)]
1255 quantity: Option<u32>,
1256 seq: Seq,
1257 },
1258 RenameHiredWorker {
1260 entity_id: EntityId,
1261 worker_instance_id: String,
1262 name: String,
1263 seq: Seq,
1264 },
1265 RenamePropertyPlot {
1267 entity_id: EntityId,
1268 plot_id: Uuid,
1269 label: String,
1270 seq: Seq,
1271 },
1272 TeachWorkerBlueprint {
1274 entity_id: EntityId,
1275 worker_instance_id: String,
1276 blueprint_id: String,
1277 seq: Seq,
1278 },
1279 AttendHiredWorker {
1281 entity_id: EntityId,
1282 worker_instance_id: String,
1283 attending: bool,
1284 seq: Seq,
1285 },
1286 BuyPlot {
1288 entity_id: EntityId,
1289 zone_id: String,
1290 x0: f32,
1291 y0: f32,
1292 x1: f32,
1293 y1: f32,
1294 seq: Seq,
1295 },
1296 BuyPlotAllFree {
1298 entity_id: EntityId,
1299 zone_id: String,
1300 seq: Seq,
1301 },
1302 SellPlotToCrown {
1304 entity_id: EntityId,
1305 plot_id: Uuid,
1306 seq: Seq,
1307 },
1308 Cultivate {
1310 entity_id: EntityId,
1311 x: f32,
1313 y: f32,
1314 seq: Seq,
1315 },
1316 PlantSeeds {
1318 entity_id: EntityId,
1319 seed_template_id: String,
1320 quantity: u32,
1321 seq: Seq,
1322 },
1323 SetPlotFarmPublic {
1325 entity_id: EntityId,
1326 plot_id: Uuid,
1327 public: bool,
1328 #[serde(default)]
1329 public_tax_discount_bps: u32,
1330 seq: Seq,
1331 },
1332 PlotFarmAllowUpsert {
1334 entity_id: EntityId,
1335 plot_id: Uuid,
1336 #[serde(default)]
1338 character_id: Option<Uuid>,
1339 #[serde(default)]
1341 character_name: String,
1342 #[serde(default)]
1343 tax_discount_bps: u32,
1344 seq: Seq,
1345 },
1346 PlotFarmAllowRemove {
1348 entity_id: EntityId,
1349 plot_id: Uuid,
1350 character_id: Uuid,
1351 seq: Seq,
1352 },
1353 StartPlotBuild {
1356 entity_id: EntityId,
1357 plot_id: Uuid,
1358 wall_material_id: String,
1359 roof_material_id: String,
1360 seq: Seq,
1361 },
1362 CancelPlotBuild {
1363 entity_id: EntityId,
1364 seq: Seq,
1365 },
1366 SetDoorLocked {
1369 entity_id: EntityId,
1370 door_id: String,
1371 locked: bool,
1372 seq: Seq,
1373 },
1374 EnterBuildingDoor {
1377 entity_id: EntityId,
1378 door_id: String,
1379 seq: Seq,
1380 },
1381 ExitBuildingDoor {
1384 entity_id: EntityId,
1385 door_id: String,
1386 seq: Seq,
1387 },
1388 ConfirmInteriorEdit {
1390 entity_id: EntityId,
1391 building_id: String,
1392 rooms: Vec<InteriorRoomEdit>,
1393 room_doors: Vec<InteriorRoomDoorEdit>,
1394 seq: Seq,
1395 },
1396 CancelInteriorEdit {
1397 entity_id: EntityId,
1398 building_id: String,
1399 seq: Seq,
1400 },
1401 BankDeposit {
1403 entity_id: EntityId,
1404 npc_id: String,
1405 #[serde(default)]
1407 amount_copper: u64,
1408 seq: Seq,
1409 },
1410 BankWithdraw {
1412 entity_id: EntityId,
1413 npc_id: String,
1414 #[serde(default)]
1416 amount_copper: u64,
1417 seq: Seq,
1418 },
1419 BankClose {
1421 entity_id: EntityId,
1422 npc_id: String,
1423 seq: Seq,
1424 },
1425 BankTransfer {
1427 entity_id: EntityId,
1428 npc_id: String,
1429 #[serde(default)]
1431 to_character_id: Option<Uuid>,
1432 #[serde(default)]
1434 to_name: String,
1435 amount_copper: u64,
1437 seq: Seq,
1438 },
1439 StorageStore {
1441 entity_id: EntityId,
1442 npc_id: String,
1443 item_instance_id: Uuid,
1444 #[serde(default)]
1445 quantity: Option<u32>,
1446 seq: Seq,
1447 },
1448 StorageTake {
1450 entity_id: EntityId,
1451 npc_id: String,
1452 item_instance_id: Uuid,
1453 #[serde(default)]
1454 quantity: Option<u32>,
1455 seq: Seq,
1456 },
1457 StorageShip {
1459 entity_id: EntityId,
1460 npc_id: String,
1461 dest_building_id: String,
1462 item_instance_id: Uuid,
1463 #[serde(default)]
1464 quantity: Option<u32>,
1465 seq: Seq,
1466 },
1467 StorageClose {
1469 entity_id: EntityId,
1470 npc_id: String,
1471 seq: Seq,
1472 },
1473 MarketList {
1476 entity_id: EntityId,
1477 npc_id: String,
1478 source: GoodsLocation,
1479 item_instance_id: Uuid,
1480 #[serde(default)]
1481 quantity: Option<u32>,
1482 unit_price_copper: u64,
1483 #[serde(default)]
1485 npc_price: bool,
1486 seq: Seq,
1487 },
1488 MarketReprice {
1490 entity_id: EntityId,
1491 npc_id: String,
1492 listing_id: Uuid,
1493 unit_price_copper: u64,
1494 seq: Seq,
1495 },
1496 MarketDelist {
1498 entity_id: EntityId,
1499 npc_id: String,
1500 listing_id: Uuid,
1501 dest: GoodsLocation,
1502 seq: Seq,
1503 },
1504 MarketBuy {
1506 entity_id: EntityId,
1507 npc_id: String,
1508 listing_id: Uuid,
1509 #[serde(default = "default_one")]
1510 quantity: u32,
1511 dest: GoodsLocation,
1512 seq: Seq,
1513 },
1514 MarketClose {
1516 entity_id: EntityId,
1517 npc_id: String,
1518 seq: Seq,
1519 },
1520 TradeRequest {
1522 entity_id: EntityId,
1523 peer_entity_id: EntityId,
1524 seq: Seq,
1525 },
1526 TradeRespond {
1528 entity_id: EntityId,
1529 peer_entity_id: EntityId,
1530 accept: bool,
1531 seq: Seq,
1532 },
1533 TradePresent {
1535 entity_id: EntityId,
1536 item_instance_id: Uuid,
1537 #[serde(default)]
1538 quantity: Option<u32>,
1539 seq: Seq,
1540 },
1541 TradeUnpresent {
1543 entity_id: EntityId,
1544 item_instance_id: Uuid,
1545 seq: Seq,
1546 },
1547 TradeSetReady {
1549 entity_id: EntityId,
1550 ready: bool,
1551 seq: Seq,
1552 },
1553 TradeCancel {
1555 entity_id: EntityId,
1556 seq: Seq,
1557 },
1558 DestroyWhisperStone {
1560 entity_id: EntityId,
1561 item_instance_id: Uuid,
1562 seq: Seq,
1563 },
1564 StowWhisperStone {
1566 entity_id: EntityId,
1567 item_instance_id: Uuid,
1568 seq: Seq,
1569 },
1570}
1571
1572fn default_block_enabled() -> bool {
1573 true
1574}
1575
1576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1578pub struct StatusEffectHud {
1579 pub effect_id: String,
1580 pub label: String,
1581 #[serde(default)]
1582 pub polarity: String,
1583 #[serde(default)]
1584 pub icon_tile_id: Option<String>,
1585 #[serde(default)]
1587 pub remaining_sec: Option<f32>,
1588}
1589
1590#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1592pub struct CombatTargetHud {
1593 pub entity_id: EntityId,
1594 #[serde(default)]
1595 pub label: String,
1596 #[serde(default)]
1597 pub level: u32,
1598 pub health: f32,
1599 pub health_max: f32,
1600 #[serde(default)]
1601 pub life_state: LifeState,
1602 #[serde(default)]
1603 pub distance_m: f32,
1604 #[serde(default)]
1605 pub statuses: Vec<StatusEffectHud>,
1606}
1607
1608#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1610#[serde(rename_all = "snake_case")]
1611pub enum TimedChannelKind {
1612 #[default]
1613 Cultivate,
1614 Plant,
1615 Harvest,
1616 Build,
1618}
1619
1620#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1622pub struct TimedChannelHud {
1623 #[serde(default)]
1624 pub label: String,
1625 #[serde(default)]
1626 pub channel: TimedChannelKind,
1627 #[serde(default)]
1628 pub cell_x: i32,
1629 #[serde(default)]
1630 pub cell_y: i32,
1631 #[serde(default)]
1633 pub x0: f32,
1634 #[serde(default)]
1635 pub y0: f32,
1636 #[serde(default)]
1637 pub x1: f32,
1638 #[serde(default)]
1639 pub y1: f32,
1640 #[serde(default)]
1641 pub ticks_remaining: u64,
1642 #[serde(default)]
1643 pub ticks_total: u64,
1644}
1645
1646impl TimedChannelHud {
1647 pub fn has_footprint(&self) -> bool {
1649 self.x1 > self.x0 && self.y1 > self.y0
1650 }
1651}
1652
1653#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1655#[serde(rename_all = "snake_case")]
1656pub enum PlotBuildMaterialSource {
1657 #[default]
1658 None,
1659 TownStorage,
1660 NearbyContainer,
1661}
1662
1663#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1665pub struct BuildingMaterialView {
1666 pub id: String,
1667 pub display_name: String,
1668 #[serde(default)]
1669 pub can_wall: bool,
1670 #[serde(default)]
1671 pub can_roof: bool,
1672 #[serde(default)]
1673 pub wall_set: String,
1674 #[serde(default)]
1675 pub roof_set: String,
1676 #[serde(default = "default_material_tick_mult")]
1677 pub tick_mult: f32,
1678 #[serde(default)]
1679 pub wall_bom: Vec<BuildingBomLineView>,
1680 #[serde(default)]
1681 pub roof_bom: Vec<BuildingBomLineView>,
1682}
1683
1684fn default_material_tick_mult() -> f32 {
1685 1.0
1686}
1687
1688#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1689pub struct BuildingBomLineView {
1690 pub template_id: String,
1691 #[serde(default)]
1692 pub display_name: String,
1693 pub per_m2: f32,
1694}
1695
1696#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1698pub struct PlotBuildStockView {
1699 pub template_id: String,
1700 #[serde(default)]
1701 pub display_name: String,
1702 pub quantity: u32,
1703}
1704
1705#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1707pub struct PlotBuildOfferHud {
1708 pub plot_id: Uuid,
1709 #[serde(default)]
1710 pub pad_width_m: f32,
1711 #[serde(default)]
1712 pub pad_depth_m: f32,
1713 #[serde(default)]
1714 pub pad_ok: bool,
1715 #[serde(default)]
1716 pub pad_error: String,
1717 #[serde(default)]
1718 pub source: PlotBuildMaterialSource,
1719 #[serde(default)]
1720 pub source_label: String,
1721 #[serde(default)]
1722 pub available: Vec<PlotBuildStockView>,
1723 #[serde(default)]
1724 pub base_ticks: u32,
1725 #[serde(default)]
1726 pub tick_per_m2: u32,
1727}
1728
1729#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1731pub struct CastProgressHud {
1732 #[serde(default)]
1733 pub ability_id: String,
1734 #[serde(default)]
1735 pub ability_label: String,
1736 #[serde(default)]
1737 pub ticks_remaining: u64,
1738 #[serde(default)]
1739 pub ticks_total: u64,
1740}
1741
1742#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1744pub struct AbilityCooldownHud {
1745 #[serde(default)]
1746 pub ability_id: String,
1747 #[serde(default)]
1748 pub label: String,
1749 #[serde(default)]
1750 pub cd_ticks: u64,
1751 #[serde(default)]
1752 pub cd_total_ticks: u64,
1753}
1754
1755#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1757pub struct CombatSlotHud {
1758 pub slot_index: u8,
1759 #[serde(default)]
1760 pub target_entity_id: Option<EntityId>,
1761 #[serde(default)]
1762 pub target_label: Option<String>,
1763 #[serde(default)]
1764 pub target: Option<CombatTargetHud>,
1765 #[serde(default)]
1766 pub preset_id: Option<String>,
1767 #[serde(default)]
1768 pub preset_label: Option<String>,
1769 #[serde(default)]
1770 pub rotation: Vec<String>,
1771 #[serde(default)]
1772 pub rotation_index: u32,
1773 #[serde(default)]
1774 pub next_ability_id: Option<String>,
1775 #[serde(default)]
1776 pub auto_enabled: bool,
1777}
1778
1779#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1781pub struct DefensePieceHud {
1782 pub slot: BodySlot,
1783 pub label: String,
1784 pub template_id: String,
1785 #[serde(default)]
1786 pub armor_physical: f32,
1787 #[serde(default)]
1788 pub resists: Vec<(String, f32)>,
1789}
1790
1791impl Default for DefensePieceHud {
1792 fn default() -> Self {
1793 Self {
1794 slot: BodySlot::Head,
1795 label: String::new(),
1796 template_id: String::new(),
1797 armor_physical: 0.0,
1798 resists: Vec::new(),
1799 }
1800 }
1801}
1802
1803#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1805pub struct DefenseHud {
1806 pub armor_physical: f32,
1807 pub vitality_contribution: f32,
1808 pub total_mitigation_rating: f32,
1809 pub estimated_physical_dr: f32,
1811 #[serde(default)]
1812 pub resists: Vec<(String, f32)>,
1813 #[serde(default)]
1814 pub pieces: Vec<DefensePieceHud>,
1815}
1816
1817#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
1819pub struct CombatHud {
1820 pub in_combat: bool,
1821 pub auto_attack: bool,
1823 pub has_los: bool,
1824 pub attack_cd_ticks: u64,
1825 #[serde(default)]
1826 pub ability_id: String,
1827 #[serde(default)]
1828 pub target_entity_id: Option<EntityId>,
1829 #[serde(default)]
1830 pub target_label: Option<String>,
1831 #[serde(default)]
1832 pub max_target_slots: u8,
1833 #[serde(default)]
1834 pub slots: Vec<CombatSlotHud>,
1835 #[serde(default)]
1836 pub rotation_presets: Vec<RotationPreset>,
1837 #[serde(default)]
1838 pub gcd_ticks: u64,
1839 #[serde(default)]
1840 pub mainhand_template_id: Option<String>,
1841 #[serde(default)]
1842 pub mainhand_label: Option<String>,
1843 #[serde(default)]
1845 pub mainhand_instance_id: Option<Uuid>,
1846 #[serde(default)]
1847 pub offhand_template_id: Option<String>,
1848 #[serde(default)]
1849 pub offhand_label: Option<String>,
1850 #[serde(default)]
1852 pub offhand_instance_id: Option<Uuid>,
1853 #[serde(default)]
1855 pub mainhand_hand_slots: u8,
1856 #[serde(default)]
1858 pub worn: Vec<(BodySlot, ItemStack)>,
1859 #[serde(default)]
1861 pub defense: Option<DefenseHud>,
1862 #[serde(default)]
1863 pub carry_mass: f32,
1864 #[serde(default)]
1865 pub carry_mass_max: f32,
1866 #[serde(default)]
1867 pub encumbrance: EncumbranceState,
1868 #[serde(default)]
1870 pub keychain: Vec<ItemStack>,
1871 #[serde(default)]
1873 pub whisper_pouch: Vec<ItemStack>,
1874 #[serde(default)]
1875 pub target: Option<CombatTargetHud>,
1876 #[serde(default)]
1877 pub cast: Option<CastProgressHud>,
1878 #[serde(default)]
1880 pub timed_channel: Option<TimedChannelHud>,
1881 #[serde(default)]
1883 pub plot_build: Option<PlotBuildOfferHud>,
1884 #[serde(default)]
1885 pub ability_cooldowns: Vec<AbilityCooldownHud>,
1886 #[serde(default)]
1887 pub blocking_active: bool,
1888 #[serde(default)]
1890 pub progression_xp: Option<ProgressionXp>,
1891 #[serde(default)]
1892 pub progression_baseline: u16,
1893 #[serde(default)]
1894 pub progression_xp_base: f64,
1895 #[serde(default)]
1896 pub progression_xp_growth: f64,
1897 #[serde(default)]
1898 pub attributes: Option<PrimaryAttributes>,
1899 #[serde(default)]
1900 pub skills: Option<PlayerSkills>,
1901 #[serde(default)]
1903 pub statuses: Vec<StatusEffectHud>,
1904 #[serde(default)]
1906 pub known_abilities: Vec<String>,
1907 #[serde(default)]
1909 pub ability_meta: Vec<AbilityMetaHud>,
1910 #[serde(default)]
1912 pub ability_mastery: Vec<AbilityMasteryHud>,
1913 #[serde(default)]
1916 pub hotbar: Vec<Option<String>>,
1917 #[serde(default)]
1919 pub max_abilities_per_rotation: u8,
1920}
1921
1922#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1924pub struct AbilityMetaHud {
1925 pub id: String,
1926 #[serde(default = "default_aim_mode_entity")]
1928 pub aim_mode: String,
1929 #[serde(default)]
1930 pub blast_radius_m: f32,
1931 #[serde(default)]
1932 pub allows_self: bool,
1933 #[serde(default)]
1934 pub is_heal: bool,
1935 #[serde(default = "default_auto_rotation_eligible")]
1938 pub auto_rotation_eligible: bool,
1939}
1940
1941fn default_auto_rotation_eligible() -> bool {
1942 true
1943}
1944
1945fn default_aim_mode_entity() -> String {
1946 "entity".into()
1947}
1948
1949pub const HOTBAR_ITEM_PREFIX: &str = "item:";
1951
1952pub fn hotbar_consumable_binding(template_id: &str) -> String {
1954 format!("{HOTBAR_ITEM_PREFIX}{}", template_id.trim())
1955}
1956
1957pub fn hotbar_consumable_template(binding: &str) -> Option<&str> {
1959 binding
1960 .strip_prefix(HOTBAR_ITEM_PREFIX)
1961 .map(str::trim)
1962 .filter(|id| !id.is_empty())
1963}
1964
1965pub fn hotbar_binding_is_consumable(binding: &str) -> bool {
1967 hotbar_consumable_template(binding).is_some()
1968}
1969
1970#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1972#[serde(rename_all = "snake_case")]
1973pub enum CombatFxKind {
1974 MeleeArc,
1975 Cone,
1976 Sphere,
1977 Beam,
1978 HitMarker,
1979}
1980
1981#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1983#[serde(rename_all = "snake_case")]
1984pub enum CombatFxHitOutcome {
1985 #[default]
1986 Hit,
1987 Blocked,
1988 Miss,
1989 Glance,
1990}
1991
1992#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1994pub struct CombatFxHit {
1995 pub entity_id: EntityId,
1996 pub x: f32,
1997 pub y: f32,
1998 pub z: f32,
1999 #[serde(default)]
2000 pub outcome: CombatFxHitOutcome,
2001}
2002
2003#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2005pub struct CombatFx {
2006 pub id: u64,
2007 pub kind: CombatFxKind,
2008 pub ability_id: String,
2009 pub caster_id: EntityId,
2010 pub origin_x: f32,
2011 pub origin_y: f32,
2012 pub origin_z: f32,
2013 #[serde(default)]
2014 pub end_x: Option<f32>,
2015 #[serde(default)]
2016 pub end_y: Option<f32>,
2017 #[serde(default)]
2018 pub end_z: Option<f32>,
2019 #[serde(default)]
2020 pub yaw: Option<f32>,
2021 #[serde(default)]
2022 pub reach_m: Option<f32>,
2023 #[serde(default)]
2024 pub arc_deg: Option<f32>,
2025 #[serde(default)]
2026 pub radius_m: Option<f32>,
2027 #[serde(default)]
2028 pub hits: Vec<CombatFxHit>,
2029 pub until_tick: u64,
2031 #[serde(default)]
2032 pub damage_type: String,
2033}
2034
2035#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2037#[serde(rename_all = "snake_case")]
2038pub enum WorkerModeView {
2039 Companion,
2040 JobLoop,
2041 Idle,
2044}
2045
2046#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2048#[serde(rename_all = "snake_case")]
2049pub enum WorkerStateView {
2050 Idle,
2051 Traveling,
2052 Working,
2053 Resting,
2054 Waiting,
2055 Strike,
2056 Dismissed,
2057}
2058
2059#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
2061pub struct WorkerVitalsSummary {
2062 pub health_pct: f32,
2063 pub stamina_pct: f32,
2064}
2065
2066#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2068#[serde(rename_all = "snake_case")]
2069pub enum WorkerRouteKindView {
2070 #[default]
2071 HarvestLoop,
2072 Ordered,
2073}
2074
2075#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2077pub struct WorkerRouteView {
2078 #[serde(default)]
2079 pub kind: WorkerRouteKindView,
2080 #[serde(default)]
2081 pub lodging_container_id: Option<String>,
2082 #[serde(default)]
2084 pub outbound_waypoints: Vec<WorkerRouteWaypointView>,
2085 #[serde(default)]
2087 pub harvest_nodes: Vec<String>,
2088 #[serde(default = "default_route_carry_ratio")]
2089 pub carry_return_ratio: f32,
2090 #[serde(default)]
2092 pub stops: Vec<WorkerRouteStopView>,
2093}
2094
2095fn default_route_carry_ratio() -> f32 {
2096 0.90
2097}
2098
2099fn default_true_view() -> bool {
2100 true
2101}
2102
2103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2105pub struct WorkerWithdrawItemView {
2106 pub template: String,
2107 #[serde(default)]
2109 pub qty: u32,
2110 #[serde(default)]
2112 pub all: bool,
2113}
2114
2115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2116pub struct WorkerRouteWaypointView {
2117 pub x: f32,
2118 pub y: f32,
2119 pub z: f32,
2120}
2121
2122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2131#[serde(rename_all = "snake_case")]
2132pub enum WorkerRouteStopView {
2133 Waypoint {
2134 x: f32,
2135 y: f32,
2136 #[serde(default)]
2137 z: f32,
2138 },
2139 HarvestNode {
2140 node_id: String,
2141 },
2142 DepositAt {
2143 container_id: String,
2144 #[serde(default)]
2145 filter: Option<Vec<String>>,
2146 },
2147 TradeWith {
2148 #[serde(default)]
2149 npc_id: Option<String>,
2150 template: String,
2151 #[serde(default = "default_true_view")]
2152 sell_all: bool,
2153 },
2154 WithdrawFrom {
2155 container_id: String,
2156 items: Vec<WorkerWithdrawItemView>,
2157 },
2158 CraftAt {
2159 device: String,
2160 blueprint: String,
2161 #[serde(default)]
2162 qty: Option<u32>,
2163 },
2164 CultivatePlot {
2165 plot_id: uuid::Uuid,
2166 },
2167 PlantPlot {
2168 plot_id: uuid::Uuid,
2169 seed_template: String,
2170 },
2171 HarvestPlot {
2172 plot_id: uuid::Uuid,
2173 },
2174 RestIfNeeded,
2175 Wait {
2176 #[serde(default)]
2177 wait_ticks: u64,
2178 },
2179}
2180
2181#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2183#[serde(rename_all = "snake_case")]
2184pub enum LedgerCategory {
2185 Workers,
2186 Hire,
2187 Train,
2188 ShopBuy,
2189 Taxes,
2190 WorkerSales,
2191 TraderSales,
2192 BankDeposit,
2193 BankWithdraw,
2194 BankTransferOut,
2195 BankTransferIn,
2196 BankTransferFee,
2197 StorageShipFee,
2198 PropertyBuy,
2200 PropertySell,
2202 TaxShare,
2204 MarketBuy,
2206 MarketSell,
2208 Other,
2209}
2210
2211impl LedgerCategory {
2212 pub fn as_str(self) -> &'static str {
2213 match self {
2214 Self::Workers => "workers",
2215 Self::Hire => "hire",
2216 Self::Train => "train",
2217 Self::ShopBuy => "shop_buy",
2218 Self::Taxes => "taxes",
2219 Self::WorkerSales => "worker_sales",
2220 Self::TraderSales => "trader_sales",
2221 Self::BankDeposit => "bank_deposit",
2222 Self::BankWithdraw => "bank_withdraw",
2223 Self::BankTransferOut => "bank_transfer_out",
2224 Self::BankTransferIn => "bank_transfer_in",
2225 Self::BankTransferFee => "bank_transfer_fee",
2226 Self::StorageShipFee => "storage_ship_fee",
2227 Self::PropertyBuy => "property_buy",
2228 Self::PropertySell => "property_sell",
2229 Self::TaxShare => "tax_share",
2230 Self::MarketBuy => "market_buy",
2231 Self::MarketSell => "market_sell",
2232 Self::Other => "other",
2233 }
2234 }
2235}
2236
2237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2238pub struct LedgerEntryView {
2239 pub id: uuid::Uuid,
2240 pub game_day: u64,
2241 pub signed_copper: i64,
2242 pub category: LedgerCategory,
2243 #[serde(default)]
2244 pub label: String,
2245}
2246
2247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2248pub struct LedgerPeriodTotals {
2249 #[serde(default)]
2251 pub expenses: std::collections::HashMap<String, u64>,
2252 #[serde(default)]
2254 pub income: std::collections::HashMap<String, u64>,
2255 pub expense_copper: u64,
2256 pub income_copper: u64,
2257 pub cash_flow_copper: i64,
2259}
2260
2261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2262pub struct PlayerLedgerView {
2263 pub current_game_day: u64,
2264 #[serde(default)]
2265 pub period_day: LedgerPeriodTotals,
2266 #[serde(default)]
2267 pub period_week: LedgerPeriodTotals,
2268 #[serde(default)]
2269 pub period_month: LedgerPeriodTotals,
2270 #[serde(default)]
2271 pub period_lifetime: LedgerPeriodTotals,
2272 #[serde(default)]
2273 pub recent: Vec<LedgerEntryView>,
2274 #[serde(default)]
2276 pub wealth_on_person_copper: u64,
2277 #[serde(default)]
2279 pub wealth_in_storage_copper: u64,
2280 #[serde(default)]
2282 pub wealth_in_bank_copper: u64,
2283 #[serde(default)]
2285 pub wealth_total_copper: u64,
2286 #[serde(default)]
2288 pub wealth_in_property_copper: u64,
2289 #[serde(default)]
2291 pub wealth_net_worth_copper: u64,
2292 #[serde(default)]
2294 pub property_assets: Vec<PropertyAssetView>,
2295 #[serde(default)]
2297 pub property_market_nearby: Vec<PropertyMarketCompView>,
2298 #[serde(default)]
2300 pub live_expense_per_interval_copper: u64,
2301 #[serde(default)]
2303 pub live_income_route_est_per_loop_copper: u64,
2304 #[serde(default)]
2306 pub live_income_avg_per_interval_copper: u64,
2307 #[serde(default)]
2309 pub live_income_avg_window_intervals: u32,
2310 #[serde(default)]
2312 pub live_net_avg_per_interval_copper: i64,
2313}
2314
2315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2317pub struct PropertyAssetView {
2318 pub plot_id: Uuid,
2319 pub label: String,
2321 pub zone_id: String,
2322 #[serde(default)]
2323 pub zone_label: Option<String>,
2324 pub area_m2: f32,
2325 pub purchase_basis_copper: u64,
2327 pub upkeep_copper_per_day: u64,
2328}
2329
2330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2332pub struct PropertyMarketCompView {
2333 pub day: u64,
2334 pub zone_id: String,
2335 #[serde(default)]
2336 pub zone_label: Option<String>,
2337 pub area_m2: f32,
2338 pub price_copper: u64,
2339 pub price_per_m2_copper: u64,
2341 pub kind: String,
2343 pub distance_m: f32,
2345}
2346
2347#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
2349#[serde(rename_all = "snake_case")]
2350pub enum AnalyticsMetric {
2351 NpcKill,
2352 WildlifeKill,
2353 Harvest,
2354 QuestComplete,
2355 QuestAccept,
2356 QuestAbandon,
2357 PlayerDeath,
2358 Craft,
2359 WorkerHire,
2360 WorkerDismiss,
2361 WorkerTeach,
2362 NpcTalk,
2363 ShopBuy,
2364 ShopSell,
2365 PlaceContainer,
2366 PickupContainer,
2367 PickupDrop,
2368 ConsumableUse,
2369 AbilityUse,
2370 DistanceWalkedM,
2371 DoorUse,
2372 BuildingEnter,
2373}
2374
2375impl AnalyticsMetric {
2376 pub fn as_str(self) -> &'static str {
2377 match self {
2378 Self::NpcKill => "npc_kill",
2379 Self::WildlifeKill => "wildlife_kill",
2380 Self::Harvest => "harvest",
2381 Self::QuestComplete => "quest_complete",
2382 Self::QuestAccept => "quest_accept",
2383 Self::QuestAbandon => "quest_abandon",
2384 Self::PlayerDeath => "player_death",
2385 Self::Craft => "craft",
2386 Self::WorkerHire => "worker_hire",
2387 Self::WorkerDismiss => "worker_dismiss",
2388 Self::WorkerTeach => "worker_teach",
2389 Self::NpcTalk => "npc_talk",
2390 Self::ShopBuy => "shop_buy",
2391 Self::ShopSell => "shop_sell",
2392 Self::PlaceContainer => "place_container",
2393 Self::PickupContainer => "pickup_container",
2394 Self::PickupDrop => "pickup_drop",
2395 Self::ConsumableUse => "consumable_use",
2396 Self::AbilityUse => "ability_use",
2397 Self::DistanceWalkedM => "distance_walked_m",
2398 Self::DoorUse => "door_use",
2399 Self::BuildingEnter => "building_enter",
2400 }
2401 }
2402
2403 pub fn from_str_key(s: &str) -> Option<Self> {
2404 Some(match s {
2405 "npc_kill" => Self::NpcKill,
2406 "wildlife_kill" => Self::WildlifeKill,
2407 "harvest" => Self::Harvest,
2408 "quest_complete" => Self::QuestComplete,
2409 "quest_accept" => Self::QuestAccept,
2410 "quest_abandon" => Self::QuestAbandon,
2411 "player_death" => Self::PlayerDeath,
2412 "craft" => Self::Craft,
2413 "worker_hire" => Self::WorkerHire,
2414 "worker_dismiss" => Self::WorkerDismiss,
2415 "worker_teach" => Self::WorkerTeach,
2416 "npc_talk" => Self::NpcTalk,
2417 "shop_buy" => Self::ShopBuy,
2418 "shop_sell" => Self::ShopSell,
2419 "place_container" => Self::PlaceContainer,
2420 "pickup_container" => Self::PickupContainer,
2421 "pickup_drop" => Self::PickupDrop,
2422 "consumable_use" => Self::ConsumableUse,
2423 "ability_use" => Self::AbilityUse,
2424 "distance_walked_m" => Self::DistanceWalkedM,
2425 "door_use" => Self::DoorUse,
2426 "building_enter" => Self::BuildingEnter,
2427 _ => return None,
2428 })
2429 }
2430}
2431
2432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2433pub struct CareerMetricRow {
2434 pub subject_id: String,
2435 pub amount: u64,
2436}
2437
2438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
2440pub struct PlayerCareerView {
2441 pub current_game_day: u64,
2442 #[serde(default)]
2443 pub kills: Vec<CareerMetricRow>,
2444 #[serde(default)]
2445 pub harvests: Vec<CareerMetricRow>,
2446 pub quests_completed: u64,
2447 #[serde(default)]
2448 pub crafts: Vec<CareerMetricRow>,
2449 pub deaths: u64,
2450 pub npc_talks: u64,
2451 pub shop_buys: u64,
2452 pub shop_sells: u64,
2453 pub distance_m: u64,
2454 #[serde(default)]
2455 pub other: Vec<CareerMetricRow>,
2456}
2457
2458#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2460pub struct HiredWorkerView {
2461 pub instance_id: String,
2462 pub entity_id: EntityId,
2463 pub def_id: String,
2464 pub label: String,
2466 pub x: f32,
2467 pub y: f32,
2468 pub z: f32,
2469 pub mode: WorkerModeView,
2470 pub state: WorkerStateView,
2471 #[serde(default)]
2472 pub step_label: String,
2473 pub vitals: WorkerVitalsSummary,
2474 #[serde(default)]
2475 pub carry_pct: f32,
2476 #[serde(default)]
2477 pub last_error: Option<String>,
2478 pub wage_copper_per_interval: u32,
2479 #[serde(default)]
2481 pub effective_wage_copper: u32,
2482 #[serde(default)]
2484 pub wage_meters_walked: f32,
2485 #[serde(default)]
2487 pub lodging_container_id: Option<String>,
2488 #[serde(default)]
2490 pub route: Option<WorkerRouteView>,
2491 #[serde(default)]
2494 pub route_stop_index: Option<u32>,
2495 #[serde(default)]
2497 pub known_blueprint_ids: Vec<String>,
2498 #[serde(default = "default_worker_view_level")]
2500 pub level: u32,
2501 #[serde(default)]
2503 pub worker_xp: f64,
2504 #[serde(default)]
2506 pub inventory: Vec<ItemStack>,
2507 #[serde(default)]
2510 pub issue_hint: Option<String>,
2511}
2512
2513fn default_worker_view_level() -> u32 {
2514 1
2515}
2516
2517#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2519pub struct TickDelta {
2520 pub tick: Tick,
2521 pub entities: Vec<EntityState>,
2522 #[serde(default)]
2523 pub resource_nodes: Vec<ResourceNodeView>,
2524 #[serde(default)]
2525 pub buildings: Vec<BuildingView>,
2526 #[serde(default)]
2527 pub doors: Vec<DoorView>,
2528 #[serde(default)]
2529 pub npcs: Vec<NpcView>,
2530 #[serde(default)]
2532 pub inventory: Vec<ItemStack>,
2533 #[serde(default)]
2534 pub blueprints: Vec<BlueprintView>,
2535 #[serde(default)]
2537 pub building_materials: Vec<BuildingMaterialView>,
2538 #[serde(default)]
2539 pub world_clock: WorldClock,
2540 #[serde(default)]
2541 pub ground_drops: Vec<GroundDropView>,
2542 #[serde(default)]
2543 pub placed_containers: Vec<PlacedContainerView>,
2544 #[serde(default)]
2545 pub combat: Option<CombatHud>,
2546 #[serde(default)]
2547 pub interior_map: Option<InteriorMapView>,
2548 #[serde(default)]
2549 pub quest_log: Vec<QuestLogEntry>,
2550 #[serde(default)]
2551 pub hired_workers: Vec<HiredWorkerView>,
2552 #[serde(default)]
2553 pub interactables: Vec<InteractableView>,
2554 #[serde(default)]
2555 pub ledger: Option<PlayerLedgerView>,
2556 #[serde(default)]
2557 pub career: Option<PlayerCareerView>,
2558 #[serde(default)]
2560 pub combat_fx: Vec<CombatFx>,
2561 #[serde(default)]
2563 pub property_plots: Vec<PropertyPlotView>,
2564 #[serde(default)]
2566 pub terrain_overlays: Vec<TerrainZoneView>,
2567}
2568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2569pub struct GroundDropView {
2570 pub id: String,
2571 pub template_id: String,
2572 pub quantity: u32,
2573 pub x: f32,
2574 pub y: f32,
2575 pub z: f32,
2576 #[serde(default)]
2578 pub tile_id: Option<String>,
2579 #[serde(default)]
2581 pub display_name: Option<String>,
2582 #[serde(default)]
2584 pub yaw: f32,
2585 #[serde(default)]
2587 pub pitch: f32,
2588 #[serde(default)]
2590 pub roll: f32,
2591 #[serde(default = "default_draw_scale")]
2593 pub draw_scale: f32,
2594}
2595
2596fn default_draw_scale() -> f32 {
2597 1.0
2598}
2599
2600#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2602pub struct Snapshot {
2603 pub tick: Tick,
2604 pub chunk_rev: u64,
2605 #[serde(default)]
2607 pub content_rev: u64,
2608 #[serde(default)]
2610 pub publish_rev: u64,
2611 pub entities: Vec<EntityState>,
2612 #[serde(default)]
2613 pub resource_nodes: Vec<ResourceNodeView>,
2614 #[serde(default)]
2616 pub world_x0: f32,
2617 #[serde(default)]
2618 pub world_y0: f32,
2619 #[serde(default)]
2621 pub world_width_m: f32,
2622 #[serde(default)]
2623 pub world_height_m: f32,
2624 #[serde(default)]
2625 pub buildings: Vec<BuildingView>,
2626 #[serde(default)]
2627 pub doors: Vec<DoorView>,
2628 #[serde(default)]
2629 pub npcs: Vec<NpcView>,
2630 #[serde(default)]
2631 pub inventory: Vec<ItemStack>,
2632 #[serde(default)]
2633 pub blueprints: Vec<BlueprintView>,
2634 #[serde(default)]
2636 pub building_materials: Vec<BuildingMaterialView>,
2637 #[serde(default)]
2638 pub world_clock: WorldClock,
2639 #[serde(default)]
2640 pub terrain_zones: Vec<TerrainZoneView>,
2641 #[serde(default)]
2642 pub z_platforms: Vec<ZPlatformView>,
2643 #[serde(default)]
2644 pub z_transitions: Vec<ZTransitionView>,
2645 #[serde(default)]
2646 pub ground_drops: Vec<GroundDropView>,
2647 #[serde(default)]
2648 pub placed_containers: Vec<PlacedContainerView>,
2649 #[serde(default)]
2650 pub combat: Option<CombatHud>,
2651 #[serde(default)]
2652 pub interior_map: Option<InteriorMapView>,
2653 #[serde(default)]
2654 pub quest_log: Vec<QuestLogEntry>,
2655 #[serde(default)]
2656 pub hired_workers: Vec<HiredWorkerView>,
2657 #[serde(default)]
2658 pub interactables: Vec<InteractableView>,
2659 #[serde(default)]
2660 pub ledger: Option<PlayerLedgerView>,
2661 #[serde(default)]
2662 pub career: Option<PlayerCareerView>,
2663 #[serde(default)]
2665 pub combat_fx: Vec<CombatFx>,
2666 #[serde(default)]
2668 pub property_zones: Vec<PropertyZoneView>,
2669 #[serde(default)]
2671 pub tax_zones: Vec<TaxZoneView>,
2672 #[serde(default)]
2674 pub boundary_zones: Vec<BoundaryZoneView>,
2675 #[serde(default)]
2677 pub encounter_zones: Vec<EncounterZoneView>,
2678 #[serde(default)]
2680 pub growth_zones: Vec<GrowthZoneView>,
2681 #[serde(default)]
2683 pub biome_zones: Vec<BiomeZoneView>,
2684 #[serde(default)]
2686 pub terrain_kind_nav: Vec<TerrainKindNavView>,
2687 #[serde(default)]
2689 pub property_plots: Vec<PropertyPlotView>,
2690 #[serde(default)]
2692 pub property_plot_settings: Option<PropertyPlotSettingsView>,
2693}
2694
2695#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2697pub struct ResourceNodeView {
2698 pub id: String,
2699 pub label: String,
2700 pub x: f32,
2701 pub y: f32,
2702 pub z: f32,
2703 pub item_template: String,
2704 #[serde(default = "default_node_state")]
2705 pub state: ResourceNodeState,
2706 #[serde(default = "default_blocking_view")]
2708 pub blocking: bool,
2709 #[serde(default = "default_blocking_radius_view")]
2711 pub blocking_radius_m: f32,
2712 #[serde(default)]
2714 pub harvest_off: bool,
2715 #[serde(default)]
2717 pub tile_id: Option<String>,
2718 #[serde(default)]
2720 pub yaw: f32,
2721 #[serde(default)]
2723 pub pitch: f32,
2724 #[serde(default)]
2726 pub roll: f32,
2727 #[serde(default = "default_draw_scale")]
2729 pub draw_scale: f32,
2730 #[serde(default)]
2732 pub sprite_mode: Option<String>,
2733 #[serde(default)]
2735 pub presentation_state: Option<String>,
2736 #[serde(default)]
2739 pub growth_progress: Option<f32>,
2740 #[serde(default)]
2742 pub channel_start_tick: Option<Tick>,
2743 #[serde(default)]
2744 pub channel_end_tick: Option<Tick>,
2745 #[serde(default)]
2747 pub harvest_drop_templates: Vec<String>,
2748}
2749
2750fn default_blocking_radius_view() -> f32 {
2751 0.8
2752}
2753
2754fn default_blocking_view() -> bool {
2755 true
2756}
2757
2758#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2759#[serde(rename_all = "snake_case")]
2760pub enum ResourceNodeState {
2761 Available,
2762 Harvesting,
2763 Cooldown,
2764}
2765fn default_node_state() -> ResourceNodeState {
2766 ResourceNodeState::Available
2767}
2768
2769#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2771#[serde(rename_all = "snake_case")]
2772pub enum ItemSpawnStateView {
2773 Spawned,
2774 PickedUp {
2775 respawn_at_tick: u64,
2776 },
2777 Consumed,
2778}
2779
2780#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2782pub struct ItemSpawnView {
2783 pub id: String,
2784 pub label: String,
2785 pub item_template: String,
2786 pub quantity: u32,
2787 pub x: f32,
2788 pub y: f32,
2789 pub z: f32,
2790 pub respawn_ticks: u32,
2791 #[serde(default)]
2792 pub building_id: Option<String>,
2793 pub state: ItemSpawnStateView,
2794}
2795
2796#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2797#[serde(rename_all = "snake_case")]
2798pub enum ItemStatusBindingMode {
2799 OnHit,
2800 WhileEquipped,
2801}
2802
2803impl Default for ItemStatusBindingMode {
2804 fn default() -> Self {
2805 Self::OnHit
2806 }
2807}
2808
2809#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2811pub struct ItemStatusBinding {
2812 pub effect_id: String,
2813 #[serde(default)]
2814 pub mode: ItemStatusBindingMode,
2815 #[serde(default)]
2817 pub source: String,
2818 #[serde(default)]
2819 pub applied_at_tick: u64,
2820 #[serde(default, skip_serializing_if = "Option::is_none")]
2822 pub expires_at_tick: Option<u64>,
2823}
2824
2825#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2826pub struct ItemStack {
2827 pub template_id: String,
2828 pub quantity: u32,
2829 #[serde(default)]
2831 pub item_instance_id: Option<Uuid>,
2832 #[serde(default)]
2834 pub props: BTreeMap<String, String>,
2835 #[serde(default)]
2837 pub status_bindings: Vec<ItemStatusBinding>,
2838 #[serde(default)]
2840 pub contents: Vec<ItemStack>,
2841 #[serde(default)]
2843 pub display_name: Option<String>,
2844 #[serde(default)]
2846 pub category: Option<String>,
2847 #[serde(default)]
2849 pub base_mass: Option<f32>,
2850 #[serde(default)]
2852 pub base_volume: Option<f32>,
2853 #[serde(default)]
2855 pub capacity_volume: Option<f32>,
2856 #[serde(default)]
2858 pub stackable: Option<bool>,
2859 #[serde(default)]
2861 pub world_placeable: Option<bool>,
2862 #[serde(default)]
2864 pub worker_lodging_capacity: Option<u32>,
2865 #[serde(default)]
2867 pub equip_slot: Option<BodySlot>,
2868 #[serde(default)]
2870 pub armor_physical: Option<f32>,
2871 #[serde(default)]
2873 pub resists: Vec<(String, f32)>,
2874 #[serde(default)]
2876 pub hand_slots: Option<u8>,
2877 #[serde(default)]
2879 pub listable: Option<bool>,
2880}
2881
2882impl ItemStack {
2883 pub fn simple(template_id: impl Into<String>, quantity: u32) -> Self {
2884 Self {
2885 template_id: template_id.into(),
2886 quantity,
2887 ..Default::default()
2888 }
2889 }
2890}
2891
2892impl Default for ItemStack {
2893 fn default() -> Self {
2894 Self {
2895 template_id: String::new(),
2896 quantity: 0,
2897 item_instance_id: None,
2898 props: BTreeMap::new(),
2899 status_bindings: Vec::new(),
2900 contents: Vec::new(),
2901 display_name: None,
2902 category: None,
2903 base_mass: None,
2904 base_volume: None,
2905 capacity_volume: None,
2906 stackable: None,
2907 world_placeable: None,
2908 worker_lodging_capacity: None,
2909 equip_slot: None,
2910 armor_physical: None,
2911 resists: Vec::new(),
2912 hand_slots: None,
2913 listable: None,
2914 }
2915 }
2916}
2917
2918#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2920#[serde(rename_all = "snake_case")]
2921pub enum EncumbranceState {
2922 #[default]
2923 Light,
2924 Heavy,
2925 Over,
2926}
2927
2928#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
2932#[serde(rename_all = "snake_case")]
2933pub enum BodySlot {
2934 Head,
2935 #[serde(alias = "body")]
2937 Chest,
2938 #[serde(alias = "arms")]
2940 Forearms,
2941 Legs,
2942 Feet,
2943 Cloak,
2944 Back,
2945 Waist,
2946 Earrings,
2947 Necklace,
2948 Eyeglasses,
2949 #[serde(rename = "ring_left_1", alias = "ring_left1")]
2951 RingLeft1,
2952 #[serde(rename = "ring_left_2", alias = "ring_left2")]
2953 RingLeft2,
2954 #[serde(rename = "ring_right_1", alias = "ring_right1")]
2955 RingRight1,
2956 #[serde(rename = "ring_right_2", alias = "ring_right2")]
2957 RingRight2,
2958}
2959
2960impl BodySlot {
2961 pub const ALL: [BodySlot; 15] = [
2963 BodySlot::Head,
2964 BodySlot::Chest,
2965 BodySlot::Forearms,
2966 BodySlot::Legs,
2967 BodySlot::Feet,
2968 BodySlot::Cloak,
2969 BodySlot::Back,
2970 BodySlot::Waist,
2971 BodySlot::Earrings,
2972 BodySlot::Necklace,
2973 BodySlot::Eyeglasses,
2974 BodySlot::RingLeft1,
2975 BodySlot::RingLeft2,
2976 BodySlot::RingRight1,
2977 BodySlot::RingRight2,
2978 ];
2979
2980 pub fn as_str(self) -> &'static str {
2981 match self {
2982 BodySlot::Head => "head",
2983 BodySlot::Chest => "chest",
2984 BodySlot::Forearms => "forearms",
2985 BodySlot::Legs => "legs",
2986 BodySlot::Feet => "feet",
2987 BodySlot::Cloak => "cloak",
2988 BodySlot::Back => "back",
2989 BodySlot::Waist => "waist",
2990 BodySlot::Earrings => "earrings",
2991 BodySlot::Necklace => "necklace",
2992 BodySlot::Eyeglasses => "eyeglasses",
2993 BodySlot::RingLeft1 => "ring_left_1",
2994 BodySlot::RingLeft2 => "ring_left_2",
2995 BodySlot::RingRight1 => "ring_right_1",
2996 BodySlot::RingRight2 => "ring_right_2",
2997 }
2998 }
2999}
3000
3001#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3003#[serde(rename_all = "snake_case")]
3004pub enum InventoryLocation {
3005 Root,
3007 Worn { slot: BodySlot },
3009 Placed { container_id: String },
3011 Keychain,
3013 WhisperPouch,
3015}
3016
3017#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3019pub struct PlacedContainerView {
3020 pub id: String,
3021 pub template_id: String,
3022 pub display_name: String,
3023 pub x: f32,
3024 pub y: f32,
3025 pub z: f32,
3026 pub locked: bool,
3027 #[serde(default)]
3029 pub accessible: bool,
3030 #[serde(default)]
3031 pub owner_character_id: Option<Uuid>,
3032 #[serde(default)]
3034 pub contents: Vec<ItemStack>,
3035 #[serde(default)]
3037 pub lock_id: Option<String>,
3038 #[serde(default)]
3040 pub capacity_volume: Option<f32>,
3041 #[serde(default)]
3043 pub item_instance_id: Option<Uuid>,
3044 #[serde(default)]
3046 pub tile_id: Option<String>,
3047 #[serde(default)]
3049 pub worker_lodging_capacity: Option<u32>,
3050 #[serde(default)]
3052 pub blocking: bool,
3053 #[serde(default)]
3055 pub blocking_radius_m: f32,
3056 #[serde(default)]
3059 pub building_id: Option<String>,
3060}
3061
3062#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3063pub struct BlueprintIngredientView {
3064 pub template_id: String,
3065 pub quantity: u32,
3066 pub consumed: bool,
3068 #[serde(default)]
3070 pub display_name: String,
3071}
3072
3073#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3074pub struct ToolRequirementView {
3075 pub item: String,
3076 pub consumed: bool,
3078 #[serde(default)]
3080 pub display_name: String,
3081}
3082
3083#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3084pub struct SkillRequirementView {
3085 pub skill: String,
3086 pub level: u32,
3087}
3088
3089#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3090pub struct BlueprintView {
3091 pub id: String,
3092 pub label: String,
3093 pub output: String,
3094 pub output_qty: u32,
3095 pub craft_ticks: u32,
3096 pub inputs: Vec<BlueprintIngredientView>,
3097 #[serde(default)]
3099 pub station: Option<String>,
3100 #[serde(default)]
3101 pub category: Option<String>,
3102 #[serde(default)]
3103 pub required_tools: Vec<ToolRequirementView>,
3104 #[serde(default)]
3105 pub skill: Option<SkillRequirementView>,
3106 #[serde(default)]
3107 pub failure_chance: f32,
3108 #[serde(default)]
3110 pub worker_train_copper: u64,
3111 #[serde(default)]
3113 pub output_display_name: String,
3114}
3115
3116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3118pub struct TerrainKindNavView {
3119 pub kind: TerrainKindView,
3120 #[serde(default = "default_move_speed_mult_one")]
3121 pub move_speed_mult: f32,
3122 #[serde(default)]
3123 pub impassable: bool,
3124}
3125
3126fn default_move_speed_mult_one() -> f32 {
3127 1.0
3128}
3129
3130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3132#[serde(rename_all = "snake_case")]
3133pub enum TerrainKindView {
3134 #[default]
3135 Grass,
3136 Dirt,
3137 Tilled,
3138 Desert,
3139 Hill,
3140 Bog,
3141 Beach,
3142 ShallowWater,
3143 DeepWater,
3144 Trail,
3145 Road,
3146 Rock,
3147}
3148
3149#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3150pub struct TerrainZoneView {
3151 pub id: String,
3152 pub x0: f32,
3153 pub y0: f32,
3154 pub x1: f32,
3155 pub y1: f32,
3156 #[serde(default)]
3157 pub kind: TerrainKindView,
3158 #[serde(default)]
3160 pub elevation: f32,
3161 #[serde(default)]
3164 pub glyph: Option<String>,
3165 #[serde(default)]
3167 pub color: Option<String>,
3168 #[serde(default)]
3170 pub tile_id: Option<String>,
3171 #[serde(default)]
3173 pub z_order: i32,
3174 #[serde(default)]
3176 pub channel_start_tick: Option<Tick>,
3177 #[serde(default)]
3178 pub channel_end_tick: Option<Tick>,
3179}
3180
3181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3183pub struct ZoneRectView {
3184 pub x0: f32,
3185 pub y0: f32,
3186 pub x1: f32,
3187 pub y1: f32,
3188}
3189
3190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3192pub struct PropertyZoneView {
3193 pub id: String,
3194 #[serde(default)]
3196 pub label: Option<String>,
3197 pub rects: Vec<ZoneRectView>,
3198 #[serde(default)]
3199 pub z_order: i32,
3200 pub crown_price_copper: u64,
3201 pub upkeep_copper_per_day: u64,
3202 #[serde(default)]
3203 pub max_area_m2: Option<f32>,
3204 #[serde(default)]
3205 pub owner_tax_discount_bps: u32,
3206}
3207
3208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3210pub struct TaxZoneView {
3211 pub id: String,
3212 #[serde(default)]
3213 pub label: Option<String>,
3214 pub rects: Vec<ZoneRectView>,
3215 #[serde(default)]
3216 pub z_order: i32,
3217 pub rate_bps: u32,
3218 #[serde(default)]
3219 pub flat_copper: u64,
3220 #[serde(default)]
3222 pub market_sales_tax_bps: u32,
3223 #[serde(default)]
3225 pub market_sales_flat_copper: u32,
3226}
3227
3228#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3230pub struct BoundaryZoneView {
3231 pub id: String,
3232 #[serde(default)]
3233 pub label: Option<String>,
3234 pub rects: Vec<ZoneRectView>,
3235 #[serde(default)]
3236 pub z_order: i32,
3237 #[serde(default, skip_serializing_if = "Option::is_none")]
3238 pub jurisdiction_id: Option<String>,
3239 #[serde(default = "default_true")]
3240 pub worker_logistics: bool,
3241 #[serde(default)]
3242 pub security_tier: String,
3243 #[serde(default)]
3244 pub pvp_mode: String,
3245 #[serde(default = "default_true")]
3246 pub crime_enabled: bool,
3247 #[serde(default)]
3248 pub guard_response: bool,
3249}
3250
3251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3253pub struct EncounterZoneView {
3254 pub id: String,
3255 #[serde(default)]
3256 pub label: Option<String>,
3257 pub rects: Vec<ZoneRectView>,
3258 #[serde(default)]
3259 pub z_order: i32,
3260}
3261
3262fn default_true() -> bool {
3263 true
3264}
3265
3266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3268pub struct GrowthZoneView {
3269 pub id: String,
3270 #[serde(default)]
3271 pub label: Option<String>,
3272 pub rects: Vec<ZoneRectView>,
3273 #[serde(default)]
3274 pub z_order: i32,
3275 #[serde(default = "default_one_f32")]
3276 pub fertility: f32,
3277}
3278
3279fn default_one_f32() -> f32 {
3280 1.0
3281}
3282
3283#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3285pub struct BiomeZoneView {
3286 pub id: String,
3287 #[serde(default)]
3288 pub label: Option<String>,
3289 pub rects: Vec<ZoneRectView>,
3290 #[serde(default)]
3291 pub z_order: i32,
3292 pub biome_id: String,
3293}
3294
3295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3297pub struct FarmGrantView {
3298 pub character_id: Uuid,
3299 #[serde(default)]
3301 pub character_label: String,
3302 pub tax_discount_bps: u32,
3303}
3304
3305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3307pub struct PropertyPlotView {
3308 pub plot_id: Uuid,
3309 pub property_zone_id: String,
3310 #[serde(default)]
3311 pub zone_label: Option<String>,
3312 pub deed_instance_id: Uuid,
3313 pub x0: f32,
3314 pub y0: f32,
3315 pub x1: f32,
3316 pub y1: f32,
3317 pub upkeep_copper_per_day: u64,
3318 pub arrears_days: u32,
3319 #[serde(default)]
3321 pub is_mine: bool,
3322 #[serde(default)]
3324 pub may_farm: bool,
3325 #[serde(default)]
3327 pub purchase_basis_copper: u64,
3328 #[serde(default)]
3329 pub farm_public: bool,
3330 #[serde(default)]
3331 pub public_tax_discount_bps: u32,
3332 #[serde(default)]
3333 pub farm_allow: Vec<FarmGrantView>,
3334 #[serde(default)]
3336 pub owner_character_id: Option<Uuid>,
3337 #[serde(default)]
3338 pub owner_label: Option<String>,
3339 #[serde(default)]
3341 pub building_id: Option<String>,
3342 #[serde(default)]
3344 pub plot_code: String,
3345 #[serde(default)]
3347 pub label: String,
3348}
3349
3350#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3352pub struct PropertyPlotSettingsView {
3353 pub min_plot_area_m2: f32,
3354 pub tax_premium_weight: f32,
3355 pub sellback_bps: u32,
3356}
3357
3358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3360pub struct ZPlatformView {
3361 pub id: String,
3362 pub z: f32,
3363 pub x0: f32,
3364 pub y0: f32,
3365 pub x1: f32,
3366 pub y1: f32,
3367}
3368
3369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3371pub struct ZTransitionView {
3372 pub id: String,
3373 pub z_from: f32,
3374 pub z_to: f32,
3375 pub x0: f32,
3376 pub y0: f32,
3377 pub x1: f32,
3378 pub y1: f32,
3379}
3380
3381#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3382pub struct BuildingView {
3383 pub id: String,
3384 pub label: String,
3385 pub x: f32,
3386 pub y: f32,
3387 pub width_m: f32,
3388 pub depth_m: f32,
3389 #[serde(default)]
3390 pub interior_blueprint: Option<String>,
3391 #[serde(default)]
3392 pub tags: Vec<String>,
3393 #[serde(default)]
3395 pub market_boundary_zone_ids: Vec<String>,
3396 #[serde(default)]
3398 pub market_max_volume: Option<f32>,
3399 #[serde(default)]
3402 pub wall_set: Option<String>,
3403 #[serde(default)]
3405 pub roof_set: Option<String>,
3406}
3407
3408pub const DEFAULT_BUILDING_ART_SET: &str = "classic_stone";
3411
3412impl BuildingView {
3413 pub fn effective_wall_set(&self) -> &str {
3414 self.wall_set
3415 .as_deref()
3416 .filter(|s| !s.is_empty())
3417 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3418 }
3419
3420 pub fn effective_roof_set(&self) -> &str {
3421 self.roof_set
3422 .as_deref()
3423 .filter(|s| !s.is_empty())
3424 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3425 }
3426}
3427
3428#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3429pub struct DoorView {
3430 pub id: String,
3431 pub building_id: String,
3432 pub x: f32,
3433 pub y: f32,
3434 #[serde(default)]
3435 pub open: bool,
3436 #[serde(default)]
3437 pub portal: Option<String>,
3438 #[serde(default)]
3440 pub locked: bool,
3441 #[serde(default = "default_door_accessible")]
3443 pub accessible: bool,
3444 #[serde(default)]
3445 pub lock_id: Option<Uuid>,
3446}
3447
3448fn default_door_accessible() -> bool {
3449 true
3450}
3451
3452#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3454pub struct InteriorRoomEdit {
3455 pub id: String,
3456 pub label: String,
3457 pub x0: f32,
3458 pub y0: f32,
3459 pub x1: f32,
3460 pub y1: f32,
3461}
3462
3463#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3464pub struct InteriorRoomDoorEdit {
3465 pub id: String,
3466 pub room_a: String,
3467 pub room_b: String,
3468 pub x: f32,
3469 pub y: f32,
3470}
3471
3472#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3473pub struct InteriorRoomView {
3474 pub id: String,
3475 pub label: String,
3476 pub floor: i32,
3477 pub x0: f32,
3478 pub y0: f32,
3479 pub x1: f32,
3480 pub y1: f32,
3481 #[serde(default)]
3482 pub floor_color: Option<String>,
3483 #[serde(default)]
3484 pub floor_glyph: Option<String>,
3485}
3486
3487#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3488pub struct InteriorDoorView {
3489 pub id: String,
3490 pub room_a: String,
3491 pub room_b: String,
3492 pub x: f32,
3493 pub y: f32,
3494 pub kind: String,
3495 #[serde(default)]
3496 pub x_a: Option<f32>,
3497 #[serde(default)]
3498 pub y_a: Option<f32>,
3499 #[serde(default)]
3500 pub x_b: Option<f32>,
3501 #[serde(default)]
3502 pub y_b: Option<f32>,
3503}
3504
3505#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3506pub struct InteriorMapView {
3507 pub building_id: String,
3508 pub blueprint_id: String,
3509 pub background_color: String,
3510 #[serde(default)]
3511 pub default_floor_color: Option<String>,
3512 #[serde(default = "default_floor_height_view")]
3513 pub floor_height_m: f32,
3514 #[serde(default)]
3516 pub z_platforms: Vec<ZPlatformView>,
3517 #[serde(default)]
3518 pub z_transitions: Vec<ZTransitionView>,
3519 pub rooms: Vec<InteriorRoomView>,
3520 #[serde(default)]
3521 pub room_doors: Vec<InteriorDoorView>,
3522}
3523
3524fn default_floor_height_view() -> f32 {
3525 3.0
3526}
3527
3528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3529pub struct NpcView {
3530 pub id: String,
3531 pub label: String,
3532 pub role: String,
3533 pub x: f32,
3534 pub y: f32,
3535 #[serde(default)]
3537 pub building_id: Option<String>,
3538 #[serde(default)]
3540 pub entity_id: Option<EntityId>,
3541 #[serde(default)]
3542 pub life_state: Option<LifeState>,
3543 #[serde(default)]
3544 pub hp_pct: Option<f32>,
3545 #[serde(default)]
3547 pub can_trade: bool,
3548 #[serde(default)]
3550 pub tile_id: Option<String>,
3551 #[serde(default)]
3553 pub behavior_state: Option<String>,
3554 #[serde(default)]
3556 pub presentation_state: Option<String>,
3557 #[serde(default)]
3559 pub sprite_mode: Option<String>,
3560 #[serde(default)]
3562 pub paperdoll_ref: Option<String>,
3563 #[serde(default = "default_draw_scale")]
3565 pub draw_scale: f32,
3566}
3567
3568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3569pub struct UseResult {
3570 pub template_id: String,
3571 pub hunger_restored: f32,
3572 pub thirst_restored: f32,
3573 #[serde(default)]
3574 pub health_restored: f32,
3575 #[serde(default)]
3576 pub mana_restored: f32,
3577 #[serde(default)]
3578 pub cleared_dot_ids: Vec<String>,
3579}
3580
3581#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3582pub struct CraftResult {
3583 pub blueprint_id: String,
3584 pub outputs: Vec<ItemStack>,
3585 pub consumed: Vec<ItemStack>,
3586 #[serde(default = "default_one")]
3588 pub batch_index: u32,
3589 #[serde(default = "default_one")]
3591 pub batch_total: u32,
3592}
3593
3594fn default_one() -> u32 {
3595 1
3596}
3597
3598#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3599pub struct DeathNotice {
3600 pub entity_id: EntityId,
3601 pub respawn_x: f32,
3602 pub respawn_y: f32,
3603 pub message: String,
3604}
3605
3606#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3607pub struct InteractionNotice {
3608 pub target_id: String,
3609 pub message: String,
3610 #[serde(default)]
3611 pub coins_delta: i32,
3612 #[serde(default)]
3613 pub inventory_delta: Vec<ItemStack>,
3614}
3615
3616#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3617#[serde(rename_all = "snake_case")]
3618pub enum NpcTalkTrustFlag {
3619 Stranger,
3620 Acquainted,
3621 Trusted,
3622}
3623
3624#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3625#[serde(rename_all = "snake_case")]
3626pub enum NpcTalkDepth {
3627 #[default]
3628 Full,
3629 Brief,
3630 Unavailable,
3631}
3632
3633#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3634pub struct NpcTalkOpened {
3635 pub npc_id: String,
3636 pub npc_label: String,
3637 pub greeting: String,
3638 pub trust_flag: NpcTalkTrustFlag,
3639 #[serde(default)]
3640 pub talk_depth: NpcTalkDepth,
3641 #[serde(default = "default_true")]
3642 pub trade_allowed: bool,
3643}
3644
3645#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3646pub struct NpcTalkPending {
3647 pub npc_id: String,
3648}
3649
3650#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3651pub struct NpcTalkReply {
3652 pub npc_id: String,
3653 pub line: String,
3654 pub trust_flag: NpcTalkTrustFlag,
3655 #[serde(default)]
3656 pub wind_down: bool,
3657 #[serde(default)]
3658 pub trade_disabled: bool,
3659}
3660
3661#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3662pub struct NpcTalkClosed {
3663 pub npc_id: String,
3664}
3665
3666#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3667pub struct NpcTalkError {
3668 pub npc_id: String,
3669 pub reason: String,
3670}
3671
3672#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3673#[serde(rename_all = "snake_case")]
3674pub enum QuestStatusView {
3675 Available,
3676 Active,
3677 Completed,
3678}
3679
3680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3681pub struct QuestObjectiveProgress {
3682 pub label: String,
3683 pub current: u32,
3684 pub required: u32,
3685 pub done: bool,
3686}
3687
3688#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3689pub struct QuestLogEntry {
3690 pub quest_id: String,
3691 pub title: String,
3692 pub description: String,
3693 pub status: QuestStatusView,
3694 #[serde(default)]
3695 pub current_step_id: Option<String>,
3696 #[serde(default)]
3697 pub current_step_title: String,
3698 #[serde(default)]
3699 pub objectives: Vec<QuestObjectiveProgress>,
3700 #[serde(default)]
3701 pub is_tracked: bool,
3702 #[serde(default)]
3703 pub can_withdraw: bool,
3704}
3705
3706#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3707pub struct InteractableView {
3708 pub id: String,
3709 pub kind: String,
3710 pub label: String,
3711 pub x: f32,
3712 pub y: f32,
3713 pub z: f32,
3714 #[serde(default)]
3715 pub board_id: Option<String>,
3716}
3717
3718#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3719pub struct QuestOffer {
3720 pub quest_id: String,
3721 pub title: String,
3722 pub description: String,
3723 #[serde(default)]
3724 pub step_count: u32,
3725}
3726
3727#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3728pub struct QuestNotice {
3729 pub quest_id: String,
3730 pub title: String,
3731 pub message: String,
3732}
3733
3734#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3735#[serde(rename_all = "snake_case")]
3736pub enum ShopOfferKind {
3737 Item,
3738 Blueprint,
3739}
3740
3741#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3742pub struct ShopOffer {
3743 pub offer_id: String,
3744 pub kind: ShopOfferKind,
3745 pub label: String,
3746 #[serde(default)]
3747 pub template_id: Option<String>,
3748 #[serde(default)]
3749 pub blueprint_id: Option<String>,
3750 pub price_copper: u32,
3751 #[serde(default)]
3752 pub affordable: bool,
3753 #[serde(default)]
3754 pub already_owned: bool,
3755}
3756
3757#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3758pub struct ShopBuyLine {
3759 pub template_id: String,
3760 pub label: String,
3761 pub quantity: u32,
3762 pub price_copper: u32,
3763}
3764
3765#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3767pub struct BankPanel {
3768 pub npc_id: String,
3769 pub npc_label: String,
3770 pub bank_balance_copper: u64,
3771 pub on_person_copper: u64,
3772 #[serde(default)]
3774 pub pending_outgoing_copper: u64,
3775 #[serde(default)]
3776 pub transfer_fee_bps: u32,
3777 #[serde(default)]
3778 pub transfer_clear_ticks: u64,
3779}
3780
3781#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3783pub struct StoragePanel {
3784 pub npc_id: String,
3785 pub npc_label: String,
3786 pub building_id: String,
3787 pub building_label: String,
3788 pub used_volume: f32,
3789 pub max_volume: f32,
3790 #[serde(default)]
3791 pub contents: Vec<ItemStack>,
3792 #[serde(default)]
3794 pub ship_destinations: Vec<StorageShipDest>,
3795}
3796
3797#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3798pub struct StorageShipDest {
3799 pub building_id: String,
3800 pub label: String,
3801 pub distance_m: f32,
3802 pub fee_copper: u64,
3803 pub travel_ticks: u64,
3804}
3805
3806#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3809pub enum GoodsLocation {
3810 Person,
3812 TownStorage { building_id: String },
3815}
3816
3817#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3820pub struct MarketListingView {
3821 pub listing_id: Uuid,
3822 pub seller_character_id: Uuid,
3823 pub seller_label: String,
3825 pub hall_building_id: String,
3826 pub hall_label: String,
3827 pub template_id: String,
3828 pub display_name: String,
3829 #[serde(default)]
3831 pub category: String,
3832 pub quantity: u32,
3833 pub unit_price_copper: u64,
3834 pub line_total_copper: u64,
3836 #[serde(default)]
3838 pub npc_price: bool,
3839 pub mine: bool,
3841}
3842
3843#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3845pub struct MarketListVault {
3846 pub building_id: String,
3847 pub building_label: String,
3849 #[serde(default)]
3850 pub contents: Vec<ItemStack>,
3851}
3852
3853#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3856pub struct MarketPanel {
3857 pub npc_id: String,
3858 pub npc_label: String,
3859 pub building_id: String,
3860 pub building_label: String,
3861 pub used_volume: f32,
3863 pub max_volume: f32,
3864 #[serde(default)]
3867 pub listings: Vec<MarketListingView>,
3868 #[serde(default)]
3870 pub tax_bps: u32,
3871 #[serde(default)]
3872 pub tax_flat_copper: u32,
3873 #[serde(default)]
3875 pub list_vaults: Vec<MarketListVault>,
3876}
3877
3878#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3879pub struct ShopCatalog {
3880 pub npc_id: String,
3881 pub npc_label: String,
3882 #[serde(default)]
3883 pub sells: Vec<ShopOffer>,
3884 #[serde(default)]
3885 pub buys: Vec<ShopBuyLine>,
3886}
3887
3888#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3889pub struct HarvestResult {
3890 pub node_id: String,
3891 pub quantity: u32,
3893 pub item_template: String,
3894 #[serde(default)]
3897 pub item_instance_id: Option<Uuid>,
3898}
3899
3900#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3902pub struct Envelope<T> {
3903 pub protocol_version: u16,
3904 pub payload: T,
3905}
3906
3907impl<T> Envelope<T> {
3908 pub fn new(payload: T) -> Self {
3909 Self {
3910 protocol_version: crate::PROTOCOL_VERSION,
3911 payload,
3912 }
3913 }
3914}
3915
3916#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3918pub struct Hello {
3919 pub client_name: String,
3920 pub protocol_version: u16,
3921 #[serde(default)]
3922 pub auth: AuthCredential,
3923 #[serde(default)]
3925 pub character_id: Option<Uuid>,
3926}
3927
3928#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3931#[serde(rename_all = "snake_case")]
3932pub enum AuthCredential {
3933 DevLocal,
3934 Session { token: String },
3935 ApiToken { token: String, character_id: Uuid },
3936}
3937
3938impl Default for AuthCredential {
3939 fn default() -> Self {
3940 Self::DevLocal
3941 }
3942}
3943
3944#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3945pub struct Welcome {
3946 pub session_id: SessionId,
3947 pub entity_id: EntityId,
3948 pub snapshot: Snapshot,
3949}
3950
3951#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3952pub enum ServerMessage {
3953 Welcome(Welcome),
3954 ContentUpdated(Snapshot),
3956 Tick(TickDelta),
3957 IntentAck {
3958 entity_id: EntityId,
3959 seq: Seq,
3960 tick: Tick,
3961 },
3962 Chat(ChatMessage),
3963 HarvestResult(HarvestResult),
3964 UseResult(UseResult),
3965 CraftResult(CraftResult),
3966 Death(DeathNotice),
3967 Interaction(InteractionNotice),
3968 ShopOpened(ShopCatalog),
3969 NpcTalkOpened(NpcTalkOpened),
3970 NpcTalkPending(NpcTalkPending),
3971 NpcTalkReply(NpcTalkReply),
3972 NpcTalkClosed(NpcTalkClosed),
3973 NpcTalkError(NpcTalkError),
3974 QuestOffer(QuestOffer),
3975 QuestAccepted(QuestNotice),
3976 QuestWithdrawn(QuestNotice),
3977 QuestStepCompleted(QuestNotice),
3978 QuestCompleted(QuestNotice),
3979 BankOpened(BankPanel),
3981 StorageOpened(StoragePanel),
3983 MarketOpened(MarketPanel),
3985 TradeOpened(TradePanel),
3987 TradeClosed {
3989 reason: String,
3990 },
3991 ConnectRejected {
3994 reason: String,
3995 },
3996}
3997
3998#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4000pub struct TradePanel {
4001 pub peer_entity_id: EntityId,
4002 pub peer_name: String,
4003 pub my_presented: Vec<ItemStack>,
4004 pub their_presented: Vec<ItemStack>,
4005 pub i_ready: bool,
4006 pub they_ready: bool,
4007 pub my_mass_after: f32,
4009 pub my_mass_max: f32,
4010 pub my_encumbrance_after: EncumbranceState,
4011 pub overburden_warning: bool,
4013}
4014
4015#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4016pub enum ClientMessage {
4017 Hello(Hello),
4018 Intent(Intent),
4019 Disconnect,
4020}
4021
4022#[cfg(test)]
4023mod tests {
4024 use super::*;
4025
4026 #[test]
4027 fn pristine_vitals_state_yields_full_pools() {
4028 let attrs = PrimaryAttributes::default();
4029 let vitals = StoredVitalsState::default().apply_to(attrs);
4030 assert!(vitals.health > 0.0);
4031 assert_eq!(vitals.health, vitals.health_max);
4032 assert!((vitals.mana_max - 61.0).abs() < 0.01);
4033 }
4034
4035 #[test]
4036 fn humanize_snake_id_title_cases_parts() {
4037 assert_eq!(humanize_snake_id("heal_touch"), "Heal Touch");
4038 assert_eq!(humanize_snake_id("fireball"), "Fireball");
4039 assert_eq!(humanize_snake_id("cone_frost"), "Cone Frost");
4040 }
4041
4042 #[test]
4043 fn saved_vitals_scale_when_pool_max_increases() {
4044 let mut attrs = PrimaryAttributes::default();
4045 attrs.intelligence = 140;
4046 attrs.wisdom = 140;
4047 let saved = StoredVitalsState {
4048 health: 100.0,
4049 mana: 14.0,
4050 stamina: 100.0,
4051 ..StoredVitalsState::default()
4052 };
4053 let vitals = saved.apply_to(attrs);
4054 assert!(vitals.mana_max > 55.0);
4055 assert!(
4056 (vitals.mana - vitals.mana_max).abs() < 0.01,
4057 "full legacy mana bar migrates to full new bar"
4058 );
4059 }
4060
4061 #[test]
4062 fn empty_vitals_state_is_pristine() {
4063 let pristine = StoredVitalsState {
4064 health: 0.0,
4065 mana: 0.0,
4066 stamina: 0.0,
4067 hunger: 0.0,
4068 thirst: 0.0,
4069 coins: 0,
4070 deaths: 0,
4071 life_state: LifeState::Alive,
4072 };
4073 assert!(pristine.is_pristine());
4074 let vitals = pristine.apply_to(PrimaryAttributes::default());
4075 assert!(vitals.health > 0.0);
4076 }
4077
4078 #[test]
4079 fn stored_vitals_roundtrip_preserves_partial_pools() {
4080 let attrs = PrimaryAttributes::default();
4081 let mut live = PlayerVitals::from_attributes(attrs);
4082 live.health = 25.0;
4083 live.hunger = 77.0;
4084 live.deaths = 2;
4085 let stored = StoredVitalsState::from_live(&live);
4086 let restored = stored.apply_to(attrs);
4087 assert!(
4088 (restored.health - 25.0).abs() < 0.01,
4089 "partial HP below cap stays absolute"
4090 );
4091 assert_eq!(restored.hunger, 77.0);
4092 assert_eq!(restored.deaths, 2);
4093 }
4094
4095 #[test]
4096 fn skill_tiers_start_at_zero() {
4097 let skill = SkillProgress::default();
4098 assert_eq!(skill.level, 0);
4099 assert_eq!(skill.display_tier(), 0);
4100 let trained = SkillProgress {
4101 level: 250,
4102 last_trained_tick: 1,
4103 };
4104 assert_eq!(trained.display_tier(), 2);
4105 }
4106
4107 #[test]
4108 fn quest_server_messages_roundtrip_json() {
4109 use crate::codec::{Codec, PostcardCodec};
4110
4111 let offer = ServerMessage::QuestOffer(QuestOffer {
4112 quest_id: "ada_goblin_hunt".into(),
4113 title: "Goblin Trouble".into(),
4114 description: "Help Ada".into(),
4115 step_count: 3,
4116 });
4117 let notice = ServerMessage::QuestAccepted(QuestNotice {
4118 quest_id: "ada_goblin_hunt".into(),
4119 title: "Goblin Trouble".into(),
4120 message: "Quest accepted".into(),
4121 });
4122 for msg in [offer, notice] {
4123 let bytes = PostcardCodec.encode(&msg).unwrap();
4124 let decoded: ServerMessage = PostcardCodec.decode(&bytes).unwrap();
4125 assert_eq!(decoded, msg);
4126 }
4127 }
4128
4129 #[test]
4130 fn hotbar_consumable_binding_roundtrips() {
4131 let binding = hotbar_consumable_binding("bottle_of_water");
4132 assert_eq!(binding, "item:bottle_of_water");
4133 assert!(hotbar_binding_is_consumable(&binding));
4134 assert_eq!(
4135 hotbar_consumable_template(&binding),
4136 Some("bottle_of_water")
4137 );
4138 assert!(!hotbar_binding_is_consumable("fireball"));
4139 assert_eq!(hotbar_consumable_template("fireball"), None);
4140 }
4141}