some building fixes

This commit is contained in:
Robin Appelman 2022-08-27 14:38:33 +02:00
commit 37cc6dda99
4 changed files with 79 additions and 62 deletions

2
Cargo.lock generated
View file

@ -562,7 +562,7 @@ dependencies = [
[[package]] [[package]]
name = "tf-demo-parser" name = "tf-demo-parser"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/demostf/parser#cb65c0d3ec07ad67587817e7475db5585e1b245f" source = "git+https://github.com/demostf/parser#cf58a0609af6d135c09c8e0839a933e109938819"
dependencies = [ dependencies = [
"bitbuffer", "bitbuffer",
"enumflags2", "enumflags2",

View file

@ -8,6 +8,7 @@ export async function parseDemo(bytes: Uint8Array, progressCallback : (progress:
let buildingCount = state.building_count; let buildingCount = state.building_count;
let boundaries = state.boundaries; let boundaries = state.boundaries;
let interval_per_tick = state.interval_per_tick; let interval_per_tick = state.interval_per_tick;
let tickCount = state.tick_count;
let kill_ticks = m.get_kill_ticks(state); let kill_ticks = m.get_kill_ticks(state);
let attackers = m.get_attacker_ids(state); let attackers = m.get_attacker_ids(state);
let assisters = m.get_assister_ids(state); let assisters = m.get_assister_ids(state);
@ -57,7 +58,8 @@ export async function parseDemo(bytes: Uint8Array, progressCallback : (progress:
}, },
data, data,
kills, kills,
playerInfo playerInfo,
tickCount,
); );
} }
@ -130,6 +132,7 @@ export interface BuildingState {
}, },
angle: number, angle: number,
health: number, health: number,
level: number,
team: Team, team: Team,
buildingType: BuildingType, buildingType: BuildingType,
} }
@ -167,7 +170,7 @@ export class ParsedDemo {
public readonly kills: Kill[]; public readonly kills: Kill[];
public readonly playerInfo: PlayerInfo[]; public readonly playerInfo: PlayerInfo[];
constructor(playerCount: number, buildingCount: number, world: WorldBoundaries, header: Header, data: Uint8Array, kills: Kill[], playerInfo: PlayerInfo[]) { constructor(playerCount: number, buildingCount: number, world: WorldBoundaries, header: Header, data: Uint8Array, kills: Kill[], playerInfo: PlayerInfo[], tickCount: number) {
this.playerCount = playerCount; this.playerCount = playerCount;
this.buildingCount = buildingCount; this.buildingCount = buildingCount;
this.world = world; this.world = world;
@ -175,7 +178,7 @@ export class ParsedDemo {
this.data = data; this.data = data;
this.kills = kills; this.kills = kills;
this.playerInfo = playerInfo; this.playerInfo = playerInfo;
this.tickCount = data.length / (playerCount * PLAYER_PACK_SIZE + buildingCount * BUILDING_PACK_SIZE); this.tickCount = tickCount;
} }
getPlayer(tick: number, playerIndex: number): PlayerState { getPlayer(tick: number, playerIndex: number): PlayerState {
@ -189,10 +192,10 @@ export class ParsedDemo {
getBuilding(tick: number, buildingIndex: number): BuildingState { getBuilding(tick: number, buildingIndex: number): BuildingState {
if (buildingIndex >= this.buildingCount) { if (buildingIndex >= this.buildingCount) {
throw new Error("Player out of bounds"); throw new Error("Building out of bounds");
} }
const base = ((buildingIndex * this.tickCount) + tick) * BUILDING_PACK_SIZE; const base = (this.playerCount * this.tickCount * PLAYER_PACK_SIZE) + ((buildingIndex * this.tickCount) + tick) * BUILDING_PACK_SIZE;
return unpackBuilding(this.data, base, this.world); return unpackBuilding(this.data, base, this.world);
} }
} }
@ -227,7 +230,8 @@ function unpackBuilding(bytes: Uint8Array, base: number, world: WorldBoundaries)
const team_type_health = bytes[base + 4] + (bytes[base + 5] << 8); const team_type_health = bytes[base + 4] + (bytes[base + 5] << 8);
const angle = unpack_angle(bytes[base + 6]); const angle = unpack_angle(bytes[base + 6]);
const health = team_type_health & 1013; const health = team_type_health & 1013;
const team = (team_type_health >> 13) as Team; const team = (((team_type_health >> 13) & 1) === 0) ? Team.Blue : Team.Red;
const level = (team_type_health >> 14);
const buildingType = ((team_type_health >> 10) & 7) as BuildingType; const buildingType = ((team_type_health >> 10) & 7) as BuildingType;
return { return {
@ -235,6 +239,7 @@ function unpackBuilding(bytes: Uint8Array, base: number, world: WorldBoundaries)
angle, angle,
health, health,
team, team,
buildingType buildingType,
level,
} }
} }

View file

@ -46,6 +46,7 @@ pub struct FlatState {
pub building_count: usize, pub building_count: usize,
pub boundaries: WorldBoundaries, pub boundaries: WorldBoundaries,
pub interval_per_tick: f32, pub interval_per_tick: f32,
pub tick_count: u32,
kill_ticks: Box<[u32]>, kill_ticks: Box<[u32]>,
attackers: Box<[u8]>, attackers: Box<[u8]>,
assisters: Box<[u8]>, assisters: Box<[u8]>,
@ -62,21 +63,24 @@ impl FlatState {
players, players,
header, header,
buildings, buildings,
max_building_count,
tick,
.. ..
} = parsed; } = parsed;
let player_count = players.len(); let player_count = players.len();
let building_count = buildings.len(); let building_count = max_building_count;
let flat: Vec<_> = players let flat: Vec<_> = players
.into_iter() .into_iter()
.chain(buildings.into_iter()) .chain(buildings.into_iter())
.flat_map(|player| player.into_iter()) .flat_map(|data| data.into_iter())
.collect(); .collect();
FlatState { FlatState {
player_count, player_count,
building_count, building_count,
tick_count: tick as u32,
boundaries: world.into(), boundaries: world.into(),
interval_per_tick: header.duration / (header.ticks as f32), interval_per_tick: header.duration / (header.ticks as f32),
data: flat.into_boxed_slice(), data: flat.into_boxed_slice(),
@ -195,6 +199,8 @@ pub fn parse_demo_inner(
} }
} }
parsed_demo.finish();
let state = ticker.into_state(); let state = ticker.into_state();
parsed_demo.kills = state.kills; parsed_demo.kills = state.kills;

View file

@ -1,7 +1,8 @@
use tf_demo_parser::demo::header::Header; use tf_demo_parser::demo::header::Header;
use tf_demo_parser::demo::parser::analyser::UserInfo; use tf_demo_parser::demo::parser::analyser::UserInfo;
use tf_demo_parser::demo::parser::gamestateanalyser::{ use tf_demo_parser::demo::parser::gamestateanalyser::{
Building, Class, GameState, Kill, PlayerState as PlayerAliveState, Team, World, Building, Class, Dispenser, GameState, Kill, PlayerState as PlayerAliveState, Sentry, Team,
Teleporter, World,
}; };
use tf_demo_parser::demo::vector::VectorXY; use tf_demo_parser::demo::vector::VectorXY;
@ -30,6 +31,7 @@ pub struct ParsedDemo {
pub kills: Vec<Kill>, pub kills: Vec<Kill>,
pub header: Header, pub header: Header,
pub player_info: Vec<UserInfo>, pub player_info: Vec<UserInfo>,
pub max_building_count: usize,
} }
impl ParsedDemo { impl ParsedDemo {
@ -40,6 +42,7 @@ impl ParsedDemo {
buildings: Vec::new(), buildings: Vec::new(),
kills: Vec::new(), kills: Vec::new(),
player_info: Vec::new(), player_info: Vec::new(),
max_building_count: 0,
header, header,
} }
} }
@ -76,23 +79,32 @@ impl ParsedDemo {
let parsed_player = &mut self.players[index]; let parsed_player = &mut self.players[index];
parsed_player.extend_from_slice(&state.pack(world)); parsed_player.extend_from_slice(&state.pack(world));
} }
for (index, building) in game_state.buildings.iter().enumerate() {
self.max_building_count = self.max_building_count.max(game_state.buildings.len());
for (index, building) in game_state.buildings.values().enumerate() {
let state = BuildingState::new(building); let state = BuildingState::new(building);
if let None = self.buildings.get(index) { if let None = self.buildings.get(index) {
let mut new_building = let new_building =
Vec::with_capacity(self.header.ticks as usize * BuildingState::PACKET_SIZE); Vec::with_capacity(self.header.ticks as usize * BuildingState::PACKET_SIZE);
new_building.resize(self.tick * BuildingState::PACKET_SIZE, 0);
self.buildings.push(new_building); self.buildings.push(new_building);
}; };
let parsed_player = &mut self.players[index]; let parsed_building = &mut self.buildings[index];
parsed_player.extend_from_slice(&state.pack(world)); parsed_building.resize(self.tick * BuildingState::PACKET_SIZE, 0);
parsed_building.extend_from_slice(&state.pack(world));
} }
self.tick += 1; self.tick += 1;
} }
} }
pub fn finish(&mut self) {
for parsed_building in self.buildings.iter_mut() {
parsed_building.resize(self.tick * BuildingState::PACKET_SIZE, 0);
}
}
pub fn size(&self) -> usize { pub fn size(&self) -> usize {
self.players self.players
.iter() .iter()
@ -255,29 +267,29 @@ impl BuildingType {
pub fn from_building(building: &Building) -> Self { pub fn from_building(building: &Building) -> Self {
match building { match building {
Building::Sentry { is_mini: true, .. } => BuildingType::MiniSentry, Building::Sentry(Sentry { is_mini: true, .. }) => BuildingType::MiniSentry,
Building::Sentry { Building::Sentry(Sentry {
is_mini: false, is_mini: false,
level: 1, level: 1,
.. ..
} => BuildingType::Level1Sentry, }) => BuildingType::Level1Sentry,
Building::Sentry { Building::Sentry(Sentry {
is_mini: false, is_mini: false,
level: 2, level: 2,
.. ..
} => BuildingType::Level2Sentry, }) => BuildingType::Level2Sentry,
Building::Sentry { Building::Sentry(Sentry {
is_mini: false, is_mini: false,
level: 3, level: 3,
.. ..
} => BuildingType::Level3Sentry, }) => BuildingType::Level3Sentry,
Building::Dispenser { .. } => BuildingType::Dispenser, Building::Dispenser(Dispenser { .. }) => BuildingType::Dispenser,
Building::Teleporter { Building::Teleporter(Teleporter {
is_entrance: true, .. is_entrance: true, ..
} => BuildingType::TeleporterEntrance, }) => BuildingType::TeleporterEntrance,
Building::Teleporter { Building::Teleporter(Teleporter {
is_entrance: false, .. is_entrance: false, ..
} => BuildingType::TeleporterExit, }) => BuildingType::TeleporterExit,
_ => BuildingType::Unknown, _ => BuildingType::Unknown,
} }
} }
@ -290,43 +302,24 @@ pub struct BuildingState {
health: u16, health: u16,
team: Team, team: Team,
ty: BuildingType, ty: BuildingType,
level: u8,
} }
impl BuildingState { impl BuildingState {
const PACKET_SIZE: usize = 7; const PACKET_SIZE: usize = 7;
pub fn new(building: &Building) -> Self { pub fn new(building: &Building) -> Self {
match building { let position = building.position();
Building::Sentry { BuildingState {
position,
angle,
health,
team,
..
}
| Building::Dispenser {
position,
angle,
health,
team,
..
}
| Building::Teleporter {
position,
angle,
health,
team,
..
} => BuildingState {
position: VectorXY { position: VectorXY {
x: position.x, x: position.x,
y: position.y, y: position.y,
}, },
angle: Angle::from(*angle), angle: Angle::from(building.angle()),
health: *health, health: building.health(),
team: *team, team: building.team(),
ty: BuildingType::from_building(building), ty: BuildingType::from_building(building),
}, level: building.level(),
} }
} }
@ -341,11 +334,15 @@ impl BuildingState {
let x = pack_f32(self.position.x, world.boundary_min.x, world.boundary_max.x).to_le_bytes(); 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(); let y = pack_f32(self.position.y, world.boundary_min.y, world.boundary_max.y).to_le_bytes();
// 1 bits reserved // 2 bits level
// 2 bits reserved // 1 bit team
// 3 bits for type // 3 bits for type
// 10 bits for health // 10 bits for health
let team_type_health = ((self.team as u16) << 13) + ((self.ty as u16) << 10) + self.health; let team = if self.team == Team::Blue { 0 } else { 1 };
let team_type_health = ((self.level as u16) << 14)
+ ((team as u16) << 13)
+ ((self.ty as u16) << 10)
+ self.health;
let combined_bytes = team_type_health.to_le_bytes(); let combined_bytes = team_type_health.to_le_bytes();
[ [
@ -379,8 +376,14 @@ impl BuildingState {
let team_type_health = u16::from_le_bytes([bytes[4], bytes[5]]); let team_type_health = u16::from_le_bytes([bytes[4], bytes[5]]);
let health = team_type_health & 1023; let health = team_type_health & 1023;
let angle = Angle(bytes[6]); let angle = Angle(bytes[6]);
let team = Team::new(team_type_health >> 13); let packed_team = (team_type_health >> 13) & 1;
let team = if packed_team == 0 {
Team::Blue
} else {
Team::Red
};
let ty = BuildingType::new((team_type_health >> 10) as u8 & 7); let ty = BuildingType::new((team_type_health >> 10) as u8 & 7);
let level = (team_type_health >> 14) as u8;
BuildingState { BuildingState {
position: VectorXY { x, y }, position: VectorXY { x, y },
@ -388,6 +391,7 @@ impl BuildingState {
health, health,
team, team,
ty, ty,
level,
} }
} }
} }
@ -417,6 +421,7 @@ fn test_building_packing() {
angle: Angle::from(213.0), angle: Angle::from(213.0),
health: 250, health: 250,
team: Team::Blue, team: Team::Blue,
level: 3,
ty: BuildingType::Level1Sentry, ty: BuildingType::Level1Sentry,
}; };
@ -427,6 +432,7 @@ fn test_building_packing() {
assert_eq!(input.health, unpacked.health); assert_eq!(input.health, unpacked.health);
assert_eq!(input.ty, unpacked.ty); assert_eq!(input.ty, unpacked.ty);
assert_eq!(input.team, unpacked.team); assert_eq!(input.team, unpacked.team);
assert_eq!(input.level, unpacked.level);
assert!(f32::abs(input.position.x - unpacked.position.x) < 0.5); assert!(f32::abs(input.position.x - unpacked.position.x) < 0.5);
assert!(f32::abs(input.position.y - unpacked.position.y) < 0.5); assert!(f32::abs(input.position.y - unpacked.position.y) < 0.5);