twgame 0.11.0

DDNet physics implementation
Documentation
mod entities;
mod ids;
mod map;
pub(crate) mod state;
mod teams;
mod tuning;

use crate::teams::GameWorld;
use ids::{PlayerUidGenerator, SnapIdGenerator, TeamId};
use log::debug;
use std::cmp;
use std::rc::Rc;
use std::str;
use std::sync::{mpsc, Arc};
use twgame_core::console::Command;
use twgame_core::database::{DatabaseResult, DatabaseWrite};
use twgame_core::net_msg::ClNetMessage;
use twgame_core::replay::{GameValidator, ReplayerConfigure};
use twgame_core::twsnap::time::Instant;
use twgame_core::twsnap::Snap;
use twgame_core::Snapper;
use twgame_core::ThHeader;
use twgame_core::{Game, Input};
use twmap::TwMap;
use vek::Vec2;

/// [`TwGame-Core`](https://crates.io/crates/twgame-core) crate
pub use twgame_core as core;
/// [`TwSnap`](https://crates.io/crates/twgame-core) crate.
pub use twgame_core::twsnap;

pub use crate::map::{coord, Map};
pub use crate::state::Bug;

pub(crate) struct SnapOuter<'a> {
    /// generate a new snap_id for new entities
    pub id_generator: &'a mut SnapIdGenerator,
}

#[derive(Debug)]
pub struct DdnetWorld {
    max_player_id: u32,
    /// Slotmap index -> Slotmap version
    players: PlayerUidGenerator,
    teams: GameWorld,
    snap_id_generator: SnapIdGenerator,
}

impl DdnetWorld {
    /// Creates DDNet world from TwMap
    ///
    /// # Example usage
    ///
    /// ```rs
    /// // TODO
    /// ```
    pub fn new(
        map: &mut TwMap,
        sender: mpsc::Sender<DatabaseWrite>,
        receiver: mpsc::Receiver<DatabaseResult>,
    ) -> Result<DdnetWorld, String> {
        let map: Map = map.try_into()?;
        let map = Arc::new(map);
        Self::new_with_map(map, sender, receiver)
    }

    /// Creates DDNet world from this crate's [`Map`] struct
    pub fn new_with_map(
        map: Arc<Map>,
        sender: mpsc::Sender<DatabaseWrite>,
        receiver: mpsc::Receiver<DatabaseResult>,
    ) -> Result<Self, String> {
        let snap_id_generator = map.snap_id_generator.clone().finalize();
        let map = Rc::new(map);
        let teams = teams::GameWorld::new(map, sender, receiver);
        Ok(Self {
            max_player_id: 0,
            players: Default::default(),
            teams,
            snap_id_generator,
        })
    }

    /// Must be called before first tick. (TODO: ensure this with type system)
    /// Uses one `EntityList` for all teams instead of one `EntityList` per Team.
    pub fn enable_ddnet_tele_compat_mode(&mut self, enabled: bool) {
        self.teams.enable_ddnet_tele_compat_mode(enabled);
    }

    /// Must be called before first tick. (TODO: ensure this with type system)
    pub fn supply_prng_compat_data(&mut self, compat_data: &str) {
        self.teams.supply_prng_compat_data(compat_data);
    }

    pub fn enable_prng_compat_data_collection(&mut self) {
        self.teams.enable_prng_compat_data_collection();
    }

    /// Must be called after last tick. (TODO: ensure this with type system)
    pub fn retrieve_prng_compat_data(&mut self) -> Option<String> {
        self.teams.retrieve_prng_compat_data()
    }

    /// Returns used game bugs detected during run
    pub fn retrieve_bugs(&mut self) -> Vec<Bug> {
        self.teams.retrieve_bugs()
    }
}

impl Game for DdnetWorld {
    fn player_join(&mut self, id: u32) {
        let player_uid = self.players.player_join(id);
        self.teams.player_join(player_uid);
        self.max_player_id = cmp::max(self.max_player_id, id + 1);
    }

    fn player_ready(&mut self, id: u32) {
        let player_uid = self.players.get(id).unwrap();
        self.teams.player_ready(player_uid);
    }

    fn player_input(&mut self, id: u32, input: &Input) {
        let player_uid = self.players.get(id).unwrap();
        self.teams.player_input(player_uid, input);
    }

    fn player_leave(&mut self, id: u32) {
        let player_uid = self.players.player_leave(id);
        self.teams.player_leave(player_uid);
    }

    fn on_net_msg(&mut self, id: u32, msg: &ClNetMessage) {
        let player_uid = self.players.get(id).unwrap();
        self.teams.on_net_msg(player_uid, msg);
    }

    fn on_command(&mut self, id: u32, command: &Command) {
        let player_uid = self.players.get(id).unwrap();
        self.teams.on_command(player_uid, command);
    }

    fn swap_tees(&mut self, id1: u32, id2: u32) {
        let player_uid1 = self.players.get(id1).unwrap();
        let player_uid2 = self.players.get(id2).unwrap();
        self.teams.swap_tees(player_uid1, player_uid2);
    }

    fn tick(&mut self, cur_time: Instant) {
        let mut snap_outer = SnapOuter {
            id_generator: &mut self.snap_id_generator,
        };
        self.teams.tick(cur_time, &mut snap_outer);
        self.teams.post_tick();
    }

    fn is_empty(&self) -> bool {
        self.teams.is_empty()
    }
}

impl Snapper for DdnetWorld {
    fn snap(&self, snapshot: &mut Snap) {
        self.teams.snap(snapshot);
    }
}

impl ReplayerConfigure for DdnetWorld {
    fn on_teehistorian_header(&mut self, _raw: &[u8], header: &ThHeader) {
        self.teams.configure_with_teehistorian_parameters(header);
    }
}

impl GameValidator for DdnetWorld {
    fn tee_pos(&self, id: u32) -> Option<Vec2<i32>> {
        let player_uid = self.players.get(id)?;
        self.teams.tee_pos(player_uid)
    }

    fn max_tee_id(&self) -> u32 {
        self.max_player_id
    }

    fn set_tee_pos(&mut self, id: u32, pos: Option<Vec2<i32>>) {
        if let Some(player_uid) = self.players.get(id) {
            self.teams.set_tee_pos(player_uid, pos);
        } else {
            // TODO: make me panic again?
            debug!("set_tee_pos called for non-existing player");
        }
    }

    fn player_team(&self, id: u32) -> i32 {
        let Some(player_uid) = self.players.get(id) else {
            return 0;
        };
        self.teams.player_team(player_uid).to_i32()
    }

    fn set_player_team(&mut self, id: u32, team: i32) {
        let player_uid = self
            .players
            .get(id)
            .expect("set_player_team called for non-existing player");
        self.teams
            .player_team_change(player_uid, TeamId::from_i32(team));
    }
}