mirror of
https://codeberg.org/demostf/tf-demos-viewer.git
synced 2026-06-03 18:14:11 +02:00
some building fixes
This commit is contained in:
parent
23c0f49a8c
commit
37cc6dda99
4 changed files with 79 additions and 62 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
19
js/parser.ts
19
js/parser.ts
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/lib.rs
10
src/lib.rs
|
|
@ -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;
|
||||||
|
|
|
||||||
102
src/state.rs
102
src/state.rs
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue