mirror of
https://github.com/lexogrine/cs2-react-hud.git
synced 2026-05-04 04:03:10 +02:00
Updated setup to vite and moved to hooks instead of class
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Bomb } from 'csgogsi-socket';
|
||||
import maps, { ScaleConfig, MapConfig, ZoomAreas } from './maps';
|
||||
import './index.css';
|
||||
import type { Bomb, Side } from 'csgogsi';
|
||||
import maps, { MapConfig, ZoomAreas } from './maps';
|
||||
import './index.scss';
|
||||
import { RadarPlayerObject, RadarGrenadeObject } from './interface';
|
||||
import config from './config';
|
||||
import { parsePosition } from './utils';
|
||||
interface IProps {
|
||||
players: RadarPlayerObject[];
|
||||
grenades: RadarGrenadeObject[];
|
||||
@@ -12,99 +12,154 @@ interface IProps {
|
||||
zoom?: ZoomAreas;
|
||||
mapConfig: MapConfig,
|
||||
reverseZoom: string,
|
||||
parsePosition: (position: number[], size: number, config: ScaleConfig) => number[]
|
||||
}
|
||||
const isShooting = (lastShoot: number) => (new Date()).getTime() - lastShoot <= 250;
|
||||
|
||||
type Explosion = {
|
||||
position: number[],
|
||||
grenadeId: string
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
/*
|
||||
const SMOKE_PARTICLE_BASE_AMOUNT = 10;
|
||||
//const PARTICLE_MAP_SOURCE = Array(SMOKE_PARTICLE_BASE_AMOUNT**2).fill(0);
|
||||
|
||||
const SMOKE_PARTICLE_SIZE_ABSOLUT = config.smokeSize/SMOKE_PARTICLE_BASE_AMOUNT;
|
||||
|
||||
const PARTICLE_SIDE = `${SMOKE_PARTICLE_SIZE_ABSOLUT}px`;
|
||||
|
||||
|
||||
|
||||
|
||||
const FRAG_RADIUS = 40;
|
||||
//const MAX_DISTANCE_BETWEEN_FRAG_AND_SMOKE = config.smokeSize + FRAG_RADIUS;
|
||||
|
||||
const getDistance = (X: number[], Y: number[]) => {
|
||||
const a = X[0] - Y[0];
|
||||
const b = X[1] - Y[1];
|
||||
|
||||
return Math.sqrt( a*a + b*b );
|
||||
}
|
||||
|
||||
const getPosOfIndex = (index: number, origin: number[]) => {
|
||||
const leftI = index%10;
|
||||
const topI = Math.floor(index/10);
|
||||
return ([origin[0] - config.smokeSize/2 + (leftI + 0.5) * SMOKE_PARTICLE_SIZE_ABSOLUT, origin[1] - config.smokeSize/2 + (topI + 0.5)*SMOKE_PARTICLE_SIZE_ABSOLUT]);
|
||||
}
|
||||
|
||||
const Particle = ({ index, explosions, origin }: { index: number, origin: number[], explosions: Explosion[] }) => {
|
||||
const PARTICLE_POSITION = getPosOfIndex(index, origin);
|
||||
const isHidden = getDistance(PARTICLE_POSITION, origin) > config.smokeSize/2 || explosions.some(expl => getDistance(expl.position, PARTICLE_POSITION) <= FRAG_RADIUS);
|
||||
return (<div className={`particle ${isHidden ? 'hide':''}`} style={{ width: PARTICLE_SIDE, height: PARTICLE_SIDE}}/>);
|
||||
}
|
||||
*/
|
||||
|
||||
const Grenade = ({ reverseZoom, type, state, visible, position, flames, side }: { explosions: Explosion[], reverseZoom: string, side: Side | null, flames: boolean, type: RadarGrenadeObject["type"], state: RadarGrenadeObject["state"], visible: boolean, position: number[] }) => {
|
||||
if (flames) {
|
||||
return null;
|
||||
}
|
||||
|
||||
renderGrenade = (grenade: RadarGrenadeObject) => {
|
||||
if ("flames" in grenade) {
|
||||
return null;
|
||||
}
|
||||
const { reverseZoom } = this.props;
|
||||
if(type === "smoke" && (state === "landed" || state === "exploded")){
|
||||
return (
|
||||
<div key={grenade.id} className={`grenade ${grenade.type} ${grenade.side || ''} ${grenade.state} ${grenade.visible ? 'visible':'hidden'}`}
|
||||
<div className={`grenade ${type} ${state} ${side || ''} ${visible ? 'visible':'hidden'}`}
|
||||
style={{
|
||||
transform: `translateX(${grenade.position[0].toFixed(2)}px) translateY(${grenade.position[1].toFixed(2)}px) translateZ(10px) scale(${reverseZoom})`,
|
||||
transform: `translateX(${position[0]}px) translateY(${position[1]}px) translateZ(10px) scale(${reverseZoom})`,
|
||||
}}>
|
||||
<div className="content" style={{ width: config.smokeSize, height: config.smokeSize }}>
|
||||
<div className="explode-point"></div>
|
||||
<div className="background"></div>
|
||||
<div className="background">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
renderDot = (player: RadarPlayerObject) => {
|
||||
const { reverseZoom } = this.props;
|
||||
return (
|
||||
<div className={`grenade ${type} ${state} ${side || ''} ${visible ? 'visible':'hidden'}`}
|
||||
style={{
|
||||
transform: `translateX(${position[0]}px) translateY(${position[1]}px) translateZ(10px) scale(${reverseZoom})`,
|
||||
}}>
|
||||
<div className="content">
|
||||
<div className="explode-point"></div>
|
||||
<div className="background"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Bomb = ({ bomb, mapConfig, reverseZoom }: { reverseZoom: string, bomb?: Bomb | null, mapConfig: MapConfig }) => {
|
||||
if(!bomb) return null;
|
||||
if(bomb.state === "carried" || bomb.state === "planting") return null;
|
||||
if("config" in mapConfig){
|
||||
const position = parsePosition(bomb.position, mapConfig.config);
|
||||
if(!position) return null;
|
||||
|
||||
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'}`}
|
||||
<div className={`bomb ${bomb.state} visible`}
|
||||
style={{
|
||||
transform: `translateX(${player.position[0].toFixed(2)}px) translateY(${player.position[1].toFixed(2)}px) translateZ(10px) scale(${reverseZoom})`,
|
||||
transform: `translateX(${position[0].toFixed(2)}px) translateY(${position[1].toFixed(2)}px) translateZ(10px) scale(${reverseZoom})`
|
||||
}}>
|
||||
<div className="content">
|
||||
<div className="explode-point"></div>
|
||||
<div className="background"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return mapConfig.configs.map(config => {
|
||||
const position = parsePosition(bomb.position, 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) scale(${reverseZoom})`
|
||||
}}>
|
||||
<div className="content">
|
||||
<div className="explode-point"></div>
|
||||
<div className="background"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
const PlayerDot = ({ player, reverseZoom }: { reverseZoom: string, player: RadarPlayerObject }) => {
|
||||
const isShootingNow = isShooting(player.lastShoot);
|
||||
//console.log('x',isShooting(player.lastShoot), player.steamid);
|
||||
return (
|
||||
<div
|
||||
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})`,
|
||||
|
||||
}}>
|
||||
<div className="content" style={{
|
||||
width: config.playerSize * player.scale,
|
||||
height: 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 className="background-fire" style={{ transform: `rotate(${-90 + player.position[2]}deg)`, opacity: isShootingNow ? 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>
|
||||
)
|
||||
}
|
||||
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;
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const style: React.CSSProperties = { backgroundImage: `url(${maps[this.props.mapName].file})` }
|
||||
|
||||
const Radar = ({ players, grenades, mapConfig, bomb, mapName, zoom, reverseZoom }: IProps) => {
|
||||
//if(players.length === 0) return null;
|
||||
|
||||
const style: React.CSSProperties = { backgroundImage: `url(${maps[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;
|
||||
const explosions = grenades.filter(grenade => grenade.type === "frag" && grenade.state === "exploded");
|
||||
return <div className="map" style={style}>
|
||||
{players.map(this.renderDot)}
|
||||
{grenades.map(this.renderGrenade)}
|
||||
{this.renderBomb()}
|
||||
{players.map(player => <PlayerDot reverseZoom={reverseZoom} key={player.id} player={player} />)}
|
||||
{grenades.map(grenade => <Grenade explosions={explosions.map(g => ({ position: g.position, grenadeId: g.id }))} reverseZoom={reverseZoom} side={grenade.side} key={grenade.id} type={grenade.type} state={grenade.state} visible={grenade.visible} position={grenade.position} flames={"flames" in grenade} />)}
|
||||
<Bomb reverseZoom={reverseZoom} bomb={bomb} mapConfig={mapConfig} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default Radar;
|
||||
|
||||
@@ -1,322 +1,79 @@
|
||||
import React from 'react';
|
||||
import { Player, Bomb } from 'csgogsi-socket';
|
||||
import maps, { ScaleConfig } from './maps';
|
||||
import { Player, Bomb, Grenade, FragOrFireBombOrFlashbandGrenade } from 'csgogsi';
|
||||
import maps from './maps';
|
||||
import LexoRadar from './LexoRadar';
|
||||
import { ExtendedGrenade, Grenade, RadarPlayerObject, RadarGrenadeObject } from './interface';
|
||||
import config from './config';
|
||||
import { RadarPlayerObject, RadarGrenadeObject } from './interface';
|
||||
import { EXPLODE_TIME_FRAG, explosionPlaces, extendGrenade, extendPlayer, grenadesStates, playersStates } from './utils';
|
||||
import { GSI } from '../../../API/HUD';
|
||||
|
||||
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
|
||||
grenades: Grenade[]
|
||||
size?: number,
|
||||
mapName: string
|
||||
}
|
||||
|
||||
class App extends React.Component<IProps> {
|
||||
round = (n: number) => {
|
||||
const r = 0.02;
|
||||
return Math.round(n / r) * r;
|
||||
GSI.prependListener("data", () => {
|
||||
|
||||
const currentGrenades = GSI.current?.grenades || []
|
||||
grenadesStates.unshift(currentGrenades);
|
||||
grenadesStates.splice(5);
|
||||
|
||||
playersStates.unshift(GSI.current?.players || []);
|
||||
playersStates.splice(5);
|
||||
});
|
||||
|
||||
GSI.prependListener("data", data => {
|
||||
const { last } = GSI;
|
||||
if(!last) return;
|
||||
|
||||
for(const grenade of data.grenades.filter((grenade): grenade is FragOrFireBombOrFlashbandGrenade => grenade.type === "frag")){
|
||||
const old = last.grenades.find((oldGrenade): oldGrenade is FragOrFireBombOrFlashbandGrenade => oldGrenade.id === grenade.id);
|
||||
if(!old) continue;
|
||||
|
||||
if(grenade.lifetime >= EXPLODE_TIME_FRAG && old.lifetime < EXPLODE_TIME_FRAG){
|
||||
explosionPlaces[grenade.id] = grenade.position;
|
||||
}
|
||||
}
|
||||
|
||||
parsePosition = (position: number[], size: number, config: ScaleConfig) => {
|
||||
if (!(this.props.mapName in maps)) {
|
||||
return [0, 0];
|
||||
for(const grenadeId of Object.keys(explosionPlaces)){
|
||||
const doesExist = data.grenades.some(grenade => grenade.id === grenadeId);
|
||||
if(!doesExist){
|
||||
delete explosionPlaces[grenadeId];
|
||||
}
|
||||
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];
|
||||
}
|
||||
const LexoRadarContainer = ({ size = 300, mapName, bomb, player, players, grenades }: IProps) => {
|
||||
const offset = (size - (size * size / 1024)) / 2;
|
||||
|
||||
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>;
|
||||
}
|
||||
if (!(mapName in maps)) {
|
||||
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'}
|
||||
/>
|
||||
Unsupported map
|
||||
</div>;
|
||||
}
|
||||
const playersExtended: RadarPlayerObject[] = players.map(pl => extendPlayer({ player: pl, steamId: player?.steamid || null, mapName })).filter((player): player is RadarPlayerObject => player !== null).flat();
|
||||
const grenadesExtended = grenades.map(grenade => extendGrenade({ grenade, side: playersExtended.find(player => player.steamid === grenade.owner)?.side || 'CT', mapName })).filter(entry => entry !== null).flat() as RadarGrenadeObject[];
|
||||
const config = maps[mapName];
|
||||
|
||||
const zooms = config && config.zooms || [];
|
||||
|
||||
const activeZoom = zooms.find(zoom => zoom.threshold(playersExtended.map(pl => pl.player)));
|
||||
|
||||
const reverseZoom = 1/(activeZoom && activeZoom.zoom || 1);
|
||||
// s*(1024-s)/2048
|
||||
return <div className="map-container" style={{ width: size, height: size, transform: `scale(${size / 1024})`, top: -offset, left: -offset }}>
|
||||
<LexoRadar
|
||||
players={playersExtended}
|
||||
grenades={grenadesExtended}
|
||||
bomb={bomb}
|
||||
mapName={mapName}
|
||||
mapConfig={config}
|
||||
zoom={activeZoom}
|
||||
reverseZoom={DESCALE_ON_ZOOM ? reverseZoom.toFixed(2) : '1'}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default LexoRadarContainer;
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,6 @@
|
||||
const config = {
|
||||
playerSize: 60,
|
||||
playerSize: 70,
|
||||
smokeSize: 50
|
||||
}
|
||||
|
||||
export default config;
|
||||
@@ -51,12 +51,20 @@ html, body, .map-container {
|
||||
}
|
||||
.map .player, .map .grenade, .map .bomb {
|
||||
position: absolute;
|
||||
height:30px;
|
||||
width:30px;
|
||||
height:0px;
|
||||
width:0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.5s ease;
|
||||
.content {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/*transition: all 0.1s ease;/**/
|
||||
}
|
||||
.map .player .background {
|
||||
@@ -66,7 +74,7 @@ html, body, .map-container {
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
transition:transform 0.2s ease;
|
||||
transition:transform 0.2s linear;
|
||||
}
|
||||
.map .player .background-fire {
|
||||
position: absolute;
|
||||
@@ -140,10 +148,12 @@ html, body, .map-container {
|
||||
}
|
||||
*/
|
||||
.map .player.active {
|
||||
width:120%;
|
||||
height:120%;
|
||||
z-index: 3;
|
||||
}
|
||||
.map .player.active .content {
|
||||
width:48px;
|
||||
height:48px;
|
||||
}
|
||||
.map .grenade .background {
|
||||
border-radius:50%;
|
||||
background-size: contain;
|
||||
@@ -157,9 +167,6 @@ html, body, .map-container {
|
||||
opacity: 1;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
.map .grenade.smoke {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
.map .grenade.smoke.inair .background {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
@@ -167,7 +174,6 @@ html, body, .map-container {
|
||||
filter: invert(1);
|
||||
}
|
||||
.map .grenade.smoke.exploded .background {
|
||||
opacity: 0;
|
||||
}
|
||||
.map .grenade.flashbang, .map .grenade.frag {
|
||||
filter: invert(1);
|
||||
@@ -183,6 +189,8 @@ html, body, .map-container {
|
||||
.map .grenade .explode-point, .map .bomb .explode-point {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
top: calc(50% - 1px);
|
||||
left: calc(50% - 1px);
|
||||
height: 2px;
|
||||
border-radius: 0.08px;
|
||||
}
|
||||
@@ -194,7 +202,6 @@ html, body, .map-container {
|
||||
opacity: 0;
|
||||
}
|
||||
.map .grenade.smoke .background {
|
||||
border: 5px solid grey;
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.map .grenade.smoke.CT .background {
|
||||
@@ -214,9 +221,8 @@ html, body, .map-container {
|
||||
width:12px;
|
||||
height:12px;
|
||||
}
|
||||
.map .grenade.smoke {
|
||||
width:60px;
|
||||
height:60px;
|
||||
.map .grenade .content {
|
||||
position: absolute;
|
||||
}
|
||||
.map .grenade.inferno .background {
|
||||
background-color: red;
|
||||
@@ -270,16 +276,29 @@ html, body, .map-container {
|
||||
background: red;
|
||||
}
|
||||
|
||||
@keyframes Hidden {
|
||||
from {
|
||||
}
|
||||
to {
|
||||
display: none !important;
|
||||
}
|
||||
.map .player.dead .background {
|
||||
display: none;
|
||||
}
|
||||
.map .player.dead .label {
|
||||
background: transparent;
|
||||
}
|
||||
.map .player.dead.CT .label, .map .player.CT .label div {
|
||||
color: var(--color-new-ct);
|
||||
}
|
||||
.map .player.dead.T .label, .map .player.T .label div {
|
||||
color: var(--color-new-t);
|
||||
}
|
||||
.map .hidden {
|
||||
opacity: 0;
|
||||
animation: Hidden 1s ease 1s 1;
|
||||
animation-fill-mode: forwards;/**/
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
}
|
||||
.map .grenade {
|
||||
&.smoke {
|
||||
&.exploded, &.landed {
|
||||
.background {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Player, Side } from "csgogsi";
|
||||
import { Player, Side, Grenade } from "csgogsi";
|
||||
|
||||
export interface RadarPlayerObject {
|
||||
id: string,
|
||||
@@ -26,30 +26,5 @@ export interface RadarGrenadeObject {
|
||||
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, };
|
||||
export type ExtendedGrenade = Grenade & { side: Side | null, };
|
||||
@@ -1,15 +0,0 @@
|
||||
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.
|
Before Width: | Height: | Size: 77 KiB |
@@ -7,8 +7,7 @@ const config = {
|
||||
"y": 340.2921393569175
|
||||
},
|
||||
"pxPerUX": 0.20118507589946494,
|
||||
"pxPerUY": -0.20138282875746794,
|
||||
"originHeight": -170,
|
||||
"pxPerUY": -0.20138282875746794
|
||||
},
|
||||
"file": radar
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Player } from 'csgogsi-socket';
|
||||
import radar from './radar.png'
|
||||
|
||||
const high = {
|
||||
@@ -32,21 +31,6 @@ const config = {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,15 @@ 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';
|
||||
import api from '../../../../API';
|
||||
import { Player } from 'csgogsi';
|
||||
|
||||
export type ZoomAreas = {
|
||||
threshold: (players: Player[]) => boolean;
|
||||
origin: number[],
|
||||
zoom: number
|
||||
}
|
||||
|
||||
export interface ScaleConfig {
|
||||
origin: {
|
||||
x:number,
|
||||
@@ -29,7 +27,7 @@ export interface ScaleConfig {
|
||||
|
||||
interface SingleLayer {
|
||||
config: ScaleConfig,
|
||||
file: string,
|
||||
file: string
|
||||
zooms?: ZoomAreas[]
|
||||
}
|
||||
|
||||
@@ -39,7 +37,7 @@ interface DoubleLayer {
|
||||
config: ScaleConfig,
|
||||
isVisible: (height: number) => boolean
|
||||
}[],
|
||||
file: string,
|
||||
file: string
|
||||
zooms?: ZoomAreas[]
|
||||
}
|
||||
|
||||
@@ -54,8 +52,7 @@ const maps: { [key: string] : MapConfig} = {
|
||||
de_overpass,
|
||||
de_nuke,
|
||||
de_vertigo,
|
||||
de_ancient,
|
||||
de_anubis
|
||||
de_ancient
|
||||
}
|
||||
|
||||
api.maps.get().then(fallbackMaps => {
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import { Player, Side, Grenade } from "csgogsi";
|
||||
import maps, { ScaleConfig } from "./maps";
|
||||
import { ExtendedGrenade, RadarGrenadeObject, RadarPlayerObject } from "./interface";
|
||||
import { InfernoGrenade } from "csgogsi";
|
||||
|
||||
export const playersStates: Player[][] = [];
|
||||
export const grenadesStates: Grenade[][] = [];
|
||||
const directions: { [key: string]: number } = {};
|
||||
|
||||
export const explosionPlaces: 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];
|
||||
}
|
||||
|
||||
export const round = (n: number) => {
|
||||
const r = 0.02;
|
||||
return Math.round(n / r) * r;
|
||||
}
|
||||
|
||||
export const parsePosition = (position: number[], config: ScaleConfig) => {
|
||||
const left = config.origin.x + (position[0] * config.pxPerUX);
|
||||
const top = config.origin.y + (position[1] * config.pxPerUY);
|
||||
|
||||
return [round(left), round(top)];
|
||||
}
|
||||
|
||||
export const parsePlayerPosition = (player: Player, mapConfig: ScaleConfig) => {
|
||||
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 => parsePosition(playerEntry.position, 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];
|
||||
}
|
||||
|
||||
|
||||
const parseGrenadePosition = (grenade: ExtendedGrenade, config: ScaleConfig) => {
|
||||
if(grenade.id in explosionPlaces) return parsePosition(explosionPlaces[grenade.id], config);
|
||||
const grenadeData = grenadesStates.slice(0, 5).map(grenades => grenades.filter(gr => gr.id === grenade.id)[0]).filter(pl => !!pl);
|
||||
if (grenadeData.length === 0) return "position" in grenade ? parsePosition(grenade.position, config) : null;
|
||||
const positions = grenadeData.map(grenadeEntry => ("position" in grenadeEntry ? parsePosition(grenadeEntry.position, config) : null)).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];
|
||||
}
|
||||
|
||||
export const EXPLODE_TIME_FRAG = 1.6;
|
||||
export const EXPLODE_TIME_FLASH = 1.45;
|
||||
|
||||
export const extendGrenade = ({grenade, mapName, side }: { side: Side, grenade: Grenade, mapName: string}) => {
|
||||
// const owner = this.props.players.find(player => player.steamid === grenade.owner);
|
||||
const extGrenade: ExtendedGrenade = {
|
||||
...grenade,
|
||||
side:/* owner?.team.side ||*/ side
|
||||
}
|
||||
const map = maps[mapName];
|
||||
if (extGrenade.type === "inferno") {
|
||||
const mapFlame = (flame: InfernoGrenade["flames"][number]) => {
|
||||
if ("config" in map) {
|
||||
return ({
|
||||
position: parsePosition(flame.position, map.config),
|
||||
id: `${flame.id}_${extGrenade.id}`,
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
return map.configs.map(config => ({
|
||||
id: `${flame}_${extGrenade.id}_${config.id}`,
|
||||
visible: config.isVisible(flame.position[2]),
|
||||
position: parsePosition(flame.position, config.config)
|
||||
}));
|
||||
}
|
||||
const flames = 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 = parseGrenadePosition(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) {
|
||||
grenadeObject.state = "landed";
|
||||
if (extGrenade.effecttime >= 16.5) {
|
||||
grenadeObject.state = 'exploded';
|
||||
}
|
||||
}
|
||||
} else if ((extGrenade.type === 'flashbang' && extGrenade.lifetime >= EXPLODE_TIME_FLASH) || (extGrenade.type === 'frag' && extGrenade.lifetime >= EXPLODE_TIME_FRAG)) {
|
||||
grenadeObject.state = 'exploded';
|
||||
}
|
||||
return grenadeObject;
|
||||
}
|
||||
return map.configs.map(config => {
|
||||
const position = parseGrenadePosition(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[2])
|
||||
}
|
||||
if (extGrenade.type === "smoke") {
|
||||
if (extGrenade.effecttime !== 0) {
|
||||
grenadeObject.state = "landed";
|
||||
if (extGrenade.effecttime >= 16.5) {
|
||||
grenadeObject.state = 'exploded';
|
||||
}
|
||||
}
|
||||
} else if ((extGrenade.type === 'flashbang' && extGrenade.lifetime >= EXPLODE_TIME_FLASH) || (extGrenade.type === 'frag' && extGrenade.lifetime >= EXPLODE_TIME_FRAG)) {
|
||||
grenadeObject.state = 'exploded';
|
||||
}
|
||||
return grenadeObject;
|
||||
}).filter((grenade): grenade is RadarGrenadeObject => grenade !== null);
|
||||
}
|
||||
|
||||
export const extendPlayer = ({ player, steamId, mapName }: { mapName: string, player: Player, steamId: string | null}): RadarPlayerObject | RadarPlayerObject[] | 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[mapName];
|
||||
const playerObject: RadarPlayerObject = {
|
||||
id: player.steamid,
|
||||
label: player.observer_slot !== undefined ? player.observer_slot : "",
|
||||
side: player.team.side,
|
||||
position: [],
|
||||
visible: true,
|
||||
isActive: steamId === player.steamid,
|
||||
forward: 0,
|
||||
scale: 1,
|
||||
steamid: player.steamid,
|
||||
flashed: player.state.flashed > 35,
|
||||
shooting: isShooting,
|
||||
lastShoot: shooting.lastShoot,
|
||||
isAlive: player.state.health > 0,
|
||||
hasBomb: !!Object.values(player.weapons).find(weapon => weapon.type === "C4"),
|
||||
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 = parsePlayerPosition(player, map.config);
|
||||
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: parsePlayerPosition(player, config.config),
|
||||
id: `${player.steamid}_${config.id}`,
|
||||
visible: config.isVisible(player.position[2])
|
||||
})
|
||||
});
|
||||
}
|
||||
+13
-43
@@ -1,50 +1,20 @@
|
||||
import React from "react";
|
||||
import { isDev } from './../../api/api';
|
||||
import { CSGO } from "csgogsi-socket";
|
||||
import { CSGO } from "csgogsi";
|
||||
import LexoRadarContainer from './LexoRadar/LexoRadarContainer';
|
||||
|
||||
|
||||
|
||||
interface Props { radarSize: number, game: CSGO }
|
||||
interface State {
|
||||
showRadar: boolean,
|
||||
loaded: boolean,
|
||||
boltobserv:{
|
||||
css: boolean,
|
||||
maps: boolean
|
||||
}
|
||||
|
||||
const Radar = ({ radarSize, game }: Props) => {
|
||||
const { players, player, bomb, grenades, map } = game;
|
||||
return <LexoRadarContainer
|
||||
players={players}
|
||||
player={player}
|
||||
bomb={bomb}
|
||||
grenades={grenades}
|
||||
size={radarSize}
|
||||
mapName={map.name.substring(map.name.lastIndexOf('/')+1)}
|
||||
/>
|
||||
}
|
||||
|
||||
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)}
|
||||
/>
|
||||
}
|
||||
}
|
||||
export default Radar;
|
||||
+44
-56
@@ -1,70 +1,58 @@
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
import "./radar.scss";
|
||||
import { Match, Veto } from "../../api/interfaces";
|
||||
import { Map, CSGO, Team } from 'csgogsi-socket';
|
||||
import { actions } from './../../App';
|
||||
import { Match, Veto } from "../../API/types";
|
||||
import { Map, CSGO, Team } from 'csgogsi';
|
||||
import Radar from './Radar'
|
||||
import TeamLogo from "../MatchBar/TeamLogo";
|
||||
|
||||
import { useAction } from "../../API/contexts/actions";
|
||||
|
||||
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 })) });
|
||||
const RadarMaps = ({ match, map, game }: Props) => {
|
||||
const [ radarSize, setRadarSize ] = useState(366);
|
||||
const [ showBig, setShowBig ] = useState(false);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
useAction('radarBigger', () => {
|
||||
setRadarSize(p => p+10);
|
||||
}, []);
|
||||
|
||||
useAction('radarSmaller', () => {
|
||||
setRadarSize(p => p-10);
|
||||
}, []);
|
||||
|
||||
useAction('toggleRadarView', () => {
|
||||
setShowBig(p => !p);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id={`radar_maps_container`} className={` ${showBig ? 'preview':''}`}>
|
||||
{match ? <MapsBar match={match} map={map} game={game} /> : null}
|
||||
<Radar radarSize={showBig ? 600: radarSize} game={game} />
|
||||
</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>
|
||||
}
|
||||
export default RadarMaps;
|
||||
|
||||
const MapsBar = ({ match, map }: 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">
|
||||
{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 className="bestof">Best of {match.matchType.replace("bo", "")}</div>
|
||||
{<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">
|
||||
<div className="bestof">Best of {match.matchType.replace("bo", "")}</div>
|
||||
{match.vetos.filter(veto => veto.type !== "ban").filter(veto => veto.teamId || veto.type === "decider").map(veto => <MapEntry key={veto.mapName} veto={veto} map={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>
|
||||
}
|
||||
const MapEntry = ({ veto, map }: { veto: Veto, map: Map, team: Team | null }) => {
|
||||
return <div className="veto_entry">
|
||||
<div className={`map_name ${map.name.includes(veto.mapName) ? 'active' : ''}`}>{veto.mapName.replace("de_", "")}</div>
|
||||
</div>
|
||||
}
|
||||
+22
-10
@@ -3,7 +3,6 @@
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
border: none;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
transition: all 1s;
|
||||
.map-container {
|
||||
transition: all 1s;
|
||||
@@ -24,18 +23,24 @@
|
||||
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);
|
||||
color: white;
|
||||
background-color: var(--sub-panel-color);
|
||||
|
||||
.bestof {
|
||||
width: 121px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
.veto_entry {
|
||||
display: flex;
|
||||
@@ -59,8 +64,15 @@
|
||||
width: 23px;
|
||||
}
|
||||
}
|
||||
.map_name.active {
|
||||
text-shadow: 0 0 15px white;
|
||||
font-weight: 600;
|
||||
.map_name {
|
||||
font-size:12px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
opacity: 0.5;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user