mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
some basic viewer
This commit is contained in:
parent
2dee28d022
commit
5910b2f35a
45 changed files with 1089 additions and 1436 deletions
|
|
@ -1,58 +0,0 @@
|
|||
"use strict";
|
||||
exports.__esModule = true;
|
||||
exports.AsyncParser = void 0;
|
||||
var parser_worker_1 = require("@demostf/parser-worker");
|
||||
var AsyncParser = /** @class */ (function () {
|
||||
function AsyncParser(buffer, progressCallback) {
|
||||
this.buffer = buffer;
|
||||
this.progressCallback = progressCallback;
|
||||
}
|
||||
AsyncParser.prototype.cache = function () {
|
||||
var _this = this;
|
||||
return new Promise(function (resolve, reject) {
|
||||
var worker = new Worker(new URL('./ParseWorker.ts', import.meta.url));
|
||||
worker.postMessage({
|
||||
buffer: _this.buffer
|
||||
}, [_this.buffer]);
|
||||
worker.onmessage = function (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) {
|
||||
var cachedData = event.data.demo;
|
||||
console.log("packed data: ".concat((cachedData.data.length / (1024 * 1024)).toFixed(1), "MB"));
|
||||
_this.world = cachedData.world;
|
||||
_this.demo = new parser_worker_1.ParsedDemo(cachedData.playerCount, cachedData.buildingCount, cachedData.world, cachedData.header, cachedData.data, cachedData.kills, cachedData.playerInfo, cachedData.tickCount);
|
||||
resolve(_this.demo);
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
AsyncParser.prototype.getPlayersAtTick = function (tick) {
|
||||
var players = [];
|
||||
for (var i = 0; i < this.demo.playerCount; i++) {
|
||||
players.push(this.demo.getPlayer(tick, i));
|
||||
}
|
||||
return players;
|
||||
};
|
||||
AsyncParser.prototype.getBuildingsAtTick = function (tick) {
|
||||
var buildings = [];
|
||||
for (var i = 0; i < this.demo.buildingCount; i++) {
|
||||
var building = this.demo.getBuilding(tick, i);
|
||||
if (building.health > 0) {
|
||||
buildings.push(building);
|
||||
}
|
||||
}
|
||||
return buildings;
|
||||
};
|
||||
AsyncParser.prototype.getKills = function () {
|
||||
return this.demo.kills;
|
||||
};
|
||||
return AsyncParser;
|
||||
}());
|
||||
exports.AsyncParser = AsyncParser;
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
import {ParsedDemo, PlayerState, WorldBoundaries, Header, Kill, BuildingState} from "@demostf/parser-worker";
|
||||
import {getMapBoundaries} from "../MapBoundries";
|
||||
import {ParsedDemo, PlayerState, WorldBoundaries, Kill, BuildingState} from "./Parser";
|
||||
|
||||
function getCacheBuster(): string {
|
||||
const url = document.querySelector('script[src*="viewer"]').attributes.src.value;
|
||||
return url.substring("/viewer.js".length);
|
||||
}
|
||||
|
||||
export class AsyncParser {
|
||||
buffer: ArrayBuffer;
|
||||
|
|
@ -14,7 +18,7 @@ export class AsyncParser {
|
|||
|
||||
cache(): Promise<ParsedDemo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(new URL('./ParseWorker.ts', import.meta.url));
|
||||
const worker = new Worker(`/parse-worker.js${getCacheBuster()}`);
|
||||
worker.postMessage({
|
||||
buffer: this.buffer
|
||||
}, [this.buffer]);
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
"use strict";
|
||||
exports.__esModule = true;
|
||||
var parser_worker_1 = require("@demostf/parser-worker");
|
||||
/**
|
||||
* @global postMessage
|
||||
* @param event
|
||||
*/
|
||||
onmessage = function (event) {
|
||||
var buffer = event.data.buffer;
|
||||
var bytes = new Uint8Array(buffer);
|
||||
(0, parser_worker_1.parseDemo)(bytes, function (progress) {
|
||||
postMessage({
|
||||
progress: progress
|
||||
});
|
||||
}).then(function (parsed) {
|
||||
postMessage({
|
||||
demo: parsed
|
||||
}, [parsed.data.buffer]);
|
||||
})["catch"](function (e) {
|
||||
console.error(e);
|
||||
postMessage({
|
||||
error: e.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {parseDemo} from "@demostf/parser-worker";
|
||||
import {parseDemo} from "./Parser";
|
||||
|
||||
declare function postMessage(message: any, transfer?: any[]): void;
|
||||
|
||||
|
|
@ -23,5 +23,4 @@ onmessage = (event: MessageEvent) => {
|
|||
error: e.message
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
|||
259
script/viewer/Analyse/Data/Parser.ts
Normal file
259
script/viewer/Analyse/Data/Parser.ts
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
import {
|
||||
get_assister_ids,
|
||||
get_attacker_ids, get_data,
|
||||
get_kill_ticks,
|
||||
get_map, get_player_entity_id,
|
||||
get_player_name, get_player_steam_id, get_player_user_id, get_victim_ids, get_weapon,
|
||||
parse_demo,
|
||||
XY
|
||||
} from '@demostf/tf-demos-viewer';
|
||||
import viewer from "@demostf/tf-demos-viewer";
|
||||
|
||||
function getCacheBuster(): string {
|
||||
const url = self.location.href;
|
||||
return url.substring(url.indexOf('?'));
|
||||
}
|
||||
|
||||
export async function parseDemo(bytes: Uint8Array, progressCallback: (progress: number) => void): Promise<ParsedDemo> {
|
||||
await viewer(`/tf-demo-viewer.wasm${getCacheBuster()}`);
|
||||
const state = parse_demo(bytes, progressCallback);
|
||||
|
||||
let playerCount = state.player_count;
|
||||
let buildingCount = state.building_count;
|
||||
let boundaries = state.boundaries;
|
||||
let interval_per_tick = state.interval_per_tick;
|
||||
let tickCount = state.tick_count;
|
||||
let kill_ticks = get_kill_ticks(state);
|
||||
let attackers = get_attacker_ids(state);
|
||||
let assisters = get_assister_ids(state);
|
||||
let victims = get_victim_ids(state);
|
||||
|
||||
let playerInfo = [];
|
||||
|
||||
for (let i = 0; i < playerCount; i++) {
|
||||
playerInfo.push({
|
||||
name: get_player_name(state, i),
|
||||
steamId: get_player_steam_id(state, i),
|
||||
entityId: get_player_entity_id(state, i),
|
||||
userId: get_player_user_id(state, i),
|
||||
})
|
||||
}
|
||||
|
||||
let kills = [];
|
||||
for (let i = 0; i < kill_ticks.length; i++) {
|
||||
kills.push({
|
||||
tick: kill_ticks[i],
|
||||
attacker: attackers[i],
|
||||
assister: assisters[i],
|
||||
victim: victims[i],
|
||||
weapon: get_weapon(state, i),
|
||||
})
|
||||
}
|
||||
|
||||
let map = get_map(state);
|
||||
let data = get_data(state);
|
||||
|
||||
return new ParsedDemo(
|
||||
playerCount,
|
||||
buildingCount,
|
||||
{
|
||||
boundary_min: {
|
||||
x: boundaries.boundary_min.x,
|
||||
y: boundaries.boundary_min.y,
|
||||
},
|
||||
boundary_max: {
|
||||
x: boundaries.boundary_max.x,
|
||||
y: boundaries.boundary_max.y,
|
||||
}
|
||||
},
|
||||
{
|
||||
map,
|
||||
interval_per_tick
|
||||
},
|
||||
data,
|
||||
kills,
|
||||
playerInfo,
|
||||
tickCount,
|
||||
);
|
||||
}
|
||||
|
||||
export interface PlayerInfo {
|
||||
entityId: number,
|
||||
name: string,
|
||||
steamId: string,
|
||||
userId: number,
|
||||
}
|
||||
|
||||
export enum Team {
|
||||
Other = 0,
|
||||
Spectator = 1,
|
||||
Red = 2,
|
||||
Blue = 3,
|
||||
}
|
||||
|
||||
export enum Class {
|
||||
Other = 0,
|
||||
Scout = 1,
|
||||
Sniper = 2,
|
||||
Solder = 3,
|
||||
Demoman = 4,
|
||||
Medic = 5,
|
||||
Heavy = 6,
|
||||
Pyro = 7,
|
||||
Spy = 8,
|
||||
Engineer = 9,
|
||||
}
|
||||
|
||||
export enum BuildingType {
|
||||
TeleporterEntrance = 0,
|
||||
TeleporterExit = 1,
|
||||
Dispenser = 2,
|
||||
Level1Sentry = 3,
|
||||
Level2Sentry = 4,
|
||||
Level3Sentry = 5,
|
||||
MiniSentry = 6,
|
||||
Unknown = 7,
|
||||
}
|
||||
|
||||
export interface WorldBoundaries {
|
||||
boundary_min: {
|
||||
x: number,
|
||||
y: number
|
||||
},
|
||||
boundary_max: {
|
||||
x: number,
|
||||
y: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface PlayerState {
|
||||
position: {
|
||||
x: number,
|
||||
y: number
|
||||
},
|
||||
angle: number,
|
||||
health: number,
|
||||
team: Team,
|
||||
playerClass: Class,
|
||||
info: PlayerInfo,
|
||||
charge: number,
|
||||
}
|
||||
|
||||
export interface BuildingState {
|
||||
position: {
|
||||
x: number,
|
||||
y: number
|
||||
},
|
||||
angle: number,
|
||||
health: number,
|
||||
level: number,
|
||||
team: Team,
|
||||
buildingType: BuildingType,
|
||||
}
|
||||
|
||||
export interface Header {
|
||||
interval_per_tick: number,
|
||||
map: string
|
||||
}
|
||||
|
||||
export interface Kill {
|
||||
tick: number,
|
||||
attacker: number,
|
||||
assister: number,
|
||||
victim: number,
|
||||
weapon: string,
|
||||
}
|
||||
|
||||
function unpack_f32(val: number, min: number, max: number): number {
|
||||
const ratio = val / (Math.pow(2, 16) - 1);
|
||||
return ratio * (max - min) + min;
|
||||
}
|
||||
|
||||
function unpack_angle(val: number): number {
|
||||
const ratio = val / (Math.pow(2, 8) - 1);
|
||||
return ratio * 360;
|
||||
}
|
||||
|
||||
export class ParsedDemo {
|
||||
public readonly playerCount: number;
|
||||
public readonly buildingCount: number;
|
||||
public readonly world: WorldBoundaries;
|
||||
public readonly data: Uint8Array;
|
||||
public readonly header: Header;
|
||||
public readonly tickCount: number;
|
||||
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) {
|
||||
this.playerCount = playerCount;
|
||||
this.buildingCount = buildingCount;
|
||||
this.world = world;
|
||||
this.header = header;
|
||||
this.data = data;
|
||||
this.kills = kills;
|
||||
this.playerInfo = playerInfo;
|
||||
this.tickCount = tickCount;
|
||||
}
|
||||
|
||||
getPlayer(tick: number, playerIndex: number): PlayerState {
|
||||
if (playerIndex >= this.playerCount) {
|
||||
throw new Error("Player out of bounds");
|
||||
}
|
||||
|
||||
const base = ((playerIndex * this.tickCount) + tick) * PLAYER_PACK_SIZE;
|
||||
return unpackPlayer(this.data, base, this.world, this.playerInfo[playerIndex]);
|
||||
}
|
||||
|
||||
getBuilding(tick: number, buildingIndex: number): BuildingState {
|
||||
if (buildingIndex >= this.buildingCount) {
|
||||
throw new Error("Building out of bounds");
|
||||
}
|
||||
|
||||
const base = (this.playerCount * this.tickCount * PLAYER_PACK_SIZE) + ((buildingIndex * this.tickCount) + tick) * BUILDING_PACK_SIZE;
|
||||
return unpackBuilding(this.data, base, this.world);
|
||||
}
|
||||
}
|
||||
|
||||
const PLAYER_PACK_SIZE = 8;
|
||||
const BUILDING_PACK_SIZE = 7;
|
||||
|
||||
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);
|
||||
const y = unpack_f32(bytes[base + 2] + (bytes[base + 3] << 8), world.boundary_min.y, world.boundary_max.y);
|
||||
const team_class_health = bytes[base + 4] + (bytes[base + 5] << 8);
|
||||
const angle = unpack_angle(bytes[base + 6]);
|
||||
const health = team_class_health & 1013;
|
||||
const team = (team_class_health >> 14) as Team;
|
||||
const playerClass = ((team_class_health >> 10) & 15) as Class;
|
||||
const charge = bytes[base + 7];
|
||||
|
||||
return {
|
||||
position: {x, y},
|
||||
angle,
|
||||
health,
|
||||
team,
|
||||
playerClass,
|
||||
info,
|
||||
charge
|
||||
}
|
||||
}
|
||||
|
||||
function unpackBuilding(bytes: Uint8Array, base: number, world: WorldBoundaries): BuildingState {
|
||||
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_health = bytes[base + 4] + (bytes[base + 5] << 8);
|
||||
const angle = unpack_angle(bytes[base + 6]);
|
||||
const health = team_type_health & 1013;
|
||||
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;
|
||||
|
||||
return {
|
||||
position: {x, y},
|
||||
angle,
|
||||
health,
|
||||
team,
|
||||
buildingType,
|
||||
level,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue