mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 10:14:13 +02:00
This commit is contained in:
parent
812f3cdd0e
commit
d33a8df45d
5 changed files with 99 additions and 17 deletions
|
|
@ -186,6 +186,7 @@ export enum MedigunType {
|
||||||
export interface MedicState {
|
export interface MedicState {
|
||||||
charge: number,
|
charge: number,
|
||||||
medigun: MedigunType
|
medigun: MedigunType
|
||||||
|
target: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpyState {
|
export interface SpyState {
|
||||||
|
|
@ -350,7 +351,8 @@ function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries, i
|
||||||
case Class.Medic:
|
case Class.Medic:
|
||||||
class_data = {
|
class_data = {
|
||||||
charge: class_bits[0],
|
charge: class_bits[0],
|
||||||
medigun: class_bits[1],
|
medigun: (class_bits[1] >> 6),
|
||||||
|
target: class_bits[1] & 31,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Class.Spy:
|
case Class.Spy:
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import {findMapAlias} from './MapBoundries';
|
||||||
import {PlayerState, Header, WorldBoundaries, BuildingState, ProjectileState, CartState} 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";
|
import {Projectile} from "./Render/Cart";
|
||||||
|
import {HealBeam} from "./Render/HealBeam";
|
||||||
|
import {medics} from "./Render/PlayerSpec";
|
||||||
|
|
||||||
export interface MapRenderProps {
|
export interface MapRenderProps {
|
||||||
header: Header;
|
header: Header;
|
||||||
|
|
@ -28,6 +30,7 @@ export function MapRender(props: MapRenderProps) {
|
||||||
const mapAlias = findMapAlias(props.header.map);
|
const mapAlias = findMapAlias(props.header.map);
|
||||||
const image = `${map_root}images/${mapAlias}.webp`;
|
const image = `${map_root}images/${mapAlias}.webp`;
|
||||||
const background = `url(${image})`;
|
const background = `url(${image})`;
|
||||||
|
const medicPlayers = () => medics(props.players);
|
||||||
|
|
||||||
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}
|
||||||
|
|
@ -65,10 +68,18 @@ export function MapRender(props: MapRenderProps) {
|
||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
|
<For each={medicPlayers()}>{(player) =>
|
||||||
|
<Show when={player.health}>
|
||||||
|
<HealBeam player={player} mapBoundary={props.world} targetSize={props.size} scale={props.scale}
|
||||||
|
players={props.players}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
}</For>
|
||||||
<For each={props.players}>{(player) =>
|
<For each={props.players}>{(player) =>
|
||||||
<Show when={player.health}>
|
<Show when={player.health}>
|
||||||
<PlayerDot player={player} mapBoundary={props.world} targetSize={props.size} scale={props.scale}
|
<PlayerDot player={player} mapBoundary={props.world} targetSize={props.size} scale={props.scale}
|
||||||
onHover={props.onHover}
|
onHover={props.onHover}
|
||||||
|
players={props.players}
|
||||||
highlighted={props.highlighted === player.info.userId}
|
highlighted={props.highlighted === player.info.userId}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
67
script/viewer/Analyse/Render/HealBeam.tsx
Normal file
67
script/viewer/Analyse/Render/HealBeam.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {Class, MedicState, PlayerState, WorldBoundaries} from "../Data/Parser";
|
||||||
|
import {Show} from "solid-js";
|
||||||
|
|
||||||
|
export interface HealBeamProp {
|
||||||
|
player: PlayerState;
|
||||||
|
mapBoundary: WorldBoundaries;
|
||||||
|
targetSize: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
scale: number;
|
||||||
|
players: PlayerState[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HealBeam(props: HealBeamProp) {
|
||||||
|
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 scaleX = (x) => (x - props.mapBoundary.boundary_min.x) / worldWidth * props.targetSize.width;
|
||||||
|
const scaleY = (y) => (worldHeight - (y - props.mapBoundary.boundary_min.y)) / worldHeight * props.targetSize.height;
|
||||||
|
const scaledX = () => scaleX(props.player.position.x);
|
||||||
|
const scaledY = () => scaleY(props.player.position.y);
|
||||||
|
|
||||||
|
const targetPosition = () => {
|
||||||
|
if (props.player.playerClass !== Class.Medic) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const target = (props.player.class_data as MedicState).target;
|
||||||
|
if (target === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let player of props.players) {
|
||||||
|
if (player.info.entityId === target) {
|
||||||
|
return {
|
||||||
|
x: scaleX(player.position.x),
|
||||||
|
y: scaleY(player.position.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const distance = () => Math.sqrt(Math.pow(Math.abs(scaledX() - targetPosition().x), 2) + Math.pow(Math.abs(scaledY() - targetPosition().y), 2));
|
||||||
|
const aimHandle = () => {
|
||||||
|
let angle = (270 - props.player.angle) / 360 * (Math.PI * 2);
|
||||||
|
return {
|
||||||
|
x: scaledX() - Math.sin(angle) * (distance() / 2),
|
||||||
|
y: scaledY() + Math.cos(angle) * (distance() / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const aimStart = () => {
|
||||||
|
let angle = (270 - props.player.angle) / 360 * (Math.PI * 2);
|
||||||
|
return {
|
||||||
|
x: scaledX() - Math.sin(angle) * (16 / props.scale),
|
||||||
|
y: scaledY() + Math.cos(angle) * (16 / props.scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = () => `M ${aimStart().x} ${aimStart().y} C ${aimHandle().x} ${aimHandle().y} ${aimHandle().x} ${aimHandle().y} ${targetPosition().x} ${targetPosition().y}`;
|
||||||
|
|
||||||
|
return <g>
|
||||||
|
<Show when={targetPosition()}>
|
||||||
|
<path
|
||||||
|
d={path()}
|
||||||
|
fill="transparent" stroke="white" stroke-width={1.5 / props.scale}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ export interface PlayerProp {
|
||||||
scale: number;
|
scale: number;
|
||||||
onHover: (userId: number) => void;
|
onHover: (userId: number) => void;
|
||||||
highlighted: boolean;
|
highlighted: boolean;
|
||||||
|
players: PlayerState[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const healthMap = {
|
const healthMap = {
|
||||||
|
|
@ -65,21 +66,22 @@ export function Player(props: PlayerProp) {
|
||||||
const teamHatch = () => (props.player.team === Team.Red) ? `url(#diagonalHatchRed)` : `url(#diagonalHatchBlue)`
|
const teamHatch = () => (props.player.team === Team.Red) ? `url(#diagonalHatchRed)` : `url(#diagonalHatchBlue)`
|
||||||
const fill = () => props.player.cloaked ? teamHatch() : teamColor();
|
const fill = () => props.player.cloaked ? teamHatch() : teamColor();
|
||||||
|
|
||||||
return <g
|
return (
|
||||||
onmouseover={() => props.onHover(props.player.info.userId)}
|
<g
|
||||||
onmouseout={() => props.onHover(0)}
|
onmouseover={() => props.onHover(props.player.info.userId)}
|
||||||
transform={transform()}>
|
onmouseout={() => props.onHover(0)}
|
||||||
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
|
transform={transform()}>
|
||||||
opacity={imageOpacity()}
|
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
|
||||||
transform={rotate()}/>
|
opacity={imageOpacity()}
|
||||||
<circle r={16} stroke-width={props.highlighted ? 4 : 1.5} stroke="white"
|
transform={rotate()}/>
|
||||||
stroke-dasharray={circleStrokeStyle()}
|
<circle r={16} stroke-width={props.highlighted ? 4 : 1.5} stroke="white"
|
||||||
fill={fill()}
|
stroke-dasharray={circleStrokeStyle()}
|
||||||
opacity={alpha()}
|
fill={fill()}
|
||||||
filter={filter()}
|
opacity={alpha()}
|
||||||
/>
|
filter={filter()}
|
||||||
{getClassImage(props.player, imageOpacity())}
|
/>
|
||||||
</g>
|
{getClassImage(props.player, imageOpacity())}
|
||||||
|
</g>)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClassImage(player: PlayerState, imageOpacity: number) {
|
function getClassImage(player: PlayerState, imageOpacity: number) {
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ function filterPlayers(players: PlayerState[], team: number): PlayerState[] {
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
function medics(players: PlayerState[]): PlayerState[] {
|
export function medics(players: PlayerState[]): PlayerState[] {
|
||||||
return players.filter(player => player.playerClass === Class.Medic);
|
return players.filter(player => player.playerClass === Class.Medic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue