This commit is contained in:
Robin Appelman 2025-06-26 20:22:16 +02:00
commit 8000c148d0
2 changed files with 72 additions and 5 deletions

View file

@ -2,6 +2,7 @@
use crate::state::{ParsedDemo, SearchableEvent};
use js_sys::Function;
use std::iter::once;
use tf_demo_parser::demo::header::Header;
use tf_demo_parser::demo::parser::analyser::UserInfo;
use tf_demo_parser::demo::parser::gamestateanalyser::{GameStateAnalyser, World};
@ -44,6 +45,7 @@ impl From<World> for WorldBoundaries {
pub struct FlatState {
pub player_count: usize,
pub building_count: usize,
pub has_cart: bool,
pub projectile_count: usize,
pub boundaries: WorldBoundaries,
pub interval_per_tick: f32,
@ -69,17 +71,20 @@ impl FlatState {
max_building_count,
max_projectile_count,
tick,
cart,
..
} = parsed;
let player_count = players.len();
let building_count = max_building_count;
let projectile_count = max_projectile_count;
let has_cart = !cart.is_empty();
let flat: Vec<_> = players
.into_iter()
.chain(buildings)
.chain(projectiles)
.chain(once(cart))
.flat_map(Vec::into_iter)
.collect();
@ -87,6 +92,7 @@ impl FlatState {
player_count,
building_count,
projectile_count,
has_cart,
tick_count: tick as u32,
boundaries: world.into(),
interval_per_tick: header.duration / (header.ticks as f32),

View file

@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize};
use tf_demo_parser::demo::data::game_state::{PlayerCondition, Projectile, ProjectileType};
use tf_demo_parser::demo::data::game_state::{
Cart, Objective, PlayerCondition, Projectile, ProjectileType,
};
use tf_demo_parser::demo::data::DemoTick;
use tf_demo_parser::demo::gamevent::GameEvent;
use tf_demo_parser::demo::header::Header;
@ -34,6 +36,7 @@ pub struct ParsedDemo {
pub players: Vec<Vec<u8>>,
pub buildings: Vec<Vec<u8>>,
pub projectiles: Vec<Vec<u8>>,
pub cart: Vec<u8>,
pub kills: Vec<Kill>,
pub events: Vec<SearchableEvent>,
pub header: Header,
@ -50,6 +53,7 @@ impl ParsedDemo {
players: Vec::new(),
buildings: Vec::new(),
projectiles: Vec::new(),
cart: Vec::new(),
kills: Vec::new(),
player_info: Vec::new(),
max_building_count: 0,
@ -135,6 +139,10 @@ impl ParsedDemo {
parsed_projectiles.extend_from_slice(&state.pack(world));
}
self.tick += 1;
if let Some(cart) = game_state.objectives.values().find_map(Objective::as_cart) {
let state = CartState::new(cart);
self.cart.extend_from_slice(&state.pack(world));
}
}
self.last_tick = game_state.tick;
}
@ -486,6 +494,7 @@ pub struct ProjectileState {
team: Team,
ty: ProjectileType,
angle: Angle,
critical: bool,
}
impl ProjectileState {
@ -501,6 +510,7 @@ impl ProjectileState {
angle: Angle::from(projectile.rotation.y),
team: projectile.team,
ty: projectile.ty,
critical: projectile.critical,
}
}
@ -509,11 +519,14 @@ impl ProjectileState {
let y = pack_f32(self.position.y, world.boundary_min.y, world.boundary_max.y).to_le_bytes();
// 1 bit team
// 3 bits for type
// 4 bits for angle, 16 angles should be enough for projectiles
let team = if self.team == Team::Blue { 0 } else { 1 };
let team_type = ((self.ty as u8) << 5) + ((team as u8) << 4);
[x[0], x[1], y[0], y[1], team_type, self.angle.0]
// 1 bit for critical
// 7 bits for angle
let angle_critical = ((self.critical as u8) << 7) + (self.angle.0 >> 1);
[x[0], x[1], y[0], y[1], team_type, angle_critical]
}
#[allow(dead_code)]
@ -536,13 +549,15 @@ impl ProjectileState {
Team::Red
};
let ty = ProjectileType::from((team_type >> 5) & 7);
let angle = Angle(bytes[5]);
let critical = bytes[5] >> 7 == 1;
let angle = Angle(bytes[5] << 1);
ProjectileState {
position: VectorXY { x, y },
angle,
team,
ty,
critical,
}
}
}
@ -559,6 +574,7 @@ fn test_projectile_packing() {
angle: Angle::from(123.0),
team: Team::Blue,
ty: ProjectileType::Flare,
critical: true,
};
let bytes = input.pack(&world);
@ -566,7 +582,8 @@ fn test_projectile_packing() {
let unpacked = ProjectileState::unpack(bytes, &world);
assert_eq!(input.ty, unpacked.ty);
assert_eq!(input.team, unpacked.team);
assert_eq!(input.angle, unpacked.angle);
assert_eq!(input.critical, unpacked.critical);
assert!(u8::abs_diff(input.angle.0, unpacked.angle.0) <= 1);
assert!(f32::abs(input.position.x - unpacked.position.x) < 0.5);
assert!(f32::abs(input.position.y - unpacked.position.y) < 0.5);
@ -635,3 +652,47 @@ impl SearchableEvent {
}
}
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct CartState {
position: VectorXY,
}
impl CartState {
const PACKET_SIZE: usize = 4;
pub fn new(cart: &Cart) -> Self {
let position = cart.position;
CartState {
position: VectorXY {
x: position.x,
y: position.y,
},
}
}
pub fn pack(&self, world: &World) -> [u8; Self::PACKET_SIZE] {
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();
[x[0], x[1], y[0], y[1]]
}
#[allow(dead_code)]
pub fn unpack(bytes: [u8; Self::PACKET_SIZE], world: &World) -> Self {
let x = unpack_f32(
u16::from_le_bytes([bytes[0], bytes[1]]),
world.boundary_min.x,
world.boundary_max.x,
);
let y = unpack_f32(
u16::from_le_bytes([bytes[2], bytes[3]]),
world.boundary_min.y,
world.boundary_max.y,
);
CartState {
position: VectorXY { x, y },
}
}
}