BIN
images/class_portraits/disguises/demoman.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
images/class_portraits/disguises/engineer.webp
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
images/class_portraits/disguises/heavy.webp
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
images/class_portraits/disguises/medic.webp
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
images/class_portraits/disguises/pyro.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
images/class_portraits/disguises/scout.webp
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
images/class_portraits/disguises/sniper.webp
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
images/class_portraits/disguises/soldier.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
images/class_portraits/disguises/spy.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
8
package-lock.json
generated
|
|
@ -6,7 +6,7 @@
|
|||
"": {
|
||||
"dependencies": {
|
||||
"@demostf/edit": "0.2.0",
|
||||
"@demostf/tf-demos-viewer": "^0.2.4",
|
||||
"@demostf/tf-demos-viewer": "^0.3.0",
|
||||
"@lutaok/solid-modal": "^0.1.1",
|
||||
"@solid-primitives/autofocus": "^0.0.111",
|
||||
"@solid-primitives/keyboard": "^1.2.8",
|
||||
|
|
@ -23,9 +23,9 @@
|
|||
"integrity": "sha512-s9wk3QVm+aTpMhIyfdGIHRm5qHp7FQ1dq/Jn0fms+lXsB1xY3wgjfWH+5gwRjjo/Dd3UMNM0o3atjO2uh+CxOQ=="
|
||||
},
|
||||
"node_modules/@demostf/tf-demos-viewer": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@demostf/tf-demos-viewer/-/tf-demos-viewer-0.2.4.tgz",
|
||||
"integrity": "sha512-3wqgqKJHJZMIBpxfXxLBV1Yx7bRIyvMJGNwq9osB1wXipyXcKDzzStmj6yEenTj9s+HfotAbPRzfZGlkVLWfog=="
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@demostf/tf-demos-viewer/-/tf-demos-viewer-0.3.0.tgz",
|
||||
"integrity": "sha512-ZhGYc9DCZLW0jX+ltqHYdk1h4TNe8gU8pTkTcfZki+yFPj3Af1dfHW09LLMHwaW6bvihoLkp7hXHuCa3neSMeg=="
|
||||
},
|
||||
"node_modules/@lutaok/solid-modal": {
|
||||
"version": "0.1.1",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@demostf/edit": "0.2.0",
|
||||
"@demostf/tf-demos-viewer": "^0.2.4",
|
||||
"@demostf/tf-demos-viewer": "^0.3.0",
|
||||
"@lutaok/solid-modal": "^0.1.1",
|
||||
"@solid-primitives/autofocus": "^0.0.111",
|
||||
"@solid-primitives/keyboard": "^1.2.8",
|
||||
|
|
|
|||
|
|
@ -169,8 +169,29 @@ export interface PlayerState {
|
|||
team: Team,
|
||||
playerClass: Class,
|
||||
info: PlayerInfo,
|
||||
charge: number,
|
||||
class_data: PlayerClassState,
|
||||
ubered: boolean,
|
||||
cloaked: boolean,
|
||||
}
|
||||
|
||||
export type PlayerClassState = MedicState | SpyState | null;
|
||||
|
||||
export enum MedigunType {
|
||||
Uber = 0,
|
||||
Kritzkrieg = 1,
|
||||
Quickfix = 2,
|
||||
Vaccinator = 3,
|
||||
}
|
||||
|
||||
export interface MedicState {
|
||||
charge: number,
|
||||
medigun: MedigunType
|
||||
}
|
||||
|
||||
export interface SpyState {
|
||||
disguise_team: Team,
|
||||
disguise_class: Class,
|
||||
cloak: number,
|
||||
}
|
||||
|
||||
export interface BuildingState {
|
||||
|
|
@ -307,7 +328,7 @@ export class ParsedDemo {
|
|||
}
|
||||
}
|
||||
|
||||
const PLAYER_PACK_SIZE = 8;
|
||||
const PLAYER_PACK_SIZE = 9;
|
||||
const BUILDING_PACK_SIZE = 7;
|
||||
const PROJECTILE_PACK_SIZE = 6;
|
||||
const CART_PACK_SIZE = 4;
|
||||
|
|
@ -321,7 +342,25 @@ function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries, i
|
|||
const health = (team_class_health >> 1) & 511;
|
||||
const team = (team_class_health >> 14) as Team;
|
||||
const playerClass = ((team_class_health >> 10) & 15) as Class;
|
||||
const charge = bytes[base + 7];
|
||||
const class_bits = [bytes[base + 7], bytes[base + 8]];
|
||||
let class_data = null;
|
||||
let cloaked = false;
|
||||
switch (playerClass) {
|
||||
case Class.Medic:
|
||||
class_data = {
|
||||
charge: class_bits[0],
|
||||
medigun: class_bits[1],
|
||||
}
|
||||
break;
|
||||
case Class.Spy:
|
||||
class_data = {
|
||||
disguise_team: class_bits[0] >> 6,
|
||||
disguise_class: class_bits[0] >> 2 & 15,
|
||||
cloak: class_bits[1] >> 1,
|
||||
}
|
||||
cloaked = (class_bits[1] & 1) === 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
position: {x, y},
|
||||
|
|
@ -330,8 +369,9 @@ function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries, i
|
|||
team,
|
||||
playerClass,
|
||||
info,
|
||||
charge,
|
||||
class_data,
|
||||
ubered,
|
||||
cloaked,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,20 @@ export function MapRender(props: MapRenderProps) {
|
|||
const image = `${map_root}images/${mapAlias}.webp`;
|
||||
const background = `url(${image})`;
|
||||
|
||||
console.log(props.cart);
|
||||
|
||||
return (
|
||||
<svg class="map-background" width={props.size.width} height={props.size.height}
|
||||
style={{"background-image": background}}>
|
||||
<defs>
|
||||
<pattern id="diagonalHatchRed" patternUnits="userSpaceOnUse" width="8" height="8"
|
||||
patternTransform="rotate(45 0 0)">
|
||||
<line x1="0" y1="0" x2="0" y2="10" style="stroke:#a75d50; stroke-width:10"/>
|
||||
</pattern>
|
||||
<pattern id="diagonalHatchBlue" patternUnits="userSpaceOnUse" width="8" height="8"
|
||||
patternTransform="rotate(45 0 0)">
|
||||
<line x1="0" y1="0" x2="0" y2="10" style="stroke:#5b818f; stroke-width:8"/>
|
||||
</pattern>
|
||||
|
||||
|
||||
<filter id="sofGlowRed" height="300%" width="300%" x="-75%" y="-75%">
|
||||
<feMorphology operator="dilate" radius="4" in="SourceAlpha" result="thicken"/>
|
||||
<feGaussianBlur in="thicken" stdDeviation="5" result="blurred"/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {PlayerState, WorldBoundaries, Team} from "../Data/Parser";
|
||||
import {Class, PlayerState, SpyState, Team, WorldBoundaries} from "../Data/Parser";
|
||||
|
||||
export interface PlayerProp {
|
||||
player: PlayerState;
|
||||
|
|
@ -52,6 +52,9 @@ export function Player(props: PlayerProp) {
|
|||
const transform = () => `translate(${scaledX()} ${scaledY()}) scale(${1 / props.scale})`;
|
||||
const rotate = () => `rotate(${270 - props.player.angle})`;
|
||||
const filter = () => props.player.ubered ? ((props.player.team === Team.Red) ? 'url(#sofGlowRed)' : 'url(#sofGlowBlue)') : '';
|
||||
const circleStrokeStyle = () => props.player.playerClass === Class.Spy ? `${(props.player.class_data as SpyState).cloak} 100` : "none";
|
||||
const teamHatch = () => (props.player.team === Team.Red) ? `url(#diagonalHatchRed)` : `url(#diagonalHatchBlue)`
|
||||
const fill = () => props.player.cloaked ? teamHatch() : teamColor();
|
||||
|
||||
return <g
|
||||
onmouseover={() => props.onHover(props.player.info.userId)}
|
||||
|
|
@ -61,7 +64,8 @@ export function Player(props: PlayerProp) {
|
|||
opacity={imageOpacity()}
|
||||
transform={rotate()}/>
|
||||
<circle r={16} stroke-width={props.highlighted ? 4 : 1.5} stroke="white"
|
||||
fill={teamColor()}
|
||||
stroke-dasharray={circleStrokeStyle()}
|
||||
fill={fill()}
|
||||
opacity={alpha()}
|
||||
filter={filter()}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import {PlayerState} from "../Data/Parser";
|
||||
import {KillFeedItem} from "./KillFeed";
|
||||
import {Class, MedigunType, PlayerState, SpyState} from "../Data/Parser";
|
||||
|
||||
export interface PlayerSpecProps {
|
||||
player: PlayerState;
|
||||
|
|
@ -68,7 +67,7 @@ function filterPlayers(players: PlayerState[], team: number): PlayerState[] {
|
|||
}
|
||||
|
||||
function medics(players: PlayerState[]): PlayerState[] {
|
||||
return players.filter(player => player.playerClass === 5);
|
||||
return players.filter(player => player.playerClass === Class.Medic);
|
||||
}
|
||||
|
||||
export function PlayersSpec(props: PlayersSpecProps) {
|
||||
|
|
@ -86,7 +85,8 @@ export function PlayersSpec(props: PlayersSpecProps) {
|
|||
<For each={redMedics()}>{(player) =>
|
||||
<UberSpec
|
||||
team={teamMap[player.team]}
|
||||
chargeLevel={player.charge}
|
||||
chargeLevel={player.class_data.charge}
|
||||
medigun={player.class_data.medigun}
|
||||
isDeath={player.health < 1}
|
||||
/>
|
||||
}</For>
|
||||
|
|
@ -99,7 +99,8 @@ export function PlayersSpec(props: PlayersSpecProps) {
|
|||
<For each={blueMedics()}>{(player) =>
|
||||
<UberSpec
|
||||
team={teamMap[player.team]}
|
||||
chargeLevel={player.charge}
|
||||
chargeLevel={player.class_data.charge}
|
||||
medigun={player.class_data.medigun}
|
||||
isDeath={player.health < 1}
|
||||
/>
|
||||
}</For>
|
||||
|
|
@ -135,7 +136,15 @@ export function PlayerSpec(props: PlayerSpecProps) {
|
|||
|
||||
function getPlayerIcon(player: PlayerState) {
|
||||
if (classMap[player.playerClass]) {
|
||||
return <div class={classMap[player.playerClass] + " class-icon"}/>
|
||||
const className = classMap[player.playerClass];
|
||||
if (player.playerClass === Class.Spy && classMap[(player.class_data as SpyState).disguise_class]) {
|
||||
const disguiseClassName = classMap[(player.class_data as SpyState).disguise_class];
|
||||
return (<div class={className + " class-icon"}>
|
||||
<div class={disguiseClassName + " disguise"}/>
|
||||
</div>)
|
||||
} else {
|
||||
return <div class={className + " class-icon"}/>
|
||||
}
|
||||
} else {
|
||||
return <div class={"class-icon"}/>
|
||||
}
|
||||
|
|
@ -143,20 +152,33 @@ function getPlayerIcon(player: PlayerState) {
|
|||
|
||||
export interface UberSpecProps {
|
||||
chargeLevel: number;
|
||||
medigun: MedigunType;
|
||||
team: string;
|
||||
isDeath: boolean;
|
||||
}
|
||||
|
||||
export function UberSpec({chargeLevel, team, isDeath}: UberSpecProps) {
|
||||
const healthStatusClass = (isDeath) ? 'dead' : '';
|
||||
export function UberSpec(props: UberSpecProps) {
|
||||
const healthStatusClass = (props.isDeath) ? 'dead' : '';
|
||||
const medigunName = () => {
|
||||
switch (props.medigun) {
|
||||
case MedigunType.Kritzkrieg:
|
||||
return "Kritzkrieg";
|
||||
case MedigunType.Quickfix:
|
||||
return "Quickfix";
|
||||
case MedigunType.Vaccinator:
|
||||
return "Vaccinator";
|
||||
default:
|
||||
return "Ubercharge";
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div class={`playerspec uber ${team} ${healthStatusClass}`}>
|
||||
<div class={`playerspec uber ${props.team} ${healthStatusClass}`}>
|
||||
<div class={"uber class-icon"}/>
|
||||
<div class="health-container">
|
||||
<div class="healthbar"
|
||||
style={{width: chargeLevel + '%'}}/>
|
||||
<span class="player-name">Charge</span>
|
||||
<span class="health">{Math.round(chargeLevel)}</span>
|
||||
style={{width: props.chargeLevel + '%'}}/>
|
||||
<span class="player-name">{medigunName()}</span>
|
||||
<span class="health">{Math.round(props.chargeLevel)}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
height: 28px;
|
||||
}
|
||||
|
||||
& .class-icon, .steam-avatar {
|
||||
& .class-icon, .steam-avatar, & .disguise {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: inline-block;
|
||||
|
|
@ -42,6 +42,12 @@
|
|||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
}
|
||||
|
||||
&.disguise {
|
||||
background-size: 28px 28px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 25%;
|
||||
}
|
||||
}
|
||||
|
||||
& .player-name {
|
||||
|
|
@ -78,6 +84,42 @@
|
|||
}
|
||||
}
|
||||
|
||||
& .disguise.scout {
|
||||
background-image: url('inline://images/class_portraits/disguises/scout.webp');
|
||||
}
|
||||
|
||||
& .disguise.soldier {
|
||||
background-image: url('inline://images/class_portraits/disguises/soldier.webp');
|
||||
}
|
||||
|
||||
& .disguise.pyro {
|
||||
background-image: url('inline://images/class_portraits/disguises/pyro.webp');
|
||||
}
|
||||
|
||||
& .disguise.demoman {
|
||||
background-image: url('inline://images/class_portraits/disguises/demoman.webp');
|
||||
}
|
||||
|
||||
& .disguise.engineer {
|
||||
background-image: url('inline://images/class_portraits/disguises/engineer.webp');
|
||||
}
|
||||
|
||||
& .disguise.heavy {
|
||||
background-image: url('inline://images/class_portraits/disguises/heavy.webp');
|
||||
}
|
||||
|
||||
& .disguise.medic {
|
||||
background-image: url('inline://images/class_portraits/disguises/medic.webp');
|
||||
}
|
||||
|
||||
& .disguise.sniper {
|
||||
background-image: url('inline://images/class_portraits/disguises/sniper.webp');
|
||||
}
|
||||
|
||||
& .disguise.spy {
|
||||
background-image: url('inline://images/class_portraits/disguises/spy.webp');
|
||||
}
|
||||
|
||||
&.red {
|
||||
& .health-container {
|
||||
background-color: #a75d50aa;
|
||||
|
|
@ -127,7 +169,7 @@
|
|||
background-image: url('inline://images/charge_red.svg');
|
||||
}
|
||||
|
||||
& .class-icon, & .steam-avatar {
|
||||
& .class-icon, & .steam-avatar, & .disguise {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
|
|
|||