use crate::enums::LaserGunType;
use crate::flags;
use crate::items;
use crate::time::{Duration, Instant};
use crate::SnapId;
use crate::{enums as twenums, events, Events, Position, SkinColor, Snap};
use arrayvec::ArrayString;
use bitflags::Flags;
use fixed::traits::Fixed;
use libtw2_gamenet_ddnet::enums::{Lasertype, Sound, Team};
use libtw2_gamenet_ddnet::{enums as libenums, snap_obj};
use std::iter;
use vek::Vec2;
fn vec2<T: Fixed<Bits = i32>>(x: i32, y: i32) -> Vec2<T> {
Vec2::new(T::from_bits(x), T::from_bits(y))
}
fn skin_color(c: i32) -> SkinColor {
let [a, h, s, l_upper] = c.to_le_bytes();
SkinColor { h, s, l_upper, a }
}
fn boolean(x: i32) -> bool {
x != 0
}
fn i32_u8(x: i32) -> u8 {
x.try_into().unwrap_or(0)
}
fn cmn(c: snap_obj::Common) -> Position {
vec2(c.x, c.y)
}
fn i32_fixed<T: Fixed<Bits = i32>>(x: i32) -> T {
T::from_bits(x)
}
fn i32_string<const N: usize, const M: usize>(str: &mut ArrayString<N>, i32s: &[i32; M]) {
assert_eq!(M * 4 - 1, N);
let mut data = [0; N];
for (n, bytes) in i32s.iter().zip(data.chunks_mut(4)) {
bytes.copy_from_slice(&n.to_be_bytes()[..bytes.len()]);
}
for byte in &mut data {
*byte = byte.wrapping_add(128);
}
let nul_position = data.iter().position(|b| *b == 0).unwrap_or(data.len());
let validated = match std::str::from_utf8(&data[..nul_position]) {
Ok(str) => str,
Err(err) => std::str::from_utf8(&data[..err.valid_up_to()]).unwrap(),
};
str.clear();
str.push_str(validated);
}
fn i32_flags<T: Flags<Bits = i32>>(bits: i32) -> T {
T::from_bits_truncate(bits)
}
fn u64_flags_lower<T: Flags<Bits = u64>>(flags: &mut T, bits: i32) {
flags.remove(T::from_bits_truncate(0x0000_0000_ffff_ffff));
flags.insert(T::from_bits_truncate(bits as u64));
}
fn u64_flags_upper<T: Flags<Bits = u64>>(flags: &mut T, bits: i32) {
flags.remove(T::from_bits_truncate(0xffff_ffff_0000_0000));
flags.insert(T::from_bits_truncate((bits as u64) << 32));
}
impl From<libenums::Weapon> for twenums::ActiveWeapon {
fn from(value: libenums::Weapon) -> Self {
match value {
libenums::Weapon::Hammer => twenums::ActiveWeapon::Hammer,
libenums::Weapon::Pistol => twenums::ActiveWeapon::Pistol,
libenums::Weapon::Shotgun => twenums::ActiveWeapon::Shotgun,
libenums::Weapon::Grenade => twenums::ActiveWeapon::Grenade,
libenums::Weapon::Rifle => twenums::ActiveWeapon::Rifle,
libenums::Weapon::Ninja => twenums::ActiveWeapon::Ninja,
}
}
}
impl From<libenums::Weapon> for twenums::CollectableWeapon {
fn from(value: libenums::Weapon) -> Self {
use libenums::Weapon;
use twenums::CollectableWeapon;
match value {
Weapon::Hammer | Weapon::Pistol => CollectableWeapon::default(),
Weapon::Shotgun => CollectableWeapon::Shotgun,
Weapon::Grenade => CollectableWeapon::Grenade,
Weapon::Rifle => CollectableWeapon::Rifle,
Weapon::Ninja => CollectableWeapon::Ninja,
}
}
}
impl From<libenums::Emote> for twenums::Emote {
fn from(value: libenums::Emote) -> Self {
match value {
libenums::Emote::Normal => twenums::Emote::Normal,
libenums::Emote::Pain => twenums::Emote::Pain,
libenums::Emote::Happy => twenums::Emote::Happy,
libenums::Emote::Surprise => twenums::Emote::Surprise,
libenums::Emote::Angry => twenums::Emote::Angry,
libenums::Emote::Blink => twenums::Emote::Blink,
}
}
}
impl From<libenums::Sound> for twenums::Sound {
fn from(value: Sound) -> Self {
Self::from(value.to_i32())
}
}
impl From<libenums::Team> for twenums::ClientTeam {
fn from(value: libenums::Team) -> Self {
match value {
Team::Spectators => twenums::ClientTeam::Spectator,
Team::Red => twenums::ClientTeam::Red,
Team::Blue => twenums::ClientTeam::Blue,
}
}
}
impl From<snap_obj::Tick> for Instant {
fn from(value: snap_obj::Tick) -> Self {
Instant::from_snap_tick(value.0)
}
}
impl From<(libenums::Powerup, libenums::Weapon)> for twenums::Powerup {
fn from(value: (libenums::Powerup, libenums::Weapon)) -> Self {
use libenums::Powerup;
use twenums::CollectableWeapon;
match value.0 {
Powerup::Health => twenums::Powerup::Health,
Powerup::Armor => twenums::Powerup::Armor,
Powerup::Weapon => twenums::Powerup::Weapon(value.1.into()),
Powerup::Ninja => twenums::Powerup::Weapon(CollectableWeapon::Ninja),
Powerup::ArmorShotgun => twenums::Powerup::Shield(CollectableWeapon::Shotgun),
Powerup::ArmorGrenade => twenums::Powerup::Shield(CollectableWeapon::Grenade),
Powerup::ArmorNinja => twenums::Powerup::Shield(CollectableWeapon::Ninja),
Powerup::ArmorLaser => twenums::Powerup::Shield(CollectableWeapon::Rifle),
}
}
}
impl From<(libenums::Lasertype, i32)> for twenums::LaserType {
fn from(value: (Lasertype, i32)) -> Self {
match value.0 {
Lasertype::Rifle => Self::Rifle,
Lasertype::Shotgun => Self::Shotgun,
Lasertype::Door => Self::Door,
Lasertype::Freeze => Self::Freeze,
Lasertype::Dragger => Self::Dragger(twenums::LaserDraggerType::from(value.1)),
Lasertype::Gun => Self::Gun(LaserGunType::from(value.1)),
Lasertype::Plasma => Self::Plasma,
}
}
}
impl Snap {
fn new_event<T: Into<Events>>(&mut self, event: T) {
self.events.push(event.into())
}
}
impl Snap {
pub fn process_next<'a, T>(&mut self, items: T)
where
T: iter::Iterator<Item = &'a (snap_obj::SnapObj, u16)>,
{
self.clear();
for (obj, id) in items {
self.merge_libtw2_ddnet_snap_obj(*id, *obj);
}
}
pub fn merge_libtw2_ddnet_snap_obj(
&mut self,
id: u16,
snap_obj: libtw2_gamenet_ddnet::SnapObj,
) {
use libtw2_gamenet_ddnet::SnapObj;
let snap_id = SnapId::from_ddnet_snap_id(id);
match snap_obj {
SnapObj::PlayerInput(_) => {} SnapObj::Projectile(snap_obj::Projectile {
x,
y,
vel_x,
vel_y,
type_,
start_tick,
}) => {
let projectile = self.projectiles.get_mut_default(snap_id);
projectile.pos = vec2(x, y);
projectile.direction = Vec2::new(vel_x, vel_y);
projectile.kind = type_.into();
projectile.start_tick = start_tick.into();
}
SnapObj::Laser(snap_obj::Laser {
x,
y,
from_x,
from_y,
start_tick,
}) => {
let laser = self.lasers.get_mut_default(snap_id);
laser.to = vec2(x, y);
laser.from = vec2(from_x, from_y);
laser.start_tick = start_tick.into();
}
SnapObj::Pickup(snap_obj::Pickup {
x,
y,
type_,
subtype,
}) => {
let pickup = self.pickups.get_mut_default(snap_id);
pickup.pos = vec2(x, y);
let type_ = libenums::Powerup::from_i32(type_).unwrap_or(libenums::Powerup::Health);
let subtype =
libenums::Weapon::from_i32(subtype).unwrap_or(libenums::Weapon::Hammer);
pickup.kind = (type_, subtype).into();
}
SnapObj::Flag(snap_obj::Flag { x, y, team }) => {
let flag = self.pvp_flags.get_mut_default(snap_id);
flag.pos = vec2(x, y);
flag.pvp_team = twenums::PvpTeam::from(team);
}
SnapObj::GameInfo(snap_obj::GameInfo {
game_flags,
game_state_flags,
round_start_tick,
warmup_timer,
score_limit,
time_limit,
round_num,
round_current,
}) => {
let game_info = self.game_infos.get_mut_default(snap_id);
game_info.flags = i32_flags(game_flags);
game_info.game_state_flags = i32_flags(game_state_flags);
game_info.round_start_tick = round_start_tick.into();
game_info.warmup_timer = Duration::from_ticks(warmup_timer);
game_info.score_limit = score_limit;
game_info.time_limit = Duration::from_ticks(time_limit);
game_info.round_num = round_num;
game_info.round_current = round_current;
}
SnapObj::GameData(_) => {} SnapObj::CharacterCore(_) => {} #[allow(unused_variables)]
SnapObj::Character(snap_obj::Character {
character_core:
snap_obj::CharacterCore {
tick,
x,
y,
vel_x,
vel_y,
angle,
direction,
jumped,
hooked_player,
hook_state,
hook_tick,
hook_x,
hook_y,
hook_dx,
hook_dy,
},
player_flags,
health,
armor,
ammo_count,
weapon,
emote,
attack_tick,
}) => {
let player = self.players.get_mut_default(snap_id);
let tee = player.tee.get_or_insert(items::Tee::default());
tee.tick = snap_obj::Tick(tick).into();
tee.pos = vec2(x, y);
tee.vel = vec2(vel_x, vel_y);
tee.angle = i32_fixed(angle);
tee.direction = twenums::Direction::from(direction);
tee.jumped = i32_flags(jumped);
tee.hooked_player = SnapId::snap_from_i32(hooked_player);
tee.hook_state = twenums::HookState::from(hook_state);
tee.hook_tick = Duration::from_ticks(hook_tick.0);
tee.hook_pos = vec2(hook_x, hook_y);
tee.hook_direction = vec2(hook_dx, hook_dy);
u64_flags_upper(&mut tee.flags, player_flags);
tee.health = health;
tee.armor = armor;
tee.ammo_count = ammo_count;
tee.weapon = weapon.into();
tee.emote = emote.into();
tee.attack_tick = snap_obj::Tick(attack_tick).into();
}
SnapObj::PlayerInfo(snap_obj::PlayerInfo {
local,
client_id,
team,
score,
latency,
}) => {
let _ = client_id;
let player = self.players.get_mut_default(snap_id);
player.local = boolean(local);
player.teeworlds_team = team.into();
player.score = score;
player.latency = latency;
}
SnapObj::ClientInfo(snap_obj::ClientInfo {
name,
clan,
country,
skin,
use_custom_color,
color_body,
color_feet,
}) => {
let player = self.players.get_mut_default(snap_id);
i32_string(&mut player.name, &name);
i32_string(&mut player.clan, &clan);
player.country = country;
i32_string(&mut player.skin, &skin);
player.use_custom_color = boolean(use_custom_color);
player.color_body = skin_color(color_body);
player.color_feet = skin_color(color_feet);
}
SnapObj::SpectatorInfo(_) => {} SnapObj::MyOwnObject(_) => {}
SnapObj::DdnetCharacter(snap_obj::DdnetCharacter {
flags,
freeze_end,
jumps,
tele_checkpoint,
strong_weak_id,
jumped_total,
ninja_activation_tick,
freeze_start,
target_x,
target_y,
}) => {
let player = self.players.get_mut_default(snap_id);
let tee = player.tee.get_or_insert(items::Tee::default());
u64_flags_lower(&mut tee.flags, flags);
tee.freeze_end = freeze_end.into();
tee.jumps = jumps;
tee.tele_checkpoint = tele_checkpoint;
tee.strong_weak_id = strong_weak_id;
tee.jumped_total = jumped_total;
tee.ninja_activation_tick = ninja_activation_tick.into();
tee.freeze_start = freeze_start.into();
tee.target = vec2(target_x, target_y);
}
SnapObj::DdnetPlayer(snap_obj::DdnetPlayer { flags, auth_level }) => {
let player = self.players.get_mut_default(snap_id);
player.flags = i32_flags(flags);
player.auth_level = twenums::Authed::from(auth_level);
}
SnapObj::GameInfoEx(snap_obj::GameInfoEx {
flags,
version,
flags2,
}) => {
let game_info = self.game_infos.get_mut_default(snap_id);
u64_flags_lower(&mut game_info.flags_ex, flags);
let _ = version;
u64_flags_upper(&mut game_info.flags_ex, flags2);
}
SnapObj::DdraceProjectile(snap_obj::DdraceProjectile {
x,
y,
angle,
data,
type_,
start_tick,
}) => {
let projectile = self.map_projectiles.get_mut_default(snap_id);
projectile.pos = vec2(x, y);
let dir = Vec2::new(100., 0.).rotated_z(angle as f32 / 1_000_000.);
projectile.direction = Vec2::new(dir.x as i32, dir.y as i32);
let _ = data;
projectile.kind = type_.into();
projectile.start_tick = start_tick.into();
}
SnapObj::DdnetLaser(snap_obj::DdnetLaser {
to_x,
to_y,
from_x,
from_y,
start_tick,
owner,
type_,
switch_number,
subtype,
}) => {
let laser = self.lasers.get_mut_default(snap_id);
laser.to = vec2(to_x, to_y);
laser.from = vec2(from_x, from_y);
laser.start_tick = start_tick.into();
laser.owner = SnapId::snap_from_i32(owner);
let laser_kind =
libenums::Lasertype::from_i32(type_).unwrap_or(libenums::Lasertype::Rifle);
laser.kind = (laser_kind, subtype).into();
laser.switch_number = i32_u8(switch_number);
}
SnapObj::DdnetProjectile(snap_obj::DdnetProjectile {
x,
y,
vel_x,
vel_y,
type_,
start_tick,
owner,
switch_number,
tune_zone,
flags,
}) => match SnapId::snap_from_i32(owner) {
None => {
let projectile = self.map_projectiles.get_mut_default(snap_id);
projectile.pos = vec2(x / 100, y / 100);
projectile.direction = Vec2::new(vel_x, vel_y);
projectile.kind = type_.into();
projectile.start_tick = start_tick.into();
projectile.switch_number = i32_u8(switch_number);
projectile.tune_zone = i32_u8(tune_zone);
projectile.flags = i32_flags(flags);
if projectile
.flags
.contains(flags::ProjectileFlags::NORMALIZE_VEL)
{
}
}
Some(owner) => {
let projectile = self.projectiles.get_mut_default(snap_id);
projectile.pos = vec2(x / 100, y / 100);
projectile.direction = Vec2::new(vel_x, vel_y);
projectile.kind = type_.into();
projectile.start_tick = start_tick.into();
projectile.owner = owner;
projectile.tune_zone = i32_u8(tune_zone);
let flags = flags::ProjectileFlags::from_bits_truncate(flags);
if flags.contains(flags::ProjectileFlags::NORMALIZE_VEL) {
}
}
},
SnapObj::DdnetPickup(snap_obj::DdnetPickup {
x,
y,
type_,
subtype,
switch_number,
}) => {
let pickup = self.pickups.get_mut_default(snap_id);
pickup.pos = vec2(x, y);
let type_ = libenums::Powerup::from_i32(type_).unwrap_or(libenums::Powerup::Health);
let subtype =
libenums::Weapon::from_i32(subtype).unwrap_or(libenums::Weapon::Hammer);
pickup.kind = (type_, subtype).into();
pickup.switch_number = i32_u8(switch_number);
}
SnapObj::Common(_) => unreachable!(),
SnapObj::Explosion(snap_obj::Explosion { common }) => {
self.new_event(events::Explosion { pos: cmn(common) })
}
SnapObj::Spawn(snap_obj::Spawn { common }) => {
self.new_event(events::Spawn { pos: cmn(common) })
}
SnapObj::HammerHit(snap_obj::HammerHit { common }) => {
self.new_event(events::HammerHit { pos: cmn(common) })
}
SnapObj::Death(snap_obj::Death { common, client_id }) => {
if let Some(player) = SnapId::snap_from_i32(client_id) {
self.new_event(events::Death {
pos: cmn(common),
player,
})
}
}
SnapObj::SoundGlobal(snap_obj::SoundGlobal { common, sound_id }) => {
let _ = common;
self.new_event(events::SoundGlobal {
sound: sound_id.into(),
})
}
SnapObj::SoundWorld(snap_obj::SoundWorld { common, sound_id }) => {
self.new_event(events::Sound {
pos: cmn(common),
sound: sound_id.into(),
})
}
SnapObj::DamageInd(snap_obj::DamageInd { common, angle }) => {
self.new_event(events::DamageIndicator {
pos: cmn(common),
angle: i32_fixed(angle),
})
}
SnapObj::MyOwnEvent(_) => unreachable!(),
SnapObj::SpecChar(_) => {} SnapObj::SwitchState(_) => {} SnapObj::EntityEx(_) => {} }
}
}