pub const PLAYER_DIRECTIONAL_BASE_MODES: &[&str] = &["idle", "walk", "run"];
pub fn cardinal_facing_label(yaw: f32) -> &'static str {
eight_way_facing_label(yaw)
}
pub fn eight_way_facing_label(yaw: f32) -> &'static str {
let deg = yaw.to_degrees().rem_euclid(360.0);
if (22.5..67.5).contains(°) {
"northeast"
} else if (67.5..112.5).contains(°) {
"east"
} else if (112.5..157.5).contains(°) {
"southeast"
} else if (157.5..202.5).contains(°) {
"south"
} else if (202.5..247.5).contains(°) {
"southwest"
} else if (247.5..292.5).contains(°) {
"west"
} else if (292.5..337.5).contains(°) {
"northwest"
} else {
"north"
}
}
pub fn four_way_facing_label(yaw: f32) -> &'static str {
let deg = yaw.to_degrees().rem_euclid(360.0);
if (45.0..135.0).contains(°) {
"east"
} else if (135.0..225.0).contains(°) {
"south"
} else if (225.0..315.0).contains(°) {
"west"
} else {
"north"
}
}
pub fn base_sprite_mode(mode: &str) -> &str {
for suffix in [
"_northeast",
"_northwest",
"_southeast",
"_southwest",
"_north",
"_east",
"_south",
"_west",
] {
if let Some(base) = mode.strip_suffix(suffix) {
return base;
}
}
mode
}
pub fn sheet_has_mode(sheet_modes: &[String], mode: &str) -> bool {
if sheet_modes.iter().any(|m| m == mode) {
return true;
}
let prefix = format!("{mode}_");
sheet_modes.iter().any(|m| m.starts_with(&prefix))
}
pub fn expand_directional_draw_mode(
sheet_modes: &[String],
base_mode: &str,
facing_yaw: f32,
) -> String {
let base = base_sprite_mode(base_mode);
let eight = eight_way_facing_label(facing_yaw);
let four = four_way_facing_label(facing_yaw);
for label in [eight, four] {
let directional = format!("{base}_{label}");
if sheet_has_mode(sheet_modes, &directional) {
return directional;
}
}
if sheet_has_mode(sheet_modes, base) {
return base.to_string();
}
for label in [eight, four] {
let idle_dir = format!("idle_{label}");
if sheet_has_mode(sheet_modes, &idle_dir) {
return idle_dir;
}
}
if sheet_has_mode(sheet_modes, "idle") {
return "idle".to_string();
}
base.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn modes(ids: &[&str]) -> Vec<String> {
ids.iter().map(|s| s.to_string()).collect()
}
#[test]
fn eight_way_facing_label_bins() {
assert_eq!(eight_way_facing_label(0.0), "north");
assert_eq!(
eight_way_facing_label(std::f32::consts::FRAC_PI_4),
"northeast"
);
assert_eq!(
eight_way_facing_label(std::f32::consts::FRAC_PI_2),
"east"
);
assert_eq!(eight_way_facing_label(std::f32::consts::PI), "south");
assert_eq!(
eight_way_facing_label(-std::f32::consts::FRAC_PI_4),
"northwest"
);
}
#[test]
fn cardinal_facing_label_bins() {
assert_eq!(cardinal_facing_label(0.0), "north");
assert_eq!(cardinal_facing_label(std::f32::consts::FRAC_PI_2), "east");
assert_eq!(cardinal_facing_label(std::f32::consts::PI), "south");
}
#[test]
fn base_sprite_mode_strips_suffix() {
assert_eq!(base_sprite_mode("walk_north"), "walk");
assert_eq!(base_sprite_mode("walk_northeast"), "walk");
assert_eq!(base_sprite_mode("idle"), "idle");
}
#[test]
fn expand_directional_when_sheet_has_variant() {
let sheet = modes(&[
"idle_north",
"walk_north",
"walk_northeast",
"walk_east",
"combat",
]);
assert_eq!(
expand_directional_draw_mode(&sheet, "walk", 0.0),
"walk_north"
);
assert_eq!(
expand_directional_draw_mode(&sheet, "walk", std::f32::consts::FRAC_PI_4),
"walk_northeast"
);
assert_eq!(
expand_directional_draw_mode(&sheet, "walk", std::f32::consts::FRAC_PI_2),
"walk_east"
);
assert_eq!(expand_directional_draw_mode(&sheet, "combat", 0.0), "combat");
}
#[test]
fn expand_falls_back_to_four_way_when_eight_missing() {
let sheet = modes(&["walk_north", "walk_east", "walk_south", "walk_west"]);
assert_eq!(
expand_directional_draw_mode(&sheet, "walk", std::f32::consts::FRAC_PI_4),
"walk_east"
);
}
#[test]
fn expand_falls_back_to_base_without_directional() {
let sheet = modes(&["walk", "idle"]);
assert_eq!(expand_directional_draw_mode(&sheet, "walk", 0.0), "walk");
}
}