pub const NPC_PRESENTATION_STATES: &[(&str, &str)] = &[
("combat", "Combat — in melee / attacking"),
("pursue", "Pursue — has target, out of attack range"),
("running", "Running — fast movement"),
("walking", "Walking — normal movement"),
("harvesting", "Harvesting — gathering resources"),
("talking", "Talking — in conversation with a player"),
("rest", "Rest / sleep — off duty, low activity"),
("work", "Work — at post / crafting / tending"),
("patrol", "Patrol — on a scheduled route"),
("idle", "Idle — stationary, available"),
("telegraph", "Telegraph — attack windup (combat NPCs)"),
];
pub const NPC_LEGACY_BEHAVIOR_ALIASES: &[(&str, &str)] = &[
("chase", "pursue"),
("alert", "pursue"),
("graze", "idle"),
("flee", "running"),
];
#[derive(Debug, Clone, Copy)]
pub struct WildlifeNpcPresentationInput<'a> {
pub fsm_state: &'a str,
pub telegraph_active: bool,
pub has_target: bool,
pub in_attack_range: bool,
pub is_moving: bool,
pub fast_movement: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct PlacedNpcPresentationInput<'a> {
pub is_talking: bool,
pub schedule_action: Option<&'a str>,
pub is_moving: bool,
}
pub fn resolve_wildlife_presentation_key(input: WildlifeNpcPresentationInput<'_>) -> &'static str {
if input.telegraph_active {
return "telegraph";
}
if input.fsm_state == "combat" && input.in_attack_range {
return "combat";
}
if input.has_target && !input.in_attack_range {
return "pursue";
}
if input.fsm_state == "combat" {
return "combat";
}
if input.is_moving && input.fast_movement {
return "running";
}
if input.is_moving {
return "walking";
}
match input.fsm_state {
"idle" | "graze" => "idle",
"flee" => "running",
_ => "idle",
}
}
pub fn resolve_placed_npc_presentation_key(input: PlacedNpcPresentationInput<'_>) -> &'static str {
if input.is_talking {
return "talking";
}
if input.is_moving {
return "walking";
}
match input.schedule_action {
Some("sleep") | Some("rest") => "rest",
Some("work") => "work",
Some("patrol") => "patrol",
Some("travel") => "walking",
Some("idle") => "idle",
_ => "idle",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wildlife_pursue_when_chasing() {
let key = resolve_wildlife_presentation_key(WildlifeNpcPresentationInput {
fsm_state: "chase",
telegraph_active: false,
has_target: true,
in_attack_range: false,
is_moving: true,
fast_movement: true,
});
assert_eq!(key, "pursue");
}
#[test]
fn wildlife_combat_in_range() {
let key = resolve_wildlife_presentation_key(WildlifeNpcPresentationInput {
fsm_state: "combat",
telegraph_active: false,
has_target: true,
in_attack_range: true,
is_moving: false,
fast_movement: false,
});
assert_eq!(key, "combat");
}
#[test]
fn placed_npc_talking_wins() {
let key = resolve_placed_npc_presentation_key(PlacedNpcPresentationInput {
is_talking: true,
schedule_action: Some("work"),
is_moving: false,
});
assert_eq!(key, "talking");
}
}