mirror of
https://codeberg.org/demostf/tf-demos-viewer.git
synced 2026-06-03 18:14:11 +02:00
spy, medigun type, building progress
This commit is contained in:
parent
8000c148d0
commit
775c8080ac
3 changed files with 145 additions and 29 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -541,9 +541,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tf-demo-parser"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf498d183c4e849900a3f0c2127ba19a273b8969463075f4358edcfa5f740c2"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"bitbuffer",
|
||||
"enumflags2",
|
||||
|
|
@ -564,7 +562,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tf-demos-viewer"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "tf-demos-viewer"
|
||||
description = "JS bindings for demo parser"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||
categories = ["wasm"]
|
||||
edition = "2021"
|
||||
|
|
@ -26,7 +26,8 @@ wasm-bindgen = "0.2.96"
|
|||
wee_alloc = { version = "0.4.2", optional = true }
|
||||
web-sys = { version = "0.3.22", features = ["console"] }
|
||||
js-sys = "0.3.22"
|
||||
tf-demo-parser = "0.6.3"
|
||||
# tf-demo-parser = { version = "0.7.0", git = "https://codeberg.org/demostf/parser" }
|
||||
tf-demo-parser = { version = "0.7.0", path = "../tf-demo-parser" }
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
|
||||
|
|
|
|||
163
src/state.rs
163
src/state.rs
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use tf_demo_parser::demo::data::game_state::{
|
||||
Cart, Objective, PlayerCondition, Projectile, ProjectileType,
|
||||
Cart, MedigunType, Objective, PlayerClassData, PlayerCondition, Projectile, ProjectileType,
|
||||
};
|
||||
use tf_demo_parser::demo::data::DemoTick;
|
||||
use tf_demo_parser::demo::gamevent::GameEvent;
|
||||
|
|
@ -77,13 +77,17 @@ impl ParsedDemo {
|
|||
},
|
||||
team: player.team,
|
||||
class: player.class,
|
||||
charge: player.charge,
|
||||
class_data: player.class_data.clone(),
|
||||
ubered: player.has_condition(PlayerCondition::Invulnerable)
|
||||
|| player.has_condition(PlayerCondition::MedigunUberBlastResist)
|
||||
|| player.has_condition(PlayerCondition::MedigunUberBulletResist)
|
||||
|| player.has_condition(PlayerCondition::MedigunUberFireResist)
|
||||
|| player.has_condition(PlayerCondition::CritBoosted)
|
||||
|| player.has_condition(PlayerCondition::MegaHeal),
|
||||
cloaked: player.has_condition(PlayerCondition::Stealthed)
|
||||
|| player.has_condition(PlayerCondition::StealthedBlink)
|
||||
|| player.has_condition(PlayerCondition::StealthedUserBuff)
|
||||
|| player.has_condition(PlayerCondition::StealthedUserBuffFading),
|
||||
};
|
||||
|
||||
if self.players.get(index).is_none() {
|
||||
|
|
@ -177,22 +181,15 @@ pub struct PlayerState {
|
|||
health: u16,
|
||||
team: Team,
|
||||
class: Class,
|
||||
charge: u8,
|
||||
class_data: PlayerClassData,
|
||||
ubered: bool,
|
||||
cloaked: bool,
|
||||
}
|
||||
|
||||
impl PlayerState {
|
||||
const PACKET_SIZE: usize = 8;
|
||||
const PACKET_SIZE: usize = 9;
|
||||
|
||||
pub fn pack(&self, world: &World) -> [u8; Self::PACKET_SIZE] {
|
||||
// for the purpose of viewing the demo in the browser we dont really need high accuracy for
|
||||
// position or angle, so we save a bunch of space by truncating those down to half the number
|
||||
// of bits
|
||||
fn pack_f32(val: f32, min: f32, max: f32) -> u16 {
|
||||
let ratio = (val - min) / (max - min);
|
||||
(ratio * u16::MAX as f32) as u16
|
||||
}
|
||||
|
||||
let x = pack_f32(self.position.x, world.boundary_min.x, world.boundary_max.x).to_le_bytes();
|
||||
let y = pack_f32(self.position.y, world.boundary_min.y, world.boundary_max.y).to_le_bytes();
|
||||
// 2 bits for team
|
||||
|
|
@ -205,6 +202,22 @@ impl PlayerState {
|
|||
+ (self.ubered as u16);
|
||||
let combined_bytes = team_class_health.to_le_bytes();
|
||||
|
||||
let class_bits = match self.class_data {
|
||||
PlayerClassData::Medic { charge, medigun } => [charge, medigun as u8],
|
||||
PlayerClassData::Spy {
|
||||
disguise_class,
|
||||
disguise_team,
|
||||
cloak,
|
||||
} => {
|
||||
let team_class = ((disguise_team as u8) << 6) + ((disguise_class as u8) << 2);
|
||||
[
|
||||
team_class,
|
||||
((cloak as u8).min(100) << 1) + (self.cloaked as u8),
|
||||
]
|
||||
}
|
||||
_ => [0, 0],
|
||||
};
|
||||
|
||||
[
|
||||
x[0],
|
||||
x[1],
|
||||
|
|
@ -213,12 +226,13 @@ impl PlayerState {
|
|||
combined_bytes[0],
|
||||
combined_bytes[1],
|
||||
self.angle.0,
|
||||
self.charge,
|
||||
class_bits[0],
|
||||
class_bits[1],
|
||||
]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn unpack(bytes: [u8; 8], world: &World) -> Self {
|
||||
pub fn unpack(bytes: [u8; Self::PACKET_SIZE], world: &World) -> Self {
|
||||
fn unpack_f32(val: u16, min: f32, max: f32) -> f32 {
|
||||
let ratio = val as f32 / (u16::MAX as f32);
|
||||
ratio * (max - min) + min
|
||||
|
|
@ -240,7 +254,27 @@ impl PlayerState {
|
|||
let angle = Angle(bytes[6]);
|
||||
let team = Team::new(team_class_health >> 14);
|
||||
let class = Class::new((team_class_health >> 10) & 15);
|
||||
let charge = bytes[7];
|
||||
let class_bits = [bytes[7], bytes[8]];
|
||||
let mut cloaked = false;
|
||||
|
||||
let class_data = match class {
|
||||
Class::Medic => PlayerClassData::Medic {
|
||||
charge: class_bits[0],
|
||||
medigun: MedigunType::try_from(class_bits[1]).unwrap_or_default(),
|
||||
},
|
||||
Class::Spy => {
|
||||
let disguise_team = Team::new(class_bits[0] >> 6);
|
||||
let disguise_class = Class::new((class_bits[0] >> 2) & 15);
|
||||
let cloak = f32::from(class_bits[1] >> 1);
|
||||
cloaked = class_bits[1] & 1 == 1;
|
||||
PlayerClassData::Spy {
|
||||
disguise_class,
|
||||
disguise_team,
|
||||
cloak,
|
||||
}
|
||||
}
|
||||
_ => PlayerClassData::None,
|
||||
};
|
||||
|
||||
PlayerState {
|
||||
position: VectorXY { x, y },
|
||||
|
|
@ -248,8 +282,9 @@ impl PlayerState {
|
|||
health,
|
||||
team,
|
||||
class,
|
||||
charge,
|
||||
class_data,
|
||||
ubered,
|
||||
cloaked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +307,7 @@ fn get_world() -> World {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_packing() {
|
||||
fn test_player_packing_demo() {
|
||||
let world = get_world();
|
||||
|
||||
let input = PlayerState {
|
||||
|
|
@ -284,8 +319,9 @@ fn test_player_packing() {
|
|||
health: 250,
|
||||
team: Team::Blue,
|
||||
class: Class::Demoman,
|
||||
charge: 7,
|
||||
class_data: PlayerClassData::None,
|
||||
ubered: false,
|
||||
cloaked: false,
|
||||
};
|
||||
|
||||
let bytes = input.pack(&world);
|
||||
|
|
@ -295,7 +331,77 @@ fn test_player_packing() {
|
|||
assert_eq!(input.health, unpacked.health);
|
||||
assert_eq!(input.class, unpacked.class);
|
||||
assert_eq!(input.team, unpacked.team);
|
||||
assert_eq!(input.charge, unpacked.charge);
|
||||
assert_eq!(input.class_data, unpacked.class_data);
|
||||
|
||||
assert!(f32::abs(input.position.x - unpacked.position.x) < 0.5);
|
||||
assert!(f32::abs(input.position.y - unpacked.position.y) < 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_packing_medic() {
|
||||
let world = get_world();
|
||||
|
||||
let input = PlayerState {
|
||||
position: VectorXY {
|
||||
x: 100.0,
|
||||
y: -5000.0,
|
||||
},
|
||||
angle: Angle::from(213.0),
|
||||
health: 250,
|
||||
team: Team::Blue,
|
||||
class: Class::Medic,
|
||||
class_data: PlayerClassData::Medic {
|
||||
charge: 52,
|
||||
medigun: MedigunType::Quickfix,
|
||||
},
|
||||
ubered: true,
|
||||
cloaked: false,
|
||||
};
|
||||
|
||||
let bytes = input.pack(&world);
|
||||
|
||||
let unpacked = PlayerState::unpack(bytes, &world);
|
||||
assert_eq!(input.angle, unpacked.angle);
|
||||
assert_eq!(input.health, unpacked.health);
|
||||
assert_eq!(input.class, unpacked.class);
|
||||
assert_eq!(input.team, unpacked.team);
|
||||
assert_eq!(input.class_data, unpacked.class_data);
|
||||
|
||||
assert!(f32::abs(input.position.x - unpacked.position.x) < 0.5);
|
||||
assert!(f32::abs(input.position.y - unpacked.position.y) < 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_player_packing_spy() {
|
||||
let world = get_world();
|
||||
|
||||
let input = PlayerState {
|
||||
position: VectorXY {
|
||||
x: 100.0,
|
||||
y: -5000.0,
|
||||
},
|
||||
angle: Angle::from(213.0),
|
||||
health: 250,
|
||||
team: Team::Blue,
|
||||
class: Class::Spy,
|
||||
class_data: PlayerClassData::Spy {
|
||||
disguise_team: Team::Red,
|
||||
disguise_class: Class::Engineer,
|
||||
cloak: 57.0,
|
||||
},
|
||||
ubered: true,
|
||||
cloaked: true,
|
||||
};
|
||||
|
||||
let bytes = input.pack(&world);
|
||||
|
||||
let unpacked = PlayerState::unpack(bytes, &world);
|
||||
assert_eq!(input.angle, unpacked.angle);
|
||||
assert_eq!(input.health, unpacked.health);
|
||||
assert_eq!(input.class, unpacked.class);
|
||||
assert_eq!(input.team, unpacked.team);
|
||||
assert_eq!(input.class_data, unpacked.class_data);
|
||||
assert_eq!(input.cloaked, unpacked.cloaked);
|
||||
|
||||
assert!(f32::abs(input.position.x - unpacked.position.x) < 0.5);
|
||||
assert!(f32::abs(input.position.y - unpacked.position.y) < 0.5);
|
||||
|
|
@ -367,9 +473,10 @@ pub struct BuildingState {
|
|||
team: Team,
|
||||
ty: BuildingType,
|
||||
level: u8,
|
||||
build_progress: u8,
|
||||
}
|
||||
|
||||
// for the purpose of viewing the demo in the browser we dont really need high accuracy for
|
||||
// for the purpose of viewing the demo in the browser we don't really need high accuracy for
|
||||
// position or angle, so we save a bunch of space by truncating those down to half the number
|
||||
// of bits
|
||||
fn pack_f32(val: f32, min: f32, max: f32) -> u16 {
|
||||
|
|
@ -382,20 +489,27 @@ fn unpack_f32(val: u16, min: f32, max: f32) -> f32 {
|
|||
}
|
||||
|
||||
impl BuildingState {
|
||||
const PACKET_SIZE: usize = 7;
|
||||
const PACKET_SIZE: usize = 8;
|
||||
|
||||
pub fn new(building: &Building) -> Self {
|
||||
let position = building.position();
|
||||
let ty = BuildingType::from_building(building);
|
||||
let angle = match building {
|
||||
Building::Teleporter(teleporter) => teleporter.yaw_to_exit),
|
||||
Building::Sentry(sentry) => sentry.angle,
|
||||
_ => 0.0,
|
||||
};
|
||||
BuildingState {
|
||||
position: VectorXY {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
},
|
||||
angle: Angle::from(building.angle()),
|
||||
angle: Angle::from(angle),
|
||||
health: building.health(),
|
||||
team: building.team(),
|
||||
ty: BuildingType::from_building(building),
|
||||
ty,
|
||||
level: building.level(),
|
||||
build_progress: ((building.construction_progress() * 100.0) as u8).min(100),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -421,6 +535,7 @@ impl BuildingState {
|
|||
combined_bytes[0],
|
||||
combined_bytes[1],
|
||||
self.angle.0,
|
||||
self.build_progress,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -447,6 +562,7 @@ impl BuildingState {
|
|||
};
|
||||
let ty = BuildingType::new((team_type_health >> 10) as u8 & 7);
|
||||
let level = (team_type_health >> 14) as u8;
|
||||
let build_progress = bytes[7];
|
||||
|
||||
BuildingState {
|
||||
position: VectorXY { x, y },
|
||||
|
|
@ -455,6 +571,7 @@ impl BuildingState {
|
|||
team,
|
||||
ty,
|
||||
level,
|
||||
build_progress,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue