twgame 0.11.0

DDNet physics implementation
Documentation
use std::fmt;
use std::fmt::Write;
use twgame_core::twsnap;

#[derive(Clone, Debug)]
pub struct Jump {
    // TODO: use `JumpFlags` from TwSnap
    /// to keep track if a jump has been made on this input (player is holding space bar)
    /// `m_Jumped & 1`
    used_jump_input: bool,
    /// `m_Jumped & 2`
    all_air_jumps_used: bool,
    infinite_jumps: bool,
    last_refill_jumps: bool,
    /// `m_JumpedTotal`
    total_air_jumps: i32,
    /// `m_Jumps`
    num_jumps: u8,
}

impl Default for Jump {
    fn default() -> Self {
        Self {
            used_jump_input: false,
            all_air_jumps_used: false,
            infinite_jumps: false,
            last_refill_jumps: false,
            total_air_jumps: 0,
            num_jumps: 2,
        }
    }
}

pub enum JumpType {
    GroundJump,
    AirJump,
}

impl Jump {
    pub fn format_save(&self, f: &mut String) -> fmt::Result {
        let mut jumped = self.used_jump_input as i32;
        jumped |= (self.all_air_jumps_used as i32) << 1;
        let jumped_total = self.total_air_jumps;
        let jumps = self.num_jumps;
        write!(f, "\t{jumped}\t{jumped_total}\t{jumps}")
    }

    pub fn load(&mut self, tee_info: &mut std::str::Split<char>) {
        let jumped = tee_info.next().unwrap().parse::<i32>().unwrap();
        self.used_jump_input = (jumped & 1) != 0;
        self.all_air_jumps_used = (jumped & 2) != 0;
        self.total_air_jumps = tee_info.next().unwrap().parse::<i32>().unwrap();
        self.num_jumps = tee_info.next().unwrap().parse::<u8>().unwrap();
    }

    pub fn load_infinite_jumps(&mut self, tee_info: &mut std::str::Split<char>) {
        self.infinite_jumps = tee_info.next().unwrap().parse::<i32>().unwrap() != 0;
    }

    pub fn on_jump(&mut self, grounded: bool) -> Option<JumpType> {
        if !self.used_jump_input {
            if grounded && (!self.all_air_jumps_used || self.num_jumps != 0) {
                if self.num_jumps <= 1 || self.num_jumps == 255 {
                    self.all_air_jumps_used = true;
                }
                self.used_jump_input = true;
                self.total_air_jumps = 0;
                return Some(JumpType::GroundJump);
            } else if !self.all_air_jumps_used {
                self.used_jump_input = true;
                if !self.infinite_jumps {
                    self.all_air_jumps_used = true;
                }
                self.total_air_jumps += 1;
                return Some(JumpType::AirJump);
            }
        }
        None
    }

    pub fn on_no_jump(&mut self) {
        self.used_jump_input = false;
    }

    pub fn on_ground(&mut self) {
        self.total_air_jumps = 0;
        self.all_air_jumps_used = false;
    }

    pub fn wall_jump(&mut self) {
        self.all_air_jumps_used = false;
        self.used_jump_input = true;
        if self.num_jumps >= 2 {
            self.total_air_jumps = self.num_jumps as i32 - 2;
        } else {
            self.total_air_jumps = 0;
        }
    }

    pub fn snap(&self, tee: &mut twsnap::items::Tee) {
        let mut jump_flags = twsnap::flags::JumpFlags::empty();
        if self.used_jump_input {
            jump_flags.insert(twsnap::flags::JumpFlags::USED_JUMP_INPUT);
        }
        if self.all_air_jumps_used {
            jump_flags.insert(twsnap::flags::JumpFlags::ALL_AIR_JUMPS_USED);
        }
        tee.jumped = jump_flags;
        tee.jumped_total = self.total_air_jumps;
        tee.jumps = self.num_jumps as i32;
    }

    pub fn set_num_jumps(&mut self, num_jumps: u8) {
        self.num_jumps = num_jumps;
    }

    pub fn set_infinite_jumps(&mut self, enabled: bool) {
        self.infinite_jumps = enabled;
    }

    // TODO: not necessary to run every tick?
    pub fn post_core_tick(&mut self) {
        // https://github.com/ddnet/ddnet/blob/b60b14bf6115cf0042e81a342f6108cb07c1d810/src/game/server/entities/character.cpp#L2095-L2115
        if self.num_jumps == 255 || self.num_jumps < 2 {
            // The player has only one ground jump, so his feet are always dark
            // The player has no jumps at all, so his feet are always dark
            self.all_air_jumps_used = true;
        } else if self.num_jumps == 1 // can't be 255 due to previous codition
            && (self.all_air_jumps_used || self.used_jump_input)
        {
            // If the player has only one jump, each jump is the last one
            self.all_air_jumps_used = true;
        } else if self.total_air_jumps < self.num_jumps as i32 - 1 && self.all_air_jumps_used {
            // The player has not yet used up all his jumps, so his feet remain light
            self.all_air_jumps_used = false;
            self.used_jump_input = true; // TODO: not necessary if not ran every tick?
        }
        // https://github.com/ddnet/ddnet/blob/b60b14bf6115cf0042e81a342f6108cb07c1d810/src/game/server/entities/character.cpp#L2117-L2121
        if self.infinite_jumps && self.all_air_jumps_used {
            // Super players and players with infinite jumps always have light feet
            self.all_air_jumps_used = false;
            // this probably an unintended side effect
            self.used_jump_input = true;
        }
    }

    pub fn has_infinite_jumps(&self) -> bool {
        self.infinite_jumps
    }

    pub fn on_no_double_jump_refresher(&mut self) {
        self.last_refill_jumps = false;
    }

    pub fn refresh_double_jump(&mut self) {
        if !self.last_refill_jumps {
            self.last_refill_jumps = true;
            self.total_air_jumps = 0;
            self.used_jump_input = false;
            self.all_air_jumps_used = false;
        }
    }
}