mirror of
https://codeberg.org/demostf/tf-demos-viewer.git
synced 2026-06-03 18:14:11 +02:00
transfer
This commit is contained in:
parent
59c410566e
commit
bb742bbd0a
3 changed files with 135 additions and 133 deletions
150
js/index.ts
150
js/index.ts
|
|
@ -1,138 +1,22 @@
|
||||||
import {FlatState, XY} from '../pkg/index.d.ts';
|
import {parseDemo} from "./parser";
|
||||||
|
|
||||||
import("../pkg/index.js")
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
.then(m => {
|
document.getElementById('file').onchange = e => {
|
||||||
document.getElementById('file').onchange = e => {
|
let file = (e.target as HTMLInputElement).files[0];
|
||||||
let file = (e.target as HTMLInputElement).files[0];
|
let reader = new FileReader();
|
||||||
let reader = new FileReader();
|
reader.readAsArrayBuffer(file);
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
|
|
||||||
reader.onload = function () {
|
reader.onload = async function () {
|
||||||
let bytes = new Uint8Array(reader.result as ArrayBuffer);
|
let bytes = new Uint8Array(reader.result as ArrayBuffer);
|
||||||
|
|
||||||
console.time('demo_parse');
|
console.time('demo_parse');
|
||||||
const state = m.parse_demo(bytes);
|
let parsed = await parseDemo(bytes);
|
||||||
|
console.timeEnd('demo_parse');
|
||||||
console.timeEnd('demo_parse');
|
console.log(parsed.getPlayer(150, 2));
|
||||||
console.time('transfer');
|
|
||||||
|
|
||||||
let playerCount = m.get_player_count(state);
|
|
||||||
let boundaries = m.get_boundaries(state);
|
|
||||||
let data = m.get_data(state);
|
|
||||||
|
|
||||||
let parsed = new ParsedDemo(playerCount, {
|
|
||||||
boundary_min: {
|
|
||||||
x: boundaries.boundary_min.x,
|
|
||||||
y: boundaries.boundary_min.y,
|
|
||||||
},
|
|
||||||
boundary_max: {
|
|
||||||
x: boundaries.boundary_max.x,
|
|
||||||
y: boundaries.boundary_max.y,
|
|
||||||
}
|
|
||||||
}, data);
|
|
||||||
|
|
||||||
console.timeEnd('transfer');
|
|
||||||
|
|
||||||
console.log(parsed, parsed.getPlayer(100, 2));
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.onerror = function () {
|
|
||||||
console.log(reader.error);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
|
|
||||||
enum Team {
|
reader.onerror = function () {
|
||||||
Other = 0,
|
console.log(reader.error);
|
||||||
Spectator = 1,
|
};
|
||||||
Red = 2,
|
};
|
||||||
Blue = 3,
|
});
|
||||||
}
|
|
||||||
|
|
||||||
enum Class {
|
|
||||||
Other = 0,
|
|
||||||
Scout = 1,
|
|
||||||
Sniper = 2,
|
|
||||||
Solder = 3,
|
|
||||||
Demoman = 4,
|
|
||||||
Medic = 5,
|
|
||||||
Heavy = 6,
|
|
||||||
Pyro = 7,
|
|
||||||
Spy = 8,
|
|
||||||
Engineer = 9,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorldBoundaries {
|
|
||||||
boundary_min: {
|
|
||||||
x: number,
|
|
||||||
y: number
|
|
||||||
},
|
|
||||||
boundary_max: {
|
|
||||||
x: number,
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PlayerState {
|
|
||||||
position: {
|
|
||||||
x: number,
|
|
||||||
y: number
|
|
||||||
},
|
|
||||||
angle: number,
|
|
||||||
health: number,
|
|
||||||
team: Team,
|
|
||||||
playerClass: Class,
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ParsedDemo {
|
|
||||||
private playerCount: number;
|
|
||||||
private world: WorldBoundaries;
|
|
||||||
private data: Uint8Array;
|
|
||||||
private tickCount: number;
|
|
||||||
|
|
||||||
constructor(playerCount: number, world: WorldBoundaries, data: Uint8Array) {
|
|
||||||
this.playerCount = playerCount;
|
|
||||||
this.world = world;
|
|
||||||
this.data = data;
|
|
||||||
this.tickCount = data.length / playerCount / PACK_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlayer(tick: number, playerIndex: number): PlayerState {
|
|
||||||
if (playerIndex >= this.playerCount) {
|
|
||||||
throw new Error("Player out of bounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = ((playerIndex * this.tickCount) + tick) * PACK_SIZE;
|
|
||||||
return unpackPlayer(this.data, base, this.world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PACK_SIZE = 8;
|
|
||||||
|
|
||||||
function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries): 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);
|
|
||||||
let health = bytes[base + 4] + (bytes[base + 5] << 8);
|
|
||||||
const angle = unpack_angle(bytes[base + 6]);
|
|
||||||
const team = (bytes[base + 7] >> 4) as Team;
|
|
||||||
const playerClass = (bytes[base + 7] & 15) as Class;
|
|
||||||
|
|
||||||
return {
|
|
||||||
position: {x, y},
|
|
||||||
angle,
|
|
||||||
health,
|
|
||||||
team,
|
|
||||||
playerClass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
115
js/parser.ts
Normal file
115
js/parser.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
import {FlatState, XY} from '../pkg/index.d.ts';
|
||||||
|
|
||||||
|
export async function parseDemo(bytes: Uint8Array): Promise<ParsedDemo> {
|
||||||
|
let m = await import("../pkg/index.js");
|
||||||
|
const state = m.parse_demo(bytes);
|
||||||
|
|
||||||
|
let playerCount = m.get_player_count(state);
|
||||||
|
let boundaries = m.get_boundaries(state);
|
||||||
|
let data = m.get_data(state);
|
||||||
|
|
||||||
|
return new ParsedDemo(playerCount, {
|
||||||
|
boundary_min: {
|
||||||
|
x: boundaries.boundary_min.x,
|
||||||
|
y: boundaries.boundary_min.y,
|
||||||
|
},
|
||||||
|
boundary_max: {
|
||||||
|
x: boundaries.boundary_max.x,
|
||||||
|
y: boundaries.boundary_max.y,
|
||||||
|
}
|
||||||
|
}, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
private playerCount: number;
|
||||||
|
private world: WorldBoundaries;
|
||||||
|
private data: Uint8Array;
|
||||||
|
private tickCount: number;
|
||||||
|
|
||||||
|
constructor(playerCount: number, world: WorldBoundaries, data: Uint8Array) {
|
||||||
|
this.playerCount = playerCount;
|
||||||
|
this.world = world;
|
||||||
|
this.data = data;
|
||||||
|
this.tickCount = data.length / playerCount / PACK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayer(tick: number, playerIndex: number): PlayerState {
|
||||||
|
if (playerIndex >= this.playerCount) {
|
||||||
|
throw new Error("Player out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = ((playerIndex * this.tickCount) + tick) * PACK_SIZE;
|
||||||
|
return unpackPlayer(this.data, base, this.world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PACK_SIZE = 8;
|
||||||
|
|
||||||
|
function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries): 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);
|
||||||
|
let health = bytes[base + 4] + (bytes[base + 5] << 8);
|
||||||
|
const angle = unpack_angle(bytes[base + 6]);
|
||||||
|
const team = (bytes[base + 7] >> 4) as Team;
|
||||||
|
const playerClass = (bytes[base + 7] & 15) as Class;
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: {x, y},
|
||||||
|
angle,
|
||||||
|
health,
|
||||||
|
team,
|
||||||
|
playerClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,9 @@ module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: dist,
|
contentBase: dist,
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue