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}
2508
2509fn default_worker_view_level() -> u32 {
2510 1
2511}
2512
2513#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2515pub struct TickDelta {
2516 pub tick: Tick,
2517 pub entities: Vec<EntityState>,
2518 #[serde(default)]
2519 pub resource_nodes: Vec<ResourceNodeView>,
2520 #[serde(default)]
2521 pub buildings: Vec<BuildingView>,
2522 #[serde(default)]
2523 pub doors: Vec<DoorView>,
2524 #[serde(default)]
2525 pub npcs: Vec<NpcView>,
2526 #[serde(default)]
2528 pub inventory: Vec<ItemStack>,
2529 #[serde(default)]
2530 pub blueprints: Vec<BlueprintView>,
2531 #[serde(default)]
2533 pub building_materials: Vec<BuildingMaterialView>,
2534 #[serde(default)]
2535 pub world_clock: WorldClock,
2536 #[serde(default)]
2537 pub ground_drops: Vec<GroundDropView>,
2538 #[serde(default)]
2539 pub placed_containers: Vec<PlacedContainerView>,
2540 #[serde(default)]
2541 pub combat: Option<CombatHud>,
2542 #[serde(default)]
2543 pub interior_map: Option<InteriorMapView>,
2544 #[serde(default)]
2545 pub quest_log: Vec<QuestLogEntry>,
2546 #[serde(default)]
2547 pub hired_workers: Vec<HiredWorkerView>,
2548 #[serde(default)]
2549 pub interactables: Vec<InteractableView>,
2550 #[serde(default)]
2551 pub ledger: Option<PlayerLedgerView>,
2552 #[serde(default)]
2553 pub career: Option<PlayerCareerView>,
2554 #[serde(default)]
2556 pub combat_fx: Vec<CombatFx>,
2557 #[serde(default)]
2559 pub property_plots: Vec<PropertyPlotView>,
2560 #[serde(default)]
2562 pub terrain_overlays: Vec<TerrainZoneView>,
2563}
2564#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2565pub struct GroundDropView {
2566 pub id: String,
2567 pub template_id: String,
2568 pub quantity: u32,
2569 pub x: f32,
2570 pub y: f32,
2571 pub z: f32,
2572 #[serde(default)]
2574 pub tile_id: Option<String>,
2575 #[serde(default)]
2577 pub display_name: Option<String>,
2578 #[serde(default)]
2580 pub yaw: f32,
2581 #[serde(default)]
2583 pub pitch: f32,
2584 #[serde(default)]
2586 pub roll: f32,
2587 #[serde(default = "default_draw_scale")]
2589 pub draw_scale: f32,
2590}
2591
2592fn default_draw_scale() -> f32 {
2593 1.0
2594}
2595
2596#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2598pub struct Snapshot {
2599 pub tick: Tick,
2600 pub chunk_rev: u64,
2601 #[serde(default)]
2603 pub content_rev: u64,
2604 #[serde(default)]
2606 pub publish_rev: u64,
2607 pub entities: Vec<EntityState>,
2608 #[serde(default)]
2609 pub resource_nodes: Vec<ResourceNodeView>,
2610 #[serde(default)]
2612 pub world_x0: f32,
2613 #[serde(default)]
2614 pub world_y0: f32,
2615 #[serde(default)]
2617 pub world_width_m: f32,
2618 #[serde(default)]
2619 pub world_height_m: f32,
2620 #[serde(default)]
2621 pub buildings: Vec<BuildingView>,
2622 #[serde(default)]
2623 pub doors: Vec<DoorView>,
2624 #[serde(default)]
2625 pub npcs: Vec<NpcView>,
2626 #[serde(default)]
2627 pub inventory: Vec<ItemStack>,
2628 #[serde(default)]
2629 pub blueprints: Vec<BlueprintView>,
2630 #[serde(default)]
2632 pub building_materials: Vec<BuildingMaterialView>,
2633 #[serde(default)]
2634 pub world_clock: WorldClock,
2635 #[serde(default)]
2636 pub terrain_zones: Vec<TerrainZoneView>,
2637 #[serde(default)]
2638 pub z_platforms: Vec<ZPlatformView>,
2639 #[serde(default)]
2640 pub z_transitions: Vec<ZTransitionView>,
2641 #[serde(default)]
2642 pub ground_drops: Vec<GroundDropView>,
2643 #[serde(default)]
2644 pub placed_containers: Vec<PlacedContainerView>,
2645 #[serde(default)]
2646 pub combat: Option<CombatHud>,
2647 #[serde(default)]
2648 pub interior_map: Option<InteriorMapView>,
2649 #[serde(default)]
2650 pub quest_log: Vec<QuestLogEntry>,
2651 #[serde(default)]
2652 pub hired_workers: Vec<HiredWorkerView>,
2653 #[serde(default)]
2654 pub interactables: Vec<InteractableView>,
2655 #[serde(default)]
2656 pub ledger: Option<PlayerLedgerView>,
2657 #[serde(default)]
2658 pub career: Option<PlayerCareerView>,
2659 #[serde(default)]
2661 pub combat_fx: Vec<CombatFx>,
2662 #[serde(default)]
2664 pub property_zones: Vec<PropertyZoneView>,
2665 #[serde(default)]
2667 pub tax_zones: Vec<TaxZoneView>,
2668 #[serde(default)]
2670 pub boundary_zones: Vec<BoundaryZoneView>,
2671 #[serde(default)]
2673 pub encounter_zones: Vec<EncounterZoneView>,
2674 #[serde(default)]
2676 pub growth_zones: Vec<GrowthZoneView>,
2677 #[serde(default)]
2679 pub biome_zones: Vec<BiomeZoneView>,
2680 #[serde(default)]
2682 pub terrain_kind_nav: Vec<TerrainKindNavView>,
2683 #[serde(default)]
2685 pub property_plots: Vec<PropertyPlotView>,
2686 #[serde(default)]
2688 pub property_plot_settings: Option<PropertyPlotSettingsView>,
2689}
2690
2691#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2693pub struct ResourceNodeView {
2694 pub id: String,
2695 pub label: String,
2696 pub x: f32,
2697 pub y: f32,
2698 pub z: f32,
2699 pub item_template: String,
2700 #[serde(default = "default_node_state")]
2701 pub state: ResourceNodeState,
2702 #[serde(default = "default_blocking_view")]
2704 pub blocking: bool,
2705 #[serde(default = "default_blocking_radius_view")]
2707 pub blocking_radius_m: f32,
2708 #[serde(default)]
2710 pub harvest_off: bool,
2711 #[serde(default)]
2713 pub tile_id: Option<String>,
2714 #[serde(default)]
2716 pub yaw: f32,
2717 #[serde(default)]
2719 pub pitch: f32,
2720 #[serde(default)]
2722 pub roll: f32,
2723 #[serde(default = "default_draw_scale")]
2725 pub draw_scale: f32,
2726 #[serde(default)]
2728 pub sprite_mode: Option<String>,
2729 #[serde(default)]
2731 pub presentation_state: Option<String>,
2732 #[serde(default)]
2735 pub growth_progress: Option<f32>,
2736 #[serde(default)]
2738 pub channel_start_tick: Option<Tick>,
2739 #[serde(default)]
2740 pub channel_end_tick: Option<Tick>,
2741 #[serde(default)]
2743 pub harvest_drop_templates: Vec<String>,
2744}
2745
2746fn default_blocking_radius_view() -> f32 {
2747 0.8
2748}
2749
2750fn default_blocking_view() -> bool {
2751 true
2752}
2753
2754#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2755#[serde(rename_all = "snake_case")]
2756pub enum ResourceNodeState {
2757 Available,
2758 Harvesting,
2759 Cooldown,
2760}
2761fn default_node_state() -> ResourceNodeState {
2762 ResourceNodeState::Available
2763}
2764
2765#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2767#[serde(rename_all = "snake_case")]
2768pub enum ItemSpawnStateView {
2769 Spawned,
2770 PickedUp {
2771 respawn_at_tick: u64,
2772 },
2773 Consumed,
2774}
2775
2776#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2778pub struct ItemSpawnView {
2779 pub id: String,
2780 pub label: String,
2781 pub item_template: String,
2782 pub quantity: u32,
2783 pub x: f32,
2784 pub y: f32,
2785 pub z: f32,
2786 pub respawn_ticks: u32,
2787 #[serde(default)]
2788 pub building_id: Option<String>,
2789 pub state: ItemSpawnStateView,
2790}
2791
2792#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2793#[serde(rename_all = "snake_case")]
2794pub enum ItemStatusBindingMode {
2795 OnHit,
2796 WhileEquipped,
2797}
2798
2799impl Default for ItemStatusBindingMode {
2800 fn default() -> Self {
2801 Self::OnHit
2802 }
2803}
2804
2805#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2807pub struct ItemStatusBinding {
2808 pub effect_id: String,
2809 #[serde(default)]
2810 pub mode: ItemStatusBindingMode,
2811 #[serde(default)]
2813 pub source: String,
2814 #[serde(default)]
2815 pub applied_at_tick: u64,
2816 #[serde(default, skip_serializing_if = "Option::is_none")]
2818 pub expires_at_tick: Option<u64>,
2819}
2820
2821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2822pub struct ItemStack {
2823 pub template_id: String,
2824 pub quantity: u32,
2825 #[serde(default)]
2827 pub item_instance_id: Option<Uuid>,
2828 #[serde(default)]
2830 pub props: BTreeMap<String, String>,
2831 #[serde(default)]
2833 pub status_bindings: Vec<ItemStatusBinding>,
2834 #[serde(default)]
2836 pub contents: Vec<ItemStack>,
2837 #[serde(default)]
2839 pub display_name: Option<String>,
2840 #[serde(default)]
2842 pub category: Option<String>,
2843 #[serde(default)]
2845 pub base_mass: Option<f32>,
2846 #[serde(default)]
2848 pub base_volume: Option<f32>,
2849 #[serde(default)]
2851 pub capacity_volume: Option<f32>,
2852 #[serde(default)]
2854 pub stackable: Option<bool>,
2855 #[serde(default)]
2857 pub world_placeable: Option<bool>,
2858 #[serde(default)]
2860 pub worker_lodging_capacity: Option<u32>,
2861 #[serde(default)]
2863 pub equip_slot: Option<BodySlot>,
2864 #[serde(default)]
2866 pub armor_physical: Option<f32>,
2867 #[serde(default)]
2869 pub resists: Vec<(String, f32)>,
2870 #[serde(default)]
2872 pub hand_slots: Option<u8>,
2873 #[serde(default)]
2875 pub listable: Option<bool>,
2876}
2877
2878impl ItemStack {
2879 pub fn simple(template_id: impl Into<String>, quantity: u32) -> Self {
2880 Self {
2881 template_id: template_id.into(),
2882 quantity,
2883 ..Default::default()
2884 }
2885 }
2886}
2887
2888impl Default for ItemStack {
2889 fn default() -> Self {
2890 Self {
2891 template_id: String::new(),
2892 quantity: 0,
2893 item_instance_id: None,
2894 props: BTreeMap::new(),
2895 status_bindings: Vec::new(),
2896 contents: Vec::new(),
2897 display_name: None,
2898 category: None,
2899 base_mass: None,
2900 base_volume: None,
2901 capacity_volume: None,
2902 stackable: None,
2903 world_placeable: None,
2904 worker_lodging_capacity: None,
2905 equip_slot: None,
2906 armor_physical: None,
2907 resists: Vec::new(),
2908 hand_slots: None,
2909 listable: None,
2910 }
2911 }
2912}
2913
2914#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2916#[serde(rename_all = "snake_case")]
2917pub enum EncumbranceState {
2918 #[default]
2919 Light,
2920 Heavy,
2921 Over,
2922}
2923
2924#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
2928#[serde(rename_all = "snake_case")]
2929pub enum BodySlot {
2930 Head,
2931 #[serde(alias = "body")]
2933 Chest,
2934 #[serde(alias = "arms")]
2936 Forearms,
2937 Legs,
2938 Feet,
2939 Cloak,
2940 Back,
2941 Waist,
2942 Earrings,
2943 Necklace,
2944 Eyeglasses,
2945 #[serde(rename = "ring_left_1", alias = "ring_left1")]
2947 RingLeft1,
2948 #[serde(rename = "ring_left_2", alias = "ring_left2")]
2949 RingLeft2,
2950 #[serde(rename = "ring_right_1", alias = "ring_right1")]
2951 RingRight1,
2952 #[serde(rename = "ring_right_2", alias = "ring_right2")]
2953 RingRight2,
2954}
2955
2956impl BodySlot {
2957 pub const ALL: [BodySlot; 15] = [
2959 BodySlot::Head,
2960 BodySlot::Chest,
2961 BodySlot::Forearms,
2962 BodySlot::Legs,
2963 BodySlot::Feet,
2964 BodySlot::Cloak,
2965 BodySlot::Back,
2966 BodySlot::Waist,
2967 BodySlot::Earrings,
2968 BodySlot::Necklace,
2969 BodySlot::Eyeglasses,
2970 BodySlot::RingLeft1,
2971 BodySlot::RingLeft2,
2972 BodySlot::RingRight1,
2973 BodySlot::RingRight2,
2974 ];
2975
2976 pub fn as_str(self) -> &'static str {
2977 match self {
2978 BodySlot::Head => "head",
2979 BodySlot::Chest => "chest",
2980 BodySlot::Forearms => "forearms",
2981 BodySlot::Legs => "legs",
2982 BodySlot::Feet => "feet",
2983 BodySlot::Cloak => "cloak",
2984 BodySlot::Back => "back",
2985 BodySlot::Waist => "waist",
2986 BodySlot::Earrings => "earrings",
2987 BodySlot::Necklace => "necklace",
2988 BodySlot::Eyeglasses => "eyeglasses",
2989 BodySlot::RingLeft1 => "ring_left_1",
2990 BodySlot::RingLeft2 => "ring_left_2",
2991 BodySlot::RingRight1 => "ring_right_1",
2992 BodySlot::RingRight2 => "ring_right_2",
2993 }
2994 }
2995}
2996
2997#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2999#[serde(rename_all = "snake_case")]
3000pub enum InventoryLocation {
3001 Root,
3003 Worn { slot: BodySlot },
3005 Placed { container_id: String },
3007 Keychain,
3009 WhisperPouch,
3011}
3012
3013#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3015pub struct PlacedContainerView {
3016 pub id: String,
3017 pub template_id: String,
3018 pub display_name: String,
3019 pub x: f32,
3020 pub y: f32,
3021 pub z: f32,
3022 pub locked: bool,
3023 #[serde(default)]
3025 pub accessible: bool,
3026 #[serde(default)]
3027 pub owner_character_id: Option<Uuid>,
3028 #[serde(default)]
3030 pub contents: Vec<ItemStack>,
3031 #[serde(default)]
3033 pub lock_id: Option<String>,
3034 #[serde(default)]
3036 pub capacity_volume: Option<f32>,
3037 #[serde(default)]
3039 pub item_instance_id: Option<Uuid>,
3040 #[serde(default)]
3042 pub tile_id: Option<String>,
3043 #[serde(default)]
3045 pub worker_lodging_capacity: Option<u32>,
3046 #[serde(default)]
3048 pub blocking: bool,
3049 #[serde(default)]
3051 pub blocking_radius_m: f32,
3052 #[serde(default)]
3055 pub building_id: Option<String>,
3056}
3057
3058#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3059pub struct BlueprintIngredientView {
3060 pub template_id: String,
3061 pub quantity: u32,
3062 pub consumed: bool,
3064 #[serde(default)]
3066 pub display_name: String,
3067}
3068
3069#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3070pub struct ToolRequirementView {
3071 pub item: String,
3072 pub consumed: bool,
3074 #[serde(default)]
3076 pub display_name: String,
3077}
3078
3079#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3080pub struct SkillRequirementView {
3081 pub skill: String,
3082 pub level: u32,
3083}
3084
3085#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3086pub struct BlueprintView {
3087 pub id: String,
3088 pub label: String,
3089 pub output: String,
3090 pub output_qty: u32,
3091 pub craft_ticks: u32,
3092 pub inputs: Vec<BlueprintIngredientView>,
3093 #[serde(default)]
3095 pub station: Option<String>,
3096 #[serde(default)]
3097 pub category: Option<String>,
3098 #[serde(default)]
3099 pub required_tools: Vec<ToolRequirementView>,
3100 #[serde(default)]
3101 pub skill: Option<SkillRequirementView>,
3102 #[serde(default)]
3103 pub failure_chance: f32,
3104 #[serde(default)]
3106 pub worker_train_copper: u64,
3107 #[serde(default)]
3109 pub output_display_name: String,
3110}
3111
3112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3114pub struct TerrainKindNavView {
3115 pub kind: TerrainKindView,
3116 #[serde(default = "default_move_speed_mult_one")]
3117 pub move_speed_mult: f32,
3118 #[serde(default)]
3119 pub impassable: bool,
3120}
3121
3122fn default_move_speed_mult_one() -> f32 {
3123 1.0
3124}
3125
3126#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
3128#[serde(rename_all = "snake_case")]
3129pub enum TerrainKindView {
3130 #[default]
3131 Grass,
3132 Dirt,
3133 Tilled,
3134 Desert,
3135 Hill,
3136 Bog,
3137 Beach,
3138 ShallowWater,
3139 DeepWater,
3140 Trail,
3141 Road,
3142 Rock,
3143}
3144
3145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3146pub struct TerrainZoneView {
3147 pub id: String,
3148 pub x0: f32,
3149 pub y0: f32,
3150 pub x1: f32,
3151 pub y1: f32,
3152 #[serde(default)]
3153 pub kind: TerrainKindView,
3154 #[serde(default)]
3156 pub elevation: f32,
3157 #[serde(default)]
3160 pub glyph: Option<String>,
3161 #[serde(default)]
3163 pub color: Option<String>,
3164 #[serde(default)]
3166 pub tile_id: Option<String>,
3167 #[serde(default)]
3169 pub z_order: i32,
3170 #[serde(default)]
3172 pub channel_start_tick: Option<Tick>,
3173 #[serde(default)]
3174 pub channel_end_tick: Option<Tick>,
3175}
3176
3177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3179pub struct ZoneRectView {
3180 pub x0: f32,
3181 pub y0: f32,
3182 pub x1: f32,
3183 pub y1: f32,
3184}
3185
3186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3188pub struct PropertyZoneView {
3189 pub id: String,
3190 #[serde(default)]
3192 pub label: Option<String>,
3193 pub rects: Vec<ZoneRectView>,
3194 #[serde(default)]
3195 pub z_order: i32,
3196 pub crown_price_copper: u64,
3197 pub upkeep_copper_per_day: u64,
3198 #[serde(default)]
3199 pub max_area_m2: Option<f32>,
3200 #[serde(default)]
3201 pub owner_tax_discount_bps: u32,
3202}
3203
3204#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3206pub struct TaxZoneView {
3207 pub id: String,
3208 #[serde(default)]
3209 pub label: Option<String>,
3210 pub rects: Vec<ZoneRectView>,
3211 #[serde(default)]
3212 pub z_order: i32,
3213 pub rate_bps: u32,
3214 #[serde(default)]
3215 pub flat_copper: u64,
3216 #[serde(default)]
3218 pub market_sales_tax_bps: u32,
3219 #[serde(default)]
3221 pub market_sales_flat_copper: u32,
3222}
3223
3224#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3226pub struct BoundaryZoneView {
3227 pub id: String,
3228 #[serde(default)]
3229 pub label: Option<String>,
3230 pub rects: Vec<ZoneRectView>,
3231 #[serde(default)]
3232 pub z_order: i32,
3233 #[serde(default, skip_serializing_if = "Option::is_none")]
3234 pub jurisdiction_id: Option<String>,
3235 #[serde(default = "default_true")]
3236 pub worker_logistics: bool,
3237 #[serde(default)]
3238 pub security_tier: String,
3239 #[serde(default)]
3240 pub pvp_mode: String,
3241 #[serde(default = "default_true")]
3242 pub crime_enabled: bool,
3243 #[serde(default)]
3244 pub guard_response: bool,
3245}
3246
3247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3249pub struct EncounterZoneView {
3250 pub id: String,
3251 #[serde(default)]
3252 pub label: Option<String>,
3253 pub rects: Vec<ZoneRectView>,
3254 #[serde(default)]
3255 pub z_order: i32,
3256}
3257
3258fn default_true() -> bool {
3259 true
3260}
3261
3262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3264pub struct GrowthZoneView {
3265 pub id: String,
3266 #[serde(default)]
3267 pub label: Option<String>,
3268 pub rects: Vec<ZoneRectView>,
3269 #[serde(default)]
3270 pub z_order: i32,
3271 #[serde(default = "default_one_f32")]
3272 pub fertility: f32,
3273}
3274
3275fn default_one_f32() -> f32 {
3276 1.0
3277}
3278
3279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3281pub struct BiomeZoneView {
3282 pub id: String,
3283 #[serde(default)]
3284 pub label: Option<String>,
3285 pub rects: Vec<ZoneRectView>,
3286 #[serde(default)]
3287 pub z_order: i32,
3288 pub biome_id: String,
3289}
3290
3291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3293pub struct FarmGrantView {
3294 pub character_id: Uuid,
3295 #[serde(default)]
3297 pub character_label: String,
3298 pub tax_discount_bps: u32,
3299}
3300
3301#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3303pub struct PropertyPlotView {
3304 pub plot_id: Uuid,
3305 pub property_zone_id: String,
3306 #[serde(default)]
3307 pub zone_label: Option<String>,
3308 pub deed_instance_id: Uuid,
3309 pub x0: f32,
3310 pub y0: f32,
3311 pub x1: f32,
3312 pub y1: f32,
3313 pub upkeep_copper_per_day: u64,
3314 pub arrears_days: u32,
3315 #[serde(default)]
3317 pub is_mine: bool,
3318 #[serde(default)]
3320 pub may_farm: bool,
3321 #[serde(default)]
3323 pub purchase_basis_copper: u64,
3324 #[serde(default)]
3325 pub farm_public: bool,
3326 #[serde(default)]
3327 pub public_tax_discount_bps: u32,
3328 #[serde(default)]
3329 pub farm_allow: Vec<FarmGrantView>,
3330 #[serde(default)]
3332 pub owner_character_id: Option<Uuid>,
3333 #[serde(default)]
3334 pub owner_label: Option<String>,
3335 #[serde(default)]
3337 pub building_id: Option<String>,
3338 #[serde(default)]
3340 pub plot_code: String,
3341 #[serde(default)]
3343 pub label: String,
3344}
3345
3346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3348pub struct PropertyPlotSettingsView {
3349 pub min_plot_area_m2: f32,
3350 pub tax_premium_weight: f32,
3351 pub sellback_bps: u32,
3352}
3353
3354#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3356pub struct ZPlatformView {
3357 pub id: String,
3358 pub z: f32,
3359 pub x0: f32,
3360 pub y0: f32,
3361 pub x1: f32,
3362 pub y1: f32,
3363}
3364
3365#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3367pub struct ZTransitionView {
3368 pub id: String,
3369 pub z_from: f32,
3370 pub z_to: f32,
3371 pub x0: f32,
3372 pub y0: f32,
3373 pub x1: f32,
3374 pub y1: f32,
3375}
3376
3377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3378pub struct BuildingView {
3379 pub id: String,
3380 pub label: String,
3381 pub x: f32,
3382 pub y: f32,
3383 pub width_m: f32,
3384 pub depth_m: f32,
3385 #[serde(default)]
3386 pub interior_blueprint: Option<String>,
3387 #[serde(default)]
3388 pub tags: Vec<String>,
3389 #[serde(default)]
3391 pub market_boundary_zone_ids: Vec<String>,
3392 #[serde(default)]
3394 pub market_max_volume: Option<f32>,
3395 #[serde(default)]
3398 pub wall_set: Option<String>,
3399 #[serde(default)]
3401 pub roof_set: Option<String>,
3402}
3403
3404pub const DEFAULT_BUILDING_ART_SET: &str = "classic_stone";
3407
3408impl BuildingView {
3409 pub fn effective_wall_set(&self) -> &str {
3410 self.wall_set
3411 .as_deref()
3412 .filter(|s| !s.is_empty())
3413 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3414 }
3415
3416 pub fn effective_roof_set(&self) -> &str {
3417 self.roof_set
3418 .as_deref()
3419 .filter(|s| !s.is_empty())
3420 .unwrap_or(DEFAULT_BUILDING_ART_SET)
3421 }
3422}
3423
3424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3425pub struct DoorView {
3426 pub id: String,
3427 pub building_id: String,
3428 pub x: f32,
3429 pub y: f32,
3430 #[serde(default)]
3431 pub open: bool,
3432 #[serde(default)]
3433 pub portal: Option<String>,
3434 #[serde(default)]
3436 pub locked: bool,
3437 #[serde(default = "default_door_accessible")]
3439 pub accessible: bool,
3440 #[serde(default)]
3441 pub lock_id: Option<Uuid>,
3442}
3443
3444fn default_door_accessible() -> bool {
3445 true
3446}
3447
3448#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3450pub struct InteriorRoomEdit {
3451 pub id: String,
3452 pub label: String,
3453 pub x0: f32,
3454 pub y0: f32,
3455 pub x1: f32,
3456 pub y1: f32,
3457}
3458
3459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3460pub struct InteriorRoomDoorEdit {
3461 pub id: String,
3462 pub room_a: String,
3463 pub room_b: String,
3464 pub x: f32,
3465 pub y: f32,
3466}
3467
3468#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3469pub struct InteriorRoomView {
3470 pub id: String,
3471 pub label: String,
3472 pub floor: i32,
3473 pub x0: f32,
3474 pub y0: f32,
3475 pub x1: f32,
3476 pub y1: f32,
3477 #[serde(default)]
3478 pub floor_color: Option<String>,
3479 #[serde(default)]
3480 pub floor_glyph: Option<String>,
3481}
3482
3483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3484pub struct InteriorDoorView {
3485 pub id: String,
3486 pub room_a: String,
3487 pub room_b: String,
3488 pub x: f32,
3489 pub y: f32,
3490 pub kind: String,
3491 #[serde(default)]
3492 pub x_a: Option<f32>,
3493 #[serde(default)]
3494 pub y_a: Option<f32>,
3495 #[serde(default)]
3496 pub x_b: Option<f32>,
3497 #[serde(default)]
3498 pub y_b: Option<f32>,
3499}
3500
3501#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3502pub struct InteriorMapView {
3503 pub building_id: String,
3504 pub blueprint_id: String,
3505 pub background_color: String,
3506 #[serde(default)]
3507 pub default_floor_color: Option<String>,
3508 #[serde(default = "default_floor_height_view")]
3509 pub floor_height_m: f32,
3510 #[serde(default)]
3512 pub z_platforms: Vec<ZPlatformView>,
3513 #[serde(default)]
3514 pub z_transitions: Vec<ZTransitionView>,
3515 pub rooms: Vec<InteriorRoomView>,
3516 #[serde(default)]
3517 pub room_doors: Vec<InteriorDoorView>,
3518}
3519
3520fn default_floor_height_view() -> f32 {
3521 3.0
3522}
3523
3524#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3525pub struct NpcView {
3526 pub id: String,
3527 pub label: String,
3528 pub role: String,
3529 pub x: f32,
3530 pub y: f32,
3531 #[serde(default)]
3533 pub building_id: Option<String>,
3534 #[serde(default)]
3536 pub entity_id: Option<EntityId>,
3537 #[serde(default)]
3538 pub life_state: Option<LifeState>,
3539 #[serde(default)]
3540 pub hp_pct: Option<f32>,
3541 #[serde(default)]
3543 pub can_trade: bool,
3544 #[serde(default)]
3546 pub tile_id: Option<String>,
3547 #[serde(default)]
3549 pub behavior_state: Option<String>,
3550 #[serde(default)]
3552 pub presentation_state: Option<String>,
3553 #[serde(default)]
3555 pub sprite_mode: Option<String>,
3556 #[serde(default)]
3558 pub paperdoll_ref: Option<String>,
3559 #[serde(default = "default_draw_scale")]
3561 pub draw_scale: f32,
3562}
3563
3564#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3565pub struct UseResult {
3566 pub template_id: String,
3567 pub hunger_restored: f32,
3568 pub thirst_restored: f32,
3569 #[serde(default)]
3570 pub health_restored: f32,
3571 #[serde(default)]
3572 pub mana_restored: f32,
3573 #[serde(default)]
3574 pub cleared_dot_ids: Vec<String>,
3575}
3576
3577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3578pub struct CraftResult {
3579 pub blueprint_id: String,
3580 pub outputs: Vec<ItemStack>,
3581 pub consumed: Vec<ItemStack>,
3582 #[serde(default = "default_one")]
3584 pub batch_index: u32,
3585 #[serde(default = "default_one")]
3587 pub batch_total: u32,
3588}
3589
3590fn default_one() -> u32 {
3591 1
3592}
3593
3594#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3595pub struct DeathNotice {
3596 pub entity_id: EntityId,
3597 pub respawn_x: f32,
3598 pub respawn_y: f32,
3599 pub message: String,
3600}
3601
3602#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3603pub struct InteractionNotice {
3604 pub target_id: String,
3605 pub message: String,
3606 #[serde(default)]
3607 pub coins_delta: i32,
3608 #[serde(default)]
3609 pub inventory_delta: Vec<ItemStack>,
3610}
3611
3612#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3613#[serde(rename_all = "snake_case")]
3614pub enum NpcTalkTrustFlag {
3615 Stranger,
3616 Acquainted,
3617 Trusted,
3618}
3619
3620#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3621#[serde(rename_all = "snake_case")]
3622pub enum NpcTalkDepth {
3623 #[default]
3624 Full,
3625 Brief,
3626 Unavailable,
3627}
3628
3629#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3630pub struct NpcTalkOpened {
3631 pub npc_id: String,
3632 pub npc_label: String,
3633 pub greeting: String,
3634 pub trust_flag: NpcTalkTrustFlag,
3635 #[serde(default)]
3636 pub talk_depth: NpcTalkDepth,
3637 #[serde(default = "default_true")]
3638 pub trade_allowed: bool,
3639}
3640
3641#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3642pub struct NpcTalkPending {
3643 pub npc_id: String,
3644}
3645
3646#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3647pub struct NpcTalkReply {
3648 pub npc_id: String,
3649 pub line: String,
3650 pub trust_flag: NpcTalkTrustFlag,
3651 #[serde(default)]
3652 pub wind_down: bool,
3653 #[serde(default)]
3654 pub trade_disabled: bool,
3655}
3656
3657#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3658pub struct NpcTalkClosed {
3659 pub npc_id: String,
3660}
3661
3662#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3663pub struct NpcTalkError {
3664 pub npc_id: String,
3665 pub reason: String,
3666}
3667
3668#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3669#[serde(rename_all = "snake_case")]
3670pub enum QuestStatusView {
3671 Available,
3672 Active,
3673 Completed,
3674}
3675
3676#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3677pub struct QuestObjectiveProgress {
3678 pub label: String,
3679 pub current: u32,
3680 pub required: u32,
3681 pub done: bool,
3682}
3683
3684#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3685pub struct QuestLogEntry {
3686 pub quest_id: String,
3687 pub title: String,
3688 pub description: String,
3689 pub status: QuestStatusView,
3690 #[serde(default)]
3691 pub current_step_id: Option<String>,
3692 #[serde(default)]
3693 pub current_step_title: String,
3694 #[serde(default)]
3695 pub objectives: Vec<QuestObjectiveProgress>,
3696 #[serde(default)]
3697 pub is_tracked: bool,
3698 #[serde(default)]
3699 pub can_withdraw: bool,
3700}
3701
3702#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3703pub struct InteractableView {
3704 pub id: String,
3705 pub kind: String,
3706 pub label: String,
3707 pub x: f32,
3708 pub y: f32,
3709 pub z: f32,
3710 #[serde(default)]
3711 pub board_id: Option<String>,
3712}
3713
3714#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3715pub struct QuestOffer {
3716 pub quest_id: String,
3717 pub title: String,
3718 pub description: String,
3719 #[serde(default)]
3720 pub step_count: u32,
3721}
3722
3723#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3724pub struct QuestNotice {
3725 pub quest_id: String,
3726 pub title: String,
3727 pub message: String,
3728}
3729
3730#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3731#[serde(rename_all = "snake_case")]
3732pub enum ShopOfferKind {
3733 Item,
3734 Blueprint,
3735}
3736
3737#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3738pub struct ShopOffer {
3739 pub offer_id: String,
3740 pub kind: ShopOfferKind,
3741 pub label: String,
3742 #[serde(default)]
3743 pub template_id: Option<String>,
3744 #[serde(default)]
3745 pub blueprint_id: Option<String>,
3746 pub price_copper: u32,
3747 #[serde(default)]
3748 pub affordable: bool,
3749 #[serde(default)]
3750 pub already_owned: bool,
3751}
3752
3753#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3754pub struct ShopBuyLine {
3755 pub template_id: String,
3756 pub label: String,
3757 pub quantity: u32,
3758 pub price_copper: u32,
3759}
3760
3761#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3763pub struct BankPanel {
3764 pub npc_id: String,
3765 pub npc_label: String,
3766 pub bank_balance_copper: u64,
3767 pub on_person_copper: u64,
3768 #[serde(default)]
3770 pub pending_outgoing_copper: u64,
3771 #[serde(default)]
3772 pub transfer_fee_bps: u32,
3773 #[serde(default)]
3774 pub transfer_clear_ticks: u64,
3775}
3776
3777#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3779pub struct StoragePanel {
3780 pub npc_id: String,
3781 pub npc_label: String,
3782 pub building_id: String,
3783 pub building_label: String,
3784 pub used_volume: f32,
3785 pub max_volume: f32,
3786 #[serde(default)]
3787 pub contents: Vec<ItemStack>,
3788 #[serde(default)]
3790 pub ship_destinations: Vec<StorageShipDest>,
3791}
3792
3793#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3794pub struct StorageShipDest {
3795 pub building_id: String,
3796 pub label: String,
3797 pub distance_m: f32,
3798 pub fee_copper: u64,
3799 pub travel_ticks: u64,
3800}
3801
3802#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3805pub enum GoodsLocation {
3806 Person,
3808 TownStorage { building_id: String },
3811}
3812
3813#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3816pub struct MarketListingView {
3817 pub listing_id: Uuid,
3818 pub seller_character_id: Uuid,
3819 pub seller_label: String,
3821 pub hall_building_id: String,
3822 pub hall_label: String,
3823 pub template_id: String,
3824 pub display_name: String,
3825 #[serde(default)]
3827 pub category: String,
3828 pub quantity: u32,
3829 pub unit_price_copper: u64,
3830 pub line_total_copper: u64,
3832 #[serde(default)]
3834 pub npc_price: bool,
3835 pub mine: bool,
3837}
3838
3839#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3841pub struct MarketListVault {
3842 pub building_id: String,
3843 pub building_label: String,
3845 #[serde(default)]
3846 pub contents: Vec<ItemStack>,
3847}
3848
3849#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3852pub struct MarketPanel {
3853 pub npc_id: String,
3854 pub npc_label: String,
3855 pub building_id: String,
3856 pub building_label: String,
3857 pub used_volume: f32,
3859 pub max_volume: f32,
3860 #[serde(default)]
3863 pub listings: Vec<MarketListingView>,
3864 #[serde(default)]
3866 pub tax_bps: u32,
3867 #[serde(default)]
3868 pub tax_flat_copper: u32,
3869 #[serde(default)]
3871 pub list_vaults: Vec<MarketListVault>,
3872}
3873
3874#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3875pub struct ShopCatalog {
3876 pub npc_id: String,
3877 pub npc_label: String,
3878 #[serde(default)]
3879 pub sells: Vec<ShopOffer>,
3880 #[serde(default)]
3881 pub buys: Vec<ShopBuyLine>,
3882}
3883
3884#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3885pub struct HarvestResult {
3886 pub node_id: String,
3887 pub quantity: u32,
3889 pub item_template: String,
3890 #[serde(default)]
3893 pub item_instance_id: Option<Uuid>,
3894}
3895
3896#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3898pub struct Envelope<T> {
3899 pub protocol_version: u16,
3900 pub payload: T,
3901}
3902
3903impl<T> Envelope<T> {
3904 pub fn new(payload: T) -> Self {
3905 Self {
3906 protocol_version: crate::PROTOCOL_VERSION,
3907 payload,
3908 }
3909 }
3910}
3911
3912#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3914pub struct Hello {
3915 pub client_name: String,
3916 pub protocol_version: u16,
3917 #[serde(default)]
3918 pub auth: AuthCredential,
3919 #[serde(default)]
3921 pub character_id: Option<Uuid>,
3922}
3923
3924#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3927#[serde(rename_all = "snake_case")]
3928pub enum AuthCredential {
3929 DevLocal,
3930 Session { token: String },
3931 ApiToken { token: String, character_id: Uuid },
3932}
3933
3934impl Default for AuthCredential {
3935 fn default() -> Self {
3936 Self::DevLocal
3937 }
3938}
3939
3940#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3941pub struct Welcome {
3942 pub session_id: SessionId,
3943 pub entity_id: EntityId,
3944 pub snapshot: Snapshot,
3945}
3946
3947#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3948pub enum ServerMessage {
3949 Welcome(Welcome),
3950 ContentUpdated(Snapshot),
3952 Tick(TickDelta),
3953 IntentAck {
3954 entity_id: EntityId,
3955 seq: Seq,
3956 tick: Tick,
3957 },
3958 Chat(ChatMessage),
3959 HarvestResult(HarvestResult),
3960 UseResult(UseResult),
3961 CraftResult(CraftResult),
3962 Death(DeathNotice),
3963 Interaction(InteractionNotice),
3964 ShopOpened(ShopCatalog),
3965 NpcTalkOpened(NpcTalkOpened),
3966 NpcTalkPending(NpcTalkPending),
3967 NpcTalkReply(NpcTalkReply),
3968 NpcTalkClosed(NpcTalkClosed),
3969 NpcTalkError(NpcTalkError),
3970 QuestOffer(QuestOffer),
3971 QuestAccepted(QuestNotice),
3972 QuestWithdrawn(QuestNotice),
3973 QuestStepCompleted(QuestNotice),
3974 QuestCompleted(QuestNotice),
3975 BankOpened(BankPanel),
3977 StorageOpened(StoragePanel),
3979 MarketOpened(MarketPanel),
3981 TradeOpened(TradePanel),
3983 TradeClosed {
3985 reason: String,
3986 },
3987 ConnectRejected {
3990 reason: String,
3991 },
3992}
3993
3994#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3996pub struct TradePanel {
3997 pub peer_entity_id: EntityId,
3998 pub peer_name: String,
3999 pub my_presented: Vec<ItemStack>,
4000 pub their_presented: Vec<ItemStack>,
4001 pub i_ready: bool,
4002 pub they_ready: bool,
4003 pub my_mass_after: f32,
4005 pub my_mass_max: f32,
4006 pub my_encumbrance_after: EncumbranceState,
4007 pub overburden_warning: bool,
4009}
4010
4011#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4012pub enum ClientMessage {
4013 Hello(Hello),
4014 Intent(Intent),
4015 Disconnect,
4016}
4017
4018#[cfg(test)]
4019mod tests {
4020 use super::*;
4021
4022 #[test]
4023 fn pristine_vitals_state_yields_full_pools() {
4024 let attrs = PrimaryAttributes::default();
4025 let vitals = StoredVitalsState::default().apply_to(attrs);
4026 assert!(vitals.health > 0.0);
4027 assert_eq!(vitals.health, vitals.health_max);
4028 assert!((vitals.mana_max - 61.0).abs() < 0.01);
4029 }
4030
4031 #[test]
4032 fn humanize_snake_id_title_cases_parts() {
4033 assert_eq!(humanize_snake_id("heal_touch"), "Heal Touch");
4034 assert_eq!(humanize_snake_id("fireball"), "Fireball");
4035 assert_eq!(humanize_snake_id("cone_frost"), "Cone Frost");
4036 }
4037
4038 #[test]
4039 fn saved_vitals_scale_when_pool_max_increases() {
4040 let mut attrs = PrimaryAttributes::default();
4041 attrs.intelligence = 140;
4042 attrs.wisdom = 140;
4043 let saved = StoredVitalsState {
4044 health: 100.0,
4045 mana: 14.0,
4046 stamina: 100.0,
4047 ..StoredVitalsState::default()
4048 };
4049 let vitals = saved.apply_to(attrs);
4050 assert!(vitals.mana_max > 55.0);
4051 assert!(
4052 (vitals.mana - vitals.mana_max).abs() < 0.01,
4053 "full legacy mana bar migrates to full new bar"
4054 );
4055 }
4056
4057 #[test]
4058 fn empty_vitals_state_is_pristine() {
4059 let pristine = StoredVitalsState {
4060 health: 0.0,
4061 mana: 0.0,
4062 stamina: 0.0,
4063 hunger: 0.0,
4064 thirst: 0.0,
4065 coins: 0,
4066 deaths: 0,
4067 life_state: LifeState::Alive,
4068 };
4069 assert!(pristine.is_pristine());
4070 let vitals = pristine.apply_to(PrimaryAttributes::default());
4071 assert!(vitals.health > 0.0);
4072 }
4073
4074 #[test]
4075 fn stored_vitals_roundtrip_preserves_partial_pools() {
4076 let attrs = PrimaryAttributes::default();
4077 let mut live = PlayerVitals::from_attributes(attrs);
4078 live.health = 25.0;
4079 live.hunger = 77.0;
4080 live.deaths = 2;
4081 let stored = StoredVitalsState::from_live(&live);
4082 let restored = stored.apply_to(attrs);
4083 assert!(
4084 (restored.health - 25.0).abs() < 0.01,
4085 "partial HP below cap stays absolute"
4086 );
4087 assert_eq!(restored.hunger, 77.0);
4088 assert_eq!(restored.deaths, 2);
4089 }
4090
4091 #[test]
4092 fn skill_tiers_start_at_zero() {
4093 let skill = SkillProgress::default();
4094 assert_eq!(skill.level, 0);
4095 assert_eq!(skill.display_tier(), 0);
4096 let trained = SkillProgress {
4097 level: 250,
4098 last_trained_tick: 1,
4099 };
4100 assert_eq!(trained.display_tier(), 2);
4101 }
4102
4103 #[test]
4104 fn quest_server_messages_roundtrip_json() {
4105 use crate::codec::{Codec, PostcardCodec};
4106
4107 let offer = ServerMessage::QuestOffer(QuestOffer {
4108 quest_id: "ada_goblin_hunt".into(),
4109 title: "Goblin Trouble".into(),
4110 description: "Help Ada".into(),
4111 step_count: 3,
4112 });
4113 let notice = ServerMessage::QuestAccepted(QuestNotice {
4114 quest_id: "ada_goblin_hunt".into(),
4115 title: "Goblin Trouble".into(),
4116 message: "Quest accepted".into(),
4117 });
4118 for msg in [offer, notice] {
4119 let bytes = PostcardCodec.encode(&msg).unwrap();
4120 let decoded: ServerMessage = PostcardCodec.decode(&bytes).unwrap();
4121 assert_eq!(decoded, msg);
4122 }
4123 }
4124
4125 #[test]
4126 fn hotbar_consumable_binding_roundtrips() {
4127 let binding = hotbar_consumable_binding("bottle_of_water");
4128 assert_eq!(binding, "item:bottle_of_water");
4129 assert!(hotbar_binding_is_consumable(&binding));
4130 assert_eq!(
4131 hotbar_consumable_template(&binding),
4132 Some("bottle_of_water")
4133 );
4134 assert!(!hotbar_binding_is_consumable("fireball"));
4135 assert_eq!(hotbar_consumable_template("fireball"), None);
4136 }
4137}