flatland-presentation 0.2.11

Gfx sprite mode resolution — maps game states to sprite sheet mode ids
Documentation
//! Client-side facing expansion for directional sprite sheets (`walk_north`, …).

/// Movement modes that may have per-direction variants on a sprite sheet.
pub const PLAYER_DIRECTIONAL_BASE_MODES: &[&str] = &["idle", "walk", "run"];

/// Cardinal facing labels for directional sprite modes (`walk_north`, …).
pub fn cardinal_facing_label(yaw: f32) -> &'static str {
    let deg = yaw.to_degrees().rem_euclid(360.0);
    if (45.0..135.0).contains(&deg) {
        "east"
    } else if (135.0..225.0).contains(&deg) {
        "south"
    } else if (225.0..315.0).contains(&deg) {
        "west"
    } else {
        "north"
    }
}

/// Strip a directional suffix so stale server modes like `walk_north` still face correctly.
pub fn base_sprite_mode(mode: &str) -> &str {
    for suffix in ["_north", "_east", "_south", "_west"] {
        if let Some(base) = mode.strip_suffix(suffix) {
            return base;
        }
    }
    mode
}

/// True when `sheet_modes` contains `mode` or a directional variant (`{mode}_north`, …).
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))
}

/// Expand a base gfx mode with client facing when the sheet has directional variants.
pub fn expand_directional_draw_mode(
    sheet_modes: &[String],
    base_mode: &str,
    facing_yaw: f32,
) -> String {
    let base = base_sprite_mode(base_mode);
    if !PLAYER_DIRECTIONAL_BASE_MODES.contains(&base) {
        return base.to_string();
    }
    let directional = format!("{}_{}", base, cardinal_facing_label(facing_yaw));
    if sheet_has_mode(sheet_modes, &directional) {
        directional
    } else {
        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 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("idle"), "idle");
    }

    #[test]
    fn expand_directional_when_sheet_has_variant() {
        let sheet = modes(&["idle_north", "walk_north", "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_2),
            "walk_east"
        );
        assert_eq!(expand_directional_draw_mode(&sheet, "combat", 0.0), "combat");
    }

    #[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");
    }
}