flatland_presentation/
npc_states.rs1pub const NPC_PRESENTATION_STATES: &[(&str, &str)] = &[
5 ("combat", "Combat — in melee / attacking"),
6 ("pursue", "Pursue — has target, out of attack range"),
7 ("running", "Running — fast movement"),
8 ("walking", "Walking — normal movement"),
9 ("harvesting", "Harvesting — gathering resources"),
10 ("talking", "Talking — in conversation with a player"),
11 ("rest", "Rest / sleep — off duty, low activity"),
12 ("work", "Work — at post / crafting / tending"),
13 ("patrol", "Patrol — on a scheduled route"),
14 ("idle", "Idle — stationary, available"),
15 ("telegraph", "Telegraph — attack windup (combat NPCs)"),
16];
17
18pub const NPC_LEGACY_BEHAVIOR_ALIASES: &[(&str, &str)] = &[
20 ("chase", "pursue"),
21 ("alert", "pursue"),
22 ("graze", "idle"),
23 ("flee", "running"),
24];
25
26#[derive(Debug, Clone, Copy)]
28pub struct WildlifeNpcPresentationInput<'a> {
29 pub fsm_state: &'a str,
30 pub telegraph_active: bool,
31 pub has_target: bool,
32 pub in_attack_range: bool,
33 pub is_moving: bool,
34 pub fast_movement: bool,
35}
36
37#[derive(Debug, Clone, Copy)]
39pub struct PlacedNpcPresentationInput<'a> {
40 pub is_talking: bool,
41 pub schedule_action: Option<&'a str>,
43 pub is_moving: bool,
44}
45
46pub fn resolve_wildlife_presentation_key(input: WildlifeNpcPresentationInput<'_>) -> &'static str {
47 if input.telegraph_active {
48 return "telegraph";
49 }
50 if input.fsm_state == "combat" && input.in_attack_range {
51 return "combat";
52 }
53 if input.has_target && !input.in_attack_range {
54 return "pursue";
55 }
56 if input.fsm_state == "combat" {
57 return "combat";
58 }
59 if input.is_moving && input.fast_movement {
60 return "running";
61 }
62 if input.is_moving {
63 return "walking";
64 }
65 match input.fsm_state {
66 "idle" | "graze" => "idle",
67 "flee" => "running",
68 _ => "idle",
69 }
70}
71
72pub fn resolve_placed_npc_presentation_key(input: PlacedNpcPresentationInput<'_>) -> &'static str {
73 if input.is_talking {
74 return "talking";
75 }
76 if input.is_moving {
77 return "walking";
78 }
79 match input.schedule_action {
80 Some("sleep") | Some("rest") => "rest",
81 Some("work") => "work",
82 Some("patrol") => "patrol",
83 Some("travel") => "walking",
84 Some("idle") => "idle",
85 _ => "idle",
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn wildlife_pursue_when_chasing() {
95 let key = resolve_wildlife_presentation_key(WildlifeNpcPresentationInput {
96 fsm_state: "chase",
97 telegraph_active: false,
98 has_target: true,
99 in_attack_range: false,
100 is_moving: true,
101 fast_movement: true,
102 });
103 assert_eq!(key, "pursue");
104 }
105
106 #[test]
107 fn wildlife_combat_in_range() {
108 let key = resolve_wildlife_presentation_key(WildlifeNpcPresentationInput {
109 fsm_state: "combat",
110 telegraph_active: false,
111 has_target: true,
112 in_attack_range: true,
113 is_moving: false,
114 fast_movement: false,
115 });
116 assert_eq!(key, "combat");
117 }
118
119 #[test]
120 fn placed_npc_talking_wins() {
121 let key = resolve_placed_npc_presentation_key(PlacedNpcPresentationInput {
122 is_talking: true,
123 schedule_action: Some("work"),
124 is_moving: false,
125 });
126 assert_eq!(key, "talking");
127 }
128}