twgame 0.11.0

DDNet physics implementation
Documentation
use crate::ids::PlayerUid;
use crate::state::{GameState, Tees};
use crate::SnapOuter;
use std::cmp::Ordering;
use std::fmt::Debug;
use tee::Tee;
use twgame_core::twsnap::time::Instant;
use twgame_core::twsnap::Snap;

// tees are special cased entities in twgame
// TODO: make not pub
pub mod tee;

// all other entities
mod laser;
mod pickup;
mod projectile;

use crate::state::SpawnMode;
pub use laser::Laser;
pub use pickup::Pickup;
pub use projectile::Projectile;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SpawnOrderEntity {
    Tee(SpawnMode),
    Projectile,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct SpawnOrder {
    player_uid: PlayerUid,
    tick: Instant,
    entity: SpawnOrderEntity,
}

impl SpawnOrder {
    pub fn new(player_uid: PlayerUid, tick: Instant, entity: SpawnOrderEntity) -> Self {
        Self {
            player_uid,
            tick,
            entity,
        }
    }
}

impl Ord for SpawnOrder {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.tick.cmp(&other.tick) {
            Ordering::Less => return Ordering::Less,
            Ordering::Greater => return Ordering::Greater,
            Ordering::Equal => {}
        }
        match (self.entity, other.entity) {
            (SpawnOrderEntity::Tee(weak1), SpawnOrderEntity::Tee(weak2)) if weak1 == weak2 => {
                self.player_uid.cmp(&other.player_uid)
            }
            (SpawnOrderEntity::Tee(_), SpawnOrderEntity::Tee(SpawnMode::Normal)) => {
                Ordering::Greater
            }
            (SpawnOrderEntity::Tee(_), SpawnOrderEntity::Tee(_)) => Ordering::Less,
            (SpawnOrderEntity::Tee(_), SpawnOrderEntity::Projectile) => Ordering::Greater,
            (SpawnOrderEntity::Projectile, SpawnOrderEntity::Tee(_)) => Ordering::Less,
            (SpawnOrderEntity::Projectile, SpawnOrderEntity::Projectile) => {
                Ordering::Equal
                // TODO: Is here a defined order?
                //       self.player_id.cmp(&other.player_id)
            }
        }
    }
}

impl PartialOrd for SpawnOrder {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

pub trait MapEntity {
    fn tick(&mut self, now: Instant, game_state: &mut GameState, tees: &mut Tees<Tee>);

    fn snap(&self, snapshot: &mut Snap);
}

pub trait SpawnableEntity {
    fn tick(&mut self, now: Instant, game_state: &mut GameState, snap_outer: &mut SnapOuter);

    fn snap(&self, now: Instant, game_state: &GameState, snapshot: &mut Snap);

    /// returns if entity should get destroyed at the end of the tick
    fn is_marked_for_destroy(&self) -> bool;

    /// used when tee joins new team to keep strong/weak order and physics the same as in DDNet
    fn spawn_order(&self) -> SpawnOrder;

    /// used when tee joins new team to move all related entities with it
    fn player_uid(&self) -> PlayerUid;

    /// used on `/swap` command to update all PlayerUid referecnes in entities
    fn on_tee_swap(&mut self, pid1: PlayerUid, pid2: PlayerUid);
}

#[derive(Debug, Clone)]
pub enum MapEntityItem {
    Pickup(Pickup),
}

impl MapEntityItem {
    pub fn pickup(pickup: Pickup) -> Self {
        Self::Pickup(pickup)
    }
}

impl MapEntity for MapEntityItem {
    fn tick(&mut self, now: Instant, game_state: &mut GameState, tees: &mut Tees<Tee>) {
        match self {
            Self::Pickup(pickup) => pickup.tick(now, game_state, tees),
        }
    }

    fn snap(&self, snapshot: &mut Snap) {
        match self {
            Self::Pickup(pickup) => pickup.snap(snapshot),
        }
    }
}

/// Internal representation
#[derive(Debug)]
pub(crate) enum EntityItem {
    Tee(PlayerUid),
    Projectile(Projectile),
    Laser(Laser),
}

// TODO: remove?
#[derive(Debug)]
pub enum EntityItemNew {
    Projectile(Projectile),
    Laser(Laser),
}

impl EntityItemNew {
    pub fn projectile(projectile: Projectile) -> Self {
        Self::Projectile(projectile)
    }

    pub fn laser(laser: Laser) -> Self {
        Self::Laser(laser)
    }
}

impl EntityItem {
    pub(crate) fn tick(
        &mut self,
        now: Instant,
        game_state: &mut GameState,
        tees: &mut Tees<Tee>,
        snap_outer: &mut SnapOuter,
    ) {
        match self {
            Self::Tee(tee_uid) => tees
                .get_tee_mut(*tee_uid)
                .unwrap()
                .tick(now, game_state, snap_outer),
            Self::Projectile(projectile) => projectile.tick(now, game_state, snap_outer),
            Self::Laser(laser) => laser.tick(now, game_state, snap_outer),
        }
    }

    pub(crate) fn snap(
        &self,
        now: Instant,
        game_state: &GameState,
        snapshot: &mut Snap,
        tees: &Tees<Tee>,
    ) {
        match self {
            Self::Tee(tee_uid) => tees
                .get_tee(*tee_uid)
                .unwrap()
                .snap(now, game_state, snapshot),
            Self::Projectile(projectile) => projectile.snap(now, game_state, snapshot),
            Self::Laser(laser) => laser.snap(now, game_state, snapshot),
        }
    }

    pub(crate) fn is_marked_for_destroy(&self, tees: &Tees<Tee>) -> bool {
        match self {
            Self::Tee(tee) => tees.get_tee(*tee).unwrap().is_marked_for_destroy(),
            Self::Projectile(projectile) => projectile.is_marked_for_destroy(),
            Self::Laser(laser) => laser.is_marked_for_destroy(),
        }
    }

    pub(crate) fn spawn_order(&self, tees: &Tees<Tee>) -> SpawnOrder {
        match self {
            Self::Tee(tee) => tees.get_tee(*tee).unwrap().spawn_order(),
            Self::Projectile(projectile) => projectile.spawn_order(),
            Self::Laser(laser) => laser.spawn_order(),
        }
    }

    pub(crate) fn player_uid(&self) -> PlayerUid {
        match self {
            Self::Tee(tee) => *tee,
            Self::Projectile(projectile) => projectile.player_uid(),
            Self::Laser(laser) => laser.player_uid(),
        }
    }

    pub(crate) fn on_tee_swap(&mut self, tees: &mut Tees<Tee>, pid1: PlayerUid, pid2: PlayerUid) {
        match self {
            Self::Tee(tee) => tees.get_tee_mut(*tee).unwrap().on_tee_swap(pid1, pid2),
            Self::Laser(laser) => laser.on_tee_swap(pid1, pid2),
            Self::Projectile(projectile) => projectile.on_tee_swap(pid1, pid2),
        }
    }
}