mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
show projectiles in viewer
This commit is contained in:
parent
213d2c6753
commit
22ad43a4b7
7 changed files with 215 additions and 89 deletions
|
|
@ -1,67 +1,79 @@
|
|||
import {ParsedDemo, PlayerState, WorldBoundaries, Kill, BuildingState} from "./Parser";
|
||||
import {BuildingState, Kill, ParsedDemo, PlayerState, ProjectileState, ProjectileType, WorldBoundaries} from "./Parser";
|
||||
|
||||
function getCacheBuster(): string {
|
||||
const url = document.querySelector('script[src*="viewer"]').attributes.src.value;
|
||||
return url.substring("/viewer.js".length);
|
||||
const url = document.querySelector('script[src*="viewer"]').attributes.src.value;
|
||||
return url.substring("/viewer.js".length);
|
||||
}
|
||||
|
||||
export class AsyncParser {
|
||||
buffer: ArrayBuffer;
|
||||
demo: ParsedDemo;
|
||||
world: WorldBoundaries;
|
||||
progressCallback: (progress: number) => void;
|
||||
buffer: ArrayBuffer;
|
||||
demo: ParsedDemo;
|
||||
world: WorldBoundaries;
|
||||
progressCallback: (progress: number) => void;
|
||||
|
||||
constructor(buffer: ArrayBuffer, progressCallback: (progress: number) => void) {
|
||||
this.buffer = buffer;
|
||||
this.progressCallback = progressCallback;
|
||||
}
|
||||
constructor(buffer: ArrayBuffer, progressCallback: (progress: number) => void) {
|
||||
this.buffer = buffer;
|
||||
this.progressCallback = progressCallback;
|
||||
}
|
||||
|
||||
cache(): Promise<ParsedDemo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(`/parse-worker.js${getCacheBuster()}`);
|
||||
worker.postMessage({
|
||||
buffer: this.buffer
|
||||
}, [this.buffer]);
|
||||
worker.onmessage = (event) => {
|
||||
if (event.data.error) {
|
||||
reject(event.data.error);
|
||||
return;
|
||||
} else if (event.data.progress) {
|
||||
this.progressCallback(event.data.progress);
|
||||
return;
|
||||
} else if (event.data.demo) {
|
||||
const cachedData: ParsedDemo = event.data.demo;
|
||||
console.log(`packed data: ${(cachedData.data.length / (1024 * 1024)).toFixed(1)}MB`);
|
||||
this.world = cachedData.world;
|
||||
this.demo = new ParsedDemo(cachedData.playerCount, cachedData.buildingCount, cachedData.world, cachedData.header, cachedData.data, cachedData.kills, cachedData.playerInfo, cachedData.tickCount);
|
||||
resolve(this.demo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
cache(): Promise<ParsedDemo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(`/parse-worker.js${getCacheBuster()}`);
|
||||
worker.postMessage({
|
||||
buffer: this.buffer
|
||||
}, [this.buffer]);
|
||||
worker.onmessage = (event) => {
|
||||
if (event.data.error) {
|
||||
reject(event.data.error);
|
||||
return;
|
||||
} else if (event.data.progress) {
|
||||
this.progressCallback(event.data.progress);
|
||||
return;
|
||||
} else if (event.data.demo) {
|
||||
const cachedData: ParsedDemo = event.data.demo;
|
||||
console.log(`packed data: ${(cachedData.data.length / (1024 * 1024)).toFixed(1)}MB`);
|
||||
this.world = cachedData.world;
|
||||
this.demo = new ParsedDemo(cachedData.playerCount, cachedData.buildingCount, cachedData.projectileCount, cachedData.world, cachedData.header, cachedData.data, cachedData.kills, cachedData.playerInfo, cachedData.tickCount);
|
||||
resolve(this.demo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPlayersAtTick(tick: number): PlayerState[] {
|
||||
const players: PlayerState[] = [];
|
||||
for (let i = 0; i < this.demo.playerCount; i++) {
|
||||
players.push(this.demo.getPlayer(tick, i));
|
||||
}
|
||||
getPlayersAtTick(tick: number): PlayerState[] {
|
||||
const players: PlayerState[] = [];
|
||||
for (let i = 0; i < this.demo.playerCount; i++) {
|
||||
players.push(this.demo.getPlayer(tick, i));
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
getBuildingsAtTick(tick: number): BuildingState[] {
|
||||
const buildings: BuildingState[] = [];
|
||||
for (let i = 0; i < this.demo.buildingCount; i++) {
|
||||
const building = this.demo.getBuilding(tick, i);
|
||||
if (building.health > 0) {
|
||||
buildings.push(building);
|
||||
}
|
||||
}
|
||||
getBuildingsAtTick(tick: number): BuildingState[] {
|
||||
const buildings: BuildingState[] = [];
|
||||
for (let i = 0; i < this.demo.buildingCount; i++) {
|
||||
const building = this.demo.getBuilding(tick, i);
|
||||
if (building.health > 0) {
|
||||
buildings.push(building);
|
||||
}
|
||||
}
|
||||
|
||||
return buildings;
|
||||
}
|
||||
return buildings;
|
||||
}
|
||||
|
||||
getKills(): Kill[] {
|
||||
return this.demo.kills
|
||||
}
|
||||
getProjectilesAtTick(tick: number): ProjectileState[] {
|
||||
const projectiles: ProjectileState[] = [];
|
||||
for (let i = 0; i < this.demo.projectileCount; i++) {
|
||||
const projectile = this.demo.getProjectile(tick, i);
|
||||
if (projectile.projectileType !== ProjectileType.Unknown && projectile.position.x > this.world.boundary_min.x && projectile.position.y > this.world.boundary_min.y) {
|
||||
projectiles.push(projectile);
|
||||
}
|
||||
}
|
||||
|
||||
return projectiles;
|
||||
}
|
||||
|
||||
getKills(): Kill[] {
|
||||
return this.demo.kills
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export async function parseDemo(bytes: Uint8Array, progressCallback: (progress:
|
|||
|
||||
let playerCount = state.player_count;
|
||||
let buildingCount = state.building_count;
|
||||
let projectileCount = state.projectile_count;
|
||||
let boundaries = state.boundaries;
|
||||
let interval_per_tick = state.interval_per_tick;
|
||||
let tickCount = state.tick_count;
|
||||
|
|
@ -56,6 +57,7 @@ export async function parseDemo(bytes: Uint8Array, progressCallback: (progress:
|
|||
return new ParsedDemo(
|
||||
playerCount,
|
||||
buildingCount,
|
||||
projectileCount,
|
||||
{
|
||||
boundary_min: {
|
||||
x: boundaries.boundary_min.x,
|
||||
|
|
@ -115,6 +117,16 @@ export enum BuildingType {
|
|||
Unknown = 7,
|
||||
}
|
||||
|
||||
export enum ProjectileType {
|
||||
Rocket = 0,
|
||||
HealingArrow = 1,
|
||||
Sticky = 2,
|
||||
Pipe = 3,
|
||||
Flare = 4,
|
||||
LooseCannon = 5,
|
||||
Unknown = 7,
|
||||
}
|
||||
|
||||
export interface WorldBoundaries {
|
||||
boundary_min: {
|
||||
x: number,
|
||||
|
|
@ -151,6 +163,16 @@ export interface BuildingState {
|
|||
buildingType: BuildingType,
|
||||
}
|
||||
|
||||
export interface ProjectileState {
|
||||
position: {
|
||||
x: number,
|
||||
y: number
|
||||
},
|
||||
angle: number,
|
||||
team: Team,
|
||||
projectileType: ProjectileType,
|
||||
}
|
||||
|
||||
export interface Header {
|
||||
interval_per_tick: number,
|
||||
map: string
|
||||
|
|
@ -177,6 +199,7 @@ function unpack_angle(val: number): number {
|
|||
export class ParsedDemo {
|
||||
public readonly playerCount: number;
|
||||
public readonly buildingCount: number;
|
||||
public readonly projectileCount: number;
|
||||
public readonly world: WorldBoundaries;
|
||||
public readonly data: Uint8Array;
|
||||
public readonly header: Header;
|
||||
|
|
@ -184,9 +207,10 @@ export class ParsedDemo {
|
|||
public readonly kills: Kill[];
|
||||
public readonly playerInfo: PlayerInfo[];
|
||||
|
||||
constructor(playerCount: number, buildingCount: number, world: WorldBoundaries, header: Header, data: Uint8Array, kills: Kill[], playerInfo: PlayerInfo[], tickCount: number) {
|
||||
constructor(playerCount: number, buildingCount: number, projectileCount: number, world: WorldBoundaries, header: Header, data: Uint8Array, kills: Kill[], playerInfo: PlayerInfo[], tickCount: number) {
|
||||
this.playerCount = playerCount;
|
||||
this.buildingCount = buildingCount;
|
||||
this.projectileCount = projectileCount;
|
||||
this.world = world;
|
||||
this.header = header;
|
||||
this.data = data;
|
||||
|
|
@ -212,10 +236,22 @@ export class ParsedDemo {
|
|||
const base = (this.playerCount * this.tickCount * PLAYER_PACK_SIZE) + ((buildingIndex * this.tickCount) + tick) * BUILDING_PACK_SIZE;
|
||||
return unpackBuilding(this.data, base, this.world);
|
||||
}
|
||||
|
||||
getProjectile(tick: number, projectileIndex: number): ProjectileState {
|
||||
if (projectileIndex >= this.projectileCount) {
|
||||
throw new Error("Projectile out of bounds");
|
||||
}
|
||||
|
||||
const base = (this.playerCount * this.tickCount * PLAYER_PACK_SIZE) +
|
||||
(this.buildingCount * this.tickCount * BUILDING_PACK_SIZE) +
|
||||
((projectileIndex * this.tickCount) + tick) * PROJECTILE_PACK_SIZE;
|
||||
return unpackProjectile(this.data, base, this.world);
|
||||
}
|
||||
}
|
||||
|
||||
const PLAYER_PACK_SIZE = 8;
|
||||
const BUILDING_PACK_SIZE = 7;
|
||||
const PROJECTILE_PACK_SIZE = 6;
|
||||
|
||||
function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries, info: PlayerInfo): PlayerState {
|
||||
const x = unpack_f32(bytes[base] + (bytes[base + 1] << 8), world.boundary_min.x, world.boundary_max.x);
|
||||
|
|
@ -257,3 +293,19 @@ function unpackBuilding(bytes: Uint8Array, base: number, world: WorldBoundaries)
|
|||
level,
|
||||
}
|
||||
}
|
||||
|
||||
function unpackProjectile(bytes: Uint8Array, base: number, world: WorldBoundaries): ProjectileState {
|
||||
const x = unpack_f32(bytes[base] + (bytes[base + 1] << 8), world.boundary_min.x, world.boundary_max.x);
|
||||
const y = unpack_f32(bytes[base + 2] + (bytes[base + 3] << 8), world.boundary_min.y, world.boundary_max.y);
|
||||
const team_type = bytes[base + 4];
|
||||
const team = (((team_type >> 4) & 1) === 0) ? Team.Blue : Team.Red;
|
||||
const projectileType = ((team_type >> 5) & 7) as ProjectileType;
|
||||
const angle = unpack_angle(bytes[base + 5]);
|
||||
|
||||
return {
|
||||
position: {x, y},
|
||||
angle,
|
||||
team,
|
||||
projectileType,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue