twgame-core 0.9.0

Game trait, helper types and helper functions for twgame
Documentation
pub mod config;
pub mod console;
pub mod database;
mod display;
pub mod net_msg;
pub mod replay;

/// [`teehistorian`](https://crates.io/crates/teehistorian) crate
pub use teehistorian;
/// [`TwSnap`](https://crates.io/crates/twsnap) crate
pub use twsnap;

pub use display::DisplayChunk;

use crate::console::Command;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use twsnap::time::Instant;
use twsnap::Snap;
use uuid::Uuid;
use vek::Vec2;

#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[repr(C)]
pub struct Input {
    pub direction: i32,
    pub target_x: i32,
    pub target_y: i32,
    pub jump: i32,
    pub fire: i32,
    pub hook: i32,
    /// range 0 - 256
    pub player_flags: i32,
    pub wanted_weapon: i32,
    pub next_weapon: i32,
    pub prev_weapon: i32,
}

impl Input {
    /// Creates new Input with non-centered cursor
    pub fn new() -> Input {
        Input {
            direction: 0,
            target_x: 0,
            target_y: -1,
            jump: 0,
            fire: 0,
            hook: 0,
            player_flags: 0,
            wanted_weapon: 0,
            next_weapon: 0,
            prev_weapon: 0,
        }
    }

    fn disallow_target_center(mut self) -> Self {
        if self.target_x == 0 && self.target_y == 0 {
            self.target_y = -1;
        }
        self
    }

    // CInputCount CountInput(int Prev, int Cur)
    fn count_presses(mut cur: i32, mut prev: i32) -> i32 {
        // TODO: something like (((cur - prev) & 63) + 1) / 2
        let mut count = 0;
        prev &= 63; // TODO: INPUT_STATE_MASK
        cur &= 63;
        while prev != cur {
            prev = (prev + 1) & 63;
            if prev & 1 != 0 {
                count += 1;
            }
        }
        count
    }

    /// count how often weapon next was pressed since the last input
    pub fn count_weapon_next(&self, prev: &Input) -> i32 {
        Input::count_presses(self.next_weapon, prev.next_weapon)
    }

    /// count how often weapon prev was pressed since the last input
    pub fn count_weapon_prev(&self, prev: &Input) -> i32 {
        Input::count_presses(self.prev_weapon, prev.prev_weapon)
    }

    /// count how often weapon fire was pressed since the last input
    pub fn count_weapon_fire(&self, prev: &Input) -> i32 {
        Input::count_presses(self.fire, prev.fire)
    }

    /// returns cursor coordinates converted to f32
    pub fn cursor(&self) -> Vec2<f32> {
        Vec2::new(self.target_x as f32, self.target_y as f32)
    }

    /// returns if at the end of the last input firing was hold
    pub fn firing(&self) -> bool {
        (self.fire & 1) != 0
    }

    /// returns the player flag for whether the spec cam is on
    pub fn spec_cam_active(&self) -> bool {
        (self.player_flags & (1 << 5)) != 0
    }
}

impl From<[i32; 10]> for Input {
    fn from(input: [i32; 10]) -> Self {
        Input {
            direction: input[0],
            target_x: input[1],
            target_y: input[2],
            jump: input[3],
            fire: input[4],
            hook: input[5],
            // range 0 - 256
            player_flags: input[6],
            wanted_weapon: input[7],
            next_weapon: input[8],
            prev_weapon: input[9],
        }
        .disallow_target_center()
    }
}

impl Input {
    pub fn add_input_diff(&mut self, input_diff: [i32; 10]) {
        self.direction += input_diff[0];
        self.target_x += input_diff[1];
        self.target_y += input_diff[2];
        self.jump += input_diff[3];
        self.fire += input_diff[4];
        self.hook += input_diff[5];
        self.player_flags += input_diff[6];
        self.wanted_weapon += input_diff[7];
        self.next_weapon += input_diff[8];
        self.prev_weapon += input_diff[9];
    }
}

pub trait Game {
    // input functions
    fn player_join(&mut self, id: u32);
    fn player_ready(&mut self, id: u32);
    fn player_input(&mut self, id: u32, input: &Input);
    fn player_leave(&mut self, id: u32);

    fn on_net_msg(&mut self, id: u32, msg: &net_msg::ClNetMessage);
    fn on_command(&mut self, id: u32, command: &Command);

    fn swap_tees(&mut self, id1: u32, id2: u32);

    // actions before player position check in teehistorian
    fn tick(&mut self, cur_time: Instant);

    /// returns whether there are still players or spawnable entities in the world
    /// When true, tick doesn't need to be called until next `player_join` event.
    fn is_empty(&self) -> bool;
}

pub trait Snapper {
    fn snap(&self, snapshot: &mut Snap);
}

#[derive(Deserialize, Debug)]
pub struct ThHeader {
    pub game_uuid: Uuid,
    pub server_version: String,
    pub prng_description: Option<String>,
    pub start_time: String,
    pub map_name: String,
    pub map_sha256: Option<String>,
    pub map_crc: String,
    pub map_size: String,
    pub version_minor: Option<String>,
    pub config: HashMap<String, String>,
    pub tuning: HashMap<String, String>,
}

impl ThHeader {
    pub fn from_buf(buf: &[u8]) -> Self {
        let buf = String::from_utf8_lossy(buf);
        serde_json::from_str(&buf).unwrap()
    }
}

/// Implement normalize function, because order of operation is important for physics
/// teehistorian_replayer_res_physics_1_weapon_shotgun_other_right_teehistorian would fail
/// otherwise. See commit fcdd5ebd375c57cf0bc4ddd92610d813f68126ad
pub fn normalize(v: Vec2<f32>) -> Vec2<f32> {
    // float divisor = length(v);
    let divisor = v.magnitude();
    // if(divisor == 0.0f)
    if divisor == 0.0 {
        // return vector2_base<float>(0.0f, 0.0f);
        return Vec2::zero();
    }
    // float l = (float)(1.0f / divisor);
    let l = 1.0 / divisor;
    // return vector2_base<float>(v.x * l, v.y * l);
    v * l
}