Skip to main content

flatland_presentation/
directional.rs

1//! Client-side facing expansion for directional sprite sheets (`walk_north`, …).
2
3/// Movement modes that may have per-direction variants on a sprite sheet.
4pub const PLAYER_DIRECTIONAL_BASE_MODES: &[&str] = &["idle", "walk", "run"];
5
6/// Cardinal facing labels for directional sprite modes (`walk_north`, …).
7pub fn cardinal_facing_label(yaw: f32) -> &'static str {
8    let deg = yaw.to_degrees().rem_euclid(360.0);
9    if (45.0..135.0).contains(&deg) {
10        "east"
11    } else if (135.0..225.0).contains(&deg) {
12        "south"
13    } else if (225.0..315.0).contains(&deg) {
14        "west"
15    } else {
16        "north"
17    }
18}
19
20/// Strip a directional suffix so stale server modes like `walk_north` still face correctly.
21pub fn base_sprite_mode(mode: &str) -> &str {
22    for suffix in ["_north", "_east", "_south", "_west"] {
23        if let Some(base) = mode.strip_suffix(suffix) {
24            return base;
25        }
26    }
27    mode
28}
29
30/// True when `sheet_modes` contains `mode` or a directional variant (`{mode}_north`, …).
31pub fn sheet_has_mode(sheet_modes: &[String], mode: &str) -> bool {
32    if sheet_modes.iter().any(|m| m == mode) {
33        return true;
34    }
35    let prefix = format!("{mode}_");
36    sheet_modes.iter().any(|m| m.starts_with(&prefix))
37}
38
39/// Expand a base gfx mode with client facing when the sheet has directional variants.
40pub fn expand_directional_draw_mode(
41    sheet_modes: &[String],
42    base_mode: &str,
43    facing_yaw: f32,
44) -> String {
45    let base = base_sprite_mode(base_mode);
46    if !PLAYER_DIRECTIONAL_BASE_MODES.contains(&base) {
47        return base.to_string();
48    }
49    let directional = format!("{}_{}", base, cardinal_facing_label(facing_yaw));
50    if sheet_has_mode(sheet_modes, &directional) {
51        directional
52    } else {
53        base.to_string()
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    fn modes(ids: &[&str]) -> Vec<String> {
62        ids.iter().map(|s| s.to_string()).collect()
63    }
64
65    #[test]
66    fn cardinal_facing_label_bins() {
67        assert_eq!(cardinal_facing_label(0.0), "north");
68        assert_eq!(cardinal_facing_label(std::f32::consts::FRAC_PI_2), "east");
69        assert_eq!(cardinal_facing_label(std::f32::consts::PI), "south");
70    }
71
72    #[test]
73    fn base_sprite_mode_strips_suffix() {
74        assert_eq!(base_sprite_mode("walk_north"), "walk");
75        assert_eq!(base_sprite_mode("idle"), "idle");
76    }
77
78    #[test]
79    fn expand_directional_when_sheet_has_variant() {
80        let sheet = modes(&["idle_north", "walk_north", "walk_east", "combat"]);
81        assert_eq!(
82            expand_directional_draw_mode(&sheet, "walk", 0.0),
83            "walk_north"
84        );
85        assert_eq!(
86            expand_directional_draw_mode(&sheet, "walk", std::f32::consts::FRAC_PI_2),
87            "walk_east"
88        );
89        assert_eq!(expand_directional_draw_mode(&sheet, "combat", 0.0), "combat");
90    }
91
92    #[test]
93    fn expand_falls_back_to_base_without_directional() {
94        let sheet = modes(&["walk", "idle"]);
95        assert_eq!(expand_directional_draw_mode(&sheet, "walk", 0.0), "walk");
96    }
97}