twgame 0.11.0

DDNet physics implementation
Documentation
use crate::entities::{SpawnOrder, SpawnOrderEntity, SpawnableEntity};
use crate::ids::PlayerUid;
use crate::map::{coord, TeleTile, TuneZone};
use crate::state::GameState;
use crate::SnapOuter;
use twgame_core::twsnap::enums::ActiveWeapon;
use twgame_core::twsnap::time::{Duration, Instant};
use twgame_core::twsnap::{items, vec2_from_bits, Snap, SnapId};
use vek::Vec2;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kind {
    Grenade,
    Pistol,
}

impl From<Kind> for ActiveWeapon {
    fn from(val: Kind) -> Self {
        match val {
            Kind::Grenade => ActiveWeapon::Grenade,
            Kind::Pistol => ActiveWeapon::Pistol,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Projectile {
    snap_id: SnapId,
    spawn_order: SpawnOrder,
    kind: Kind,
    owner: PlayerUid,
    pos: Vec2<f32>,
    cursor_pos: Vec2<f32>,
    direction: Vec2<f32>,
    start_tick: Instant,
    tune_zone: TuneZone,
    life_span: Duration,
    speed: f32,
    curvature: f32,
    marked_for_destroy: bool,
}

impl Projectile {
    pub fn new_grenade(
        game_state: &GameState,
        snap_outer: &mut SnapOuter,
        owner: PlayerUid,
        pos: Vec2<f32>,
        cursor_pos: Vec2<f32>,
        start_tick: Instant,
        tune_zone: TuneZone,
    ) -> Projectile {
        let snap_id = snap_outer.id_generator.next_projectile();
        let spawn_order = SpawnOrder::new(owner, start_tick, SpawnOrderEntity::Projectile);
        let tuning = game_state.map.tuning(tune_zone);
        Self {
            snap_id,
            spawn_order,
            kind: Kind::Grenade,
            owner,
            pos,
            cursor_pos,
            direction: cursor_pos.normalized(),
            start_tick,
            tune_zone,
            life_span: Duration::from_secs_f32(tuning.grenade_lifetime),
            speed: tuning.grenade_speed,
            curvature: tuning.grenade_curvature,
            marked_for_destroy: false,
        }
    }

    pub fn new_pistol(
        game_state: &GameState,
        snap_outer: &mut SnapOuter,
        owner: PlayerUid,
        pos: Vec2<f32>,
        cursor_pos: Vec2<f32>,
        start_tick: Instant,
        tune_zone: TuneZone,
    ) -> Self {
        let snap_id = snap_outer.id_generator.next_projectile();
        let spawn_order = SpawnOrder::new(owner, start_tick, SpawnOrderEntity::Projectile);
        let tuning = game_state.map.tuning(tune_zone);
        Self {
            snap_id,
            spawn_order,
            kind: Kind::Pistol,
            owner,
            pos,
            cursor_pos,
            direction: cursor_pos.normalized(),
            start_tick,
            tune_zone,
            life_span: Duration::from_secs_f32(tuning.gun_lifetime),
            speed: tuning.gun_speed,
            curvature: tuning.gun_curvature,
            marked_for_destroy: false,
        }
    }
}

impl Projectile {
    fn get_pos(&self, now: Instant, previous: bool) -> Vec2<f32> {
        let mut time = if previous {
            now.duration_since(self.start_tick)
                .unwrap()
                .decrement()
                .map(|d| d.seconds())
                .unwrap()
        } else {
            now.duration_since(self.start_tick).unwrap().seconds()
        };
        time *= self.speed;
        let x = self.pos.x + self.direction.x * time;
        let y = self.pos.y + self.direction.y * time + self.curvature / 10000.0 * (time * time);
        Vec2::new(x, y)
    }

    fn get_tee_collision(
        &self,
        game_state: &GameState,
        from: Vec2<f32>,
        to: Vec2<f32>,
    ) -> Option<(PlayerUid, Vec2<f32>)> {
        if let Some(tee) = game_state.tee_cores.get_tee(self.owner) {
            if tee.is_solo() {
                return None;
            }
        };

        // TODO: different radius on freeze
        game_state
            .tee_cores
            .intersect_tees(&game_state.tee_order, Some(self.owner), from, to, 6.0)
    }
}

impl SpawnableEntity for Projectile {
    fn tick(&mut self, now: Instant, game_state: &mut GameState, _snap_outer: &mut SnapOuter) {
        let from = self.get_pos(now, true);
        let to = self.get_pos(now, false);
        let mut collision = game_state.map.intersect_projectile(from, to);

        // find tee collision
        if let Some((_, collision_point)) =
            self.get_tee_collision(game_state, from, collision.unwrap_or(to))
        {
            collision = Some(collision_point);
        }

        if let Some(collision) = collision {
            if self.kind == Kind::Grenade {
                let explosion_strength = game_state.map.tuning(self.tune_zone).explosion_strength;
                game_state.create_explosion(self.owner, collision, explosion_strength);
            }
            self.marked_for_destroy = true;
            return;
        }
        if let Some(remaining) = self.life_span.decrement() {
            self.life_span = remaining;
        } else {
            if self.kind == Kind::Grenade {
                let explosion_strength = game_state.map.tuning(self.tune_zone).explosion_strength;
                game_state.create_explosion(self.owner, to, explosion_strength);
            }
            self.marked_for_destroy = true;
            return;
        }
        // teleport
        if let Some((TeleTile::Weapon, tele_id)) = game_state.map.get_tele_tile(coord::to_int(from))
        {
            if let Some(tele_out) =
                game_state
                    .map
                    .select_tele_out(now, &mut game_state.prng, tele_id)
            {
                self.pos = tele_out;
                self.start_tick = now;
            }
        }
    }

    fn snap(&self, _now: Instant, _game_state: &GameState, snapshot: &mut Snap) {
        snapshot.projectiles.insert(
            self.snap_id,
            items::Projectile {
                pos: vec2_from_bits(Vec2::new(self.pos.x, self.pos.y)),
                direction: Vec2::new(
                    self.cursor_pos.x.round() as i32,
                    self.cursor_pos.y.round() as i32,
                ),
                kind: self.kind.into(),
                start_tick: self.start_tick,
                owner: self.owner.snap_id(),
                tune_zone: self.tune_zone.to_save(),
            },
        );
    }

    fn is_marked_for_destroy(&self) -> bool {
        self.marked_for_destroy
    }

    fn spawn_order(&self) -> SpawnOrder {
        self.spawn_order
    }

    fn player_uid(&self) -> PlayerUid {
        self.owner
    }

    fn on_tee_swap(&mut self, pid1: PlayerUid, pid2: PlayerUid) {
        // dont update spawn_order, because that one doesn't change
        if self.owner == pid1 {
            self.owner = pid2;
        } else if self.owner == pid2 {
            self.owner = pid1;
        }
    }
}