mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
render cart
This commit is contained in:
parent
c96f2e6b7b
commit
a4d835f0c6
9 changed files with 97 additions and 12 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -6,7 +6,7 @@
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@demostf/edit": "0.2.0",
|
"@demostf/edit": "0.2.0",
|
||||||
"@demostf/tf-demos-viewer": "^0.2.3",
|
"@demostf/tf-demos-viewer": "^0.2.4",
|
||||||
"@lutaok/solid-modal": "^0.1.1",
|
"@lutaok/solid-modal": "^0.1.1",
|
||||||
"@solid-primitives/autofocus": "^0.0.111",
|
"@solid-primitives/autofocus": "^0.0.111",
|
||||||
"@solid-primitives/keyboard": "^1.2.8",
|
"@solid-primitives/keyboard": "^1.2.8",
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
"integrity": "sha512-s9wk3QVm+aTpMhIyfdGIHRm5qHp7FQ1dq/Jn0fms+lXsB1xY3wgjfWH+5gwRjjo/Dd3UMNM0o3atjO2uh+CxOQ=="
|
"integrity": "sha512-s9wk3QVm+aTpMhIyfdGIHRm5qHp7FQ1dq/Jn0fms+lXsB1xY3wgjfWH+5gwRjjo/Dd3UMNM0o3atjO2uh+CxOQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@demostf/tf-demos-viewer": {
|
"node_modules/@demostf/tf-demos-viewer": {
|
||||||
"version": "0.2.3",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@demostf/tf-demos-viewer/-/tf-demos-viewer-0.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@demostf/tf-demos-viewer/-/tf-demos-viewer-0.2.4.tgz",
|
||||||
"integrity": "sha512-pN+D6qKy+qy835ituO5DJ8zHGOdX7/ubwXpwWST0ae3u1QARegw/KdJzKrvhU47hLzn5zp1uUgRWqZdOReBckw=="
|
"integrity": "sha512-3wqgqKJHJZMIBpxfXxLBV1Yx7bRIyvMJGNwq9osB1wXipyXcKDzzStmj6yEenTj9s+HfotAbPRzfZGlkVLWfog=="
|
||||||
},
|
},
|
||||||
"node_modules/@lutaok/solid-modal": {
|
"node_modules/@lutaok/solid-modal": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@demostf/edit": "0.2.0",
|
"@demostf/edit": "0.2.0",
|
||||||
"@demostf/tf-demos-viewer": "^0.2.3",
|
"@demostf/tf-demos-viewer": "^0.2.4",
|
||||||
"@lutaok/solid-modal": "^0.1.1",
|
"@lutaok/solid-modal": "^0.1.1",
|
||||||
"@solid-primitives/autofocus": "^0.0.111",
|
"@solid-primitives/autofocus": "^0.0.111",
|
||||||
"@solid-primitives/keyboard": "^1.2.8",
|
"@solid-primitives/keyboard": "^1.2.8",
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,7 @@ export const Analyser = (props: AnalyseProps) => {
|
||||||
const players = () => parser.getPlayersAtTick(tick());
|
const players = () => parser.getPlayersAtTick(tick());
|
||||||
const buildings = () => parser.getBuildingsAtTick(tick());
|
const buildings = () => parser.getBuildingsAtTick(tick());
|
||||||
const projectiles = () => parser.getProjectilesAtTick(tick());
|
const projectiles = () => parser.getProjectilesAtTick(tick());
|
||||||
|
const cart = () => parser.getCart(tick());
|
||||||
const events = parser.getEvents();
|
const events = parser.getEvents();
|
||||||
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
const playButtonText = () => (playing()) ? '⏸' : '▶️';
|
||||||
const inShared = session && !session.isOwner();
|
const inShared = session && !session.isOwner();
|
||||||
|
|
@ -280,6 +281,7 @@ export const Analyser = (props: AnalyseProps) => {
|
||||||
players={players()}
|
players={players()}
|
||||||
buildings={buildings()}
|
buildings={buildings()}
|
||||||
projectiles={projectiles()}
|
projectiles={projectiles()}
|
||||||
|
cart={cart()}
|
||||||
header={props.header}
|
header={props.header}
|
||||||
world={backgroundBoundaries}
|
world={backgroundBoundaries}
|
||||||
scale={scale()}
|
scale={scale()}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
BuildingState,
|
BuildingState, CartState,
|
||||||
Event,
|
Event,
|
||||||
Kill,
|
Kill,
|
||||||
ParsedDemo,
|
ParsedDemo,
|
||||||
|
|
@ -46,6 +46,7 @@ export class AsyncParser {
|
||||||
cachedData.playerCount,
|
cachedData.playerCount,
|
||||||
cachedData.buildingCount,
|
cachedData.buildingCount,
|
||||||
cachedData.projectileCount,
|
cachedData.projectileCount,
|
||||||
|
cachedData.hasCart,
|
||||||
cachedData.world,
|
cachedData.world,
|
||||||
cachedData.header,
|
cachedData.header,
|
||||||
cachedData.data,
|
cachedData.data,
|
||||||
|
|
@ -93,6 +94,10 @@ export class AsyncParser {
|
||||||
return projectiles;
|
return projectiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCart(tick: number): CartState | null {
|
||||||
|
return this.demo.getCart(tick);
|
||||||
|
}
|
||||||
|
|
||||||
getKills(): Kill[] {
|
getKills(): Kill[] {
|
||||||
return this.demo.kills
|
return this.demo.kills
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export async function parseDemo(bytes: Uint8Array, progressCallback: (progress:
|
||||||
let playerCount = state.player_count;
|
let playerCount = state.player_count;
|
||||||
let buildingCount = state.building_count;
|
let buildingCount = state.building_count;
|
||||||
let projectileCount = state.projectile_count;
|
let projectileCount = state.projectile_count;
|
||||||
|
let hasCart = state.has_cart;
|
||||||
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 tickCount = state.tick_count;
|
||||||
|
|
@ -76,6 +77,7 @@ export async function parseDemo(bytes: Uint8Array, progressCallback: (progress:
|
||||||
playerCount,
|
playerCount,
|
||||||
buildingCount,
|
buildingCount,
|
||||||
projectileCount,
|
projectileCount,
|
||||||
|
hasCart,
|
||||||
{
|
{
|
||||||
boundary_min: {
|
boundary_min: {
|
||||||
x: boundaries.boundary_min.x,
|
x: boundaries.boundary_min.x,
|
||||||
|
|
@ -191,6 +193,7 @@ export interface ProjectileState {
|
||||||
angle: number,
|
angle: number,
|
||||||
team: Team,
|
team: Team,
|
||||||
projectileType: ProjectileType,
|
projectileType: ProjectileType,
|
||||||
|
critical: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Header {
|
export interface Header {
|
||||||
|
|
@ -206,6 +209,13 @@ export interface Kill {
|
||||||
weapon: string,
|
weapon: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CartState {
|
||||||
|
position: {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
function unpack_f32(val: number, min: number, max: number): number {
|
function unpack_f32(val: number, min: number, max: number): number {
|
||||||
const ratio = val / (Math.pow(2, 16) - 1);
|
const ratio = val / (Math.pow(2, 16) - 1);
|
||||||
return ratio * (max - min) + min;
|
return ratio * (max - min) + min;
|
||||||
|
|
@ -220,6 +230,7 @@ export class ParsedDemo {
|
||||||
public readonly playerCount: number;
|
public readonly playerCount: number;
|
||||||
public readonly buildingCount: number;
|
public readonly buildingCount: number;
|
||||||
public readonly projectileCount: number;
|
public readonly projectileCount: number;
|
||||||
|
public readonly hasCart: boolean;
|
||||||
public readonly world: WorldBoundaries;
|
public readonly world: WorldBoundaries;
|
||||||
public readonly data: Uint8Array;
|
public readonly data: Uint8Array;
|
||||||
public readonly header: Header;
|
public readonly header: Header;
|
||||||
|
|
@ -232,6 +243,7 @@ export class ParsedDemo {
|
||||||
playerCount: number,
|
playerCount: number,
|
||||||
buildingCount: number,
|
buildingCount: number,
|
||||||
projectileCount: number,
|
projectileCount: number,
|
||||||
|
hasCart: boolean,
|
||||||
world: WorldBoundaries,
|
world: WorldBoundaries,
|
||||||
header: Header,
|
header: Header,
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
|
|
@ -243,6 +255,7 @@ export class ParsedDemo {
|
||||||
this.playerCount = playerCount;
|
this.playerCount = playerCount;
|
||||||
this.buildingCount = buildingCount;
|
this.buildingCount = buildingCount;
|
||||||
this.projectileCount = projectileCount;
|
this.projectileCount = projectileCount;
|
||||||
|
this.hasCart = hasCart;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
@ -280,11 +293,24 @@ export class ParsedDemo {
|
||||||
((projectileIndex * this.tickCount) + tick) * PROJECTILE_PACK_SIZE;
|
((projectileIndex * this.tickCount) + tick) * PROJECTILE_PACK_SIZE;
|
||||||
return unpackProjectile(this.data, base, this.world);
|
return unpackProjectile(this.data, base, this.world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCart(tick: number): CartState | null {
|
||||||
|
if (this.hasCart) {
|
||||||
|
const base = (this.playerCount * this.tickCount * PLAYER_PACK_SIZE) +
|
||||||
|
(this.buildingCount * this.tickCount * BUILDING_PACK_SIZE) +
|
||||||
|
(this.projectileCount * this.tickCount * PROJECTILE_PACK_SIZE) +
|
||||||
|
(tick * CART_PACK_SIZE);
|
||||||
|
return unpackCart(this.data, base, this.world);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const PLAYER_PACK_SIZE = 8;
|
const PLAYER_PACK_SIZE = 8;
|
||||||
const BUILDING_PACK_SIZE = 7;
|
const BUILDING_PACK_SIZE = 7;
|
||||||
const PROJECTILE_PACK_SIZE = 6;
|
const PROJECTILE_PACK_SIZE = 6;
|
||||||
|
const CART_PACK_SIZE = 4;
|
||||||
|
|
||||||
function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries, info: PlayerInfo): PlayerState {
|
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 x = unpack_f32(bytes[base] + (bytes[base + 1] << 8), world.boundary_min.x, world.boundary_max.x);
|
||||||
|
|
@ -335,13 +361,24 @@ function unpackProjectile(bytes: Uint8Array, base: number, world: WorldBoundarie
|
||||||
const team_type = bytes[base + 4];
|
const team_type = bytes[base + 4];
|
||||||
const team = (((team_type >> 4) & 1) === 0) ? Team.Blue : Team.Red;
|
const team = (((team_type >> 4) & 1) === 0) ? Team.Blue : Team.Red;
|
||||||
const projectileType = ((team_type >> 5) & 7) as ProjectileType;
|
const projectileType = ((team_type >> 5) & 7) as ProjectileType;
|
||||||
const angle = unpack_angle(bytes[base + 5]);
|
const critical = bytes[base + 5] >> 7 == 1;
|
||||||
|
const angle = unpack_angle(bytes[base + 5] >> 1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: {x, y},
|
position: {x, y},
|
||||||
angle,
|
angle,
|
||||||
team,
|
team,
|
||||||
projectileType,
|
projectileType,
|
||||||
|
critical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpackCart(bytes: Uint8Array, base: number, world: WorldBoundaries): CartState {
|
||||||
|
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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
position: {x, y},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,16 @@ import {Player as PlayerDot} from './Render/Player';
|
||||||
import {Building as BuildingDot} from './Render/Building';
|
import {Building as BuildingDot} from './Render/Building';
|
||||||
import {Projectile as ProjectileDot} from './Render/Projectile';
|
import {Projectile as ProjectileDot} from './Render/Projectile';
|
||||||
import {findMapAlias} from './MapBoundries';
|
import {findMapAlias} from './MapBoundries';
|
||||||
import {PlayerState, Header, WorldBoundaries, BuildingState, ProjectileState} from "./Data/Parser";
|
import {PlayerState, Header, WorldBoundaries, BuildingState, ProjectileState, CartState} from "./Data/Parser";
|
||||||
import {Show} from "solid-js";
|
import {Show} from "solid-js";
|
||||||
|
import {Projectile} from "./Render/Cart";
|
||||||
|
|
||||||
export interface MapRenderProps {
|
export interface MapRenderProps {
|
||||||
header: Header;
|
header: Header;
|
||||||
players: PlayerState[];
|
players: PlayerState[];
|
||||||
buildings: BuildingState[];
|
buildings: BuildingState[];
|
||||||
projectiles: ProjectileState[];
|
projectiles: ProjectileState[];
|
||||||
|
cart: CartState | null;
|
||||||
size: {
|
size: {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
|
@ -27,6 +29,8 @@ export function MapRender(props: MapRenderProps) {
|
||||||
const image = `${map_root}images/${mapAlias}.webp`;
|
const image = `${map_root}images/${mapAlias}.webp`;
|
||||||
const background = `url(${image})`;
|
const background = `url(${image})`;
|
||||||
|
|
||||||
|
console.log(props.cart);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg class="map-background" width={props.size.width} height={props.size.height}
|
<svg class="map-background" width={props.size.width} height={props.size.height}
|
||||||
style={{"background-image": background}}>
|
style={{"background-image": background}}>
|
||||||
|
|
@ -73,6 +77,10 @@ export function MapRender(props: MapRenderProps) {
|
||||||
scale={props.scale}/>
|
scale={props.scale}/>
|
||||||
</Show>
|
</Show>
|
||||||
}</For>
|
}</For>
|
||||||
|
<Show when={props.cart}>
|
||||||
|
<Projectile cart={props.cart} mapBoundary={props.world} targetSize={props.size}
|
||||||
|
scale={props.scale}/>
|
||||||
|
</Show>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
script/viewer/Analyse/Render/Cart.tsx
Normal file
32
script/viewer/Analyse/Render/Cart.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import {CartState, WorldBoundaries} from "../Data/Parser";
|
||||||
|
|
||||||
|
export interface CartProp {
|
||||||
|
cart: CartState;
|
||||||
|
mapBoundary: WorldBoundaries;
|
||||||
|
targetSize: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
scale: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Projectile(props: CartProp) {
|
||||||
|
const worldWidth = props.mapBoundary.boundary_max.x - props.mapBoundary.boundary_min.x;
|
||||||
|
const worldHeight = props.mapBoundary.boundary_max.y - props.mapBoundary.boundary_min.y;
|
||||||
|
const x = () => props.cart.position.x - props.mapBoundary.boundary_min.x;
|
||||||
|
const y = () => props.cart.position.y - props.mapBoundary.boundary_min.y;
|
||||||
|
const scaledX = () => x() / worldWidth * props.targetSize.width;
|
||||||
|
const scaledY = () => (worldHeight - y()) / worldHeight * props.targetSize.height;
|
||||||
|
|
||||||
|
const transform = () => `translate(${scaledX()} ${scaledY()}) scale(${1 / props.scale})`;
|
||||||
|
const rotate = () => `rotate(0)`; // todo
|
||||||
|
try {
|
||||||
|
return <g transform={transform()}>
|
||||||
|
<path fill="#5b818f" stroke="#fff" stroke-width="1.5" stroke-linejoin="round"
|
||||||
|
transform="translate(-12 -12) scale(0.75)"
|
||||||
|
d="m1 5 3.6 13h7.9l-.11 3.1c-.4-.95-1.6-1.9-3.8-1.8-2.2.06-4.2 1.5-4.1 4.1.03 2.6 2.3 4.6 4.6 4.4 1.7-.1 3.9-1.4 4.1-4.1l5.9.05c.07 2.3 1.7 4 4.1 4 2.5-.08 3.8-2.2 3.9-4.1.08-1.9-1.1-4.2-4-4.2-2.2-.04-2.9 1-3.6 1.8l.11-3.3H27l3.6-13z"/>
|
||||||
|
</g>
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,8 +53,6 @@ export function Player(props: PlayerProp) {
|
||||||
const rotate = () => `rotate(${270 - props.player.angle})`;
|
const rotate = () => `rotate(${270 - props.player.angle})`;
|
||||||
const filter = () => props.player.ubered ? ((props.player.team === Team.Red) ? 'url(#sofGlowRed)' : 'url(#sofGlowBlue)') : '';
|
const filter = () => props.player.ubered ? ((props.player.team === Team.Red) ? 'url(#sofGlowRed)' : 'url(#sofGlowBlue)') : '';
|
||||||
|
|
||||||
console.log(props.player);
|
|
||||||
|
|
||||||
return <g
|
return <g
|
||||||
onmouseover={() => props.onHover(props.player.info.userId)}
|
onmouseover={() => props.onHover(props.player.info.userId)}
|
||||||
onmouseout={() => props.onHover(0)}
|
onmouseout={() => props.onHover(0)}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export function Projectile(props: ProjectileProp) {
|
||||||
const scaledX = () => x() / worldWidth * props.targetSize.width;
|
const scaledX = () => x() / worldWidth * props.targetSize.width;
|
||||||
const scaledY = () => (worldHeight - y()) / worldHeight * props.targetSize.height;
|
const scaledY = () => (worldHeight - y()) / worldHeight * props.targetSize.height;
|
||||||
const teamColor = () => (props.projectile.team === Team.Red) ? '#a75d50' : '#5b818f';
|
const teamColor = () => (props.projectile.team === Team.Red) ? '#a75d50' : '#5b818f';
|
||||||
|
const filter = () => props.projectile.critical ? ((props.projectile.team === Team.Red) ? 'url(#sofGlowRed)' : 'url(#sofGlowBlue)') : '';
|
||||||
|
|
||||||
const transform = () => `translate(${scaledX()} ${scaledY()}) scale(${1 / props.scale})`;
|
const transform = () => `translate(${scaledX()} ${scaledY()}) scale(${1 / props.scale})`;
|
||||||
const rotate = () => `rotate(${270 - props.projectile.angle})`;
|
const rotate = () => `rotate(${270 - props.projectile.angle})`;
|
||||||
|
|
@ -26,10 +27,12 @@ export function Projectile(props: ProjectileProp) {
|
||||||
return <g transform={transform()}>
|
return <g transform={transform()}>
|
||||||
<Show when={projectileIsAngled(props.projectile.projectileType)}>
|
<Show when={projectileIsAngled(props.projectile.projectileType)}>
|
||||||
<polygon points="-3,-4 0,0 3,-4 0,8" stroke="white" fill={teamColor()}
|
<polygon points="-3,-4 0,0 3,-4 0,8" stroke="white" fill={teamColor()}
|
||||||
transform={rotate()}/>
|
transform={rotate()}
|
||||||
|
filter={filter()}/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!projectileIsAngled(props.projectile.projectileType)}>
|
<Show when={!projectileIsAngled(props.projectile.projectileType)}>
|
||||||
<circle r={3} stroke-width={1} stroke="white" fill={teamColor()}/>
|
<circle r={3} stroke-width={1} stroke="white" fill={teamColor()}
|
||||||
|
filter={filter()}/>
|
||||||
</Show>
|
</Show>
|
||||||
</g>
|
</g>
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue