liquidwar7core 0.2.0

Liquidwar7 core logic library, low-level things which are game-engine agnostic.
Documentation
// Copyright (C) 2025 Christian Mauduit <ufoot@ufoot.org>

//! Cursor types for player control.
//!
//! This module contains the [`Cursor`] trait and its implementations
//! ([`Cursor2D`], [`Cursor3D`]) which represent player-controlled positions
//! on the battlefield. Fighters belonging to a team move towards their team's cursor.

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// A cursor represents a player's position on the battlefield.
///
/// Each cursor is associated with a team and defines where that team's
/// fighters should move towards. The cursor can be moved around the map
/// to direct the flow of fighters.
///
/// # Implementations
///
/// - [`Cursor2D`] - For 2D games (x, y coordinates)
/// - [`Cursor3D`] - For 3D games (x, y, z coordinates)
pub trait Cursor {
    /// Returns the team ID this cursor belongs to.
    fn team_id(&self) -> u64;
    /// Returns the x coordinate of the cursor.
    fn x(&self) -> f32;
    /// Returns the y coordinate of the cursor.
    fn y(&self) -> f32;
    /// Returns the z coordinate of the cursor ([`DEFAULT_Z`](crate::DEFAULT_Z) for 2D cursors).
    fn z(&self) -> f32;
}

/// A 2D cursor with x and y coordinates.
///
/// Used in 2D games to represent a player's target position on the battlefield.
/// The z coordinate always returns [`DEFAULT_Z`](crate::DEFAULT_Z) for compatibility with 3D systems.
///
/// # Example
///
/// ```
/// use liquidwar7core::Cursor2D;
///
/// let team_id = 0x1234567890abcdef_u64;
/// let mut cursor = Cursor2D::new(team_id, 10.0, 20.0);
/// cursor.set_position(15.0, 25.0);
/// ```
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Cursor2D {
    team_id: u64,
    x: f32,
    y: f32,
}

impl Eq for Cursor2D {}

impl Cursor2D {
    /// Creates a new 2D cursor at the specified position.
    pub fn new(team_id: u64, x: f32, y: f32) -> Self {
        Self { team_id, x, y }
    }

    /// Moves the cursor to a new position.
    pub fn set_position(&mut self, x: f32, y: f32) {
        self.x = x;
        self.y = y;
    }
}

impl Cursor for Cursor2D {
    fn team_id(&self) -> u64 {
        self.team_id
    }

    fn x(&self) -> f32 {
        self.x
    }

    fn y(&self) -> f32 {
        self.y
    }

    fn z(&self) -> f32 {
        crate::default_values::DEFAULT_Z
    }
}

impl std::fmt::Display for Cursor2D {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Cursor2D(x={:.1}, y={:.1})", self.x, self.y)
    }
}

/// A 3D cursor with x, y, and z coordinates.
///
/// Used in 3D games to represent a player's target position on the battlefield.
/// The cursor also caches the gradient index of the nearest walkable cell,
/// which may be at a different z-level than the visual cursor position.
///
/// # Example
///
/// ```
/// use liquidwar7core::Cursor3D;
///
/// let team_id = 0x1234567890abcdef_u64;
/// let mut cursor = Cursor3D::new(team_id, 10.0, 20.0, 5.0);
/// cursor.set_position(15.0, 25.0, 10.0);
/// ```
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Cursor3D {
    team_id: u64,
    x: f32,
    y: f32,
    z: f32,
    /// Cached gradient index of the nearest walkable cell.
    /// This is computed by BFS and may be at a different z-level.
    gradient_index: Option<usize>,
}

impl Eq for Cursor3D {}

impl Cursor3D {
    /// Creates a new 3D cursor at the specified position.
    pub fn new(team_id: u64, x: f32, y: f32, z: f32) -> Self {
        Self {
            team_id,
            x,
            y,
            z,
            gradient_index: None,
        }
    }

    /// Moves the cursor to a new position.
    /// Clears the cached gradient index so it will be recomputed.
    pub fn set_position(&mut self, x: f32, y: f32, z: f32) {
        self.x = x;
        self.y = y;
        self.z = z;
        self.gradient_index = None; // Will be recomputed on next apply
    }

    /// Returns the cached gradient index, if any.
    pub fn gradient_index(&self) -> Option<usize> {
        self.gradient_index
    }

    /// Sets the cached gradient index.
    pub fn set_gradient_index(&mut self, index: Option<usize>) {
        self.gradient_index = index;
    }
}

impl Cursor for Cursor3D {
    fn team_id(&self) -> u64 {
        self.team_id
    }

    fn x(&self) -> f32 {
        self.x
    }

    fn y(&self) -> f32 {
        self.y
    }

    fn z(&self) -> f32 {
        self.z
    }
}

impl std::fmt::Display for Cursor3D {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Cursor3D(x={:.1}, y={:.1}, z={:.1})", self.x, self.y, self.z)
    }
}