use crate::entities::tee::jump::JumpType;
use crate::entities::tee::{Tee, TEE_PROXIMITY_SQUARED};
use crate::map::{coord, CantMove, Map, Tile, TuneZone};
use crate::state;
use crate::tuning::Tuning;
use bitflags::bitflags;
use twgame_core::twsnap::enums::{Direction, Sound};
use twgame_core::twsnap::flags::TeeFlags;
use twgame_core::twsnap::time::{Duration, Instant};
use twgame_core::twsnap::Position;
use twgame_core::twsnap::{vec2_from_bits, Velocity};
use twgame_core::{normalize, Input};
use vek::num_traits::Inv;
use vek::Vec2;
bitflags! {
#[derive(Debug)]
pub struct Core: u32 {
const SOLO = 1 << 0;
const TEE_COLLISION_OFF = 1 << 1;
const WEAPON_HAMMER_OFF = 1 << 2;
const WEAPON_SHOTGUN_OFF = 1 << 3;
const WEAPON_GRENADE_OFF = 1 << 4;
const WEAPON_LASER_OFF = 1 << 5;
const TELEGUN_GUN = 1 << 6;
const TELEGUN_LASER = 1 << 7;
const TELEGUN_GRENADE = 1 << 8;
const FINISHED = 1 << 9;
const FROZEN_LAST_TICK = 1 << 10;
}
}
#[derive(Debug)]
pub struct TeeCore {
pos: Vec2<f32>,
vel: Vec2<f32>,
core: Core,
freeze: Option<Duration>,
freeze_tick: Instant,
move_restrictions: CantMove,
last_hook_release: Instant,
}
impl TeeCore {
pub fn new(pos: Vec2<f32>) -> Self {
Self {
pos,
vel: Vec2::new(0.0, 0.0),
core: Core::empty(),
freeze: None,
freeze_tick: Instant::zero(),
move_restrictions: CantMove::empty(),
last_hook_release: Instant::zero(),
}
}
pub fn with_vel(pos: Vec2<f32>, vel: Vec2<f32>) -> Self {
Self {
pos,
vel,
core: Core::empty(),
freeze: None,
freeze_tick: Instant::zero(),
move_restrictions: CantMove::empty(),
last_hook_release: Instant::zero(),
}
}
}
impl TeeCore {
pub(super) fn save_freeze_time(&self) -> i32 {
self.freeze.map(|duration| duration.ticks()).unwrap_or(0)
}
pub(super) fn save_freeze_start(&self, now: Instant) -> i32 {
now.snap_tick() - self.freeze_tick.snap_tick()
}
pub(super) fn load_freeze(&mut self, now: Instant, tee_info: &mut std::str::Split<char>) {
let freeze_time = tee_info.next().unwrap().parse::<i32>().unwrap();
if freeze_time != 0 {
self.freeze = Some(Duration::from_ticks(freeze_time));
} else {
self.freeze = None;
}
let freeze_start = tee_info.next().unwrap().parse::<i32>().unwrap();
self.freeze_tick = now - Duration::from_ticks(freeze_start);
}
pub(super) fn load_pos(&mut self, tee_info: &mut std::str::Split<char>) {
self.pos.x = tee_info.next().unwrap().parse::<i32>().unwrap() as f32;
self.pos.y = tee_info.next().unwrap().parse::<i32>().unwrap() as f32;
}
pub(super) fn load_vel(&mut self, tee_info: &mut std::str::Split<char>) {
self.vel.x = tee_info.next().unwrap().parse::<f32>().unwrap();
self.vel.y = tee_info.next().unwrap().parse::<f32>().unwrap();
}
}
impl TeeCore {
pub(super) fn snap(&self, now: Instant, snap_tee: &mut twgame_core::twsnap::items::Tee) {
let (pos, vel) = self.get_pos_vel();
snap_tee.pos = pos;
snap_tee.vel = vel;
if self.freeze.is_some() {
snap_tee.flags.insert(TeeFlags::IN_FREEZE);
}
if let Some(freeze) = self.freeze {
snap_tee.freeze_end = now + freeze;
snap_tee.freeze_start = self.freeze_tick;
}
if self.is_solo() {
snap_tee.flags.insert(TeeFlags::SOLO);
}
}
pub(crate) fn round(&mut self) {
let (pos, vel) = self.get_pos_vel();
self.pos.x = pos.x.to_bits() as f32;
self.pos.y = pos.y.to_bits() as f32;
self.vel.x = vel.x.to_bits() as f32 / 256.0;
self.vel.y = vel.y.to_bits() as f32 / 256.0;
}
pub(crate) fn get_pos_vel(&self) -> (Position, Velocity) {
let pos: Position = vec2_from_bits(self.pos.round());
let vel: Velocity = vec2_from_bits((self.vel * 256.0).round());
(pos, vel)
}
}
impl TeeCore {
pub(crate) fn set_move_restrictions(&mut self, map: &Map) {
self.move_restrictions = map.get_move_restrictions(self.pos);
}
pub(crate) fn apply_gravity(&mut self, tuning: &Tuning) {
self.vel.y += tuning.gravity;
}
pub(super) fn on_jump(&mut self, events: &mut state::Events, tuning: &Tuning, jump: JumpType) {
match jump {
JumpType::GroundJump => {
events.create_sound(self.pos, Sound::PlayerJump);
self.vel.y = -tuning.ground_jump_impulse;
}
JumpType::AirJump => {
events.create_sound(self.pos, Sound::PlayerAirjump);
self.vel.y = -tuning.air_jump_impulse;
}
}
}
pub(crate) fn apply_directions(
&mut self,
tuning: &Tuning,
is_grounded: bool,
direction: Direction,
) {
let max_speed: f32 = if is_grounded {
tuning.ground_control_speed
} else {
tuning.air_control_speed
};
let accel: f32 = if is_grounded {
tuning.ground_control_accel
} else {
tuning.air_control_accel
};
let friction: f32 = if is_grounded {
tuning.ground_friction
} else {
tuning.air_friction
};
self.vel.x = match direction {
Direction::Left => Self::saturated_add(max_speed, self.vel.x, -accel),
Direction::Right => Self::saturated_add(max_speed, self.vel.x, accel),
Direction::None => self.vel.x * friction,
};
}
pub(crate) fn cap_vel(&mut self) {
if self.vel.magnitude() > 6000.0 {
self.vel = normalize(self.vel) * 6000.0;
}
}
pub(crate) fn move_vel_ramp(&mut self, map: &Map, tune_zone: TuneZone) {
let tuning = map.tuning(tune_zone);
let vel_ramp = Tee::vel_ramp(
self.vel.magnitude() * 50.0,
tuning.velramp_start,
tuning.velramp_range,
tuning.velramp_curvature,
);
self.vel.x *= vel_ramp;
let (new_pos, new_vel) = map.tee_move_box(self.pos, self.vel);
self.pos = new_pos;
self.vel = new_vel;
self.vel.x *= vel_ramp.inv();
}
}
impl TeeCore {
pub fn pos(&self) -> Vec2<f32> {
self.pos
}
pub fn vel(&self) -> Vec2<f32> {
self.vel
}
pub(crate) fn impact(&mut self, force: Vec2<f32>, _pain: bool) {
self.vel = self.move_restrictions.apply_on_vel(self.vel + force);
}
pub(crate) fn is_on_stoppers(&self) -> bool {
self.move_restrictions.contains(CantMove::DOWN)
}
pub(crate) fn is_in_kill_area(&self, no_kill_immunity: bool, map: &Map) -> bool {
map.is_tee_in_skippable_radius(self.pos, Tile::Kill)
&& no_kill_immunity
|| map.is_out_of_map(coord::to_int(self.pos))
}
pub(super) fn set_pos(&mut self, pos: Vec2<f32>) {
self.pos = pos;
}
pub(crate) fn set_vel(&mut self, vel: Vec2<f32>) {
self.vel = vel;
}
pub(super) fn reset_vel(&mut self) {
self.vel = Vec2::new(0.0, 0.0);
}
pub(super) fn apply_move_restrictions(&mut self) {
self.vel = self.move_restrictions.apply_on_vel(self.vel);
}
pub(crate) fn saturated_add(max: f32, current: f32, modifier: f32) -> f32 {
debug_assert!(max >= 0.0);
if modifier < 0.0 {
if current < -max {
current
} else {
(-max).max(current + modifier)
}
} else if current > max {
current
} else {
max.min(current + modifier)
}
}
pub(crate) fn satured_impact(&mut self, force: Vec2<f32>, max: f32) {
self.vel = Vec2::new(
Self::saturated_add(max, self.vel.x, force.x),
Self::saturated_add(max, self.vel.y, force.y),
);
self.apply_move_restrictions()
}
pub(crate) fn freeze(&mut self, now: Instant, duration: Duration) -> bool {
if duration == Duration::T0MS
|| !now
.duration_passed_since(self.freeze_tick, Duration::from_secs(1) + Duration::T20MS)
{
return false;
}
self.freeze = Some(duration);
self.freeze_tick = now;
true
}
pub(crate) fn unfreeze(&mut self) {
if self.freeze.is_some() {
self.unfreeze_unchecked();
}
}
pub(crate) fn is_frozen(&self) -> bool {
self.freeze.is_some()
}
fn unfreeze_unchecked(&mut self) {
self.freeze = None;
self.freeze_tick = Instant::zero();
self.core.insert(Core::FROZEN_LAST_TICK);
}
pub(super) fn frozen_last_tick(&self) -> bool {
self.core.contains(Core::FROZEN_LAST_TICK)
}
pub(super) fn reset_frozen_last_tick(&mut self) {
self.core.remove(Core::FROZEN_LAST_TICK);
}
pub(crate) fn input_apply_freeze(&mut self, mut input: Input, live_freeze: bool) -> Input {
if live_freeze {
input.direction = 0;
input.jump = 0;
}
if let Some(duration) = self.freeze {
self.freeze = duration.decrement();
input.direction = 0;
input.jump = 0;
input.hook = 0;
if self.freeze == Some(Duration::T20MS) {
self.unfreeze_unchecked();
}
}
input
}
pub(crate) fn other_tees_release_hook(&mut self, now: Instant) {
self.last_hook_release = now;
}
pub(crate) fn last_hook_release(&self) -> Instant {
self.last_hook_release
}
pub(crate) fn on_finish(&mut self) {
self.core.insert(Core::FINISHED);
}
pub(crate) fn has_finished(&self) -> bool {
self.core.contains(Core::FINISHED)
}
}
impl TeeCore {
pub(crate) fn enable_weapon_hit(&mut self) {
self.core.remove(
Core::WEAPON_HAMMER_OFF
| Core::WEAPON_SHOTGUN_OFF
| Core::WEAPON_GRENADE_OFF
| Core::WEAPON_LASER_OFF,
);
}
pub(crate) fn disable_weapon_hit(&mut self) {
self.core.insert(
Core::WEAPON_HAMMER_OFF
| Core::WEAPON_SHOTGUN_OFF
| Core::WEAPON_GRENADE_OFF
| Core::WEAPON_LASER_OFF,
);
}
pub(crate) fn can_hammer(&self) -> bool {
!self.core.intersects(Core::SOLO | Core::WEAPON_HAMMER_OFF)
}
pub(crate) fn tee_collision_disabled(&self) -> bool {
self.core.intersects(Core::SOLO | Core::TEE_COLLISION_OFF)
}
pub(crate) fn is_solo(&self) -> bool {
self.core.intersects(Core::SOLO)
}
pub(crate) fn set_solo(&mut self, enabled: bool) {
self.core.set(Core::SOLO, enabled);
}
pub(crate) fn can_be_ninjaed(&self) -> bool {
!self.core.intersects(Core::SOLO | Core::TEE_COLLISION_OFF)
}
pub(crate) fn telegun_gun(&self) -> bool {
self.core.intersects(Core::TELEGUN_GUN)
}
pub(crate) fn set_telegun_gun(&mut self, enabled: bool) {
self.core.set(Core::TELEGUN_GUN, enabled);
}
pub(crate) fn telegun_laser(&self) -> bool {
self.core.intersects(Core::TELEGUN_LASER)
}
pub(crate) fn set_telegun_laser(&mut self, enabled: bool) {
self.core.set(Core::TELEGUN_LASER, enabled);
}
pub(crate) fn telegun_grenade(&self) -> bool {
self.core.intersects(Core::TELEGUN_GRENADE)
}
pub(crate) fn set_telegun_grenade(&mut self, enabled: bool) {
self.core.set(Core::TELEGUN_GRENADE, enabled);
}
pub(crate) fn save_tee_collision(&self) -> bool {
!self.core.intersects(Core::TEE_COLLISION_OFF)
}
pub(crate) fn set_tee_collision(&mut self, enabled: bool) {
self.core.set(Core::TEE_COLLISION_OFF, !enabled);
}
pub(crate) fn save_weapon_hit(&self) -> i32 {
let mut flags = 0;
if self.core.intersects(Core::WEAPON_HAMMER_OFF) {
flags |= 1;
}
if self.core.intersects(Core::WEAPON_SHOTGUN_OFF) {
flags |= 2;
}
if self.core.intersects(Core::WEAPON_GRENADE_OFF) {
flags |= 4;
}
if self.core.intersects(Core::WEAPON_LASER_OFF) {
flags |= 8;
}
flags
}
pub(crate) fn load_weapon_hit(&mut self, flags: i32) {
self.core.set(Core::WEAPON_HAMMER_OFF, flags & 1 != 0);
self.core.set(Core::WEAPON_SHOTGUN_OFF, flags & 2 != 0);
self.core.set(Core::WEAPON_GRENADE_OFF, flags & 4 != 0);
self.core.set(Core::WEAPON_LASER_OFF, flags & 8 != 0);
}
}
impl TeeCore {
pub(crate) fn collides_point(&self, cood: Vec2<f32>) -> bool {
self.pos.distance_squared(cood) <= TEE_PROXIMITY_SQUARED
}
}
impl TeeCore {
pub(crate) fn get_tee_pos(&self) -> Vec2<i32> {
Vec2::new(self.pos.x.round() as i32, self.pos.y.round() as i32)
}
pub(crate) fn set_tee_pos(&mut self, pos: Vec2<i32>) {
self.pos = Vec2::new(pos.x as f32, pos.y as f32);
}
}
#[cfg(test)]
mod test {
use super::TeeCore;
#[test]
fn saturated_add() {
assert_eq!(TeeCore::saturated_add(10.0, 5.0, 3.0), 8.0);
assert_eq!(TeeCore::saturated_add(10.0, 5.0, 8.0), 10.0);
assert_eq!(TeeCore::saturated_add(10.0, 13.0, 3.0), 13.0);
assert_eq!(TeeCore::saturated_add(10.0, 13.0, -1.0), 12.0);
}
#[test]
fn saturated_add_neg() {
assert_eq!(TeeCore::saturated_add(10.0, -5.0, -3.0), -8.0);
assert_eq!(TeeCore::saturated_add(10.0, -5.0, -8.0), -10.0);
assert_eq!(TeeCore::saturated_add(10.0, -13.0, -3.0), -13.0);
assert_eq!(TeeCore::saturated_add(10.0, -13.0, 1.0), -12.0);
}
}