1
0
mirror of https://github.com/lexogrine/cs2-react-hud.git synced 2026-05-04 04:03:10 +02:00

initial commit

This commit is contained in:
Hubert Walczak
2023-09-11 12:37:32 +02:00
commit 989ede8638
247 changed files with 7656 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
import React from 'react';
import { Bomb } from 'csgogsi-socket';
import maps, { ScaleConfig, MapConfig, ZoomAreas } from './maps';
import './index.css';
import { RadarPlayerObject, RadarGrenadeObject } from './interface';
import config from './config';
interface IProps {
players: RadarPlayerObject[];
grenades: RadarGrenadeObject[];
bomb?: Bomb | null;
mapName: string;
zoom?: ZoomAreas;
mapConfig: MapConfig,
reverseZoom: string,
parsePosition: (position: number[], size: number, config: ScaleConfig) => number[]
}
const isShooting = (lastShoot: number) => (new Date()).getTime() - lastShoot <= 250;
class App extends React.Component<IProps> {
constructor(props: IProps) {
super(props);
this.state = {
players: [],
grenades: [],
bomb: null
}
}
renderGrenade = (grenade: RadarGrenadeObject) => {
if ("flames" in grenade) {
return null;
}
const { reverseZoom } = this.props;
return (
<div key={grenade.id} className={`grenade ${grenade.type} ${grenade.side || ''} ${grenade.state} ${grenade.visible ? 'visible':'hidden'}`}
style={{
transform: `translateX(${grenade.position[0].toFixed(2)}px) translateY(${grenade.position[1].toFixed(2)}px) translateZ(10px) scale(${reverseZoom})`,
}}>
<div className="explode-point"></div>
<div className="background"></div>
</div>
)
}
renderDot = (player: RadarPlayerObject) => {
const { reverseZoom } = this.props;
return (
<div key={player.id}
className={`player ${player.shooting? 'shooting':''} ${player.flashed ? 'flashed':''} ${player.side} ${player.hasBomb ? 'hasBomb':''} ${player.isActive ? 'active' : ''} ${!player.isAlive ? 'dead' : ''} ${player.visible ? 'visible':'hidden'}`}
style={{
transform: `translateX(${player.position[0].toFixed(2)}px) translateY(${player.position[1].toFixed(2)}px) translateZ(10px) scale(${reverseZoom})`,
width: config.playerSize * player.scale,
height: config.playerSize * player.scale,
}}>
<div className="background-fire" style={{ transform: `rotate(${-90 + player.position[2]}deg)`, opacity: isShooting(player.lastShoot) ? 1 : 0 }} ><div className="bg"/></div>
<div className="background" style={{ transform: `rotate(${45 + player.position[2]}deg)` }}></div>
<div className="label">{player.label}</div>
</div>
)
}
renderBomb = () => {
const { bomb, mapConfig, reverseZoom } = this.props;
if(!bomb) return null;
if(bomb.state === "carried" || bomb.state === "planting") return null;
if("config" in mapConfig){
const position = this.props.parsePosition(bomb.position, 30, mapConfig.config);
if(!position) return null;
return (
<div className={`bomb ${bomb.state} visible`}
style={{
transform: `translateX(${position[0].toFixed(2)}px) translateY(${position[1].toFixed(2)}px) translateZ(10px) scale(${reverseZoom})`
}}>
<div className="explode-point"></div>
<div className="background"></div>
</div>
)
}
return mapConfig.configs.map(config => {
const position = this.props.parsePosition(bomb.position, 30, config.config);
if(!position) return null;
return (
<div className={`bomb ${bomb.state} ${config.isVisible(bomb.position[2]) ? 'visible':'hidden'}`}
style={{
transform: `translateX(${position[0].toFixed(2)}px) translateY(${position[1].toFixed(2)}px) translateZ(10px)`
}}>
<div className="explode-point"></div>
<div className="background"></div>
</div>
)
});
}
render() {
const { players, grenades, zoom } = this.props;
const style: React.CSSProperties = { backgroundImage: `url(${maps[this.props.mapName].file})` }
if(zoom){
style.transform = `scale(${zoom.zoom})`;
style.transformOrigin = `${zoom.origin[0]}px ${zoom.origin[1]}px`;
}
//if(players.length === 0) return null;
return <div className="map" style={style}>
{players.map(this.renderDot)}
{grenades.map(this.renderGrenade)}
{this.renderBomb()}
</div>;
}
}
export default App;
@@ -0,0 +1,322 @@
import React from 'react';
import { Player, Bomb } from 'csgogsi-socket';
import maps, { ScaleConfig } from './maps';
import LexoRadar from './LexoRadar';
import { ExtendedGrenade, Grenade, RadarPlayerObject, RadarGrenadeObject } from './interface';
import config from './config';
const DESCALE_ON_ZOOM = true;
let playersStates: Player[][] = [];
let grenadesStates: ExtendedGrenade[][] = [];
const directions: Record<string, number> = {};
type ShootingState = {
ammo: number,
weapon: string,
lastShoot: number
}
let shootingState: Record<string, ShootingState> = {};
const calculateDirection = (player: Player) => {
if (directions[player.steamid] && !player.state.health) return directions[player.steamid];
const [forwardV1, forwardV2] = player.forward;
let direction = 0;
const [axisA, axisB] = [Math.asin(forwardV1), Math.acos(forwardV2)].map(axis => axis * 180 / Math.PI);
if (axisB < 45) {
direction = Math.abs(axisA);
} else if (axisB > 135) {
direction = 180 - Math.abs(axisA);
} else {
direction = axisB;
}
if (axisA < 0) {
direction = -(direction -= 360);
}
if (!directions[player.steamid]) {
directions[player.steamid] = direction;
}
const previous = directions[player.steamid];
let modifier = previous;
modifier -= 360 * Math.floor(previous / 360);
modifier = -(modifier -= direction);
if (Math.abs(modifier) > 180) {
modifier -= 360 * Math.abs(modifier) / modifier;
}
directions[player.steamid] += modifier;
return directions[player.steamid];
}
interface IProps {
players: Player[],
bomb?: Bomb | null,
player: Player | null,
grenades?: any
size?: number,
mapName: string
}
class App extends React.Component<IProps> {
round = (n: number) => {
const r = 0.02;
return Math.round(n / r) * r;
}
parsePosition = (position: number[], size: number, config: ScaleConfig) => {
if (!(this.props.mapName in maps)) {
return [0, 0];
}
const left = config.origin.x + (position[0] * config.pxPerUX) - (size / 2);
const top = config.origin.y + (position[1] * config.pxPerUY) - (size / 2);
return [this.round(left), this.round(top)];
}
parseGrenadePosition = (grenade: ExtendedGrenade, config: ScaleConfig) => {
if (!("position" in grenade)) {
return null;
}
let size = 30;
if (grenade.type === "smoke") {
size = 60;
}
return this.parsePosition(grenade.position.split(", ").map(pos => Number(pos)), size, config);
}
getGrenadePosition = (grenade: ExtendedGrenade, config: ScaleConfig) => {
const grenadeData = grenadesStates.slice(0, 5).map(grenades => grenades.filter(gr => gr.id === grenade.id)[0]).filter(pl => !!pl);
if (grenadeData.length === 0) return null;
const positions = grenadeData.map(grenadeEntry => this.parseGrenadePosition(grenadeEntry, config)).filter(posData => posData !== null) as number[][];
if (positions.length === 0) return null;
const entryAmount = positions.length;
let x = 0;
let y = 0;
for (const position of positions) {
x += position[0];
y += position[1];
}
return [x / entryAmount, y / entryAmount];
}
getPosition = (player: Player, mapConfig: ScaleConfig, scale: number) => {
const playerData = playersStates.slice(0, 5).map(players => players.filter(pl => pl.steamid === player.steamid)[0]).filter(pl => !!pl);
if (playerData.length === 0) return [0, 0];
const positions = playerData.map(playerEntry => this.parsePosition(playerEntry.position, config.playerSize * scale, mapConfig));
const entryAmount = positions.length;
let x = 0;
let y = 0;
for (const position of positions) {
x += position[0];
y += position[1];
}
const degree = calculateDirection(player);
return [x / entryAmount, y / entryAmount, degree];
}
mapPlayer = (active: Player | null) => (player: Player): RadarPlayerObject | RadarPlayerObject[] | null => {
if (!(this.props.mapName in maps)) {
return null;
}
const weapons = player.weapons ? Object.values(player.weapons) : [];
const weapon = weapons.find(weapon => weapon.state === "active" && weapon.type !== "C4" && weapon.type !== "Knife" && weapon.type !== "Grenade");
const shooting: ShootingState = { ammo: weapon && weapon.ammo_clip || 0, weapon: weapon && weapon.name || '', lastShoot: 0 };
const lastShoot = shootingState[player.steamid] || shooting;
let isShooting = false;
if (shooting.weapon === lastShoot.weapon && shooting.ammo < lastShoot.ammo) {
isShooting = true;
}
shooting.lastShoot = isShooting ? (new Date()).getTime() : lastShoot.lastShoot;
shootingState[player.steamid] = shooting;
const map = maps[this.props.mapName];
const playerObject: RadarPlayerObject = {
id: player.steamid,
label: player.observer_slot !== undefined ? player.observer_slot : "",
side: player.team.side,
position: [],
visible: true,
isActive: !!active && active.steamid === player.steamid,
forward: 0,
steamid: player.steamid,
isAlive: player.state.health > 0,
hasBomb: !!Object.values(player.weapons).find(weapon => weapon.type === "C4"),
flashed: player.state.flashed > 35,
shooting: isShooting,
lastShoot: shooting.lastShoot,
scale: 1,
player
}
if ("config" in map) {
const scale = map.config.originHeight === undefined ? 1 : (1 + (player.position[2] - map.config.originHeight) / 1000);
playerObject.scale = scale;
const position = this.getPosition(player, map.config, scale);
playerObject.position = position;
return playerObject;
}
return map.configs.map(config => {
const scale = config.config.originHeight === undefined ? 1 : (1 + (player.position[2] - config.config.originHeight) / 750);
playerObject.scale = scale;
return ({
...playerObject,
position: this.getPosition(player, config.config, scale),
id: `${player.steamid}_${config.id}`,
visible: config.isVisible(player.position[2])
})
});
}
mapGrenade = (extGrenade: ExtendedGrenade) => {
if (!(this.props.mapName in maps)) {
return null;
}
const map = maps[this.props.mapName];
if (extGrenade.type === "inferno") {
const mapFlame = (id: string) => {
if ("config" in map) {
return ({
position: this.parsePosition(extGrenade.flames[id].split(", ").map(pos => Number(pos)), 12, map.config),
id: `${id}_${extGrenade.id}`,
visible: true
});
}
return map.configs.map(config => ({
id: `${id}_${extGrenade.id}_${config.id}`,
visible: config.isVisible(extGrenade.flames[id].split(", ").map(Number)[2]),
position: this.parsePosition(extGrenade.flames[id].split(", ").map(pos => Number(pos)), 12, config.config)
}));
}
const flames = Object.keys(extGrenade.flames).map(mapFlame).flat();
const flameObjects: RadarGrenadeObject[] = flames.map(flame => ({
...flame,
side: extGrenade.side,
type: 'inferno',
state: 'landed'
}));
return flameObjects;
}
if ("config" in map) {
const position = this.getGrenadePosition(extGrenade, map.config);
if (!position) return null;
const grenadeObject: RadarGrenadeObject = {
type: extGrenade.type,
state: 'inair',
side: extGrenade.side,
position,
id: extGrenade.id,
visible: true
}
if (extGrenade.type === "smoke") {
if (extGrenade.effecttime !== "0.0") {
grenadeObject.state = "landed";
if (Number(extGrenade.effecttime) >= 16.5) {
grenadeObject.state = 'exploded';
}
}
} else if (extGrenade.type === 'flashbang' || extGrenade.type === 'frag') {
if (Number(extGrenade.lifetime) >= 1.25) {
grenadeObject.state = 'exploded';
}
}
return grenadeObject;
}
return map.configs.map(config => {
const position = this.getGrenadePosition(extGrenade, config.config);
if (!position) return null;
const grenadeObject: RadarGrenadeObject = {
type: extGrenade.type,
state: 'inair',
side: extGrenade.side,
position,
id: `${extGrenade.id}_${config.id}`,
visible: config.isVisible(extGrenade.position.split(", ").map(Number)[2])
}
if (extGrenade.type === "smoke") {
if (extGrenade.effecttime !== "0.0") {
grenadeObject.state = "landed";
if (Number(extGrenade.effecttime) >= 16.5) {
grenadeObject.state = 'exploded';
}
}
} else if (extGrenade.type === 'flashbang' || extGrenade.type === 'frag') {
if (Number(extGrenade.lifetime) >= 1.25) {
grenadeObject.state = 'exploded';
}
}
return grenadeObject;
}).filter((grenade): grenade is RadarGrenadeObject => grenade !== null);
}
getSideOfGrenade = (grenade: Grenade) => {
const owner = this.props.players.find(player => player.steamid === grenade.owner);
if (!owner) return null;
return owner.team.side;
}
render() {
const players: RadarPlayerObject[] = this.props.players.map(this.mapPlayer(this.props.player)).filter((player): player is RadarPlayerObject => player !== null).flat();
playersStates.unshift(this.props.players);
if (playersStates.length > 5) {
playersStates = playersStates.slice(0, 5);
}
let grenades: RadarGrenadeObject[] = [];
const currentGrenades = Object.keys(this.props.grenades as { [key: string]: Grenade }).map(grenadeId => ({ ...this.props.grenades[grenadeId], id: grenadeId, side: this.getSideOfGrenade(this.props.grenades[grenadeId]) })) as ExtendedGrenade[];
if (currentGrenades) {
grenades = currentGrenades.map(this.mapGrenade).filter(entry => entry !== null).flat() as RadarGrenadeObject[];
grenadesStates.unshift(currentGrenades);
}
if (grenadesStates.length > 5) {
grenadesStates = grenadesStates.slice(0, 5);
}
const size = this.props.size || 300;
const offset = (size - (size * size / 1024)) / 2;
const config = maps[this.props.mapName];
const zooms = config && config.zooms || [];
const activeZoom = zooms.find(zoom => zoom.threshold(players.map(pl => pl.player)));
const reverseZoom = 1/(activeZoom && activeZoom.zoom || 1);
// s*(1024-s)/2048
if (!(this.props.mapName in maps)) {
return <div className="map-container" style={{ width: size, height: size, transform: `scale(${size / 1024})`, top: -offset, left: -offset }}>
Unsupported map
</div>;
}
return <div className="map-container" style={{ width: size, height: size, transform: `scale(${size / 1024})`, top: -offset, left: -offset }}>
<LexoRadar
players={players}
grenades={grenades}
parsePosition={this.parsePosition}
bomb={this.props.bomb}
mapName={this.props.mapName}
mapConfig={maps[this.props.mapName]}
zoom={activeZoom}
reverseZoom={DESCALE_ON_ZOOM ? reverseZoom.toFixed(2) : '1'}
/>
</div>;
}
}
export default App;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
const config = {
playerSize: 60,
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,5 @@
import Firebomb from './firebomb.png'
import Flash from './flash.png'
import Smoke from './smoke.png'
export { Firebomb, Flash, Smoke };
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+285
View File
@@ -0,0 +1,285 @@
html, body, .map-container {
width:100%;
height:100%;
margin: 0;
}
@keyframes FlashOrFragDeployed {
0% {
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.8);
opacity: 1;
}
100% {
box-shadow: 0 0 0 50px rgba(0, 0, 0, 0.8);
opacity:0;
}
}
@keyframes BombPlanted {
0% {
box-shadow: 0 0 0 0 rgba(185, 5, 5, 0.8);
}
100% {
box-shadow: 0 0 0 50px rgba(185, 5, 5, 0);
}
}
@keyframes BombExploded {
0% {
box-shadow: 0 0 0 0 rgba(185, 5, 5, 0.8);
}
100% {
box-shadow: 0 0 0 150px rgba(185, 5, 5, 0);
}
}
@keyframes BombDefused {
0% {
box-shadow: 0 0 0 0 rgba(5, 185, 5, 0.8);
}
100% {
box-shadow: 0 0 0 150px rgba(5, 185, 5, 0);
}
}
.map-container {
position: relative;
}
.map-container .map {
width:1024px;
height: 1024px;
position: relative;
transform: scale(1);
transition: all 0.5s;
}
.map .player, .map .grenade, .map .bomb {
position: absolute;
height:30px;
width:30px;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.5s ease;
/*transition: all 0.1s ease;/**/
}
.map .player .background {
/*background-color:white;*/
/*clip-path: polygon(0 0, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0 0);*/
background-image: url('./assets/playerBg.png');
background-position: center;
background-size: contain;
background-repeat: no-repeat;
transition:transform 0.2s ease;
}
.map .player .background-fire {
position: absolute;
width:200%;
height:200%;
display: flex;
align-items: center;
justify-content: center;
transition:transform 0.2s ease;
/*background-color:white;*/
/*clip-path: polygon(0 0, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0 0);*/
}
@keyframes Blink {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.map .player .background-fire .bg {
width:100%;
height:100%;
background-image: url('./assets/shootFire.png');
background-position: center;
background-size: contain;
background-repeat: no-repeat;
position: relative;
left: 50%;
animation: Blink;
animation-duration: 0.25s;
animation-iteration-count: infinite;
}
.map .player.dead {
opacity: 0.2;
z-index: 1;
}
.map .player {
transition:transform 0.1s ease, opacity 1s;
image-rendering: -moz-crisp-edges; /* Firefox */
image-rendering: -o-crisp-edges; /* Opera */
image-rendering: -webkit-optimize-contrast; /* Webkit (non-standard naming) */
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor; /* IE (non-standard property) */
}
.map .player:not(.dead) {
z-index: 2;
}
.map .player.flashed.CT:not(.dead) .label {
background: rgb(159 197 255);
}
.map .player.flashed.T:not(.dead) .label {
background: rgb(255 219 165);
}
.map .player.active .background {
width:120%;
height:120%;
}
/*
.map .player.shooting .background {
width: 110%;
height: 110%;
}
.map .player.active.shooting .background {
width: 132%;
height: 132%;
}
*/
.map .player.active {
width:120%;
height:120%;
z-index: 3;
}
.map .grenade .background {
border-radius:50%;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
opacity:1;
transition: opacity 0.25s;
}
.map .grenade.smoke .background {
background-color: rgba(255,255,255,0.5);
opacity: 1;
transition: opacity 1s;
}
.map .grenade.smoke {
transition: all 0.5s;
}
.map .grenade.smoke.inair .background {
background-color: transparent !important;
border: none !important;
background-image: url('./grenades/smoke.png');
filter: invert(1);
}
.map .grenade.smoke.exploded .background {
opacity: 0;
}
.map .grenade.flashbang, .map .grenade.frag {
filter: invert(1);
}
.map .grenade.flashbang .background {
background-image: url('./grenades/flash.png');
background-color: transparent;
}
.map .grenade.frag .background {
background-image: url('./grenades/frag.png');
background-color: transparent;
}
.map .grenade .explode-point, .map .bomb .explode-point {
position: absolute;
width: 2px;
height: 2px;
border-radius: 0.08px;
}
.map .grenade.flashbang.exploded .explode-point, .map .grenade.frag.exploded .explode-point {
animation: FlashOrFragDeployed 0.25s 1 forwards;
}
.map .grenade.flashbang.exploded .background, .map .grenade.frag.exploded .background {
opacity: 0;
}
.map .grenade.smoke .background {
border: 5px solid grey;
background-color: rgba(255,255,255,0.5);
}
.map .grenade.smoke.CT .background {
border: 5px solid var(--color-new-ct);
background-color: rgba(255, 255, 255,0.5);
}
.map .grenade.smoke.T .background {
border: 5px solid var(--color-new-t);
background-color: rgba(255, 255, 255,0.5);
}
.map .grenade.firebomb .background {
background-color:transparent;
background-image: url('./grenades/firebomb.png');
filter: invert(1);
}
.map .grenade.inferno {
width:12px;
height:12px;
}
.map .grenade.smoke {
width:60px;
height:60px;
}
.map .grenade.inferno .background {
background-color: red;
opacity: 0.5;
border: 2px solid orange;
}
.map .player .background, .map .player .label,.map .grenade .background, .map .bomb .background {
position: absolute;
width:100%;
height:100%;
display: flex;
align-items: center;
justify-content: center;
}
.map .player .label {
color: white;
font-weight: 600;
border-radius: 50%;
transition: background-color 0.5s;
-webkit-font-smoothing: crisp-edges;
font-size: 37px;
text-shadow: 4px 4px 0 black;
}
.map .bomb {
transition:transform 0.1s ease;
}
.map .bomb .background {
background-image: url('./grenades/bomb.png');
background-size: 66%;
background-position: center;
background-repeat: no-repeat;
border-radius:50%;
}
.map .bomb.planted .explode-point, .map .bomb.defusing .explode-point {
animation: BombPlanted 2s infinite;
}
.map .bomb.exploded .explode-point {
animation: BombExploded 2s 1 forwards;
}
.map .bomb.defused .explode-point {
animation: BombDefused 2s 1 forwards;
}
.map .player.CT .label {
background: rgb(16, 88, 197)
}
.map .player.T .label {
background: rgb(255, 153, 0);
}
.map .player.T.hasBomb .label {
background: red;
}
@keyframes Hidden {
from {
}
to {
display: none !important;
}
}
.map .hidden {
opacity: 0;
animation: Hidden 1s ease 1s 1;
animation-fill-mode: forwards;/**/
}
+55
View File
@@ -0,0 +1,55 @@
import { Player, Side } from "csgogsi";
export interface RadarPlayerObject {
id: string,
label: string | number,
visible: boolean,
side: Side,
position: number[],
forward: number,
isActive: boolean,
isAlive: boolean,
steamid: string,
hasBomb: boolean,
flashed: boolean,
shooting: boolean,
lastShoot: number,
scale: number,
player: Player
}
export interface RadarGrenadeObject {
state: 'inair' | 'landed' | 'exploded'
side: Side | null,
type: 'decoy' | 'smoke' | 'frag' | 'firebomb' | 'flashbang' | 'inferno',
position: number[],
visible: boolean,
id: string,
}
export interface GrenadeBase {
owner: string,
type: 'decoy' | 'smoke' | 'frag' | 'firebomb' | 'flashbang' | 'inferno'
lifetime: string
}
export interface DecoySmokeGrenade extends GrenadeBase {
position: string,
velocity: string,
type: 'decoy' | 'smoke',
effecttime: string,
}
export interface DefaultGrenade extends GrenadeBase {
position: string,
type: 'frag' | 'firebomb' | 'flashbang',
velocity: string,
}
export interface InfernoGrenade extends GrenadeBase {
type: 'inferno',
flames: { [key: string]: string }
}
export type Grenade = DecoySmokeGrenade | DefaultGrenade | InfernoGrenade;
export type ExtendedGrenade = Grenade & { id: string, side: Side | null, };
@@ -0,0 +1,15 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 583.2590342775677,
"y": 428.92222042149115
},
"pxPerUX": 0.1983512056034216,
"pxPerUY": -0.20108163914549304
},
"file":radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@@ -0,0 +1,15 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 536.3392873296655,
"y": 638.0789844851904
},
"pxPerUX": 0.1907910426894958,
"pxPerUY": -0.18993888105312648
},
"file":radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

@@ -0,0 +1,15 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 361.7243823603619,
"y": 579.553558767951
},
"pxPerUX": 0.1830927328891829,
"pxPerUY": -0.17650705879909936
},
"file":radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,15 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 563.1339320329055,
"y": 736.9535330430065
},
"pxPerUX": 0.2278315639654376,
"pxPerUY": -0.22776482548619972
},
"file":radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@@ -0,0 +1,15 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 426.51386123945593,
"y": 790.7266981544722
},
"pxPerUX": 0.2041685571162696,
"pxPerUY": -0.20465735943851654
},
"file":radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

@@ -0,0 +1,16 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 645.7196725473384,
"y": 340.2921393569175
},
"pxPerUX": 0.20118507589946494,
"pxPerUY": -0.20138282875746794,
"originHeight": -170,
},
"file": radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

@@ -0,0 +1,37 @@
import radar from './radar.png'
const high = {
"origin": {
"x": 473.1284773048749,
"y": 165.7329003801045
},
"pxPerUX": 0.14376095926926907,
"pxPerUY": -0.14736670935219626
};
const low = {
"origin": {
"x": 473.66746071612374,
"y": 638.302101754172
},
"pxPerUX": 0.1436068746398272,
"pxPerUY": -0.14533406508526941
};
const config = {
configs: [
{
id: 'high',
config: high,
isVisible: (height: number) => height >= -450,
},
{
id: 'low',
config: low,
isVisible: (height: number) => height < -450,
},
],
file: radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1,15 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 927.3988878244819,
"y": 343.8221009185496
},
"pxPerUX": 0.1923720959212443,
"pxPerUY": -0.19427507725530338
},
"file":radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

@@ -0,0 +1,15 @@
import radar from './radar.png'
const config = {
"config": {
"origin": {
"x": 527.365542903922,
"y": 511.81469648562296
},
"pxPerUX": 0.21532584158170223,
"pxPerUY": -0.21299254526091588
},
"file":radar
}
export default config;
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,67 @@
import { Player } from 'csgogsi-socket';
import radar from './radar.png'
const high = {
"origin": {
"x": 784.4793452283254,
"y": 255.42597837029027
},
"pxPerUX": 0.19856123172015677,
"pxPerUY": -0.19820052722907044
};
const low = {
"origin": {
"x": 780.5145858437052,
"y": 695.4259783702903
},
"pxPerUX": 0.1989615567841087,
"pxPerUY": -0.19820052722907044
};
const config = {
configs: [
{
id: 'high',
config: high,
isVisible: (height: number) => height >= 11700,
},
{
id: 'low',
config: low,
isVisible: (height: number) => height < 11700,
},
],
zooms: [{
threshold: (players: Player[]) => {
const alivePlayers = players.filter(player => player.state.health);
return alivePlayers.length > 0 && alivePlayers.every(player => player.position[2] < 11700)
},
origin: [472, 1130],
zoom: 2
}, {
threshold: (players: Player[]) => {
const alivePlayers = players.filter(player => player.state.health);
return alivePlayers.length > 0 && players.filter(player => player.state.health).every(player => player.position[2] >= 11700);
},
origin: [528, 15],
zoom: 1.75
}],
file: radar
}
export default config;
/*
import radar from './radar.png'
export default {
"config": {
"origin": {
"x": 971.5536135341899,
"y": 424.5618319055844
},
"pxPerUX": 0.34708183044632246,
"pxPerUY": -0.3450882697407333
},
"file":radar
}*/
Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

+71
View File
@@ -0,0 +1,71 @@
import de_mirage from './de_mirage';
import de_cache from './de_cache';
import de_dust2 from './de_dust2';
import de_inferno from './de_inferno';
import de_train from './de_train';
import de_overpass from './de_overpass';
import de_nuke from './de_nuke';
import de_vertigo from './de_vertigo';
import de_anubis from './de_anubis';
import de_ancient from './de_ancient';
import api from '../../../../api/api';
import { Player } from 'csgogsi-socket';
export type ZoomAreas = {
threshold: (players: Player[]) => boolean;
origin: number[],
zoom: number
}
export interface ScaleConfig {
origin: {
x:number,
y:number
},
pxPerUX: number,
pxPerUY: number,
originHeight?: number
}
interface SingleLayer {
config: ScaleConfig,
file: string,
zooms?: ZoomAreas[]
}
interface DoubleLayer {
configs: {
id: string,
config: ScaleConfig,
isVisible: (height: number) => boolean
}[],
file: string,
zooms?: ZoomAreas[]
}
export type MapConfig = SingleLayer | DoubleLayer;
const maps: { [key: string] : MapConfig} = {
de_mirage,
de_cache,
de_inferno,
de_dust2,
de_train,
de_overpass,
de_nuke,
de_vertigo,
de_ancient,
de_anubis
}
api.maps.get().then(fallbackMaps => {
const mapNames = Object.keys(fallbackMaps);
for(const mapName of mapNames){
if(mapName in maps){
continue;
}
maps[mapName] = fallbackMaps[mapName];
}
}).catch(() => {});
export default maps;
+50
View File
@@ -0,0 +1,50 @@
import React from "react";
import { isDev } from './../../api/api';
import { CSGO } from "csgogsi-socket";
import LexoRadarContainer from './LexoRadar/LexoRadarContainer';
interface Props { radarSize: number, game: CSGO }
interface State {
showRadar: boolean,
loaded: boolean,
boltobserv:{
css: boolean,
maps: boolean
}
}
export default class Radar extends React.Component<Props, State> {
state = {
showRadar: true,
loaded: !isDev,
boltobserv: {
css: true,
maps: true
}
}
async componentDidMount(){
/*if(isDev){
const response = await fetch('hud.json');
const hud = await response.json();
const boltobserv = {
css: Boolean(hud && hud.boltobserv && hud.boltobserv.css),
maps: Boolean(hud && hud.boltobserv && hud.boltobserv.maps)
}
this.setState({boltobserv, loaded: true});
}*/
}
render() {
const { players, player, bomb, grenades, map } = this.props.game;
return <LexoRadarContainer
players={players}
player={player}
bomb={bomb}
grenades={grenades}
size={this.props.radarSize}
mapName={map.name.substring(map.name.lastIndexOf('/')+1)}
/>
}
}
+70
View File
@@ -0,0 +1,70 @@
import React from "react";
import "./radar.scss";
import { Match, Veto } from "../../api/interfaces";
import { Map, CSGO, Team } from 'csgogsi-socket';
import { actions } from './../../App';
import Radar from './Radar'
import TeamLogo from "../MatchBar/TeamLogo";
interface Props { match: Match | null, map: Map, game: CSGO }
interface State { showRadar: boolean, radarSize: number, showBig: boolean }
export default class RadarMaps extends React.Component<Props, State> {
state = {
showRadar: true,
radarSize: 350,
showBig: false
}
componentDidMount() {
actions.on('radarBigger', () => this.radarChangeSize(20));
actions.on('radarSmaller', () => this.radarChangeSize(-20));
actions.on('toggleRadar', () => { this.setState(state => ({ showRadar: !state.showRadar })) });
actions.on("toggleRadarView", () => {
this.setState({showBig:!this.state.showBig});
});
}
radarChangeSize = (delta: number) => {
const newSize = this.state.radarSize + delta;
this.setState({ radarSize: newSize > 0 ? newSize : this.state.radarSize });
}
render() {
const { match } = this.props;
const { radarSize, showBig, showRadar } = this.state;
const size = showBig ? 600 : radarSize;
return (
<div id={`radar_maps_container`} className={`${!showRadar ? 'hide' : ''} ${showBig ? 'preview':''}`}>
<div className="radar-component-container" style={{width: `${size}px`, height: `${size}px`}}><Radar radarSize={size} game={this.props.game} /></div>
{match ? <MapsBar match={this.props.match} map={this.props.map} game={this.props.game} /> : null}
</div>
);
}
}
class MapsBar extends React.PureComponent<Props> {
render() {
const { match, map } = this.props;
if (!match || !match.vetos.length) return '';
const picks = match.vetos.filter(veto => veto.type !== "ban" && veto.mapName);
if (picks.length > 3) {
const current = picks.find(veto => map.name.includes(veto.mapName));
if (!current) return null;
return <div id="maps_container">
{<MapEntry veto={current} map={map} team={current.type === "decider" ? null : map.team_ct.id === current.teamId ? map.team_ct : map.team_t} />}
</div>
}
return <div id="maps_container">
{match.vetos.filter(veto => veto.type !== "ban").filter(veto => veto.teamId || veto.type === "decider").map(veto => <MapEntry key={veto.mapName} veto={veto} map={this.props.map} team={veto.type === "decider" ? null : map.team_ct.id === veto.teamId ? map.team_ct : map.team_t} />)}
</div>
}
}
class MapEntry extends React.PureComponent<{ veto: Veto, map: Map, team: Team | null }> {
render() {
const { veto, map, team } = this.props;
return <div className="veto_entry">
<div className="team_logo">{team ? <TeamLogo team={team} /> : null}</div>
<div className={`map_name ${map.name.includes(veto.mapName) ? 'active' : ''}`}>{veto.mapName}</div>
</div>
}
}
+66
View File
@@ -0,0 +1,66 @@
#radar_maps_container {
position: fixed;
top: 10px;
left: 10px;
border: none;
background-color: rgba(0,0,0,0.5);
transition: all 1s;
.map-container {
transition: all 1s;
}
}
#iframe_radar {
border: none;
}
#radar_maps_container.hide {
opacity: 0;
}
#radar_maps_container.preview {
transform: rotate3d(1, 0, 0, 14deg) translateX(-50%);
transform-style: preserve-3d;
left: 50%;
top: 100px;
.map {
perspective: 500px;
}
}
.radar-component-container {
width: 350px;
height:350px;
overflow: hidden;
}
#maps_container {
width: 100%;
height: 30px;
display: flex;
flex-direction: row;
justify-content: space-evenly;
background-color: rgba(0,0,0,0.5);
}
.veto_entry {
display: flex;
justify-content: center;
>div {
display: flex;
justify-content: center;
align-items: center;
color: white;
text-transform: uppercase;
font-size: 10pt;
}
.team_logo {
img {
max-height: 23px;
padding-right: 3px;
max-width: 23px;
}
.logo {
height: 23px;
width: 23px;
}
}
.map_name.active {
text-shadow: 0 0 15px white;
font-weight: 600;
}
}