commit 989ede863807fb53bb1d6f8d8b3433ca9b40ce73 Author: Hubert Walczak Date: Mon Sep 11 12:37:32 2023 +0200 initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..79099d8 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +GENERATE_SOURCEMAP=false +BROWSER=none \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..fb1adaf --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +PUBLIC_URL=/dev/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1947a0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +package-lock.json +yarn.lock +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63aefbf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Lexogrine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/OpenBrowserPlugin.js b/OpenBrowserPlugin.js new file mode 100644 index 0000000..ddde924 --- /dev/null +++ b/OpenBrowserPlugin.js @@ -0,0 +1,49 @@ +const open = require('open'); + +module.exports = class OpenBrowser { + constructor(options) { + if (typeof options === 'string') { + this.options = Object.assign( + { + hasOpen: false + }, + { + url: options + } + ); + } else { + this.options = Object.assign( + { + port: 8080, + host: 'localhost', + protocol: 'http:', + hasOpen: false + }, + options + ); + } + } + + apply(compiler) { + const options = this.options; + let url; + let hasOpen = options.hasOpen; + if (options.protocol && !options.protocol.endsWith(':')) options.protocol += ':'; + if (options.url) url = options.url; + else url = `${options.protocol}//${options.host}:${options.port}`; + if (compiler.hooks) { + compiler.hooks.afterEmit.tap('openBrowser', () => { + if (!hasOpen) open(url); + hasOpen = true; + this.options.hasOpen = true; + }); + } else { + compiler.plugin('after-emit', (c, cb) => { + if (!hasOpen) open(url); + hasOpen = true; + this.options.hasOpen = true; + return cb(); + }); + } + } +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..81dcd8a --- /dev/null +++ b/README.md @@ -0,0 +1,143 @@ + +

+

LEXOGRINE HUD

+

Logo

+

Powered by « Lexogrine HUD Manager »

+

+ +# Lexogrine HUD + + + +Fullfledged example of the React HUD made for HUD Manager. It has: + +- Custom actions +- Keybinds +- Killfeed +- Player cam feed +- Custom Radar + +## Keybinds: +### **Left Alt + S** +>Makes radar smaller by 20px; +### **Left Alt + B** +>Makes radar bigger by 20px; +### **Left Alt + T** +>Shows trivia box +### **Left Alt + M** +>Toggles upcoming match box +### **Left Alt + P** +>Toggles player preview +### **Left Alt + C** +>Toggles camera feed +### **Left Ctrl + B** +>Make radar invisible + +## **Panel** +## Trivia settings + +| Field|Description | +|--|--| +| Trivia title| `Text` | +| Trivia content| `Text` | + + +## Display settings + + +| Field|Description | +|--|--| +| Left/right box's title| `Text` | +| Left/right box's title| `Text` | +| Left/right box's image logo| `Image file` | + +## Example settings + +![Preview of HUDs settings](settings.png) + +## Preview + +![Preview of HUDs panel in action](preview.png) + +# Download + +To download it just click here: [DOWNLOAD HUD](https://github.com/lexogrine/csgo-react-hud/releases/latest) + +# Instruction +## Setting up +Fork this repo, clone it, and then run `npm install` and `npm start`. HUD should start on the 3500 port. For this to work have HUD Manager opened so it will pass CS:GO data to the HUD. + +## Identifying HUD +In `/public` directory edit hud.json so it fits you - fill HUD's name, author, version, specify the radar and killfeed functionalities. At the end replace the thumb.png with your icon :) + +## Building & distributing +To build version to distribute and move around, in the root directory run `npm run pack`. It will create the zip file for distribution. Now you can just drag and drop this file into the HUD Managers upload area. + +## Signing + +To create Signed HUD to prevent at least from modyfing compiled Javascript files run `npm run sign`. It's the same as `npm run pack` command but with additional step of signing .js and .css files and hud.json. + + + ## File structure + The HUD is seperated into two parts - the API part, that connects to the HUD Manager API and communicate with it: `src/App.tsx` file and `src/api` directory. Usually, you don't want to play with it, so the whole runs without a problem. + The second part is the render part - `src/HUD`, `src/fonts` and `src/assets` are the directories you want to modify. In the `src/HUD` each element of the HUD is seperated into its own folder. Styles are kept in the `src/HUD/styles`. Names are quite self-explanatory, and to modify style of the element you should just find the styling by the file and class name. + + +## `panel.json` API +To get the incoming data from the HUD Manager, let's take a look at the `src/HUD/SideBoxes/SideBox.tsx` `componentDidMount()` method: +```javascript +import {configs} from './../../App'; +... +configs.onChange((data:any) => { + if(!data) return; + + const display = data.display_settings; + + if(!display) return; + + if(display[`${this.props.side}_title`]){ + this.setState({title:display[`${this.props.side}_title`]}) + } + if(display[`${this.props.side}_subtitle`]){ + this.setState({subtitle:display[`${this.props.side}_subtitle`]}) + } + if(display[`${this.props.side}_image`]){ + this.setState({image:display[`${this.props.side}_image`]}) + } +}); +``` +To retrieve incoming data, you should just import `configs` object and then listen for the changes with `onChange` method. Usually you want to check for the specific data, as in the callback it will always serve the full form from the Manager. +However it looks different in the case of action input. In this case, let's look at the `src/HUD/Trivia/Trivia.tsx`: +```javascript +import {configs, actions} from './../../App'; +... +actions.on("triviaState", (state: any) => { + this.setState({show: state === "show"}) +}); +``` +For the action input we need to import the `actions` object and create listener with the parameter on it. +## `keybinds.json` API +Keybinds API works in very similiar to `panel.json` action API. One more time the example will be from `src/HUD/Trivia/Trivia.tsx`: +```javascript +import {configs, actions} from './../../App'; +... +actions.on("toggleTrivia", () => { + this.setState({show: !this.state.show}) +}); +``` +Keybinds listener works on the same object as action input, in this case however there are no parameter to retrieve. + +## Killfeed +Because our `csgogsi` has the ability to process input from HLAE's MIRV, listening for kills is very easy. We can see than in `src/HUD/Killfeed/Killfeed.tsx`: +```javascript +componentDidMount() { + GSI.on("kill", kill => { + this.addKill(kill); + }); +} +``` +The Killfeed component basically just keeps kills in the state during the round, and after the round it cleans the state. Kills have CSS animation, that makes them gently show, and after a few seconds disappear, the experience is very smooth. You can fiddle with the styling in the `killfeed.css` +This killfeed detects who killed whom, if there was an assist (flash assist as well), used weapon, headshot and wallbang. + +## Radar +Radar is custom React-based component, made by Hubert Walczak, and is easily editable from css. \ No newline at end of file diff --git a/aco b/aco new file mode 100644 index 0000000..d48558e --- /dev/null +++ b/aco @@ -0,0 +1,8 @@ +{"map":"de_train","areas":[{"name":"areaA","polygonCorners":[[151.55847926267285,46.0799999999998],[337.3234562211982,-245.0100000000002],[978.2126267281108,17.909999999999798],[838.8888940092168,327.7799999999998]],"configs":[],"priority":0},{"name":"siteB","polygonCorners":[[-359.2952073732719,-1043.16],[-359.2952073732719,-1409.3700000000001],[393.05294930875584,-1465.71],[374.4764516129033,-1033.7700000000002]],"configs":[],"priority":0}],"_id":"6jYobxlAez134aOT"} +{"map":"de_mirage","areas":[{"name":"T_SPAWN_B","polygonCorners":[[176.3284,691.3078],[186.2224,301.0953],[1393.2881,231.0572],[1403.182,711.3187]],"configs":["spec_mode 5;spec_mode 6;spec_goto 1313.0 421.7 73.9 22.2 -124.2;spec_lerpto 1134.3 662.2 79.3 23.2 -156.0 4 4"],"priority":0},{"name":"T_SPAWN","polygonCorners":[[1101.7962,100.5449],[1443.0434,120.2615],[1453.08,-392.3705],[1121.8695,-382.5122]],"configs":["spec_mode 5;spec_mode 6;spec_goto 1278.9 655.6 -211.9 11.2 -88.8;spec_lerpto 1271.7 166.9 -52.3 11.0 -91.6 8 8","spec_mode 5;spec_mode 6;spec_goto 1084.7 181.2 349.6 65.6 -73.6; spec_lerpto 1295.3 -791.6 323.8 52.1 96.5 15 15;","spec_mode 5;spec_mode 6;spec_goto 1125.1 -1179.0 12.6 12.1 77.8; spec_lerpto 1376.7 -703.9 -39.9 6.9 102.85 5 5;"],"priority":0},{"name":"T_SPAWN_A","polygonCorners":[[713.7751,-564.6353],[1393.004,-564.6353],[1422.97,-1508.112],[673.8205,-1448.524]],"configs":["spec_mode 5;spec_mode 6;spec_goto 1278.9 655.6 -211.9 11.2 -88.8;spec_lerpto 1271.7 166.9 -52.3 11.0 -91.6 4 4","spec_mode 5;spec_mode 6;spec_goto 1286.5 -977.1 99.7 22.2 -155.5; spec_lerpto 794.2 -1085.7 11.5 19.5 -118.8 5 5;"],"priority":0},{"name":"ctSpawn","polygonCorners":[[-2016.6489523809526,-1736.5326666666667],[-2046.4722380952383,-2004.6786666666667],[-1440.0654285714288,-2054.3353333333334],[-1430.1243333333337,-1567.7]],"configs":["CT SPAWN: spec_mode 5;spec_mode 6;spec_goto -995.2 -2655.4 118.8 18.6 136.9; spec_lerpto -2028.2 -2334.6 161.5 34.2 68.0 15 15;"],"priority":0},{"name":"topMid","polygonCorners":[[150.50980952380934,-167.38199999999992],[160.45090476190458,-971.8199999999999],[577.9769047619046,-961.8886666666666],[538.2125238095236,-48.20599999999992],[180.33309523809507,41.17600000000009]],"configs":["spec_mode 5;spec_mode 6;spec_goto -244.9 -547.0 213.9 31.7 17.4; spec_lerpto 491.5 -942.7 123.9 31.2 124.2 10 10;"],"priority":0},{"name":"stairsArea","polygonCorners":[[-475.77919047619065,-1120.79],[-386.30933333333354,-1617.3566666666666],[-793.8942380952383,-1587.5626666666667],[-774.0120476190478,-1289.6226666666666],[-764.0709523809526,-1180.378]],"configs":["spec_mode 5;spec_mode 6;spec_goto -425.7 -1817.0 106.7 22.3 115.9; spec_lerpto -837.8 -1386.0 -37.9 14.3 21.0 5 5;","spec_mode 5;spec_mode 6;spec_goto -883.4 -1313.5 -27.6 8.5 -25.9; spec_lerpto -633.6 -1920.3 -29.0 5.7 68.3 5 5;"],"priority":0},{"name":"bombsiteAm","polygonCorners":[[-78.13538095238113,-2411.8633333333332],[-764.0709523809526,-2362.206666666667],[-754.1298571428573,-1518.0433333333333],[-8.547714285714463,-1369.0733333333333]],"configs":["spec_mode 5;spec_mode 6;spec_goto -733.5 -2334.7 157.2 27.2 36.1; spec_lerpto -629.3 -1559.9 329.8 39.1 -51.2 5 5;","spec_mode 5;spec_mode 6;spec_goto 158.8 -1571.9 96.3 17.6 -152.1; spec_lerpto -803.1 -1621.9 158.3 19.8 -38.8 5 5;"],"priority":0},{"name":"palaceDeski","polygonCorners":[[-58.25319047619065,-1716.67],[-78.13538095238113,-2382.0693333333334],[190.2741904761903,-2382.0693333333334],[220.09747619047602,-1706.7386666666666]],"configs":["spec_mode 5;spec_mode 6;spec_goto -168.0 -1862.4 -70.4 8.5 -24.1; spec_lerpto -210.1 -2082.5 11.0 7.0 -3.4 5 5;"],"priority":0},{"name":"palaceInside","polygonCorners":[[259.861857142857,-1915.2966666666666],[259.861857142857,-2392.000666666667],[1124.7371428571428,-2382.0693333333334],[1134.678238095238,-1905.3653333333334]],"configs":["spec_mode 5;spec_mode 6;spec_goto 1032.8 -2013.3 30.5 3.0 -157.9; spec_lerpto 658.0 -2348.4 29.4 -1.0 146.3 5 5;"],"priority":0},{"name":"rampSetup","polygonCorners":[[269.8029523809522,-1379.0046666666667],[239.9796666666665,-1766.3266666666666],[836.4453809523808,-1736.5326666666667],[886.150857142857,-981.7513333333333],[577.9769047619046,-1011.5453333333334]],"configs":["spec_mode 5;spec_mode 6;spec_goto 234.0 -1463.4 -113.1 4.1 -68.6; spec_lerpto 583.3 -1687.6 -144.5 4.6 67.4 5 5;","spec_mode 5;spec_mode 6;spec_goto 743.8 -1066.2 133.7 52.4 -103.2; spec_lerpto 592.7 -1700.6 6.1 36.9 68.0 5 5;","spec_mode 5;spec_mode 6;spec_goto 980.0 -975.0 74.6 21.5 -115.7; spec_lerpto 732.6 -967.8 130.0 24.1 -95.0 5 5;"],"priority":0},{"name":"jungle","polygonCorners":[[-1271.0668095238098,-1160.5153333333333],[-1310.8311904761906,-1577.6313333333333],[-734.2476666666669,-1567.7],[-754.1298571428573,-1220.1033333333332]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1128.8 -491.1 -47.8 10.3 -89.8; spec_lerpto -1225.4 -1285.7 -111.7 0.7 -30.0 5 5;"],"priority":0},{"name":"kitchen","polygonCorners":[[-2424.2338571428572,-207.10733333333326],[-2434.1749523809526,-683.8113333333333],[-1479.8298095238097,-733.468],[-1489.770904761905,-226.96999999999994]],"configs":["spec_mode 5;spec_mode 6;spec_goto -2341.3 -400.1 -16.3 29.1 -48.4; spec_lerpto -2332.6 -671.4 -45.6 10.0 22.3 5 5;"],"priority":0},{"name":"apsOut","polygonCorners":[[-1927.1790952380954,845.6140000000001],[-1887.4147142857146,637.0560000000002],[-1211.4202380952383,607.2620000000002],[-1161.714761904762,339.1160000000001],[-803.8353333333336,319.25333333333344],[-883.3640952380954,865.4766666666668]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1392.9 727.3 0.0 2.8 -1.8; spec_lerpto -2471.5 674.4 139.4 23.1 10.6 5 5;"],"priority":0},{"name":"bench","polygonCorners":[[-2682.7023333333336,587.3993333333334],[-2642.9379523809525,110.69533333333342],[-2374.528380952381,100.76400000000008],[-2354.646190476191,587.3993333333334]],"configs":["spec_mode 5;spec_mode 6;spec_goto -2622.9 533.9 -16.7 22.7 -33.4; spec_lerpto -2647.1 149.6 -47.6 11.8 22.4 5 5;"],"priority":0},{"name":"edward","polygonCorners":[[-1678.6517142857144,557.6053333333334],[-1201.479142857143,527.8113333333334],[-1181.5969523809526,319.25333333333344],[-754.1298571428573,319.25333333333344],[-724.3065714285716,1.4506666666667483],[-1658.7695238095241,110.69533333333342]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1067.7 140.8 73.8 26.9 140.0; spec_lerpto -1856.5 156.7 -34.2 16.1 27.6 5 5;"],"priority":0},{"name":"wyskokZaps","polygonCorners":[[-2404.351666666667,835.6826666666668],[-2404.351666666667,597.3306666666667],[-2175.706476190476,478.1546666666668],[-1847.6503333333335,458.2920000000001],[-1718.4160952380955,885.3393333333335]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1801.0 449.5 137.2 24.8 143.3; spec_lerpto -2647.1 393.7 232.2 34.4 4.4 5 5;"],"priority":0},{"name":"under","polygonCorners":[[-1092.1270952380955,537.7426666666668],[-923.1284761904764,537.7426666666668],[-923.1284761904764,-842.7126666666667],[-1092.1270952380955,-832.7813333333334]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1006.1 427.3 -259.7 5.7 -87.9; spec_lerpto -1008.5 -269.5 -317.9 -10.2 -86.8 5 5;","spec_mode 5;spec_mode 6;spec_goto -876.8 193.3 -307.6 2.1 -146.4; spec_lerpto -805.7 -70.9 -312.6 0.4 143.5 5 5;"],"priority":0},{"name":"midlawka","polygonCorners":[[-1082.1860000000001,-524.91],[-436.01480952380973,-534.8413333333333],[-436.01480952380973,-822.8499999999999],[-1092.1270952380955,-852.644]],"configs":["spec_mode 5;spec_mode 6;spec_goto -756.6 -420.1 -18.6 29.2 -128.4; spec_lerpto -456.5 -760.5 9.5 23.8 151.8 5 5;","spec_mode 5;spec_mode 6;spec_goto -519.3 -494.9 -119.0 9.6 -121.3; spec_lerpto -820.1 -455.4 27.7 34.5 -112.0 5 5;"],"priority":0},{"name":"ladder","polygonCorners":[[-1231.302428571429,-18.41199999999992],[-1241.243523809524,-346.14599999999996],[-863.481904761905,-366.0086666666666],[-883.3640952380954,-28.343333333333252]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1209.8 -63.4 73.7 22.6 -33.4; spec_lerpto -1014.8 -53.1 -1.6 13.7 -123.0 5 5;"],"priority":0},{"name":"setupB","polygonCorners":[[-972.8339523809526,617.1933333333335],[-485.7202857142859,686.7126666666668],[-475.77919047619065,954.8586666666667],[-48.31209523809542,934.9960000000001],[-78.13538095238113,339.1160000000001],[-982.7750476190478,349.0473333333334]],"configs":["spec_mode 5;spec_mode 6;spec_goto -247.5 937.2 239.1 41.3 -139.2; spec_lerpto -159.4 374.0 210.9 36.9 150.3 5 5;"],"priority":0},{"name":"tv","polygonCorners":[[-247.13400000000019,944.9273333333334],[-227.2518095238097,726.4380000000001],[786.7399047619047,716.5066666666668],[746.9755238095237,915.1333333333334]],"configs":["spec_mode 5;spec_mode 6;spec_goto -456.1 757.0 76.4 21.4 6.9; spec_lerpto 30.4 804.1 -59.1 6.4 1.8 5 5;"],"priority":0},{"name":"short","polygonCorners":[[-933.0695714285716,-8.480666666666586],[-704.4243809523812,-38.27466666666659],[90.86323809523792,-306.4206666666666],[61.03995238095221,-514.9786666666666],[-952.9517619047621,-505.04733333333326]],"configs":["spec_mode 5;spec_mode 6;spec_goto -880.2 -145.4 88.0 31.7 -52.7; spec_lerpto -787.5 -806.6 77.4 22.2 66.1 5 5;"],"priority":0},{"name":"midCenter","polygonCorners":[[-406.19152380952397,-326.28333333333325],[-396.25042857142876,-961.8886666666666],[289.6851428571427,-961.8886666666666],[289.6851428571427,-256.76399999999995]],"configs":["spec_mode 5;spec_mode 6;spec_goto 585.6 -445.1 223.7 31.3 -169.4; spec_lerpto -183.3 -810.3 -64.7 11.6 156.8 5 5;"],"priority":0},{"name":"windowAndUnder","polygonCorners":[[-1300.8900952380955,-405.7339999999999],[-1310.8311904761906,-793.0559999999999],[-873.4230000000002,-812.9186666666666],[-863.481904761905,-415.66533333333325]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1248.1 -530.9 -71.5 9.4 -16.0; spec_lerpto -1232.2 -765.2 -68.7 11.5 28.9 5 5;"],"priority":0},{"name":"ticketCt","polygonCorners":[[-1360.5366666666669,-2064.266666666667],[-1549.4174761904765,-2441.6573333333336],[-803.8353333333336,-2640.2840000000006],[-833.6586190476193,-2203.3053333333332]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1847.6 -2102.1 -39.4 16.8 -14.4; spec_lerpto -1471.8 -2231.8 -45.6 13.4 -23.0 5 5;"],"priority":0},{"name":"kitchenEntrance","polygonCorners":[[-1768.1215714285715,-703.674],[-1758.1804761904764,-1607.4253333333334],[-1420.1832380952383,-1508.112],[-1310.8311904761906,-812.9186666666666]],"configs":["spec_mode 5;spec_mode 6;spec_goto -1596.2 -954.9 244.1 77.9 40.9; spec_lerpto -1704.6 -1350.2 111.2 38.5 62.7 5 5;"],"priority":0},{"name":"bombSiteBArea","polygonCorners":[[-2384.4694761904766,557.6053333333334],[-2374.528380952381,-256.76399999999995],[-1519.5941904761908,-266.6953333333333],[-1529.535285714286,607.2620000000002]],"configs":["spec_mode 5;spec_mode 6;spec_goto -2335.3 -178.7 -87.7 4.7 67.4; spec_lerpto -1587.3 -222.8 209.8 30.2 129.9 5 5;"],"priority":0}],"_id":"M7JVzXFDaBJqKgfv"} +{"map":"de_vertigo","areas":[{"name":"vgjvg","polygonCorners":[[-2721.9782056451613,-1799.0568774703559],[-2691.760826612903,-2525.5937944664033],[-2087.413245967742,-2515.503003952569],[-2137.775544354839,-1778.8752964426878]],"configs":[],"priority":0}],"_id":"SKUivlXVDaw41YQj"} +{"map":"de_cache","areas":[{"name":"dddd","polygonCorners":[[438.44240223463703,-1169.6214453781515],[416.5955493482311,-1588.8681344537817],[1235.8525325884543,-1566.2061512605046],[1203.0822532588454,-1203.6144201680675]],"configs":["asdfsafd"],"priority":0},{"name":"vent","polygonCorners":[[372.90184357541915,76.78763025210057],[361.9784171322162,-229.14914285714312],[809.8389013035383,-229.14914285714312],[798.9154748603353,122.1115966386552],[602.2937988826817,507.3653109243695]],"configs":[],"priority":0}],"_id":"UG43eWK17T5rgwV9"} +{"map":"de_nuke","areas":[{"name":"silo","polygonCorners":[[-174.7934726688099,-1440.4006206896552],[-202.61743837084634,-1738.975517241379],[117.35816720257269,-1874.6913793103447],[326.03790996784596,-1711.8323448275862],[326.03790996784596,-1508.258551724138],[200.830064308682,-1358.971103448276],[33.88627009646338,-1345.3995172413793]],"configs":[],"priority":0},{"name":"BombSiteA","polygonCorners":[[256.4779957127549,-259.67262068965505],[228.65403001071846,-883.9655862068965],[1049.4610182207934,-856.8224137931035],[1049.4610182207934,-246.10103448275854]],"configs":[],"priority":0}],"_id":"WGsUR0Nd1ecSLKWS"} +{"map":"de_inferno","areas":[{"name":"ct","polygonCorners":[[-1873.519931972789,951.4766470588233],[-1834.3366213151928,-162.5805294117647],[-1266.1786167800453,-74.62864705882355],[-1364.1368934240363,1029.6560980392155]],"configs":[],"priority":0}],"_id":"cbOGJKqsrx6dzLPq"} +{"map":"de_overpass","areas":[{"name":"PLAYGROUND","polygonCorners":[[-2970.27947368421,-2255.4509063745018],[-3053.451619433198,-2502.5232569721115],[-2107.3684615384614,-2574.5860258964144],[-2065.7823886639676,-2255.4509063745018]],"configs":[],"priority":0}],"_id":"fztBzTnGZVjgCmlM"} +{"map":"de_dust2","areas":[{"name":"bSite","polygonCorners":[[-2164.4671328671325,3209.2467811158795],[-2199.5807925407926,2471.643862660944],[-1330.5177156177156,2454.0818884120167],[-1312.9608857808857,2805.3213733905577]],"configs":["spec_mode 5;spec_mode 6;spec_goto -880.2 -145.4 88.0 31.7 -52.7; spec_lerpto -787.5 -806.6 77.4 22.2 66.1 5 5;"],"priority":0}],"_id":"naE9a8T3gdiu6iK1"} diff --git a/craco.config.js b/craco.config.js new file mode 100644 index 0000000..4d6f774 --- /dev/null +++ b/craco.config.js @@ -0,0 +1,45 @@ +const path = require('path'); +const fs = require('fs'); +const homedir = require('os').homedir(); +const internalIp = require('internal-ip'); +const OpenBrowserPlugin = require('./OpenBrowserPlugin'); + +const pathToConfig = path.join(process.env.APPDATA || path.join(homedir, '.config'), 'hud-manager', 'databases', 'config'); +let port = 1349; + +const getPort = () => { + if(!fs.existsSync(pathToConfig)){ + console.warn('LHM Config file unavailable'); + return port; + } + + try { + const config = JSON.parse(fs.readFileSync(pathToConfig, 'utf-8')); + + if(!config.port){ + console.warn('LHM Port unavailable'); + } + + console.warn('LHM Port detected as', config.port); + return config.port; + + } catch { + console.warn('LHM Config file invalid'); + return port; + } +} + +port = getPort(); + +module.exports = { + devServer: { + port: 3500, + open: false + }, + webpack: { + configure: (webpackConfig) => { + webpackConfig.plugins.push(new OpenBrowserPlugin({ url: `http://${internalIp.v4.sync()}:${port}/development/`})) + return webpackConfig; + } + } +}; \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..95bc49a Binary files /dev/null and b/icon.png differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..ec89e64 --- /dev/null +++ b/package.json @@ -0,0 +1,60 @@ +{ + "name": "lexogrine_hud", + "version": "1.0.0", + "homepage": "./", + "private": true, + "dependencies": { + "@craco/craco": "^5.7.0", + "@types/jest": "24.0.19", + "@types/node": "16.11.20", + "@types/react": "17.0.38", + "@types/react-dom": "17.0.11", + "@types/simple-peer": "^9.11.3", + "buffer": "^6.0.3", + "csgogsi-socket": "^2.7.1", + "query-string": "^6.12.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-scripts": "5.0.0", + "simple-peer": "^9.11.0", + "simple-websockets": "^1.1.0", + "typescript": "^4.5.4", + "uuid": "^8.3.2" + }, + "license": "GPL-3.0", + "scripts": { + "zip": "npm-build-zip", + "start": "craco start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "pack": "npm run build && npm run zip", + "sign": "npm run build && node sign.js && npm-build-zip --name=signed" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/history": "^4.7.5", + "@types/socket.io-client": "^1.4.32", + "@types/uuid": "^8.3.1", + "internal-ip": "^6.2.0", + "jsonwebtoken": "^8.5.1", + "npm-build-zip": "^1.0.2", + "open": "^8.0.2", + "sass": "^1.32.5", + "socket.io-client": "^2.4.0" + } +} diff --git a/preview.png b/preview.png new file mode 100644 index 0000000..3516f86 Binary files /dev/null and b/preview.png differ diff --git a/public/hud.json b/public/hud.json new file mode 100644 index 0000000..32d48c4 --- /dev/null +++ b/public/hud.json @@ -0,0 +1,13 @@ +{ + "name":"Lexogrine CS2 HUD", + "version":"1.0.0", + "author":"Lexogrine", + "legacy": false, + "radar": true, + "killfeed": false, + "game":"cs2", + "boltobserv":{ + "css":true, + "maps":true + } +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..2342c68 --- /dev/null +++ b/public/index.html @@ -0,0 +1,23 @@ + + + + + + + React App + + + +
+ + + diff --git a/public/keybinds.json b/public/keybinds.json new file mode 100644 index 0000000..26c411e --- /dev/null +++ b/public/keybinds.json @@ -0,0 +1,6 @@ +[ + { + "bind":"Alt+C", + "action":"toggleCams" + } +] \ No newline at end of file diff --git a/public/panel.json b/public/panel.json new file mode 100644 index 0000000..0aa1428 --- /dev/null +++ b/public/panel.json @@ -0,0 +1,148 @@ +[ + { + "label": "Trivia", + "name":"trivia", + "inputs": [ + { + "type": "text", + "name": "title", + "label": "Trivia title" + }, + { + "type": "text", + "name": "content", + "label": "Trivia content" + }, + { + "type": "action", + "name": "triviaState", + "values": [ + { + "name": "show", + "label": "Show trivia" + }, + { + "name": "hide", + "label": "Hide trivia" + } + ] + } + ] + }, + { + "label": "Display settings", + "name":"display_settings", + "inputs": [ + { + "type": "text", + "name": "left_title", + "label": "Left box's title" + }, + { + "type": "text", + "name": "right_title", + "label": "Right box's title" + }, + { + "type": "text", + "name": "left_subtitle", + "label": "Left box's subtitle" + }, + { + "type": "text", + "name": "right_subtitle", + "label": "Right box's subtitle" + }, + { + "type":"image", + "name":"left_image", + "label":"Left box's image logo" + }, + { + "type":"image", + "name":"right_image", + "label":"Right box's image logo" + }, + { + "type": "action", + "name": "boxesState", + "values": [ + { + "name": "show", + "label": "Show boxes" + }, + { + "name": "hide", + "label": "Hide boxes" + } + ] + }, + { + "type": "action", + "name": "toggleRadarView", + "values": [ + { + "name": "toggler", + "label": "Toggle radar view" + } + ] + } + ] + }, + { + "label": "Player & Match overview", + "name":"preview_settings", + "inputs": [ + { + "type": "match", + "name": "match_preview", + "label": "Pick an upcoming match" + }, + { + "type": "select", + "name": "select_preview", + "label": "Mood indicator", + "values": [ + { + "name": "show", + "label": ":)" + }, + { + "name": "hide", + "label": ":(" + } + ] + }, + { + "type": "player", + "name": "player_preview", + "label": "Pick a player to preview" + }, + { + "type": "checkbox", + "name": "player_preview_toggle", + "label": "Show player preview" + }, + { + "type": "checkbox", + "name": "match_preview_toggle", + "label": "Show upcoming match" + }, + { + "type": "action", + "name": "showTournament", + "values": [ + { + "name": "show", + "label": "Show tournament" + }, + { + "name": "hide", + "label": "Hide tournament" + } + ] + } + + ] + } +] \ No newline at end of file diff --git a/public/radar.css b/public/radar.css new file mode 100644 index 0000000..8d305dd --- /dev/null +++ b/public/radar.css @@ -0,0 +1,48 @@ +/* + * Use this file to apply custom styles to the radar image + */ + +/* Any player dot */ +div.dot {} + +/* Players that are CT or T */ +div.dot.CT {} +div.dot.T {} + +/* The player with the bomb */ +div.dot.bomb{} + +/* The player currently being observed */ +div.dot.active {} + +/* A dead player */ +div.dot.dead {} + +/* The number on a player dot */ +div.label {} + +/* The number on the dot being spectated */ +div.label.active {} + +/* The dropped or planted bomb on the map */ +#bomb { + height: 4vmin; + width: 4vmin; + background-repeat:no-repeat; +} + +/* Smoke circles on the map */ +#smokes { + display: none; +} +#smokes > div { + display: none; +} + +/* Inferno circles on the map */ +.inferno > div {} + +/* The advisory on screen */ +#advisory { + display: none !important; +} \ No newline at end of file diff --git a/public/thumb.png b/public/thumb.png new file mode 100644 index 0000000..89b2727 Binary files /dev/null and b/public/thumb.png differ diff --git a/settings.png b/settings.png new file mode 100644 index 0000000..c389c0f Binary files /dev/null and b/settings.png differ diff --git a/sign.js b/sign.js new file mode 100644 index 0000000..835e2c9 --- /dev/null +++ b/sign.js @@ -0,0 +1,75 @@ +const path = require('path'); +const fs = require('fs'); +const crypto = require('crypto'); +const jwt = require('jsonwebtoken'); + +const sign = () => { + const getAllFilesToSign = (hudDir) => { + const files = []; + const getFiles = (dir) => { + fs.readdirSync(dir).forEach(file => { + const fileDirectory = path.join(dir, file); + if (fs.statSync(fileDirectory).isDirectory()) return getFiles(fileDirectory); + else if (fileDirectory.endsWith('.js') || fileDirectory.endsWith('.css')) return files.push(fileDirectory); + }) + } + getFiles(hudDir) + return files; + } + + const dir = path.join(__dirname, 'build'); + + const keyFile = path.join(dir, 'key'); + + if (fs.existsSync(keyFile)) { + return true; + } + + const filesToSign = getAllFilesToSign(dir); + const passphrase = crypto.randomBytes(48).toString('hex'); + + filesToSign.push(path.join(dir, 'hud.json')); + + const keys = crypto.generateKeyPairSync('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase + } + }); + + let success = true; + + const fileToContent = {}; + + filesToSign.forEach(file => { + if (!success) { + return; + } + const content = fs.readFileSync(file, 'utf8'); + try { + const signed = jwt.sign(content, { key: keys.privateKey.toString(), passphrase }, { algorithm: 'RS256' }); + fileToContent[file] = signed; + } catch { + success = false; + } + + }); + + if (!success) return false; + + filesToSign.forEach(file => { + fs.writeFileSync(file, fileToContent[file]); + }); + + fs.writeFileSync(keyFile, keys.publicKey.toString()); + + return success; +} +sign(); \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..780a1f2 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,215 @@ +import React from 'react'; +import Layout from './HUD/Layout/Layout'; +import api, { port, isDev } from './api/api'; +import { loadAvatarURL } from './api/avatars'; +import ActionManager, { ConfigManager } from './api/actionManager'; + +import { CSGO, PlayerExtension, GSISocket, CSGORaw } from "csgogsi-socket"; +import { Match } from './api/interfaces'; +import { initiateConnection } from './HUD/Camera/mediaStream'; + +let isInWindow = !!window.parent.ipcApi; + +export const { GSI, socket } = GSISocket(isDev ? `localhost:${port}` : '/', "update"); + +GSI.regulationMR = 12; + +if(isInWindow){ + window.parent.ipcApi.receive('raw', (data: CSGORaw, damage?: RoundDamage[]) => { + if(damage){ + GSI.damage = damage; + } + GSI.digest(data); + }); +} + +type RoundPlayerDamage = { + steamid: string; + damage: number; +}; +type RoundDamage = { + round: number; + players: RoundPlayerDamage[]; +}; + +socket.on('update', (_csgo: any, damage?: RoundDamage[]) => { + if(damage) GSI.damage = damage; +}); + +export const actions = new ActionManager(); +export const configs = new ConfigManager(); + +export const hudIdentity = { + name: '', + isDev: false +}; + +interface DataLoader { + match: Promise | null +} + +const dataLoader: DataLoader = { + match: null +} + +class App extends React.Component { + constructor(props: any) { + super(props); + this.state = { + game: null, + steamids: [], + match: null, + checked: false + } + } + + verifyPlayers = async (game: CSGO) => { + const steamids = game.players.map(player => player.steamid); + steamids.forEach(steamid => { + loadAvatarURL(steamid); + }) + + if (steamids.every(steamid => this.state.steamids.includes(steamid))) { + return; + } + + const loaded = GSI.players.map(player => player.steamid); + + const notCheckedPlayers = steamids.filter(steamid => !loaded.includes(steamid)); + + const extensioned = await api.players.get(notCheckedPlayers); + + const lacking = notCheckedPlayers.filter(steamid => extensioned.map(player => player.steamid).includes(steamid)); + + const players: PlayerExtension[] = extensioned + .filter(player => lacking.includes(player.steamid)) + .map(player => ( + { + id: player._id, + name: player.username, + realName: `${player.firstName} ${player.lastName}`, + steamid: player.steamid, + country: player.country, + avatar: player.avatar, + extra: player.extra, + }) + ); + + const gsiLoaded = GSI.players; + + gsiLoaded.push(...players); + + GSI.players = gsiLoaded; + this.setState({ steamids }); + } + + + componentDidMount() { + this.loadMatch(); + const href = window.location.href; + socket.emit("started"); + let isDev = false; + let name = ''; + if (href.indexOf('/huds/') === -1) { + isDev = true; + name = (Math.random() * 1000 + 1).toString(36).replace(/[^a-z]+/g, '').substr(0, 15); + hudIdentity.isDev = true; + } else { + const segment = href.substr(href.indexOf('/huds/') + 6); + name = segment.substr(0, segment.lastIndexOf('/')); + hudIdentity.name = name; + } + + socket.on("readyToRegister", () => { + socket.emit("register", name, isDev, "cs2", isInWindow ? "IPC" : "DEFAULT"); + initiateConnection(); + }); + socket.on(`hud_config`, (data: any) => { + configs.save(data); + }); + socket.on(`hud_action`, (data: any) => { + actions.execute(data.action, data.data); + }); + socket.on('keybindAction', (action: string) => { + actions.execute(action); + }); + + socket.on("refreshHUD", () => { + window.top?.location.reload(); + }); + + socket.on("update_mirv", (data: any) => { + GSI.digestMIRV(data); + }) + GSI.on('data', game => { + if (!this.state.game || this.state.steamids.length) this.verifyPlayers(game); + + const wasLoaded = !!this.state.game; + + this.setState({ game }, () => { + if(!wasLoaded) this.loadMatch(true); + }); + }); + socket.on('match', () => { + + this.loadMatch(true); + }); + + } + + loadMatch = async (force = false) => { + if (!dataLoader.match || force) { + dataLoader.match = new Promise((resolve) => { + api.match.getCurrent().then(match => { + if (!match) { + GSI.teams.left = null; + GSI.teams.right = null; + return; + } + this.setState({ match }); + + let isReversed = false; + if (GSI.last) { + const mapName = GSI.last.map.name.substring(GSI.last.map.name.lastIndexOf('/') + 1); + const current = match.vetos.filter(veto => veto.mapName === mapName)[0]; + if (current && current.reverseSide) { + isReversed = true; + } + this.setState({ checked: true }); + } + if (match.left.id) { + api.teams.getOne(match.left.id).then(left => { + const gsiTeamData = { id: left._id, name: left.name, country: left.country, logo: left.logo, map_score: match.left.wins, extra: left.extra }; + + if (!isReversed) { + GSI.teams.left = gsiTeamData; + } + else GSI.teams.right = gsiTeamData; + }); + } + if (match.right.id) { + api.teams.getOne(match.right.id).then(right => { + const gsiTeamData = { id: right._id, name: right.name, country: right.country, logo: right.logo, map_score: match.right.wins, extra: right.extra }; + + if (!isReversed) GSI.teams.right = gsiTeamData; + else GSI.teams.left = gsiTeamData; + }); + } + + + + }).catch(() => { + //dataLoader.match = null; + }); + }); + } + } + render() { + if (!this.state.game) return null; + return ( + + ); + } + +} +export default App; diff --git a/src/HUD/Camera/Camera.tsx b/src/HUD/Camera/Camera.tsx new file mode 100644 index 0000000..21cebd0 --- /dev/null +++ b/src/HUD/Camera/Camera.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from "react"; +import { mediaStreams } from "./mediaStream"; +import { v4 as uuidv4 } from 'uuid'; +type Props = { + steamid: string, + visible: boolean; +} + +const CameraView = ({ steamid, visible }: Props) => { + const [uuid] = useState(uuidv4()); + const [ forceHide, setForceHide ] = useState(false); + + useEffect(() => { + + }, []) + + useEffect(() => { + const mountStream = (stream: MediaStream) => { + console.log("mounting video"); + const remoteVideo = document.getElementById(`remote-video-${steamid}-${uuid}`) as HTMLVideoElement; + if(!remoteVideo || !stream){ + console.log("no video element") + } + if (!remoteVideo || !stream) return; + + remoteVideo.srcObject = stream; + + remoteVideo.play(); + } + + const mountExistingStream = () => { + const currentStream = mediaStreams.players.find(player => player.steamid === steamid); + if(!currentStream || !currentStream.peerConnection || !currentStream.peerConnection._remoteStreams) return; + + const stream = currentStream.peerConnection._remoteStreams[0]; + + if(!stream) return; + + mountStream(stream); + } + + const onStreamCreate = (stream: MediaStream) => { + mountStream(stream); + } + + + + const onStreamDestroy = () => { + const remoteVideo = document.getElementById(`remote-video-${steamid}-${uuid}`) as HTMLVideoElement; + + if (!remoteVideo) return; + + remoteVideo.srcObject = null; + } + + const onBlockedUpdate = (steamids: string[]) => { + setForceHide(steamids.includes(steamid)); + } + + mediaStreams.onStreamCreate(onStreamCreate, steamid); + mediaStreams.onStreamDestroy(onStreamDestroy, steamid); + mediaStreams.onBlockedUpdate(onBlockedUpdate); + + mountExistingStream(); + + return () => { + mediaStreams.removeListener(onStreamCreate); + mediaStreams.removeListener(onStreamDestroy); + mediaStreams.removeListener(onBlockedUpdate); + } + }, [steamid]); + + return + + +} + +export default CameraView; \ No newline at end of file diff --git a/src/HUD/Camera/Container.tsx b/src/HUD/Camera/Container.tsx new file mode 100644 index 0000000..09dc952 --- /dev/null +++ b/src/HUD/Camera/Container.tsx @@ -0,0 +1,24 @@ +import React, { useEffect, useState } from "react"; +import PlayerCamera from "./Camera"; +import api from "../../api/api"; +import "./index.scss"; + + + +const CameraContainer = ({ observedSteamid }: { observedSteamid: string | null }) => { + const [ players, setPlayers ] = useState([]); + + useEffect(() => { + api.camera.get().then(response => { + setPlayers(response.availablePlayers.map(player => player.steamid)); + }); + }, []); + + return
+ { + players.map(steamid => ()) + } +
+} + +export default CameraContainer; \ No newline at end of file diff --git a/src/HUD/Camera/index.scss b/src/HUD/Camera/index.scss new file mode 100644 index 0000000..350358d --- /dev/null +++ b/src/HUD/Camera/index.scss @@ -0,0 +1,13 @@ +#cameras-container { + overflow: hidden; + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + video { + height: 100%; + position: absolute; + } +} \ No newline at end of file diff --git a/src/HUD/Camera/mediaStream.ts b/src/HUD/Camera/mediaStream.ts new file mode 100644 index 0000000..12e6a0b --- /dev/null +++ b/src/HUD/Camera/mediaStream.ts @@ -0,0 +1,153 @@ +import { Instance, SignalData } from 'simple-peer'; +import api from '../../api/api'; +import { socket as Socket } from "../../App"; +const Peer = require('simple-peer'); + +const wait = (ms: number) => new Promise(r => setTimeout(r, ms)); + +type OfferData = { + offer: SignalData, +} + +type PeerInstance = Instance & { _remoteStreams: MediaStream[] } + +type MediaStreamPlayer = { + peerConnection: PeerInstance | null; + steamid: string; +} + +type ListenerType = ({ listener: (stream: MediaStream) => void, event: 'create', steamid: string } | ({ listener: () => void, event: 'destroy', steamid: string })); + +type MediaStreamManager = { + blocked: string[]; + blockedListeners: ((blocked: string[]) => void)[], + players: MediaStreamPlayer[]; + onStreamCreate: (listener: (stream: MediaStream) => void, steamid: string) => void; + onStreamDestroy: (listener: () => void, steamid: string) => void; + onBlockedUpdate: (listener: (steamids: string[]) => void) => void; + removeListener: (listener: any) => void; + listeners: ListenerType[]; +} + +const mediaStreams: MediaStreamManager = { + blocked: [], + blockedListeners: [], + players: [], + listeners: [], + onStreamCreate: (listener: (stream: MediaStream) => void, steamid: string) => { + mediaStreams.listeners.push({ listener, event: "create", steamid }); + }, + onBlockedUpdate: (listener: (blocked: string[]) => void) => { + mediaStreams.blockedListeners.push(listener); + }, + onStreamDestroy: (listener: () => void, steamid: string) => { + mediaStreams.listeners.push({ listener, event: "destroy", steamid }); + }, + removeListener: (listenerToRemove: any) => { + mediaStreams.listeners = mediaStreams.listeners.filter(listener => listener !== listenerToRemove); + mediaStreams.blockedListeners = mediaStreams.blockedListeners.filter(listener => listener !== listenerToRemove); + } +}; + +const getConnectionInfo = (steamid: string) => mediaStreams.players.find(player => player.steamid === steamid) || null; + +const closeConnection = (steamid: string) => { + const connectionInfo = getConnectionInfo(steamid); + try { + if (connectionInfo) { + if (connectionInfo.peerConnection) { + connectionInfo.peerConnection.removeAllListeners(); + connectionInfo.peerConnection.destroy(); + } + connectionInfo.peerConnection = null; + } + } catch { + + } + + for (const listener of mediaStreams.listeners.filter(listener => listener.steamid === steamid)) { + if (listener.event === "destroy") listener.listener(); + } + mediaStreams.players = mediaStreams.players.filter(player => player.steamid !== steamid); + console.log(mediaStreams.players) +} + +const initiateConnection = async () => { + const socket = Socket as SocketIOClient.Socket; + const camera = await api.camera.get(); + await wait(1000); + + socket.emit("registerAsHUD", camera.uuid); + + socket.on('playersCameraStatus', (players: { steamid: string, label: string, allow: boolean, active: boolean }[]) => { + const blockedSteamids = players.filter(player => !player.allow).map(player => player.steamid); + mediaStreams.blocked = blockedSteamids; + + for (const listener of mediaStreams.blockedListeners) { + listener(blockedSteamids); + } + }); + + socket.on('offerFromPlayer', async (roomId: string, offerData: OfferData, steamid: string) => { + // It's not from available player, ignore incoming request + /*if(!camera.availablePlayers.find(player => player.steamid === steamid)){ + console.log("Wrong player"); + return; + }*/ + const currentConnection = getConnectionInfo(steamid); + + // Connection already made, ignore incoming request + if (currentConnection) { + console.log("Connection has been made already"); + return; + } + + if (camera.uuid !== roomId) return; + + const peerConnection: PeerInstance = new Peer({ initiator: false, trickle: false }); + + const mediaStreamPlayer: MediaStreamPlayer = { peerConnection, steamid }; + + mediaStreams.players.push(mediaStreamPlayer); + + peerConnection.on('signal', answer => { + console.log("SIGNAL COMING IN"); + const offer = JSON.parse(JSON.stringify(answer)) as RTCSessionDescriptionInit; + socket.emit("offerFromHUD", roomId, { offer }, steamid); + }); + + peerConnection.on('error', (err) => { + console.log(err) + closeConnection(steamid); + }); + + peerConnection.on('stream', () => { + console.log("STREAM COMING IN"); + const currentConnection = getConnectionInfo(steamid); + if (!currentConnection) { + console.log("Connection not established"); + closeConnection(steamid); + return; + } + if (peerConnection._remoteStreams.length === 0) { + console.log('no stream?'); + return; + } + for (const listener of mediaStreams.listeners.filter(listener => listener.steamid === steamid)) { + if (listener.event === "create") listener.listener(peerConnection._remoteStreams[0]); + } + }); + + peerConnection.on('close', () => { + console.log("CLOSE COMING IN"); + const currentConnection = getConnectionInfo(steamid); + if (!currentConnection) return; + + closeConnection(steamid); + }); + console.log("Sending offer"); + peerConnection.signal(offerData.offer); + }); +} + +export { mediaStreams, initiateConnection }; \ No newline at end of file diff --git a/src/HUD/Indicators/Armor.tsx b/src/HUD/Indicators/Armor.tsx new file mode 100644 index 0000000..261ec8d --- /dev/null +++ b/src/HUD/Indicators/Armor.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Player } from 'csgogsi-socket'; +import {ArmorHelmet, ArmorFull} from './../../assets/Icons'; +export default class Armor extends React.Component<{ player: Player }> { + render() { + const { player } = this.props; + if(!player.state.health || !player.state.armor) return ''; + return ( +
+ {player.state.helmet ? : } +
+ ); + } + +} diff --git a/src/HUD/Indicators/Bomb.tsx b/src/HUD/Indicators/Bomb.tsx new file mode 100644 index 0000000..9928e5e --- /dev/null +++ b/src/HUD/Indicators/Bomb.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Player } from 'csgogsi-socket'; +import {Bomb as BombIcon} from './../../assets/Icons'; +export default class Bomb extends React.Component<{ player: Player }> { + render() { + const { player } = this.props; + if(Object.values(player.weapons).every(weapon => weapon.type !== "C4")) return ''; + return ( +
+ +
+ ); + } + +} diff --git a/src/HUD/Indicators/Defuse.tsx b/src/HUD/Indicators/Defuse.tsx new file mode 100644 index 0000000..c7bf66e --- /dev/null +++ b/src/HUD/Indicators/Defuse.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Player } from 'csgogsi-socket'; +import {Defuse as DefuseIcon} from './../../assets/Icons'; +export default class Defuse extends React.Component<{ player: Player }> { + render() { + const { player } = this.props; + if(!player.state.health || !player.state.defusekit) return ''; + return ( +
+ +
+ ); + } + +} diff --git a/src/HUD/Killfeed/Kill.tsx b/src/HUD/Killfeed/Kill.tsx new file mode 100644 index 0000000..7119df0 --- /dev/null +++ b/src/HUD/Killfeed/Kill.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import Weapon from './../Weapon/Weapon'; +import flash_assist from './../../assets/flash_assist.png'; + + +import { C4, Defuse, FlashedKill, Headshot, NoScope, SmokeKill, Suicide, Wallbang } from "./../../assets/Icons" +import { ExtendedKillEvent, BombEvent } from "./Killfeed" +export default class Kill extends React.Component<{ event: ExtendedKillEvent | BombEvent }> { + render() { + const { event } = this.props; + if (event.type !== "kill") { + return ( +
+
{event.player.name}
+
+ {event.type === "plant" ? : } +
+
{event.type === "plant" ? "planted the bomb" : "defused the bomb"}
+ +
+ + ) + } + let weapon = ; + if(event.killer === event.victim) { + weapon = ; + } else if(event.weapon === 'planted_c4'){ + weapon = + } + return ( +
+
+ {event.attackerblind ? : null} + {event.killer ?
{event.killer.name}
: null} + {event.assister ? + +
+
+ {event.flashed ? {'[FLASH]'} : null} +
{event.assister.name}
+
+ : ''} +
+ {weapon} + {event.thrusmoke ? : null} + {event.noscope ? : null} + {event.wallbang ? : null} + {event.headshot ? : null} +
+
{event.victim.name}
+
+
+ ); + } + +} diff --git a/src/HUD/Killfeed/Killfeed.tsx b/src/HUD/Killfeed/Killfeed.tsx new file mode 100644 index 0000000..683c11d --- /dev/null +++ b/src/HUD/Killfeed/Killfeed.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { GSI } from './../../App'; +import { KillEvent, Player } from 'csgogsi-socket'; +import Kill from './Kill'; +import './killfeed.scss'; + + +export interface ExtendedKillEvent extends KillEvent { + type: 'kill' +} + +export interface BombEvent { + player: Player, + type: 'plant' | 'defuse' +} + +export default class Killfeed extends React.Component { + + constructor(props: any){ + super(props); + this.state = { + events: [] + } + } + addKill = (kill: KillEvent) => { + this.setState(state => { + state.events.push({...kill, type: 'kill'}); + return state; + }) + } + + addBombEvent = (player: Player, type: 'plant' | 'defuse') => { + if(!player) return; + const event: BombEvent = { + player: player, + type: type + } + this.setState(state => { + state.events.push(event); + return state; + }) + } + + async componentDidMount() { + GSI.on("kill", kill => { + this.addKill(kill); + }); + GSI.on("data", data => { + + if(data.round && data.round.phase === "freezetime"){ + if(Number(data.phase_countdowns.phase_ends_in) < 10 && this.state.events.length > 0){ + this.setState({events:[]}) + } + } + }); + + /* + GSI.on("bombPlant", player => { + this.addBombEvent(player, 'plant'); + }) + GSI.on("bombDefuse", player => { + this.addBombEvent(player, 'defuse'); + }) + + */ + + } + render() { + return ( +
+ {this.state.events.map(event => )} +
+ ); + } + +} diff --git a/src/HUD/Killfeed/killfeed.scss b/src/HUD/Killfeed/killfeed.scss new file mode 100644 index 0000000..9fb2d43 --- /dev/null +++ b/src/HUD/Killfeed/killfeed.scss @@ -0,0 +1,112 @@ +@keyframes KillLifeCycle { + 0% { + opacity: 0; + height: 0; + } + 5% { + opacity: 1; + height: 43px; + } + 95% { + opacity: 1; + height: 43px; + } + 100% { + opacity: 0; + height: 0; + } +} + +.killfeed { + position: fixed; + right: 20px; + top: 93px; + display: flex; + flex-direction: column; + align-items: flex-end; + .single_kill_container { + animation: KillLifeCycle 5s linear 1; + animation-fill-mode: forwards;/**/ + overflow: hidden; + } + .single_kill { + display: flex; + flex-direction: row; + background-color: rgba(0,0,0,0.64); + margin-bottom:5px; + color:white; + height: 38px; + font-size: 14px; + overflow: hidden; + align-items: center; + font-family: Montserrat; + font-weight:500; + padding-left:15px; + padding-right:15px; + + > svg { + max-height: 24px; + max-width: 28px; + } + + div { + margin:10px; + display:flex; + align-items:center; + } + + svg.weapon { + height: 28px; + filter: invert(0%); + } + + .CT { + color: var(--color-new-ct); + } + + .T { + color: var(--color-new-t); + } + + .way .headshot, .way .wallbang, .flash_assist, .flash_kill, .smoke_kill, .noscope_kill { + max-height:20px; + margin-left:5px; + } + + .flash_assist { + margin-left: 0; + margin-right:5px; + } + + .plus { + margin-left: 0; + font-weight: 600; + margin-right: 8px; + } + + .assister_name { + margin-left:0px; + } + + .way { + text-align: center; + + .wallbang { + max-height: 24px; + } + svg:not(.weapon) { + max-height: 24px; + max-width: 28px; + } + } + &.hide { + transition:height 1s, padding 1s, opacity 1s, margin 1s; + opacity:0; + height:0; + margin:0; + } + &.show { + opacity:1; + } + } +} \ No newline at end of file diff --git a/src/HUD/Layout/Layout.tsx b/src/HUD/Layout/Layout.tsx new file mode 100644 index 0000000..c5ef70f --- /dev/null +++ b/src/HUD/Layout/Layout.tsx @@ -0,0 +1,135 @@ +import React from "react"; +import TeamBox from "./../Players/TeamBox"; +import MatchBar from "../MatchBar/MatchBar"; +import SeriesBox from "../MatchBar/SeriesBox"; +import Observed from "./../Players/Observed"; +import { CSGO, Team } from "csgogsi-socket"; +import { Match } from "../../api/interfaces"; +import RadarMaps from "./../Radar/RadarMaps"; +import Trivia from "../Trivia/Trivia"; +import SideBox from '../SideBoxes/SideBox'; +import { GSI, actions } from "./../../App"; +import MoneyBox from '../SideBoxes/Money'; +import UtilityLevel from '../SideBoxes/UtilityLevel'; +import Killfeed from "../Killfeed/Killfeed"; +import MapSeries from "../MapSeries/MapSeries"; +import Overview from "../Overview/Overview"; +import Tournament from "../Tournament/Tournament"; +import Pause from "../PauseTimeout/Pause"; +import Timeout from "../PauseTimeout/Timeout"; +import PlayerCamera from "../Camera/Camera"; + +interface Props { + game: CSGO, + match: Match | null +} + +interface State { + winner: Team | null, + showWin: boolean, + forceHide: boolean +} + +export default class Layout extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + winner: null, + showWin: false, + forceHide: false + } + } + + componentDidMount() { + GSI.on('roundEnd', score => { + this.setState({ winner: score.winner, showWin: true }, () => { + setTimeout(() => { + this.setState({ showWin: false }) + }, 4000) + }); + }); + actions.on("boxesState", (state: string) => { + if (state === "show") { + this.setState({ forceHide: false }); + } else if (state === "hide") { + this.setState({ forceHide: true }); + } + }); + } + + getVeto = () => { + const { game, match } = this.props; + const { map } = game; + if (!match) return null; + const mapName = map.name.substring(map.name.lastIndexOf('/') + 1); + const veto = match.vetos.find(veto => veto.mapName === mapName); + if (!veto) return null; + return veto; + } + + render() { + const { game, match } = this.props; + const left = game.map.team_ct.orientation === "left" ? game.map.team_ct : game.map.team_t; + const right = game.map.team_ct.orientation === "left" ? game.map.team_t : game.map.team_ct; + + const leftPlayers = game.players.filter(player => player.team.side === left.side); + const rightPlayers = game.players.filter(player => player.team.side === right.side); + const isFreezetime = (game.round && game.round.phase === "freezetime") || game.phase_countdowns.phase === "freezetime"; + const { forceHide } = this.state; + + return ( +
+
+
Players alive
+
+
{leftPlayers.filter(player => player.state.health > 0).length}
+
VS
+
{rightPlayers.filter(player => player.state.health > 0).length}
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + player.state.equip_value).reduce((pre, now) => pre + now, 0)} + money={leftPlayers.map(player => player.state.money).reduce((pre, now) => pre + now, 0)} + show={isFreezetime && !forceHide} + /> +
+
+ + + player.state.equip_value).reduce((pre, now) => pre + now, 0)} + money={rightPlayers.map(player => player.state.money).reduce((pre, now) => pre + now, 0)} + show={isFreezetime && !forceHide} + /> +
+
+ ); + } +} diff --git a/src/HUD/MapSeries/MapSeries.tsx b/src/HUD/MapSeries/MapSeries.tsx new file mode 100644 index 0000000..5b0f0ad --- /dev/null +++ b/src/HUD/MapSeries/MapSeries.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import * as I from "csgogsi-socket"; +import { Match, Veto } from '../../api/interfaces'; +import TeamLogo from "../MatchBar/TeamLogo"; +import "./mapseries.scss"; + +interface IProps { + match: Match | null; + teams: I.Team[]; + isFreezetime: boolean; + map: I.Map +} + +interface IVetoProps { + veto: Veto; + teams: I.Team[]; + active: boolean; +} + +class VetoEntry extends React.Component { + render(){ + const { veto, teams, active } = this.props; + return
+
+ {veto.mapName} +
+
+ team.id === veto.teamId)[0]} /> +
+
+ team.id === veto.winner)[0]} /> +
+
+ {Object.values((veto.score || ['-','-'])).sort().join(":")} +
+
+
Currently playing
+
+
+ } +} + +export default class MapSeries extends React.Component { + + render() { + const { match, teams, isFreezetime, map } = this.props; + if (!match || !match.vetos.length) return null; + return ( +
+
+
Picked
+
Winner
+
Score
+
+ {match.vetos.filter(veto => veto.type !== "ban").map(veto => { + if(!veto.mapName) return null; + return + })} +
+ ); + } +} diff --git a/src/HUD/MapSeries/mapseries.scss b/src/HUD/MapSeries/mapseries.scss new file mode 100644 index 0000000..8c8d042 --- /dev/null +++ b/src/HUD/MapSeries/mapseries.scss @@ -0,0 +1,153 @@ +.map_series_container { + position: fixed; + top: 100px; + right: 20px; + width: 347px; + display: flex; + opacity: 0; + transition: opacity 0.5s; + flex-direction: column; + font-size: 13px; + .veto_container { + display: flex; + background-color: rgba(0,0,0,0.8); + color: white; + align-items: center; + height: 40px; + &.active { + .veto_winner { + display: none; + } + .veto_score { + display: none; + } + } + .veto_map_name { + width: 116px; + text-transform: uppercase; + background-color: rgba(255,255,255,1); + color: black; + height: 100%; + display: flex; + font-family: Montserrat; + font-weight: 700; + align-items: center; + justify-content: center; + } + div { + img { + max-height: 30px; + max-width: 30px; + } + } + .veto_picker { + width: 77px; + text-align: center; + } + .veto_winner { + width: 77px; + text-align: center; + } + .active_container { + width: 154px; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + .active { + background: white; + color: black; + width: 117px; + height: 25px; + display: flex; + align-items: center; + justify-content: center; + font-size: 9px; + font-weight: 700; + font-family: Montserrat; + text-transform: uppercase; + } + } + &:not(.active) { + .active_container { + display: none; + } + } + .veto_score { + width: 77px; + text-align: center; + font-size: 14px; + } + .logo { + display: flex; + align-items: center; + justify-content: center; + } + } + .title_bar { + display: flex; + background-color: rgba(0,0,0,0.64); + height: 25px; + color: white; + border-radius: 6px 6px 0 0; + font-family: Montserrat; + font-size: 13px; + font-weight: 600; + margin-bottom: 2px; + div { + width: 77px; + display: flex; + align-items: center; + justify-content: center; + } + .picked { + margin-left: auto; + } + } + &.show { + opacity: 1; + } +} + +#timeout { + text-transform: uppercase; + position: absolute; + left: 50%; + transform: translateX(-50%); + top: 150px; + background-color: var(--sub-panel-color); + width: 600px; + display: flex; + align-items: center; + justify-content: center; + height: 100px; + font-size: 35px; + transition: opacity 1.5s; + opacity: 0; +} +#pause { + text-transform: uppercase; + position: absolute; + left: 50%; + transform: translateX(-50%); + top: 150px; + background-color: var(--sub-panel-color); + width: 600px; + display: flex; + align-items: center; + justify-content: center; + height: 100px; + font-size: 35px; + transition: opacity 1.5s; + opacity: 0; + color: white; +} +#timeout.show, #pause.show { + opacity: 1; +} +#timeout.t { + color: var(--color-new-t); +} +#timeout.ct { + color: var(--color-new-ct); +} diff --git a/src/HUD/MatchBar/MatchBar.tsx b/src/HUD/MatchBar/MatchBar.tsx new file mode 100644 index 0000000..f39e536 --- /dev/null +++ b/src/HUD/MatchBar/MatchBar.tsx @@ -0,0 +1,203 @@ +import React from "react"; +import * as I from "csgogsi-socket"; +import "./matchbar.scss"; +import TeamScore from "./TeamScore"; +import Bomb from "./../Timers/BombTimer"; +import Countdown from "./../Timers/Countdown"; +import { GSI } from "../../App"; +import { Match } from "../../api/interfaces"; + +function stringToClock(time: string | number, pad = true) { + if (typeof time === "string") { + time = parseFloat(time); + } + const countdown = Math.abs(Math.ceil(time)); + const minutes = Math.floor(countdown / 60); + const seconds = countdown - minutes * 60; + if (pad && seconds < 10) { + return `${minutes}:0${seconds}`; + } + return `${minutes}:${seconds}`; +} + +interface IProps { + match: Match | null; + map: I.Map; + phase: I.PhaseRaw, + bomb: I.Bomb | null, +} + +export interface Timer { + width: number; + active: boolean; + countdown: number; + side: "left"|"right"; + type: "defusing" | "planting"; + player: I.Player | null; +} + +interface IState { + defusing: Timer, + planting: Timer, + winState: { + side: "left"|"right", + show: boolean + } +} + +export default class TeamBox extends React.Component { + constructor(props: IProps){ + super(props); + this.state = { + defusing: { + width: 0, + active: false, + countdown: 10, + side: "left", + type: "defusing", + player: null + }, + planting: { + width: 0, + active: false, + countdown: 10, // Fake + side: "right", + type: "planting", + player: null + }, + winState: { + side: 'left', + show: false + } + } + } + plantStop = () => this.setState(state => { + state.planting.active = false; + return state; + }); + + setWidth = (type: 'defusing' | 'planting', width: number) => { + this.setState(state => { + state[type].width = width; + return state; + }) + } + + initPlantTimer = () => { + const bomb = new Countdown(time => { + let width = time * 100; + this.setWidth("planting", width/3); + }); + GSI.on("bombPlantStart", player => { + if(!player || !player.team) return; + this.setState(state => { + state.planting.active = true; + state.planting.side = player.team.orientation; + state.planting.player = player; + }) + }) + GSI.on("data", data => { + if(!data.bomb || !data.bomb.countdown || data.bomb.state !== "planting") return this.plantStop(); + this.setState(state => { + state.planting.active = true; + }) + return bomb.go(data.bomb.countdown); + }); + } + + defuseStop = () => this.setState(state => { + state.defusing.active = false; + state.defusing.countdown = 10; + return state; + }); + + initDefuseTimer = () => { + const bomb = new Countdown(time => { + let width = time > this.state.defusing.countdown ? this.state.defusing.countdown*100 : time * 100; + this.setWidth("defusing", width/this.state.defusing.countdown); + }); + GSI.on("defuseStart", player => { + if(!player || !player.team) return; + this.setState(state => { + state.defusing.active = true; + state.defusing.countdown = !Boolean(player.state.defusekit) ? 10 : 5; + state.defusing.side = player.team.orientation; + state.defusing.player = player; + return state; + }) + }) + GSI.on("data", data => { + if(!data.bomb || !data.bomb.countdown || data.bomb.state !== "defusing") return this.defuseStop(); + this.setState(state => { + state.defusing.active = true; + return state; + }) + return bomb.go(data.bomb.countdown); + }); + } + + resetWin = () => { + setTimeout(() => { + this.setState(state => { + state.winState.show = false; + return state; + }) + }, 6000); + } + + componentDidMount(){ + this.initDefuseTimer(); + this.initPlantTimer(); + GSI.on("roundEnd", score => { + this.setState(state => { + state.winState.show = true; + state.winState.side = score.winner.orientation; + return state; + }, this.resetWin); + }); + } + getRoundLabel = () => { + const { map } = this.props; + const round = map.round + 1; + if (round <= 30) { + return `Round ${round}/30`; + } + const additionalRounds = round - 30; + const OT = Math.ceil(additionalRounds/6); + return `OT ${OT} (${additionalRounds - (OT - 1)*6}/6)`; + } + render() { + const { defusing, planting, winState } = this.state; + const { bomb, match, map, phase } = this.props; + const time = stringToClock(phase.phase_ends_in); + const left = map.team_ct.orientation === "left" ? map.team_ct : map.team_t; + const right = map.team_ct.orientation === "left" ? map.team_t : map.team_ct; + const isPlanted = bomb && (bomb.state === "defusing" || bomb.state === "planted"); + const bo = (match && Number(match.matchType.substr(-1))) || 0; + let leftTimer: Timer | null = null, rightTimer: Timer | null = null; + if(defusing.active || planting.active){ + if(defusing.active){ + if(defusing.side === "left") leftTimer = defusing; + else rightTimer = defusing; + } else { + if(planting.side === "left") leftTimer = planting; + else rightTimer = planting; + } + } + return ( + <> +
+ +
{left.score}
+
+
{time}
+
{this.getRoundLabel()}
+ +
+
{right.score}
+ +
+ + ); + } +} diff --git a/src/HUD/MatchBar/SeriesBox.tsx b/src/HUD/MatchBar/SeriesBox.tsx new file mode 100644 index 0000000..47bbdbf --- /dev/null +++ b/src/HUD/MatchBar/SeriesBox.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import * as I from "csgogsi-socket"; +import { Match } from "../../api/interfaces"; + +interface Props { + map: I.Map; + phase: I.PhaseRaw; + match: Match | null; +} + +export default class SeriesBox extends React.Component { + render() { + const { match, map } = this.props; + const amountOfMaps = (match && Math.floor(Number(match.matchType.substr(-1)) / 2) + 1) || 0; + const bo = (match && Number(match.matchType.substr(-1))) || 0; + const left = map.team_ct.orientation === "left" ? map.team_ct : map.team_t; + const right = map.team_ct.orientation === "left" ? map.team_t : map.team_ct; + return ( +
+
+
+
+ {new Array(amountOfMaps).fill(0).map((_, i) => ( +
i ? "win" : ""} ${left.side}`} /> + ))} +
+
+
+
+
{ bo ? `BEST OF ${bo}` : '' }
+
+
+
+
+ {new Array(amountOfMaps).fill(0).map((_, i) => ( +
i ? "win" : ""} ${right.side}`} /> + ))} +
+
+
+
+ ); + } +} diff --git a/src/HUD/MatchBar/TeamLogo.tsx b/src/HUD/MatchBar/TeamLogo.tsx new file mode 100644 index 0000000..53a40e5 --- /dev/null +++ b/src/HUD/MatchBar/TeamLogo.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Team } from 'csgogsi-socket'; +import * as I from '../../api/interfaces'; +import { apiUrl } from './../../api/api'; + +export default class TeamLogo extends React.Component<{ team?: Team | I.Team | null, height?: number, width?: number}> { + render(){ + const { team } = this.props; + if(!team) return null; + let id = ''; + const { logo } = team; + if('_id' in team){ + id = team._id; + } else if('id' in team && team.id){ + id = team.id; + } + return ( +
+ { logo && id ? {'Team : ''} +
+ ); + } + +} diff --git a/src/HUD/MatchBar/TeamScore.tsx b/src/HUD/MatchBar/TeamScore.tsx new file mode 100644 index 0000000..4e7c718 --- /dev/null +++ b/src/HUD/MatchBar/TeamScore.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import * as I from "csgogsi-socket"; +import WinIndicator from "./WinIndicator"; +import { Timer } from "./MatchBar"; +import TeamLogo from './TeamLogo'; +import PlantDefuse from "../Timers/PlantDefuse" + +interface IProps { + team: I.Team; + orientation: "left" | "right"; + timer: Timer | null; + showWin: boolean; +} + +export default class TeamScore extends React.Component { + render() { + const { orientation, timer, team, showWin } = this.props; + return ( + <> +
+
{team.name}
+ +
+
+ + + + ); + } +} diff --git a/src/HUD/MatchBar/WinIndicator.tsx b/src/HUD/MatchBar/WinIndicator.tsx new file mode 100644 index 0000000..1fd3294 --- /dev/null +++ b/src/HUD/MatchBar/WinIndicator.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Team } from 'csgogsi-socket'; + +export default class WinAnnouncement extends React.Component<{ team: Team | null, show: boolean }> { + render() { + const { team, show } = this.props; + if(!team) return null; + return
+ WINS THE ROUND! +
+ } +} diff --git a/src/HUD/MatchBar/matchbar.scss b/src/HUD/MatchBar/matchbar.scss new file mode 100644 index 0000000..56a8fc8 --- /dev/null +++ b/src/HUD/MatchBar/matchbar.scss @@ -0,0 +1,391 @@ +@keyframes ShowWinCycle { + 0% { + opacity: 0; + height: 0; + } + 5% { + opacity: 1; + height: 50px; + } + 95% { + opacity: 1; + height: 50px; + } + 100% { + opacity: 0; + height: 0; + } +} +#matchbar { + display: flex; + flex-direction: row; + position: fixed; + justify-content: center; + width: 1148px; + height: 70px; + top: 10px; + left: 50%; + transform: translateX(-50%); + .CT { + color: var(--color-new-ct); + .round-thingy { + .inner { + background-color: #28abff; + } + background-color: #28abff80; + } + } + .T { + color: var(--color-new-t); + .round-thingy { + .inner { + background-color: #ffc600; + } + background-color: #ffc60080; + } + } + #timer { + display: flex; + flex-direction: column; + position: relative; + width: 126px; + height: 115px; + margin-left: 8px; + margin-right: 8px; + background-color: var(--sub-panel-color); + top: -10px; + &.no-bo { + height: 87px; + } + } + #bomb_container { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + position: absolute; + width: 100%; + height: 70px; + z-index: 0; + .bomb_timer { + width: 100%; + top: 0; + height: 0; + background-color: var(--color-bomb); + &.hide { + display: none; + } + } + .bomb_icon { + position: absolute; + width: 100%; + height: 100%; + svg { + position: absolute; + left: 50%; + transform: translateX(-50%); + top: 6px; + max-height: 80%; + max-width: 80%; + } + &.hide { + display: none; + } + } + } + #round_timer_text { + display: flex; + width: 100%; + height: 55px; + justify-content: center; + font-size: 34px; + font-weight: bold; + z-index: 1; + color: var(--white-full); + align-items: flex-end; + &.hide { + display: none; + } + } + #round_now { + display: flex; + flex-direction: column; + width: 100%; + height: 27px; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: bold; + z-index: 1; + color: var(--white-full); + &.hide { + display: none; + } + } + .team { + width: 426px; + display: flex; + align-items: center; + .logo { + display: flex; + flex-direction: row; + width: 105px; + height: 70px; + align-items: center; + overflow: hidden; + background-color: var(--sub-panel-color); + img { + max-width: 90%; + max-height: 65%; + } + } + &.left { + justify-content: center; + flex-direction: row-reverse; + .round-thingy { + left: -30px; + } + .logo { + justify-content: flex-end; + } + } + &.right { + justify-content: center; + flex-direction: row; + .round-thingy { + right: -30px; + } + .logo { + justify-content: flex-start; + } + } + } + .team-name { + display: flex; + width: 360px; + height: 70px; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 30px; + background-color: var(--sub-panel-color); + } + .round-thingy { + width: 60px; + height: 60px; + position: absolute; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + .inner { + width: 35px; + height: 35px; + border-radius: 50%; + } + } + .score { + display: flex; + flex-direction: row; + width: 77px; + height: 70px; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 36px; + background-color: var(--sub-panel-color); + } + .bar { + display: flex; + flex-direction: row; + width: 10px; + height: 70px; + &.CT { + background-color: var(--color-new-ct); + } + &.T { + background-color: var(--color-new-t); + } + } + +} +.win_text { + position: fixed; + display: none; + opacity: 1; + width: 503px; + height: 50px; + top: 70px; + align-items: center; + color: black; + justify-content: center; + background-color: white; + font-size: 20px; + font-family: Montserrat; + font-weight: 600; + &.show { + display: flex; + animation: ShowWinCycle 5s linear 1; + animation-fill-mode: forwards; + } + &.right { + left: calc(50% + 71px); + } + &.left { + right: calc(50% + 71px); + } +} + +.defuse_plant_container { + position: fixed; + display: flex; + opacity: 1; + width: 503px; + height: 49px; + top: 70px; + align-items: center; + color: white; + justify-content: center; + background-color: rgba(0,0,0,0.65); + .defuse_plant_bar { + height: 49px; + background-color: #3c3c3c; + position: absolute; + width: 0%; + } + .defuse_plant_caption { + z-index: 1; + display: flex; + text-transform: uppercase; + align-items: flex-end; + svg { + margin-right: 13px; + } + } + &.right { + left: calc(50% + 71px); + .defuse_plant_bar { + left: 0; + } + } + &.left { + right: calc(50% + 71px); + .defuse_plant_bar { + right: 0; + } + } + &.hide { + opacity: 0; + } +} +#encapsulator { + overflow: hidden; + display: flex; + flex-direction: row; + position: fixed; + justify-content: center; + top: 80px; + width: 1148px; + height: 50px; + left: 50%; + transform: translateX(-50%); + .CT { + color: var(--color-new-ct); + } + .T { + color: var(--color-new-t); + } + .wins_bar { + display: flex; + flex-direction: row; + width: 10px; + height: 30px; + } + .wins_bar.CT { + background-color: var(--color-new-ct); + } + .wins_bar.T { + background-color: var(--color-new-t); + } +} +.alert_bar.CT { + background-color: var(--color-new-ct); +} +.alert_bar.T { + background-color: var(--color-new-t); +} +#series_container { + display: flex; + flex-direction: row; + width: 126px; + height: 30px; +} +#series_text { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: bold; + color: var(--white-full); +} +.container { + display: flex; + flex-direction: row; + width: 511px; + height: 100%; +} +.container.left { + justify-content: flex-end; +} +.container.right { + justify-content: flex-start; +} +.series_wins { + display: flex; + flex-direction: row; + width: 400px; + height: 30px; + z-index: 1; + padding-left: 6px; + padding-right: 6px; + top: -30px; + transition: top 0.5s; +} +.series_wins.show { + top: 0px; +} +.wins_box_container { + display: flex; + flex-direction: row; + width: 100%; + height: 100%; + align-items: flex-start; + justify-content: flex-start; +} +.series_wins.left { + .wins_box_container { + flex-direction: row-reverse; + } +} +.wins_box { + width: 77px; + height: 7px; + margin-left: 2px; + margin-right: 2px; + box-sizing: border-box; +} +.wins_box.CT { + background-color: rgba(0,0,0,0.6); +} +.wins_box.CT.win { + background-color: var(--color-new-ct); +} +.wins_box.T { + background-color: rgba(0,0,0,0.6); +} +.wins_box.T.win { + background-color: var(--color-new-t); +} diff --git a/src/HUD/MatchOverview/MatchOverview.tsx b/src/HUD/MatchOverview/MatchOverview.tsx new file mode 100644 index 0000000..b372af0 --- /dev/null +++ b/src/HUD/MatchOverview/MatchOverview.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import * as I from '../../api/interfaces'; +import TeamLogo from '../MatchBar/TeamLogo'; + +interface IProps { + match: I.Match, + show: boolean, + teams: I.Team[], + veto: I.Veto | null +} + +export default class MatchOverview extends React.Component { + render() { + const { match, teams, show } = this.props; + const left = teams.find(team => team._id === match.left.id); + const right = teams.find(team => team._id === match.right.id); + if(!match || !left || !right) return null; + return ( +
+
+ Upcoming match +
+
+
+
+ +
+
{left.name}
+
+
vs
+
+
+ +
+
{right.name}
+
+
+
+ ); + } +} diff --git a/src/HUD/Overview/Overview.tsx b/src/HUD/Overview/Overview.tsx new file mode 100644 index 0000000..c6031b7 --- /dev/null +++ b/src/HUD/Overview/Overview.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { actions, configs } from '../../App'; +import * as I from '../../api/interfaces'; +import PlayerOverview from '../PlayerOverview/PlayerOverview'; +import MatchOverview from '../MatchOverview/MatchOverview'; +import TeamOverview from '../TeamOverview/TeamOverview'; +import { Map, Player } from 'csgogsi-socket'; +import api from '../../api/api'; + +interface IState { + player: { + data: I.Player | null, + show: boolean + }, + match: { + data: I.Match | null, + show: boolean, + teams: I.Team[], + }, + team: { + data: I.Team | null, + show: boolean + } +} + +interface IProps { + match: I.Match | null, + map: Map, + players: Player[] +} + +export default class Overview extends React.Component { + constructor(props: IProps){ + super(props); + this.state = { + player: { + data: null, + show: false + }, + match: { + data: null, + show: false, + teams: [] + }, + team: { + data: null, + show: false, + } + } + } + loadTeams = async () => { + const { match } = this.state; + if(!match.data || !match.data.left.id || !match.data.right.id) return; + const teams = await Promise.all([api.teams.getOne(match.data.left.id), api.teams.getOne(match.data.right.id)]); + if(!teams[0] || !teams[1]) return; + this.setState(state => { + state.match.teams = teams; + return state; + }); + } + componentDidMount() { + configs.onChange((data: any) => { + if(!data || !data.preview_settings) return; + this.setState({ + player: { + data: (data.preview_settings.player_preview && data.preview_settings.player_preview.player) || null, + show: Boolean(data.preview_settings.player_preview_toggle) + }, + team: { + data: (data.preview_settings.team_preview && data.preview_settings.team_preview.team) || null, + show: Boolean(data.preview_settings.team_preview_toggle) + }, + match: { + data: (data.preview_settings.match_preview && data.preview_settings.match_preview.match) || null, + show: Boolean(data.preview_settings.match_preview_toggle), + teams: this.state.match.teams + } + }, this.loadTeams); + }); + actions.on("toggleUpcomingMatch", () => { + this.setState(state => { + state.match.show = !state.match.show; + return state; + }) + }) + actions.on("togglePlayerPreview", () => { + this.setState(state => { + state.player.show = !state.player.show; + return state; + }) + }) + } + getVeto = () => { + const { map, match } = this.props; + if(!match) return null; + const mapName = map.name.substring(map.name.lastIndexOf('/')+1); + const veto = match.vetos.find(veto => veto.mapName === mapName); + if(!veto) return null; + return veto; + } + renderPlayer = () => { + const { player } = this.state; + if(!player.data) return null; + return + } + renderMatch = () => { + const { match } = this.state; + if(!match.data || !match.teams[0] || !match.teams[1]) return null; + return + } + renderTeam = () => { + const { team } = this.state; + if(!team.data) return null; + return + } + render() { + return ( + <> + {this.renderPlayer()} + {this.renderMatch()} + {this.renderTeam()} + + ); + } +} diff --git a/src/HUD/PauseTimeout/Pause.tsx b/src/HUD/PauseTimeout/Pause.tsx new file mode 100644 index 0000000..4379547 --- /dev/null +++ b/src/HUD/PauseTimeout/Pause.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { PhaseRaw } from "csgogsi-socket"; + +interface IProps { + phase: PhaseRaw | null +} + +export default class Pause extends React.Component { + render() { + const { phase } = this.props; + return ( +
+ PAUSE +
+ ); + } +} diff --git a/src/HUD/PauseTimeout/Timeout.tsx b/src/HUD/PauseTimeout/Timeout.tsx new file mode 100644 index 0000000..bbc0521 --- /dev/null +++ b/src/HUD/PauseTimeout/Timeout.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { Map, PhaseRaw } from "csgogsi-socket"; + + +interface IProps { + phase: PhaseRaw | null, + map: Map +} + +export default class Timeout extends React.Component { + render() { + const { phase, map } = this.props; + const time = phase && Math.abs(Math.ceil(parseFloat(phase.phase_ends_in))); + const team = phase && phase.phase === "timeout_t" ? map.team_t : map.team_ct; + + return ( +
2 && phase && (phase.phase === "timeout_t" || phase.phase === "timeout_ct") ? 'show' : ''} ${phase && (phase.phase === "timeout_t" || phase.phase === "timeout_ct") ? phase.phase.substr(8): ''}`}> + { team.name } TIMEOUT +
+ ); + } +} diff --git a/src/HUD/PlayerOverview/PlayerOverview.tsx b/src/HUD/PlayerOverview/PlayerOverview.tsx new file mode 100644 index 0000000..133934c --- /dev/null +++ b/src/HUD/PlayerOverview/PlayerOverview.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import * as I from '../../api/interfaces'; +import { avatars } from './../../api/avatars'; +import { apiUrl } from '../../api/api'; +import { getCountry } from '../countries'; +import { Player } from 'csgogsi-socket'; +import "./playeroverview.scss"; + +interface IProps { + player: I.Player, + show: boolean, + veto: I.Veto | null + players: Player[], + round: number +} + +export default class PlayerOverview extends React.Component { + sum = (data: number[]) => data.reduce((a, b) => a + b, 0); + + getData = () => { + const { veto, player, round } = this.props; + if(!player || !veto || !veto.rounds) return null; + const stats = veto.rounds.map(round => round ? round.players[player.steamid] : { + kills: 0, + killshs: 0, + damage: 0 + }).filter(data => !!data); + const overall = { + damage: this.sum(stats.map(round => round.damage)), + kills: this.sum(stats.map(round => round.kills)), + killshs: this.sum(stats.map(round => round.killshs)), + }; + const data = { + adr: stats.length !== 0 ? (overall.damage/(round-1)).toFixed(0) : '0', + kills: overall.kills, + killshs: overall.kills, + kpr: stats.length !== 0 ? (overall.kills/stats.length).toFixed(2) : 0, + hsp: overall.kills !== 0 ? (100*overall.killshs/overall.kills).toFixed(0) : '0' + } + return data; + } + calcWidth = (val: number | string, max?: number) => { + const value = Number(val); + if(value === 0) return 0; + let maximum = max; + if(!maximum) { + maximum = Math.ceil(value/100)*100; + } + if(value > maximum){ + return 100; + } + return 100*value/maximum; + } + render() { + const { player, veto, players } = this.props; + const data = this.getData(); + if(!player || !veto || !veto.rounds || !data) return null; + let url = null; + // const avatarData = avatars.find(avatar => avatar.steamid === player.steamid); + const avatarData = avatars[player.steamid]; + if(avatarData && avatarData.url){ + url = avatarData.url; + } + const countryName = player.country ? getCountry(player.country) : null; + let side = ''; + const inGamePlayer = players.find(inGamePlayer => inGamePlayer.steamid === player.steamid); + if(inGamePlayer) side = inGamePlayer.team.side; + return ( +
+
+ {url ? {`${player.username}'s : null} +
+
{url && countryName ? {countryName}/ : null }{player.username.toUpperCase()}
+ +
+
+
KILLS: {data.kills}
+
+
+
+
+
+
HS: {data.hsp}%
+
+
+
+
+
+
ADR: {data.adr}
+
+
+
+
+
+
KPR: {data.kpr}
+
+
+
+
+
+
+ ); + } +} diff --git a/src/HUD/PlayerOverview/playeroverview.scss b/src/HUD/PlayerOverview/playeroverview.scss new file mode 100644 index 0000000..2f67ff6 --- /dev/null +++ b/src/HUD/PlayerOverview/playeroverview.scss @@ -0,0 +1,74 @@ +.player-overview { + position: fixed; + top: 130px; + background-color: #000000a8; + left: 50%; + transform: translateX(-50%); + width: 300px; + padding: 30px; + color: white; + opacity: 0; + transition: opacity 0.75s; +} +.player-overview.show { + opacity: 1; +} +.player-overview-stat { + display: flex; + flex-direction: column; + margin: 8px 0; + .panel { + height: 10px; + margin: 3px 0; + .filling { + height: 100%; + } + } +} +.player-overview.CT { + .player-overview-stat { + .panel { + .filling { + background-color: var(--color-new-ct); + } + background-color: #082435; + } + } +} +.player-overview.T { + .player-overview-stat { + .panel { + .filling { + background-color: var(--color-new-t); + } + background-color: #331b00; + } + } +} +.player-overview-picture { + display: flex; + align-items: center; + justify-content: center; + position: relative; + img { + border-radius: 50%; + margin-bottom: 20px; + max-width: 170px; + max-height: 170px; + } +} +.player-overview-username { + text-align: center; + position: relative; + display: flex; + align-items: center; + justify-content: center; +} +img.flag { + border-radius: 0; + margin: 0; + left: 0; + bottom: 0; + height: 22px; + margin-right: 8px; +} \ No newline at end of file diff --git a/src/HUD/Players/Avatar.tsx b/src/HUD/Players/Avatar.tsx new file mode 100644 index 0000000..96c25cb --- /dev/null +++ b/src/HUD/Players/Avatar.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import CameraContainer from '../Camera/Container'; +import PlayerCamera from "./../Camera/Camera"; + +import { avatars } from './../../api/avatars'; + +import { Skull } from './../../assets/Icons'; + +interface IProps { + steamid: string, + slot?: number, + height?: number, + width?: number, + showSkull?: boolean, + showCam?: boolean, + sidePlayer?: boolean +} +export default class Avatar extends React.Component { + + render() { + const { showCam, steamid, width, height, showSkull, sidePlayer } = this.props; + //const url = avatars.filter(avatar => avatar.steamid === this.props.steamid)[0]; + const avatarData = avatars[this.props.steamid]; + if (!avatarData || !avatarData.url) { + return null; + } + + return ( +
+ { + showCam ? ( sidePlayer ?
: ) : null + } + { + showSkull ? : {'Avatar'} + } + +
+ ); + } + +} diff --git a/src/HUD/Players/Observed.tsx b/src/HUD/Players/Observed.tsx new file mode 100644 index 0000000..e9595bb --- /dev/null +++ b/src/HUD/Players/Observed.tsx @@ -0,0 +1,106 @@ +import React from "react"; +import { Player } from "csgogsi-socket"; +import Weapon from "./../Weapon/Weapon"; +import Avatar from "./Avatar"; +import TeamLogo from "./../MatchBar/TeamLogo"; +import "./observed.scss"; +import { apiUrl } from './../../api/api'; +import { getCountry } from "./../countries"; +import { ArmorHelmet, ArmorFull, HealthFull, Bullets } from './../../assets/Icons'; +import { Veto } from "../../api/interfaces"; +import { actions } from "../../App"; + +class Statistic extends React.PureComponent<{ label: string; value: string | number, }> { + render() { + return ( +
+
{this.props.label}
+
{this.props.value}
+
+ ); + } +} + +export default class Observed extends React.Component<{ player: Player | null, veto: Veto | null, round: number }, { showCam: boolean }> { + constructor(props: any){ + super(props); + this.state = { + showCam: true + } + } + componentDidMount() { + actions.on('toggleCams', () => { + console.log(this.state.showCam) + this.setState({ showCam: !this.state.showCam }); + }); + } + getAdr = () => { + const { veto, player } = this.props; + if (!player || !veto || !veto.rounds) return null; + const damageInRounds = veto.rounds.map(round => round ? round.players[player.steamid] : { + kills: 0, + killshs: 0, + damage: 0 + }).filter(data => !!data).map(roundData => roundData.damage); + + return damageInRounds.reduce((a, b) => a + b, 0) / (this.props.round - 1); + } + render() { + if (!this.props.player) return ''; + const { player } = this.props; + const country = player.country || player.team.country; + const weapons = Object.values(player.weapons).map(weapon => ({ ...weapon, name: weapon.name.replace("weapon_", "") })); + const currentWeapon = weapons.filter(weapon => weapon.state === "active")[0]; + const grenades = weapons.filter(weapon => weapon.type === "Grenade"); + const { stats } = player; + const ratio = stats.deaths === 0 ? stats.kills : stats.kills / stats.deaths; + const countryName = country ? getCountry(country) : null; + return ( +
+
+ {} + +
+
{player.name}
+
{player.realName}
+
+
{countryName ? {countryName} : ''}
+
+ {grenades.map(grenade => + + { + grenade.ammo_reserve === 2 ? : null} + )} +
+
+
+
+
+ +
+
{player.state.health}
+
+ {player.state.helmet ? : } +
+
{player.state.armor}
+
+
+ + + + +
+
+
+ +
+
+
{(currentWeapon && currentWeapon.ammo_clip) || "-"}
+
/{(currentWeapon && currentWeapon.ammo_reserve) || "-"}
+
+
+
+
+ ); + } +} diff --git a/src/HUD/Players/Player.tsx b/src/HUD/Players/Player.tsx new file mode 100644 index 0000000..31edcc6 --- /dev/null +++ b/src/HUD/Players/Player.tsx @@ -0,0 +1,135 @@ +import React from "react"; +import * as I from "csgogsi-socket"; +import Weapon from "./../Weapon/Weapon"; +import Avatar from "./Avatar"; +import Armor from "./../Indicators/Armor"; +import Bomb from "./../Indicators/Bomb"; +import Defuse from "./../Indicators/Defuse"; + +interface IProps { + player: I.Player, + isObserved: boolean, +} + +const compareWeapon = (weaponOne: I.WeaponRaw, weaponTwo: I.WeaponRaw) => { + if (weaponOne.name === weaponTwo.name && + weaponOne.paintkit === weaponTwo.paintkit && + weaponOne.type === weaponTwo.type && + weaponOne.ammo_clip === weaponTwo.ammo_clip && + weaponOne.ammo_clip_max === weaponTwo.ammo_clip_max && + weaponOne.ammo_reserve === weaponTwo.ammo_reserve && + weaponOne.state === weaponTwo.state + ) return true; + + return false; +} + +const compareWeapons = (weaponsObjectOne: { [key: string]: I.WeaponRaw }, weaponsObjectTwo: { [key: string]: I.WeaponRaw }) => { + const weaponsOne = Object.values(weaponsObjectOne).sort((a, b) => (a.name as any) - (b.name as any)) + const weaponsTwo = Object.values(weaponsObjectTwo).sort((a, b) => (a.name as any) - (b.name as any)) + + if (weaponsOne.length !== weaponsTwo.length) return false; + + return weaponsOne.every((weapon, i) => compareWeapon(weapon, weaponsTwo[i])); +} + +const arePlayersEqual = (playerOne: I.Player, playerTwo: I.Player) => { + if (playerOne.name === playerTwo.name && + playerOne.steamid === playerTwo.steamid && + playerOne.observer_slot === playerTwo.observer_slot && + playerOne.defaultName === playerTwo.defaultName && + playerOne.clan === playerTwo.clan && + playerOne.stats.kills === playerTwo.stats.kills && + playerOne.stats.assists === playerTwo.stats.assists && + playerOne.stats.deaths === playerTwo.stats.deaths && + playerOne.stats.mvps === playerTwo.stats.mvps && + playerOne.stats.score === playerTwo.stats.score && + playerOne.state.health === playerTwo.state.health && + playerOne.state.armor === playerTwo.state.armor && + playerOne.state.helmet === playerTwo.state.helmet && + playerOne.state.defusekit === playerTwo.state.defusekit && + playerOne.state.flashed === playerTwo.state.flashed && + playerOne.state.smoked === playerTwo.state.smoked && + playerOne.state.burning === playerTwo.state.burning && + playerOne.state.money === playerTwo.state.money && + playerOne.state.round_killhs === playerTwo.state.round_killhs && + playerOne.state.round_kills === playerTwo.state.round_kills && + playerOne.state.round_totaldmg === playerTwo.state.round_totaldmg && + playerOne.state.equip_value === playerTwo.state.equip_value && + playerOne.state.adr === playerTwo.state.adr && + playerOne.avatar === playerTwo.avatar && + playerOne.country === playerTwo.country && + playerOne.realName === playerTwo.realName && + compareWeapons(playerOne.weapons, playerTwo.weapons) + ) return true; + + return false; +} + +const Player = ({ player, isObserved }: IProps) => { + + const weapons = Object.values(player.weapons).map(weapon => ({ ...weapon, name: weapon.name.replace("weapon_", "") })); + const primary = weapons.filter(weapon => !['C4', 'Pistol', 'Knife', 'Grenade', undefined].includes(weapon.type))[0] || null; + const secondary = weapons.filter(weapon => weapon.type === "Pistol")[0] || null; + const grenades = weapons.filter(weapon => weapon.type === "Grenade"); + const isLeft = player.team.orientation === "left"; + return ( +
+
+ +
+
+
K
+
A
+
D
+
+
+
{player.stats.kills}
+
{player.stats.assists}
+
{player.stats.deaths}
+
+
+
+
+
+ {player.state.health} +
+
+
{isLeft ? {player.observer_slot} : null} {player.name} {!isLeft ? {player.observer_slot} : null}
+ {primary || secondary ? : ""} + {player.state.round_kills ?
{player.state.round_kills}
: null} +
+
+
+
+
+ + + +
+
${player.state.money}
+
+ {grenades.map(grenade => ( + [ + , + grenade.ammo_reserve === 2 ? : null, + ] + ))} +
+
{primary && secondary ? : ""}
+
+
+
+
+
+ ); +} + +const arePropsEqual = (prevProps: Readonly, nextProps: Readonly) => { + if (prevProps.isObserved !== nextProps.isObserved) return false; + + return arePlayersEqual(prevProps.player, nextProps.player); +} + +//export default React.memo(Player, arePropsEqual); +export default Player; diff --git a/src/HUD/Players/TeamBox.tsx b/src/HUD/Players/TeamBox.tsx new file mode 100644 index 0000000..fbd548d --- /dev/null +++ b/src/HUD/Players/TeamBox.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import Player from './Player' +import * as I from 'csgogsi-socket'; +import './players.scss'; + +interface Props { + players: I.Player[], + team: I.Team, + side: 'right' | 'left', + current: I.Player | null, +} + +export default class TeamBox extends React.Component { + render() { + return ( +
+ {this.props.players.map(player => )} +
+ ); + } +} diff --git a/src/HUD/Players/observed.scss b/src/HUD/Players/observed.scss new file mode 100644 index 0000000..d8d06d8 --- /dev/null +++ b/src/HUD/Players/observed.scss @@ -0,0 +1,247 @@ +.observed { + position: fixed; + width: 620px; + bottom: 120px; + margin-left: -310px; + left: 50%; + height: 86px; + display: flex; + flex-direction: column; + color: white; + background-color: rgba(0,0,0,0.6); + + .main_row, .stats_row { + flex: 1; + display: flex; + align-items: center; + flex-direction: row; + position: relative; + height: 43px; + } + + .stats_row { + height: 28px; + + .stats_cell { + height: 100%; + width: 56px; + display: flex; + flex-direction: column; + text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7); + + .label { + height: 13px; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + color: #b2b2b2; + } + + .stat { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + } + } + .ammo { + height: 100%; + width: 140px; + display: flex; + .ammo_counter { + flex: 1; + display: flex; + font-size: 18px; + font-weight: 600; + > div { + width: 32px; + display: flex; + align-items: center; + } + .ammo_clip { + justify-content: flex-end; + } + .ammo_reserve { + justify-content: flex-start; + } + } + .ammo_icon_container { + display: flex; + height: 100%; + width: 48px; + align-items: center; + justify-content: center; + svg { + max-width: 30px; + fill: white; + } + } + } + .health_armor_container { + display: flex; + height: 100%; + width: 140px; + justify-content: center; + .text { + width: 30px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 21px; + height: 100%; + padding-top: 2px; + } + + .icon { + display: flex; + width: 30px; + align-items: center; + justify-content: center; + svg { + max-height:30px; + max-width:22px; + } + } + } + .statistics { + height: 100%; + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + .stat { + display: flex; + font-weight: 600; + font-size: 18px; + margin-right: 24px; + } + } + } + + .main_row { + font-weight: 600; + .logo { + height: 43px; + display: flex; + align-items: center; + justify-content: center; + } + .grenade_container { + height: 100%; + display: flex; + align-items: center; + width: 140px; + justify-content: center; + + } + .username_container { + height: 100%; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + .real_name { + font-size: 7pt; + color: rgba(255, 255, 255, 0.5); + } + } + .weapon.grenade { + height: 28px; + /*filter: invert(1);*/ + + &:last-child { + margin-right: 5px; + } + } + + } + .sidebar { + width: 10px; + height: 100%; + position: absolute; + &:first-child { + left: -10px; + } + &:last-child { + right: -10px; + } + } + .flag { + height: 43px; + display: flex; + align-items: center; + justify-content: center; + img { + height: 43px; + } + } + .avatar { + align-self: flex-end; + display: flex; + position: relative; + width: 140px; + height: 140px; + img { + border-radius:50%; + } + } + + &.T { + border-bottom: 6px solid var(--color-new-t); + .main_row .username_container .username, .stats_row .ammo .ammo_counter .ammo_reserve { + color: var(--color-new-t) + } + .sidebar { + background-color: var(--color-new-t) + } + .stats_row .statistics .stat .label { + color: var(--color-new-t); + margin-right: 2px; + } + .stats_row .health_armor_container .icon, .stats_row .ammo .ammo_icon_container { + svg { + fill: var(--color-new-t); + } + } + } + &.CT { + border-bottom: 6px solid var(--color-new-ct); + .main_row .username_container .username, .stats_row .ammo .ammo_counter .ammo_reserve { + color: var(--color-new-ct) + } + .sidebar { + background-color: var(--color-new-ct) + } + .stats_row .statistics .stat .label { + color: var(--color-new-ct); + margin-right: 2px; + } + .stats_row .health_armor_container .icon, .stats_row .ammo .ammo_icon_container { + svg { + fill: var(--color-new-ct); + } + } + + } +} + +#cameraFeed { + width: 150px; + height: 150px; + overflow: hidden; + position: absolute; + transform: scale(0.94); + left: 0px; + bottom: 0; + transform-origin: bottom left; + iframe { + position: relative; + border: none; + width: 750px; + height: 300px; + } +} \ No newline at end of file diff --git a/src/HUD/Players/players.scss b/src/HUD/Players/players.scss new file mode 100644 index 0000000..b253982 --- /dev/null +++ b/src/HUD/Players/players.scss @@ -0,0 +1,371 @@ +.teambox { + position: fixed; + bottom: 10px; + display: flex; + flex-direction: column; + opacity: 1; + transition: opacity 0.75s; + &.CT { + .player .hp_bar { + background-color: var(--color-new-ct); + } + } + &.T { + .player .hp_bar { + background-color: var(--color-new-t); + } + } + &.hide { + opacity: 0; + } + &.left { + left: 10px; + .player { + .row { + flex-direction: row; + .grenades { + padding-right: 5px; + } + + .armor_and_utility { + justify-content: flex-start; + } + .money { + margin-right: auto; + } + .username .roundkills-container { + right: 115px; + } + .secondary_weapon { + padding-right: 10px; + } + + } + .dead-stats { + right: 8px; + } + } + } + &.right { + right: 10px; + .player { + flex-direction: row-reverse; + .dead-stats { + left: 8px; + } + .player_data { + flex-direction: row-reverse; + .hp_bar { + align-self: flex-end; + } + .row { + flex-direction: row-reverse; + .grenades { + padding-left: 5px; + } + .username { + flex-direction: row-reverse; + .roundkills-container { + left: 115px; + } + } + .secondary_weapon { + padding-left: 10px; + } + .armor_and_utility { + justify-content: flex-end; + } + .money { + margin-left: auto; + justify-content: flex-end; + } + .weapon { + transform: scaleX(-1); + } + } + .avatar { + justify-content: flex-start; + } + } + } + } + .player { + width: 645px; + height: 70px; + margin-bottom: 4px; + display: flex; + flex-direction: row; + align-items: center; + &.active { + .player_data { + border: 2px solid white; + } + } + &.dead { + opacity: 0.7; + .player_side_bar { + background-color: var(--main-panel-color) !important; + } + .player_data { + .avatar { + filter: grayscale(100%); + } + .dead-stats { + display: flex; + } + .row { + .hp_background_2 { + opacity: 0; + } + .health { + color: #b2b2b2; + overflow: hidden; + } + .username { + color: #b2b2b2; + } + .armor_and_utility { + width: 0px; + overflow: hidden; + } + .money { + color: #466722; + } + } + } + } + &:last-child .player_data { + border-radius: 0 0 20px 20px; + } + .player_side_bar { + width: 10px; + height: 70px; + &.CT { + background-color: var(--color-new-ct); + } + &.T { + background-color: var(--color-new-t); + } + } + .dead-stats { + position: absolute; + height: 85%; + width: 60px; + display: none; + flex-direction: column; + font-weight: 600; + color: white; + opacity: 0.75; + top: 10%; + .labels, .values { + display: flex; + flex-direction: row; + flex: 1; + .stat-label, .stat-value { + display: flex; + flex: 1; + align-items: center; + justify-content: center; + } + } + } + .player_data { + background-color: var(--sub-panel-color); + width: 415px; + display: flex; + flex-direction: row; + position: relative; + height: 100%; + .player_stats { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; + .hp_bar { + height: 2px; + &.low { + background-color: red; + } + } + .row { + flex: 1; + display: flex; + position: relative; + svg.weapon { + filter: invert(45%); + &.active { + filter: invert(0); + } + } + .hp_background, .hp_background_2 { + position: absolute; + width: 100%; + height: 100%; + z-index: 0; + } + .hp_background_2 { + background-color: var(--color-bomb); + transition: width 0.75s 1.5s; + } + .armor_and_utility { + width: 39px; + display: flex; + align-items: center; + padding-left: 5px; + padding-right: 5px; + .armor_indicator, .bomb_indicator, .defuse_indicator { + svg { + max-height: 20px; + fill: white; + } + } + div { + display: flex; + width: 50%; + } + } + .username { + flex: 1; + display: flex; + align-items: center; + z-index: 1; + color: white; + font-weight: 600; + max-width: calc(100% - 49px); + justify-content: space-between; + overflow: hidden; + font-size: 18px; + text-overflow: ellipsis; + white-space: nowrap; + .roundkills-container { + position: absolute; + background-image: url('./../../assets/images/icon_skull_default.svg'); + background-repeat: no-repeat; + background-size: 10px; + background-position: left 2px; + padding-left: 16px; + font-size: 13px; + } + div span { + opacity: 0.6; + font-size:15px; + } + svg.weapon { + max-height: 30px; + width: auto; + margin-left: 5px; + margin-right: 5px; + max-width: 100px; + height: 30px; + } + } + .money { + width: 60px; + color: var(--color-moneys); + font-weight: 600; + display: flex; + align-items: center; + justify-content: flex-start; + } + .grenades { + display: flex; + align-items: center; + justify-content: space-around; + svg.grenade { + max-height: 20px; + height: 20px; + } + } + .health { + width: 49px; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + color: white; + font-weight: 600; + font-size:18px; + } + .secondary_weapon { + display: flex; + align-items: center; + svg { + max-height: 30px; + height: 30px; + } + } + } + } + .avatar { + width: 70px; + display: flex; + align-items: center; + justify-content: flex-end; + overflow: hidden; + position: relative; + .videofeed { + width:70px; + height:70px; + position: absolute; + display: flex; + justify-content: center; + video { + height: 70px; + position: absolute; + } + } + img { + border-radius:50%; + } + } + } + } +} + +.players_alive { + display: flex; + flex-direction: column; + width: 180px; + background-color: rgba(0,0,0,0.5); + position: fixed; + right: 10px; + top: 10px; + opacity: 1; + transition: opacity 1s; + .counter_container { + display: flex; + height: 45px; + > div { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + font-size:30px; + color: white; + background-color: rgba(0,0,0,0.5); + } + .team_counter { + background-color: rgba(0,0,0,0.75); + } + .CT { + color: var(--color-new-ct); + } + .T { + color: var(--color-new-t); + } + } + .title_container { + color: white; + text-transform: uppercase; + text-align: center; + height:20px; + font-size:14px; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0,0,0,0.75); + } + &.hide { + opacity: 0; + } +} diff --git a/src/HUD/Radar/LexoRadar/LexoRadar.tsx b/src/HUD/Radar/LexoRadar/LexoRadar.tsx new file mode 100644 index 0000000..8473520 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/LexoRadar.tsx @@ -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 { + 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 ( +
+
+
+
+ ) + } + renderDot = (player: RadarPlayerObject) => { + const { reverseZoom } = this.props; + return ( +
+
+
+
{player.label}
+
+ ) + } + 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 ( +
+
+
+
+ ) + } + return mapConfig.configs.map(config => { + const position = this.props.parsePosition(bomb.position, 30, config.config); + if(!position) return null; + return ( +
+
+
+
+ ) + }); + } + 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
+ {players.map(this.renderDot)} + {grenades.map(this.renderGrenade)} + {this.renderBomb()} +
; + } +} + +export default App; diff --git a/src/HUD/Radar/LexoRadar/LexoRadarContainer.tsx b/src/HUD/Radar/LexoRadar/LexoRadarContainer.tsx new file mode 100644 index 0000000..4468d67 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/LexoRadarContainer.tsx @@ -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 = {}; +type ShootingState = { + ammo: number, + weapon: string, + lastShoot: number +} +let shootingState: Record = {}; + +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 { + 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
+ Unsupported map +
; + } + return
+ +
; + } +} + +export default App; diff --git a/src/HUD/Radar/LexoRadar/assets/playerBg.png b/src/HUD/Radar/LexoRadar/assets/playerBg.png new file mode 100644 index 0000000..876a6d1 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/assets/playerBg.png differ diff --git a/src/HUD/Radar/LexoRadar/assets/shootFire.png b/src/HUD/Radar/LexoRadar/assets/shootFire.png new file mode 100644 index 0000000..864ffad Binary files /dev/null and b/src/HUD/Radar/LexoRadar/assets/shootFire.png differ diff --git a/src/HUD/Radar/LexoRadar/assets/shootFire.zip b/src/HUD/Radar/LexoRadar/assets/shootFire.zip new file mode 100644 index 0000000..2d4524b Binary files /dev/null and b/src/HUD/Radar/LexoRadar/assets/shootFire.zip differ diff --git a/src/HUD/Radar/LexoRadar/config.ts b/src/HUD/Radar/LexoRadar/config.ts new file mode 100644 index 0000000..59f230e --- /dev/null +++ b/src/HUD/Radar/LexoRadar/config.ts @@ -0,0 +1,5 @@ +const config = { + playerSize: 60, +} + +export default config; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/grenades/bomb.png b/src/HUD/Radar/LexoRadar/grenades/bomb.png new file mode 100644 index 0000000..b4e1b08 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/grenades/bomb.png differ diff --git a/src/HUD/Radar/LexoRadar/grenades/firebomb.png b/src/HUD/Radar/LexoRadar/grenades/firebomb.png new file mode 100644 index 0000000..aabef8e Binary files /dev/null and b/src/HUD/Radar/LexoRadar/grenades/firebomb.png differ diff --git a/src/HUD/Radar/LexoRadar/grenades/flash.png b/src/HUD/Radar/LexoRadar/grenades/flash.png new file mode 100644 index 0000000..39240d7 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/grenades/flash.png differ diff --git a/src/HUD/Radar/LexoRadar/grenades/frag.png b/src/HUD/Radar/LexoRadar/grenades/frag.png new file mode 100644 index 0000000..ef1380b Binary files /dev/null and b/src/HUD/Radar/LexoRadar/grenades/frag.png differ diff --git a/src/HUD/Radar/LexoRadar/grenades/index.ts b/src/HUD/Radar/LexoRadar/grenades/index.ts new file mode 100644 index 0000000..e990715 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/grenades/index.ts @@ -0,0 +1,5 @@ +import Firebomb from './firebomb.png' +import Flash from './flash.png' +import Smoke from './smoke.png' + +export { Firebomb, Flash, Smoke }; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/grenades/smoke.png b/src/HUD/Radar/LexoRadar/grenades/smoke.png new file mode 100644 index 0000000..3a1f469 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/grenades/smoke.png differ diff --git a/src/HUD/Radar/LexoRadar/grenades/weapon_decoy.png b/src/HUD/Radar/LexoRadar/grenades/weapon_decoy.png new file mode 100644 index 0000000..e7206e6 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/grenades/weapon_decoy.png differ diff --git a/src/HUD/Radar/LexoRadar/grenades/weapon_incgrenade.png b/src/HUD/Radar/LexoRadar/grenades/weapon_incgrenade.png new file mode 100644 index 0000000..8f3ef1f Binary files /dev/null and b/src/HUD/Radar/LexoRadar/grenades/weapon_incgrenade.png differ diff --git a/src/HUD/Radar/LexoRadar/index.css b/src/HUD/Radar/LexoRadar/index.css new file mode 100644 index 0000000..72dea71 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/index.css @@ -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;/**/ + +} \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/interface.ts b/src/HUD/Radar/LexoRadar/interface.ts new file mode 100644 index 0000000..9614aac --- /dev/null +++ b/src/HUD/Radar/LexoRadar/interface.ts @@ -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, }; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_ancient/index.ts b/src/HUD/Radar/LexoRadar/maps/de_ancient/index.ts new file mode 100644 index 0000000..9bf4045 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_ancient/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_ancient/radar.png b/src/HUD/Radar/LexoRadar/maps/de_ancient/radar.png new file mode 100644 index 0000000..6df892e Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_ancient/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_anubis/index.ts b/src/HUD/Radar/LexoRadar/maps/de_anubis/index.ts new file mode 100644 index 0000000..44d5fb0 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_anubis/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_anubis/radar.png b/src/HUD/Radar/LexoRadar/maps/de_anubis/radar.png new file mode 100644 index 0000000..496673a Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_anubis/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_cache/index.ts b/src/HUD/Radar/LexoRadar/maps/de_cache/index.ts new file mode 100644 index 0000000..1d38d30 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_cache/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_cache/radar.png b/src/HUD/Radar/LexoRadar/maps/de_cache/radar.png new file mode 100644 index 0000000..40c6839 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_cache/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_dust2/index.ts b/src/HUD/Radar/LexoRadar/maps/de_dust2/index.ts new file mode 100644 index 0000000..8acb235 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_dust2/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_dust2/radar.png b/src/HUD/Radar/LexoRadar/maps/de_dust2/radar.png new file mode 100644 index 0000000..cb98891 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_dust2/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_inferno/index.ts b/src/HUD/Radar/LexoRadar/maps/de_inferno/index.ts new file mode 100644 index 0000000..8b6bd8c --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_inferno/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_inferno/radar.png b/src/HUD/Radar/LexoRadar/maps/de_inferno/radar.png new file mode 100644 index 0000000..65fbbb1 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_inferno/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_mirage/index.ts b/src/HUD/Radar/LexoRadar/maps/de_mirage/index.ts new file mode 100644 index 0000000..5a42ebd --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_mirage/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_mirage/radar.png b/src/HUD/Radar/LexoRadar/maps/de_mirage/radar.png new file mode 100644 index 0000000..ac1a19c Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_mirage/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_nuke/index.ts b/src/HUD/Radar/LexoRadar/maps/de_nuke/index.ts new file mode 100644 index 0000000..b0c7414 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_nuke/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_nuke/radar.png b/src/HUD/Radar/LexoRadar/maps/de_nuke/radar.png new file mode 100644 index 0000000..3e620f7 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_nuke/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_overpass/index.ts b/src/HUD/Radar/LexoRadar/maps/de_overpass/index.ts new file mode 100644 index 0000000..fa8aa24 --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_overpass/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_overpass/radar.png b/src/HUD/Radar/LexoRadar/maps/de_overpass/radar.png new file mode 100644 index 0000000..6cd0d66 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_overpass/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_train/index.ts b/src/HUD/Radar/LexoRadar/maps/de_train/index.ts new file mode 100644 index 0000000..db574de --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_train/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_train/radar.png b/src/HUD/Radar/LexoRadar/maps/de_train/radar.png new file mode 100644 index 0000000..9d40355 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_train/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/de_vertigo/index.ts b/src/HUD/Radar/LexoRadar/maps/de_vertigo/index.ts new file mode 100644 index 0000000..0724aae --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/de_vertigo/index.ts @@ -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 +}*/ \ No newline at end of file diff --git a/src/HUD/Radar/LexoRadar/maps/de_vertigo/radar.png b/src/HUD/Radar/LexoRadar/maps/de_vertigo/radar.png new file mode 100644 index 0000000..6cddcf5 Binary files /dev/null and b/src/HUD/Radar/LexoRadar/maps/de_vertigo/radar.png differ diff --git a/src/HUD/Radar/LexoRadar/maps/index.ts b/src/HUD/Radar/LexoRadar/maps/index.ts new file mode 100644 index 0000000..cbb99ad --- /dev/null +++ b/src/HUD/Radar/LexoRadar/maps/index.ts @@ -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; \ No newline at end of file diff --git a/src/HUD/Radar/Radar.tsx b/src/HUD/Radar/Radar.tsx new file mode 100644 index 0000000..ae0b7f5 --- /dev/null +++ b/src/HUD/Radar/Radar.tsx @@ -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 { + 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 + } +} diff --git a/src/HUD/Radar/RadarMaps.tsx b/src/HUD/Radar/RadarMaps.tsx new file mode 100644 index 0000000..d5c8f24 --- /dev/null +++ b/src/HUD/Radar/RadarMaps.tsx @@ -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 { + 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 ( +
+
+ {match ? : null} +
+ ); + } +} + +class MapsBar extends React.PureComponent { + 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
+ {} +
+ } + return
+ {match.vetos.filter(veto => veto.type !== "ban").filter(veto => veto.teamId || veto.type === "decider").map(veto => )} +
+ } +} + +class MapEntry extends React.PureComponent<{ veto: Veto, map: Map, team: Team | null }> { + render() { + const { veto, map, team } = this.props; + return
+
{team ? : null}
+
{veto.mapName}
+
+ } +} \ No newline at end of file diff --git a/src/HUD/Radar/radar.scss b/src/HUD/Radar/radar.scss new file mode 100644 index 0000000..e45b815 --- /dev/null +++ b/src/HUD/Radar/radar.scss @@ -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; + } +} diff --git a/src/HUD/SideBoxes/Money.tsx b/src/HUD/SideBoxes/Money.tsx new file mode 100644 index 0000000..dba91dc --- /dev/null +++ b/src/HUD/SideBoxes/Money.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +class LossBox extends React.PureComponent<{ active: boolean, side: 'CT' | 'T' }>{ + render(){ + return
+ } +} + +interface Props { + side: 'left' | 'right', + team: 'CT' | 'T', + loss: number, + equipment: number, + money: number, + show: boolean, +} + +export default class Money extends React.PureComponent { + + render() { + return ( +
+
+ = 4} /> + = 3} /> + = 2} /> + = 1} /> +
+
+
Loss Bonus
+
${this.props.loss}
+
+
+
Team Money
+
${this.props.money}
+
+
+
Equipment Value
+
${this.props.equipment}
+
+
+ ); + } + +} diff --git a/src/HUD/SideBoxes/SideBox.tsx b/src/HUD/SideBoxes/SideBox.tsx new file mode 100644 index 0000000..7c9d98e --- /dev/null +++ b/src/HUD/SideBoxes/SideBox.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import './sideboxes.scss' +import {configs, hudIdentity} from './../../App'; +import { apiUrl } from '../../api/api'; + +export default class SideBox extends React.Component<{ side: 'left' | 'right', hide: boolean}, { title: string, subtitle: string, image?: string }> { + constructor(props: any) { + super(props); + this.state = { + title:'Title', + subtitle:'Content', + } + } + + componentDidMount() { + configs.onChange((data:any) => { + if(!data) return; + const display = data.display_settings; + if(!display) return; + if(`${this.props.side}_title` in display){ + this.setState({title:display[`${this.props.side}_title`]}) + } + if(`${this.props.side}_subtitle` in display){ + this.setState({subtitle:display[`${this.props.side}_subtitle`]}) + } + if(`${this.props.side}_image` in display){ + const imageUrl = `${apiUrl}api/huds/${hudIdentity.name || 'dev'}/display_settings/${this.props.side}_image?isDev=${hudIdentity.isDev}&cache=${(new Date()).getTime()}`; + this.setState({image:imageUrl}) + } + }); + } + + render() { + const { image, title, subtitle} = this.state; + if(!title) return ''; + return ( +
+
+
{title}
+
{subtitle}
+
+
+ {image ? {'Left'}/:''} +
+
+ ); + } + +} diff --git a/src/HUD/SideBoxes/UtilityLevel.tsx b/src/HUD/SideBoxes/UtilityLevel.tsx new file mode 100644 index 0000000..7ef49f7 --- /dev/null +++ b/src/HUD/SideBoxes/UtilityLevel.tsx @@ -0,0 +1,108 @@ +import React from "react"; +import Weapon from "./../Weapon/Weapon"; +import { Player, WeaponRaw, Side } from "csgogsi-socket"; + +interface Props { + sides?: 'reversed', + show: boolean; + side: 'CT' | 'T', + players: Player[] +} + +function utilityState(amount: number) { + if (amount === 20) { + return "Full"; + } + if (amount > 14) { + return "Great"; + } + if (amount > 9) { + return "Good"; + } + if (amount > 5) { + return "Low"; + } + if (amount > 0) { + return "Poor"; + } + return "None"; +} + +function utilityColor(amount: number) { + if (amount === 20) { + return "#22f222"; + } + if (amount > 14) { + return "#32f218"; + } + if (amount > 9) { + return "#8ef218"; + } + if (amount > 5) { + return "#f29318"; + } + if (amount > 0) { + return "#f25618"; + } + return "#f21822"; +} + +function sum(grenades: WeaponRaw[], name: string) { + return ( + grenades.filter(grenade => grenade.name === name).reduce((prev, next) => ({ ...next, ammo_reserve: (prev.ammo_reserve || 0) + (next.ammo_reserve || 0) }), { name: "", ammo_reserve: 0 }) + .ammo_reserve || 0 + ); +} + +function parseGrenades(players: Player[], side: Side) { + const grenades = players + .filter(player => player.team.side === side) + .map(player => Object.values(player.weapons).filter(weapon => weapon.type === "Grenade")) + .flat() + .map(grenade => ({ ...grenade, name: grenade.name.replace("weapon_", "") })); + return grenades; +} + +export function summarise(players: Player[], side: Side) { + const grenades = parseGrenades(players, side); + return { + hg: sum(grenades, "hegrenade"), + flashes: sum(grenades, "flashbang"), + smokes: sum(grenades, "smokegrenade"), + inc: sum(grenades, "incgrenade") + sum(grenades, "molotov") + }; +} + +class GrenadeContainer extends React.PureComponent<{ grenade: string; amount: number }> { + render() { + return ( +
+
+ +
+
x{this.props.amount}
+
+ ); + } +} + +export default class SideBox extends React.Component { + render() { + const grenades = summarise(this.props.players, this.props.side); + const total = Object.values(grenades).reduce((a, b) => a+b, 0); + return ( +
+
+
Utility Level - 
+
{utilityState(total)}
+
+
+ + + + +
+
+ ); + } +} diff --git a/src/HUD/SideBoxes/sideboxes.scss b/src/HUD/SideBoxes/sideboxes.scss new file mode 100644 index 0000000..ad56fd7 --- /dev/null +++ b/src/HUD/SideBoxes/sideboxes.scss @@ -0,0 +1,221 @@ +.boxes { + position: fixed; + bottom: 384px; +} +.boxes.left { + left: 10px; +} +.boxes.right { + right: 10px; +} +.boxes.hide { + .utilitybox { + height: 0; + opacity: 0; + } + .moneybox { + height: 0; + opacity: 0; + } + .sidebox { + height: 0; + opacity: 0; + } +} +.utilitybox.hide { + height: 0; + opacity: 0; +} +.moneybox.hide { + height: 0; + opacity: 0; +} +.sidebox.hide { + height: 0; + opacity: 0; +} +.sidebox { + width: 415px; + height: 70px; + display: flex; + flex-direction: row; + opacity: 1; + transition: all 0.75s; + background-color: var(--sub-panel-color); + margin-bottom: 4px; + margin-top: 4px; + align-items: center; + .title_container { + color: var(--white-full); + height: 60px; + width: 100%; + font-size: 20px; + display: flex; + flex-direction: column; + >div { + flex: 1; + display: flex; + align-items: center; + font-weight: 600; + font-size: 18px; + } + } + .image_container { + width: 70px; + display: flex; + align-items: center; + justify-content: center; + img { + max-width: 70px; + max-height: 70px; + } + } +} +.utilitybox { + width: 415px; + height: 70px; + display: flex; + flex-direction: row; + opacity: 1; + transition: all 0.75s; + background-color: var(--sub-panel-color); + margin-bottom: 4px; + margin-top: 4px; + flex-direction: column; + margin-bottom: 4px; + border-radius: 20px 20px 0 0; + .title_container { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-weight: 600; + font-size: 16px; + height: 24px; + } + .title { + height: 20px; + display: flex; + align-items: center; + justify-content: center; + } + .grenades_container { + display: flex ; + + flex: 1; + height: 46px; + .grenade_container { + display: flex; + flex: 1; + color: white; + align-items: center; + font-weight: 600; + font-size: 18px; + justify-content: center; + + svg { + height: 30px; + width:auto; + } + } + } +} +.moneybox { + width: 415px; + height: 70px; + display: flex; + flex-direction: row; + opacity: 1; + transition: all 0.75s; + background-color: var(--sub-panel-color); + margin-bottom: 4px; + margin-top: 4px; + font-weight: 600; + margin-bottom: 0px; + .money_container { + display: flex; + flex-direction: column; + flex: 1; + color: white; + .title { + height: 25px; + display: flex; + align-items: flex-end; + font-size: 16px; + justify-content: center; + } + .value { + flex: 1; + display: flex; + align-items: center; + font-size: 18px; + justify-content: center; + } + } + .loss_container { + width: 4px; + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + .loss-box { + height: 13px; + width: 100%; + background-color: #515254; + margin-top: 1px; + margin-bottom: 1px; + } + .loss-box.active.CT { + background-color: var(--color-new-ct); + } + .loss-box.active.T { + background-color: var(--color-new-t); + } + } +} +.sidebox.right { + flex-direction: row-reverse; + .title_container { + >div { + flex-direction: row-reverse; + text-align: right; + padding-right: 10px; + } + } +} +.sidebox.left { + .title_container { + >div { + flex-direction: row; + text-align: left; + padding-left: 10px; + } + } +} +.utilitybox.CT { + .title { + color: var(--color-new-ct); + } +} +.utilitybox.T { + .title { + color: var(--color-new-t); + } +} +.moneybox.right { + flex-direction: row-reverse; +} +.moneybox.CT { + .money_container { + .title { + color: var(--color-new-ct); + } + } +} +.moneybox.T { + .money_container { + .title { + color: var(--color-new-t); + } + } +} diff --git a/src/HUD/TeamOverview/TeamOverview.tsx b/src/HUD/TeamOverview/TeamOverview.tsx new file mode 100644 index 0000000..3733386 --- /dev/null +++ b/src/HUD/TeamOverview/TeamOverview.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import * as I from '../../api/interfaces'; +import "./teamoverview.scss"; + +interface IProps { + team: I.Team, + show: boolean, + veto: I.Veto | null +} + +export default class TeamOverview extends React.Component { + render() { + if(!this.props.team) return null; + return ( + null + ); + } +} diff --git a/src/HUD/TeamOverview/teamoverview.scss b/src/HUD/TeamOverview/teamoverview.scss new file mode 100644 index 0000000..cf79af9 --- /dev/null +++ b/src/HUD/TeamOverview/teamoverview.scss @@ -0,0 +1,42 @@ +.match-overview { + opacity: 0; + transition: opacity 0.75s; + position: fixed; + display: flex; + flex-direction: column; + right: 10px; + top: 375px; + background-color: #000000a8; + text-transform: uppercase; + width: 250px; + color: white; +} +.match-overview.show { + opacity: 1; +} +.match-overview-teams { + display: flex; + >div { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 5px 0; + &:not(.match-overview-vs) { + background-color: rgba(0,0,0,0.7); + } + } +} +.match-overview-title { + text-align: center; + font-weight: 600; + background-color: rgba(0,0,0,0.5); + padding: 3px 0; +} +.match-overview-vs { + width: 20px; + flex: unset; + background-color: rgba(0,0,0,0.6); + font-size: 30px; +} diff --git a/src/HUD/Timers/BombTimer.tsx b/src/HUD/Timers/BombTimer.tsx new file mode 100644 index 0000000..9a8284b --- /dev/null +++ b/src/HUD/Timers/BombTimer.tsx @@ -0,0 +1,49 @@ +import React from "react"; + +import { GSI } from "./../../App"; +import BombTimer from "./Countdown"; +import { C4 } from "./../../assets/Icons"; + +export default class Bomb extends React.Component { + constructor(props: any) { + super(props); + this.state = { + height: 0, + show: false + }; + } + hide = () => { + this.setState({ show: false, height: 100 }); + }; + componentDidMount() { + const bomb = new BombTimer(time => { + let height = time > 40 ? 4000 : time * 100; + this.setState({ height: height / 40 }); + }); + bomb.onReset(this.hide); + GSI.on("data", data => { + if (data.bomb && data.bomb.countdown) { + if (data.bomb.state === "planted") { + this.setState({ show: true }); + return bomb.go(data.bomb.countdown); + } + if (data.bomb.state !== "defusing") { + this.hide(); + } + } else { + this.hide(); + } + }); + } + + render() { + return ( +
+
+
+ +
+
+ ); + } +} diff --git a/src/HUD/Timers/Countdown.ts b/src/HUD/Timers/Countdown.ts new file mode 100644 index 0000000..3b92af1 --- /dev/null +++ b/src/HUD/Timers/Countdown.ts @@ -0,0 +1,46 @@ +export default class Countdown { + last: number; + time: number; + step: (time: number) => void; + on: boolean; + resetFunc?: Function; + + constructor(step: (time: number) => void){ + this.last = 0; + this.time = 0; + this.on = false; + this.step = step; + } + onReset(func: Function) { + this.resetFunc = func; + } + stepWrapper = (time: number) =>{ + if(this.time < 0) return this.reset(); + if(!this.on) return this.reset(); + if(!this.last) this.last = time; + if(this.time !== Number((this.time - (time - this.last)/1000))){ + this.time = Number((this.time - (time - this.last)/1000)); + this.step(this.time); + } + this.last =time; + + + if(this.last) requestAnimationFrame(this.stepWrapper) + } + + go(duration: string | number){ + //console.log("STARTED WITH ", duration); + if(typeof duration === "string") duration = Number(duration); + if(Math.abs(duration - this.time) > 2) this.time = duration; + this.on = true; + if(!this.last ) requestAnimationFrame(this.stepWrapper); + + } + + reset(){ + this.last = 0; + this.time = 0; + this.on = false; + if(this.resetFunc) this.resetFunc(); + } +} \ No newline at end of file diff --git a/src/HUD/Timers/PlantDefuse.tsx b/src/HUD/Timers/PlantDefuse.tsx new file mode 100644 index 0000000..027049e --- /dev/null +++ b/src/HUD/Timers/PlantDefuse.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import { Timer } from "../MatchBar/MatchBar"; +import { Player } from "csgogsi"; +import * as I from "./../../assets/Icons"; + +interface IProps { + timer: Timer | null; + side: "right" | "left" +} + +export default class Bomb extends React.Component { + getCaption = (type: "defusing" | "planting", player: Player | null) => { + if(!player) return null; + if(type === "defusing"){ + return <> + +
{player.name} is defusing the bomb
+ ; + } + return <> + +
{player.name} is planting the bomb
+ ; + } + render() { + const { side, timer } = this.props; + return ( +
+ { + timer ? +
+ {this.getCaption(timer.type, timer.player)} +
: null + } + +
+
+ ); + } +} diff --git a/src/HUD/Tournament/Ladder.tsx b/src/HUD/Tournament/Ladder.tsx new file mode 100644 index 0000000..e245596 --- /dev/null +++ b/src/HUD/Tournament/Ladder.tsx @@ -0,0 +1,145 @@ +import React from 'react'; +import * as I from './../../api/interfaces'; + +interface MatchData { + left: { name: string; score: string | number; logo: string }; + right: { name: string; score: string | number; logo: string }; +} +interface Props { + tournament: I.Tournament, + matches: I.Match[], + teams: I.Team[] +} + +export default class Ladder extends React.Component { + joinParents = (matchup: I.TournamentMatchup, matchups: I.TournamentMatchup[]) => { + const { tournament } = this.props; + if (!tournament || !matchup) return matchup; + + if (matchup.parents.length) return matchup; + + const parents = matchups.filter(m => m.winner_to === matchup._id || m.loser_to === matchup._id); + if (!parents.length) return matchup; + matchup.parents.push(...parents.map(parent => this.joinParents(parent, matchups))); + + return matchup; + }; + + copyMatchups = (): I.DepthTournamentMatchup[] => { + if (!this.props.tournament) return []; + const matchups = JSON.parse(JSON.stringify(this.props.tournament.matchups)) as I.DepthTournamentMatchup[]; + return matchups; + }; + + setDepth = (matchups: I.DepthTournamentMatchup[], matchup: I.DepthTournamentMatchup, depth: number, force = false) => { + const getParents = (matchup: I.DepthTournamentMatchup) => { + return matchups.filter(parent => parent.loser_to === matchup._id || parent.winner_to === matchup._id); + }; + + if (!matchup.depth || force) { + matchup.depth = depth; + getParents(matchup).forEach(matchup => this.setDepth(matchups, matchup, depth + 1)); + } + if (matchup.depth <= depth - 1) { + this.setDepth(matchups, matchup, depth - 1, true); + } + return matchup; + }; + + getMatch = (matchup: I.TournamentMatchup) => { + const { matches } = this.props; + const matchData: MatchData = { + left: { name: 'TBD', score: '-', logo: '' }, + right: { name: 'TBD', score: '-', logo: '' } + }; + const match = matches.find(match => match.id === matchup.matchId); + if (!match) return matchData; + const teams = [ + this.props.teams.find(team => team._id === match.left.id), + this.props.teams.find(team => team._id === match.right.id) + ]; + if (teams[0]) { + matchData.left.name = teams[0].name; + matchData.left.score = match.left.wins; + matchData.left.logo = teams[0].logo; + } + if (teams[1]) { + matchData.right.name = teams[1].name; + matchData.right.score = match.right.wins; + matchData.right.logo = teams[1].logo; + } + return matchData; + }; + + renderBracket = ( + matchup: I.DepthTournamentMatchup | null | undefined, + depth: number, + fromChildId: string | undefined, + childVisibleParents: number, + isLast = false + ) => { + const { tournament, matches } = this.props; + if (!matchup || !tournament) return null; + const match = this.getMatch(matchup); + + if (fromChildId === matchup.loser_to) return null; + const parentsToRender = matchup.parents.filter(matchupParent => matchupParent.loser_to !== matchup._id); + if (matchup.depth > depth) { + return ( +
+ {this.renderBracket(matchup, depth + 1, fromChildId, parentsToRender.length)} +
+
+ ); + } + const currentMatch = matches.find(mtch => mtch.current); + const isCurrent = currentMatch && currentMatch.id === matchup.matchId; + return ( +
+
+ {this.renderBracket(matchup.parents[0], depth + 1, matchup._id, parentsToRender.length)} + {this.renderBracket(matchup.parents[1], depth + 1, matchup._id, parentsToRender.length)} +
+
+
+ {parentsToRender.length === 1 ?
: null} +
+
+
+ {match.left.logo ? Logo : null} +
+
{match.left.name}
+
{match.left.score}
+
+
+
+ {match.right.logo ? Logo : null} +
+
{match.right.name}
+
{match.right.score}
+
+
+
+ + {childVisibleParents === 2 ? ( +
+ ) : null} +
+ ); + }; + + render() { + const { tournament } = this.props; + if (!tournament) return null; + const matchups = this.copyMatchups(); + const gf = matchups.find(matchup => matchup.winner_to === null); + if (!gf) return null; + const joinedParents = this.joinParents(gf, matchups); + const matchupWithDepth = this.setDepth(matchups, joinedParents as I.DepthTournamentMatchup, 0); + return this.renderBracket(matchupWithDepth, 0, undefined, 2, true); + } +} diff --git a/src/HUD/Tournament/Tournament.tsx b/src/HUD/Tournament/Tournament.tsx new file mode 100644 index 0000000..3507ea3 --- /dev/null +++ b/src/HUD/Tournament/Tournament.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import './tournament.scss'; +import { actions } from '../../App'; +import * as I from './../../api/interfaces'; +import api from '../../api/api'; +import Ladder from './Ladder'; +interface State { + tournament: I.Tournament | null, + teams: I.Team[], + matches: I.Match[], + show: boolean, +} +export default class Tournament extends React.Component<{}, State> { + constructor(props: {}) { + super(props); + this.state = { + tournament: null, + matches: [], + teams: [], + show: false + } + } + async componentDidMount() { + const { tournament } = await api.tournaments.get(); + if(tournament){ + actions.on("showTournament", async (show: string) => { + if(show !== "show"){ + return this.setState({show: false}); + } + + this.setState({tournament}, () => { + this.setState({show:true}) + }); + }); + + Promise.all([api.match.get(), api.teams.get()]).then(([matches, teams]) =>{ + this.setState({matches, teams}); + }); + } + } + render() { + const { tournament, matches, teams, show } = this.state; + if(!tournament) return null; + return ( +
+
+ { tournament.logo ? {tournament.name} : null } +
+ {tournament.name} +
+
+ + +
+ ); + } + +} diff --git a/src/HUD/Tournament/tournament.scss b/src/HUD/Tournament/tournament.scss new file mode 100644 index 0000000..1d23f68 --- /dev/null +++ b/src/HUD/Tournament/tournament.scss @@ -0,0 +1,147 @@ +.ladder-container { + position: absolute; + margin-left: 50%; + transform: translateX(-50%); + background-color: rgba(0,0,0,0.85); + top: 200px; + opacity: 0; + transition: opacity 1s; + color: white; + >.bracket { + >.connector { + display: none; + } + } +} +.ladder-container.show { + opacity: 1; +} +.bracket { + display: flex; + justify-content: flex-end; + position: relative; + &:first-child { + >.connector { + top: 50%; + } + } + &:nth-child(2) { + >.connector { + top: 0%; + } + } +} +.parent-brackets { + display: flex; + flex-direction: column; + justify-content: center; + position: relative; +} +.empty-bracket { + position: relative; + >.bracket { + padding-right: 240px; + } + >.connector { + width: 240px; + height: 1px; + top: 50%; + } + &:nth-child(2) { + >.bracket { + >.connector { + top: 0%; + } + } + } +} +.loser-parent-indicator { + position: absolute; + width: 15px; + height: 15px; + left: 5px; + top: calc(50% - 20px); + background-size: contain; + opacity: 0.2; +} +.connector { + position: absolute; + right: 0; + height: 50%; + background-color: #ffd700; + width: 1px; +} +.bracket-details { + .match-details { + .team-data { + &:first-child { + border-bottom: 1px solid transparent; + } + display: flex; + .team-name { + flex: 1; + display: flex; + align-items: center; + } + .team-score { + width: 10px; + display: flex; + align-items: center; + } + .team-logo { + width: 30px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + img { + height: 20px; + } + } + } + display: flex; + flex-direction: column; + width: 200px; + margin: 10px 20px; + background: rgba(0,0,0,0.5); + border-radius: 2px; + padding: 5px; + z-index: 1; + cursor: pointer; + } + display: flex; + justify-content: center; + align-items: center; + position: relative; +} +.match-connector { + width: 100%; + position: absolute; + height: 1px; + background: #ffd700; +} +.match-connector.last-match { + width: 220px; + left: 0; +} +.match-connector.first-match { + width: 220px; + right: 0; +} +.tournament-data { + img { + max-height: 64px; + } + display: flex; + padding: 10px; + align-items: center; + justify-content: center; +} +.tournament-name { + text-transform: uppercase; + font-size: 45px; + padding-left: 22px; +} +.match-details.current { + border: 1px solid gold; +} diff --git a/src/HUD/Trivia/Trivia.tsx b/src/HUD/Trivia/Trivia.tsx new file mode 100644 index 0000000..fae4f2d --- /dev/null +++ b/src/HUD/Trivia/Trivia.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import './trivia.scss'; + +import {configs, actions} from './../../App'; + +export default class Trivia extends React.Component { + constructor(props: any) { + super(props); + this.state = { + title:'Title', + content:'Content', + show: false + } + } + + componentDidMount() { + configs.onChange((data:any) => { + if(!data) return; + const trivia = data.trivia; + if(!trivia) return; + + if(trivia.title && trivia.content){ + this.setState({title:trivia.title, content:trivia.content}) + } + }); + actions.on("triviaState", (state: any) => { + this.setState({show: state === "show"}) + }); + actions.on("toggleTrivia", () => { + this.setState({show: !this.state.show}) + }); + } + + render() { + return ( +
+
{this.state.title}
+
{this.state.content}
+
+ ); + } + +} diff --git a/src/HUD/Trivia/trivia.scss b/src/HUD/Trivia/trivia.scss new file mode 100644 index 0000000..80b03be --- /dev/null +++ b/src/HUD/Trivia/trivia.scss @@ -0,0 +1,27 @@ +.trivia_container { + position: absolute; + left:20px; + background-color:black; + color: white; + width: 450px; + top:830px; + opacity:1; + transition: opacity 1s; + + font-size: 16pt; +} +.trivia_container .title { + height: 30px; + display: flex; + align-items: center; + padding-left:30px; + font-weight:bold; + padding-top:10px; +} +.trivia_container .content { + padding-left:30px; + padding-bottom:15px; +} +.trivia_container.hide { + opacity:0; +} \ No newline at end of file diff --git a/src/HUD/Weapon/Weapon.tsx b/src/HUD/Weapon/Weapon.tsx new file mode 100644 index 0000000..2e9225b --- /dev/null +++ b/src/HUD/Weapon/Weapon.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import * as Weapons from './../../assets/Weapons'; + +interface IProps extends React.SVGProps { + weapon: string, + active: boolean, + isGrenade?: boolean +} +export default class WeaponImage extends React.Component { + render() { + const { weapon, active, isGrenade, ...rest } = this.props; + const Weapon = (Weapons as any)[weapon]; + const { className, ...svgProps } = rest; + if(!Weapon) return null; + return ( + + ); + } +} \ No newline at end of file diff --git a/src/HUD/countries.ts b/src/HUD/countries.ts new file mode 100644 index 0000000..a08667e --- /dev/null +++ b/src/HUD/countries.ts @@ -0,0 +1,259 @@ +export const countries: any = { + 'AF' : 'Afghanistan', + 'AX' : 'Aland Islands', + 'AL' : 'Albania', + 'DZ' : 'Algeria', + 'AS' : 'American Samoa', + 'AD' : 'Andorra', + 'AO' : 'Angola', + 'AI' : 'Anguilla', + 'AQ' : 'Antarctica', + 'AG' : 'Antigua And Barbuda', + 'AR' : 'Argentina', + 'AM' : 'Armenia', + 'AW' : 'Aruba', + 'AU' : 'Australia', + 'AT' : 'Austria', + 'AZ' : 'Azerbaijan', + 'BS' : 'Bahamas', + 'BH' : 'Bahrain', + 'BD' : 'Bangladesh', + 'BB' : 'Barbados', + 'BY' : 'Belarus', + 'BE' : 'Belgium', + 'BZ' : 'Belize', + 'BJ' : 'Benin', + 'BM' : 'Bermuda', + 'BT' : 'Bhutan', + 'BO' : 'Bolivia', + 'BA' : 'Bosnia and Herzegovina', + 'BW' : 'Botswana', + 'BV' : 'Bouvet Island', + 'BR' : 'Brazil', + 'IO' : 'British Indian Ocean Territory', + 'BN' : 'Brunei', + 'BG' : 'Bulgaria', + 'BF' : 'Burkina Faso', + 'BI' : 'Burundi', + 'CIS': 'CIS', + 'KH' : 'Cambodia', + 'CM' : 'Cameroon', + 'CA' : 'Canada', + 'CV' : 'Cape Verde', + 'KY' : 'Cayman Islands', + 'CF' : 'Central African Republic', + 'TD' : 'Chad', + 'CL' : 'Chile', + 'CN' : 'China', + 'CX' : 'Christmas Island', + 'CC' : 'Cocos (Keeling) Islands', + 'CO' : 'Colombia', + 'KM' : 'Comoros', + 'CG' : 'Congo', + 'CD' : 'Congo, Democratic Republic', + 'CK' : 'Cook Islands', + 'CR' : 'Costa Rica', + 'CI' : 'Cote D\'Ivoire', + 'HR' : 'Croatia', + 'CU' : 'Cuba', + 'EU' : 'European Union', + 'CY' : 'Cyprus', + 'CZ' : 'Czech Republic', + 'DK' : 'Denmark', + 'DJ' : 'Djibouti', + 'DM' : 'Dominica', + 'DO' : 'Dominican Republic', + 'EC' : 'Ecuador', + 'EG' : 'Egypt', + 'SV' : 'El Salvador', + 'GQ' : 'Equatorial Guinea', + 'ER' : 'Eritrea', + 'EE' : 'Estonia', + 'ET' : 'Ethiopia', + 'FK' : 'Falkland Islands', + 'FO' : 'Faroes', + 'FJ' : 'Fiji', + 'FI' : 'Finland', + 'FR' : 'France', + 'GF' : 'French Guiana', + 'PF' : 'French Polynesia', + 'TF' : 'French Southern Territories', + 'GA' : 'Gabon', + 'GM' : 'Gambia', + 'GE' : 'Georgia', + 'DE' : 'Germany', + 'GH' : 'Ghana', + 'GI' : 'Gibraltar', + 'GR' : 'Greece', + 'GL' : 'Greenland', + 'GD' : 'Grenada', + 'GP' : 'Guadeloupe', + 'GU' : 'Guam', + 'GT' : 'Guatemala', + 'GG' : 'Guernsey', + 'GN' : 'Guinea', + 'GW' : 'Guinea-Bissau', + 'GY' : 'Guyana', + 'HT' : 'Haiti', + 'HM' : 'Heard Island & Mcdonald Islands', + 'VA' : 'Holy See (Vatican City State)', + 'HN' : 'Honduras', + 'HK' : 'Hong Kong', + 'HU' : 'Hungary', + 'IS' : 'Iceland', + 'IN' : 'India', + 'ID' : 'Indonesia', + 'IR' : 'Iran', + 'IQ' : 'Iraq', + 'IE' : 'Ireland', + 'IM' : 'Isle of Man', + 'IL' : 'Israel', + 'IT' : 'Italy', + 'JM' : 'Jamaica', + 'JP' : 'Japan', + 'JE' : 'Jersey', + 'JO' : 'Jordan', + 'KZ' : 'Kazakhstan', + 'KE' : 'Kenya', + 'KI' : 'Kiribati', + 'KR' : 'South Korea', + 'KP' : 'North Korea', + 'KW' : 'Kuwait', + 'KG' : 'Kyrgyzstan', + 'LA' : 'Laos', + 'LV' : 'Latvia', + 'LB' : 'Lebanon', + 'LS' : 'Lesotho', + 'LR' : 'Liberia', + 'LY' : 'Libya', + 'LI' : 'Liechtenstein', + 'LT' : 'Lithuania', + 'LU' : 'Luxembourg', + 'MO' : 'Macau', + 'MK' : 'Macedonia', + 'MG' : 'Madagascar', + 'MW' : 'Malawi', + 'MY' : 'Malaysia', + 'MV' : 'Maldives', + 'ML' : 'Mali', + 'MT' : 'Malta', + 'MH' : 'Marshall Islands', + 'MQ' : 'Martinique', + 'MR' : 'Mauritania', + 'MU' : 'Mauritius', + 'YT' : 'Mayotte', + 'MX' : 'Mexico', + 'FM' : 'Micronesia', + 'MD' : 'Moldova', + 'MC' : 'Monaco', + 'MN' : 'Mongolia', + 'ME' : 'Montenegro', + 'MS' : 'Montserrat', + 'MA' : 'Morocco', + 'MZ' : 'Mozambique', + 'MM' : 'Myanmar', + 'NA' : 'Namibia', + 'NR' : 'Nauru', + 'NP' : 'Nepal', + 'NL' : 'Netherlands', + 'AN' : 'Netherlands Antilles', + 'NC' : 'New Caledonia', + 'NZ' : 'New Zealand', + 'NI' : 'Nicaragua', + 'NE' : 'Niger', + 'NG' : 'Nigeria', + 'NU' : 'Niue', + 'NF' : 'Norfolk Island', + 'MP' : 'Northern Mariana Islands', + 'NO' : 'Norway', + 'OM' : 'Oman', + 'PK' : 'Pakistan', + 'PW' : 'Palau', + 'PS' : 'Palestine', + 'PA' : 'Panama', + 'PG' : 'Papua New Guinea', + 'PY' : 'Paraguay', + 'PE' : 'Peru', + 'PH' : 'Philippines', + 'PN' : 'Pitcairn Islands', + 'PL' : 'Poland', + 'PT' : 'Portugal', + 'PR' : 'Puerto Rico', + 'QA' : 'Qatar', + 'RE' : 'Reunion', + 'RO' : 'Romania', + 'RU' : 'Russia', + 'RW' : 'Rwanda', + 'BL' : 'Saint Barthelemy', + 'SH' : 'Saint Helena', + 'KN' : 'Saint Kitts and Nevis', + 'LC' : 'Saint Lucia', + 'MF' : 'Saint Martin', + 'PM' : 'Saint Pierre And Miquelon', + 'VC' : 'Saint Vincent and the Grenadines', + 'WS' : 'Samoa', + 'SM' : 'San Marino', + 'ST' : 'Sao Tome and Principe', + 'SA' : 'Saudi Arabia', + 'SN' : 'Senegal', + 'RS' : 'Serbia', + 'SC' : 'Seychelles', + 'SL' : 'Sierra Leone', + 'SG' : 'Singapore', + 'SK' : 'Slovakia', + 'SI' : 'Slovenia', + 'SB' : 'Solomon Islands', + 'SO' : 'Somalia', + 'ZA' : 'South Africa', + 'GS' : 'South Georgia and the South Sandwich Islands', + 'ES' : 'Spain', + 'LK' : 'Sri Lanka', + 'SD' : 'Sudan', + 'SR' : 'Suriname', + 'SS' : 'South Sudan', + 'SJ' : 'Svalbard And Jan Mayen', + 'SZ' : 'Swaziland', + 'SE' : 'Sweden', + 'CH' : 'Switzerland', + 'SY' : 'Syria', + 'TW' : 'Taiwan', + 'TJ' : 'Tajikistan', + 'TZ' : 'Tanzania', + 'TH' : 'Thailand', + 'TL' : 'Timor-Leste', + 'TG' : 'Togo', + 'TK' : 'Tokelau', + 'TO' : 'Tonga', + 'TT' : 'Trinidad and Tobago', + 'TN' : 'Tunisia', + 'TR' : 'Turkey', + 'TM' : 'Turkmenistan', + 'TC' : 'Turks and Caicos Islands', + 'TV' : 'Tuvalu', + 'UG' : 'Uganda', + 'UA' : 'Ukraine', + 'AE' : 'United Arab Emirates', + 'GB' : 'United Kingdom', + 'GB-ENG': 'England', + 'GB-SCT': 'Scotland', + 'GB-WLS': 'Wales', + 'US' : 'United States', + 'UM' : 'United States Outlying Islands', + 'UY' : 'Uruguay', + 'UZ' : 'Uzbekistan', + 'VU' : 'Vanuatu', + 'VE' : 'Venezuela', + 'VN' : 'Vietnam', + 'VG' : 'Virgin Islands, British', + 'VI' : 'Virgin Islands, U.S.', + 'WF' : 'Wallis And Futuna', + 'EH' : 'Western Sahara', + 'YE' : 'Yemen', + 'ZM' : 'Zambia', + 'ZW' : 'Zimbabwe', + 'XK' : 'Kosovo' +}; + +export const getCountry = (iso: string) => { + return countries[iso.toUpperCase()]; +} diff --git a/src/HUD/isSvg.ts b/src/HUD/isSvg.ts new file mode 100644 index 0000000..58dff3e --- /dev/null +++ b/src/HUD/isSvg.ts @@ -0,0 +1,11 @@ +const regex = /^\s*(?:<\?xml[^>]*>\s*)?(?:]*\s*(?:\[?(?:\s*]*>\s*)*\]?)*[^>]*>\s*)?(?:]*>[^]*<\/svg>|]*\/\s*>)\s*$/i; + +const isSvg = (img: Buffer) => + regex.test( + img + .toString() + .replace(/\s*/gim, '') + .replace(//g, '') + ); + +export default isSvg; diff --git a/src/api/actionManager.ts b/src/api/actionManager.ts new file mode 100644 index 0000000..bfc4f73 --- /dev/null +++ b/src/api/actionManager.ts @@ -0,0 +1,65 @@ +export default class ActionManager { + listeners: Map; + + constructor(){ + this.listeners = new Map(); + + /*this.on('data', _data => { + });*/ + } + execute(eventName: string, argument?: any){ + const listeners = this.listeners.get(eventName); + if(!listeners) return false; + listeners.forEach(callback => { + if(argument) callback(argument); + else callback(); + }); + return true; + } + + on(eventName: string, listener: Function){ + const listOfListeners = this.listeners.get(eventName) || []; + listOfListeners.push(listener); + this.listeners.set(eventName, listOfListeners); + + return true; + } +} +export class ConfigManager { + listeners: Function[]; + data: any; + + constructor(){ + this.listeners = []; + this.data = {}; + } + save(data: any){ + this.data = data; + this.execute(); + + /*const listeners = this.listeners.get(eventName); + if(!listeners) return false; + listeners.forEach(callback => { + if(argument) callback(argument); + else callback(); + }); + return true;*/ + } + + execute(){ + const listeners = this.listeners; + if(!listeners || !listeners.length) return false; + listeners.forEach(listener => { + listener(this.data); + }); + return true; + } + + onChange(listener: Function){ + const listOfListeners = this.listeners || []; + listOfListeners.push(listener); + this.listeners = listOfListeners; + + return true; + } +} \ No newline at end of file diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100644 index 0000000..0b4d29c --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,56 @@ +import * as I from './interfaces'; +import queryString from 'query-string'; +import { MapConfig } from '../HUD/Radar/LexoRadar/maps'; + + +const query = queryString.parseUrl(window.location.href).query; +export const variant = query?.variant || "default"; + +export const port = (query && Number(query.port)) || 1349; + +export const isDev = !query.isProd; + +export const config = {apiAddress:isDev ? `http://localhost:${port}/` : '/'} +export const apiUrl = config.apiAddress; + +export async function apiV2(url: string, method = 'GET', body?: any) { + const options: RequestInit = { + method, + headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, + } + if (body) { + options.body = JSON.stringify(body) + } + let data: any = null; + return fetch(`${apiUrl}api/${url}`, options) + .then(res => { + data = res; + return res.json().catch(_e => data && data.status < 300) + }); +} + +const api = { + match: { + get: async (): Promise => apiV2(`match`), + getCurrent: async (): Promise => apiV2(`match/current`) + }, + camera: { + get: (): Promise<{ availablePlayers: ({steamid:string, label: string})[], uuid: string }> => apiV2('camera') + }, + teams: { + getOne: async (id: string): Promise => apiV2(`teams/${id}`), + get: (): Promise => apiV2(`teams`), + }, + players: { + get: async (steamids?: string[]): Promise => apiV2(steamids ? `players?steamids=${steamids.join(';')}` :`players`), + getAvatarURLs: async (steamid: string): Promise<{custom: string, steam: string}> => apiV2(`players/avatar/steamid/${steamid}`) + }, + tournaments: { + get: () => apiV2('tournament') + }, + maps: { + get: (): Promise<{ [key: string] : MapConfig}> => apiV2('radar/maps') + } +} + +export default api; \ No newline at end of file diff --git a/src/api/avatars.ts b/src/api/avatars.ts new file mode 100644 index 0000000..94adf6e --- /dev/null +++ b/src/api/avatars.ts @@ -0,0 +1,24 @@ +import api from './api'; +interface AvatarLoader { + loader: Promise, + url: string, +} + +export const avatars: { [key: string]: AvatarLoader } = {}; + +export const loadAvatarURL = (steamid: string) => { + if(!steamid) return; + if(avatars[steamid]) return avatars[steamid].url; + avatars[steamid] = { + url: '', + loader: new Promise((resolve) => { + api.players.getAvatarURLs(steamid).then(result => { + avatars[steamid].url = result.custom || result.steam; + resolve(result.custom || result.custom); + }).catch(() => { + delete avatars[steamid]; + resolve(''); + }); + }) + } +} diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts new file mode 100644 index 0000000..d988cf3 --- /dev/null +++ b/src/api/interfaces.ts @@ -0,0 +1,151 @@ +export interface Player { + _id: string; + firstName: string; + lastName: string; + username: string; + avatar: string; + country: string; + steamid: string; + team: string; + extra: Record; +} + +export interface Team { + _id: string; + name: string; + country: string; + shortName: string; + logo: string; + extra: Record; +} +/* +export interface HUD { + name: string, + version: string, + author: string, + legacy: boolean, + dir: string +} + +export interface Config { + port: number, + steamApiKey: string, + token: string, +}*/ +export interface TournamentMatchup { + _id: string; + loser_to: string | null; // IDs of Matchups, not Matches + winner_to: string | null; + label: string; + matchId: string | null; + parents: TournamentMatchup[]; +} + +export interface DepthTournamentMatchup extends TournamentMatchup { + depth: number; + parents: DepthTournamentMatchup[]; +} + +export interface Tournament { + _id: string; + name: string; + logo: string; + matchups: TournamentMatchup[]; + autoCreate: boolean; +} +export interface RoundData { + round: number, + players: { + [steamid: string]: PlayerRoundData + }, + winner: 'CT' | 'T' | null, + win_type: 'bomb' | 'elimination' | 'defuse' | 'time', +} + +export interface PlayerRoundData { + kills: number, + killshs: number, + damage: number, +} + +export interface Veto { + teamId: string; + mapName: string; + side: "CT" | "T" | "NO"; + type: "ban" | "pick" | "decider"; + reverseSide?: boolean; + rounds?: (RoundData | null)[], + score?: { + [key: string]: number; + }; + winner?: string; + mapEnd: boolean; +} + +export interface Match { + id: string; + current: boolean; + left: { + id: string | null; + wins: number; + }; + right: { + id: string | null; + wins: number; + }; + matchType: "bo1" | "bo2" | "bo3" | "bo5"; + vetos: Veto[]; +} + +export type Weapon = + | "ak47" + | "aug" + | "awp" + | "bizon" + | "famas" + | "g3sg1" + | "galilar" + | "m4a1" + | "m4a1_silencer" + | "m249" + | "mac10" + | "mag7" + | "mp5sd" + | "mp7" + | "mp9" + | "negev" + | "nova" + | "p90" + | "sawedoff" + | "scar20" + | "sg556" + | "ssg08" + | "ump45" + | "xm1014" + | Pistol + | Knife; + +export type Pistol = "c75a" | "deagle" | "elite" | "fiveseven" | "glock" | "hkp2000" | "p250" | "revolver" | "taser" | "tec9" | "usp_silencer"; + +export type Knife = +| "knife"// +| "knife_css"//-- +| "knife_butterfly"// +| "knife_falchion"// +| "knife_flip"// +| "knife_outdoor" // Nomad Knife +| "knife_gut"// +| "knife_gypsy_jackknife"// +| "knife_karambit"// +| "knife_bayonet" // +| "knife_cord" // +| "knife_m9_bayonet"// +| "knife_push" // Shadow daggers +| "knife_stiletto"// +| "knife_survival_bowie"// +| "knife_t"// +| "knife_skeleton" // +| "knife_tactical"// +| "knife_ursus"// +| "knife_widowmaker"// +| "knife_canis";// diff --git a/src/assets/Icons.tsx b/src/assets/Icons.tsx new file mode 100644 index 0000000..1e4421b --- /dev/null +++ b/src/assets/Icons.tsx @@ -0,0 +1,61 @@ +import { ReactComponent as ArmorFull } from './../assets/images/icon_armor_full_default.svg'; +import { ReactComponent as ArmorHalf } from './../assets/images/icon_armor_half_default.svg'; +import { ReactComponent as ArmorHalfHelmet } from './../assets/images/icon_armor_half_helmet_default.svg'; +import { ReactComponent as ArmorHelmet } from './../assets/images/icon_armor_helmet_default.svg'; +import { ReactComponent as ArmorNone } from './../assets/images/icon_armor_none_default.svg'; +import { ReactComponent as Blind } from './../assets/images/icon_blind.svg'; +import { ReactComponent as Bomb } from './../assets/images/icon_bomb_default.svg'; +import { ReactComponent as BombExplosion } from './../assets/images/icon_bomb_explosion_default.svg'; +import { ReactComponent as Bullets } from './../assets/images/icon_bullets_default.svg'; +import { ReactComponent as Burning } from './../assets/images/icon_burning.svg'; +import { ReactComponent as C4 } from './../assets/images/icon_c4_default.svg'; +import { ReactComponent as Defuse } from './../assets/images/icon_defuse_default.svg'; +import { ReactComponent as Health } from './../assets/images/icon_health_default.svg'; +import { ReactComponent as HealthFull } from './../assets/images/icon_health_full_default.svg'; +import { ReactComponent as Hourglass } from './../assets/images/icon_hourglass_default.svg'; +import { ReactComponent as Microphone } from './../assets/images/icon_microphone.svg'; +import { ReactComponent as Pause } from './../assets/images/icon_pause_default.svg'; +import { ReactComponent as Skull } from './../assets/images/icon_skull_default.svg'; +import { ReactComponent as Timer } from './../assets/images/icon_timer_default.svg'; +import { ReactComponent as FlashedKill } from './../assets/images/flashed_kill.svg'; +import { ReactComponent as Headshot } from './../assets/images/headshot.svg'; +import { ReactComponent as NoScope } from './../assets/images/noscope.svg'; +import { ReactComponent as SmokeKill } from './../assets/images/smoke_kill.svg'; +import { ReactComponent as Suicide } from './../assets/images/suicide.svg'; +import { ReactComponent as Wallbang } from './../assets/images/wallbang.svg'; +import LogoCT from './../assets/images/logo_CT_default.png'; +import LogoT from './../assets/images/logo_T_default.png'; +import { ReactComponent as SmallBomb } from "./../assets/images/bomb.svg"; + + +export { + SmallBomb, + ArmorFull, + ArmorHalf, + FlashedKill, + Headshot, + NoScope, + SmokeKill, + Suicide, + Wallbang, + ArmorHalfHelmet, + ArmorHelmet, + ArmorNone, + Blind, + Bomb, + BombExplosion, + Bullets, + Burning, + C4, + Defuse, + Health, + HealthFull, + Hourglass, + Microphone, + Pause, + Skull, + Timer, + LogoCT, + LogoT, +} + \ No newline at end of file diff --git a/src/assets/Weapons.tsx b/src/assets/Weapons.tsx new file mode 100644 index 0000000..9cd5697 --- /dev/null +++ b/src/assets/Weapons.tsx @@ -0,0 +1,142 @@ +import { ReactComponent as ak47 } from './weapons/ak47.svg'; +import { ReactComponent as aug } from './weapons/aug.svg'; +import { ReactComponent as awp } from './weapons/awp.svg'; +import { ReactComponent as bayonet } from './weapons/bayonet.svg'; +import { ReactComponent as bizon } from './weapons/bizon.svg'; +import { ReactComponent as c4 } from './weapons/c4.svg'; +import { ReactComponent as cz75a } from './weapons/cz75a.svg'; +import { ReactComponent as deagle } from './weapons/deagle.svg'; +import { ReactComponent as decoy } from './weapons/decoy.svg'; +import { ReactComponent as elite } from './weapons/elite.svg'; +import { ReactComponent as famas } from './weapons/famas.svg'; +import { ReactComponent as fiveseven } from './weapons/fiveseven.svg'; +import { ReactComponent as flashbang } from './weapons/flashbang.svg'; +import { ReactComponent as g3sg1 } from './weapons/g3sg1.svg'; +import { ReactComponent as galilar } from './weapons/galilar.svg'; +import { ReactComponent as glock } from './weapons/glock.svg'; +import { ReactComponent as hegrenade } from './weapons/hegrenade.svg'; +import { ReactComponent as hkp2000 } from './weapons/hkp2000.svg'; +import { ReactComponent as incgrenade } from './weapons/incgrenade.svg'; +import { ReactComponent as inferno } from './weapons/inferno.svg'; +import { ReactComponent as knife } from './weapons/knife.svg'; +import { ReactComponent as knife_bayonet } from './weapons/knife_bayonet.svg'; +import { ReactComponent as knife_butterfly } from './weapons/knife_butterfly.svg'; +import { ReactComponent as knife_canis } from './weapons/knife_canis.svg'; +import { ReactComponent as knife_cord } from './weapons/knife_cord.svg'; +import { ReactComponent as knife_css } from './weapons/knife_css.svg'; +import { ReactComponent as knife_falchion } from './weapons/knife_falchion.svg'; +import { ReactComponent as knife_flip } from './weapons/knife_flip.svg'; +import { ReactComponent as knife_gut } from './weapons/knife_gut.svg'; +import { ReactComponent as knife_gypsy_jackknife } from './weapons/knife_gypsy_jackknife.svg'; +import { ReactComponent as knife_karambit } from './weapons/knife_karambit.svg'; +import { ReactComponent as knife_m9_bayonet } from './weapons/knife_m9_bayonet.svg'; +import { ReactComponent as knife_outdoor } from './weapons/knife_outdoor.svg'; +import { ReactComponent as knife_push } from './weapons/knife_push.svg'; +import { ReactComponent as knife_skeleton } from './weapons/knife_skeleton.svg'; +import { ReactComponent as knife_stiletto } from './weapons/knife_stiletto.svg'; +import { ReactComponent as knife_survival_bowie } from './weapons/knife_survival_bowie.svg'; +import { ReactComponent as knife_t } from './weapons/knife_t.svg'; +import { ReactComponent as knife_tactical } from './weapons/knife_tactical.svg'; +import { ReactComponent as knife_ursus } from './weapons/knife_ursus.svg'; +import { ReactComponent as knife_widowmaker } from './weapons/knife_widowmaker.svg'; +import { ReactComponent as m249 } from './weapons/m249.svg'; +import { ReactComponent as m4a1 } from './weapons/m4a1.svg'; +import { ReactComponent as m4a1_silencer } from './weapons/m4a1_silencer.svg'; +import { ReactComponent as m4a1_silencer_off } from './weapons/m4a1_silencer_off.svg'; +import { ReactComponent as mac10 } from './weapons/mac10.svg'; +import { ReactComponent as mag7 } from './weapons/mag7.svg'; +import { ReactComponent as molotov } from './weapons/molotov.svg'; +import { ReactComponent as mp5sd } from './weapons/mp5sd.svg'; +import { ReactComponent as mp7 } from './weapons/mp7.svg'; +import { ReactComponent as mp9 } from './weapons/mp9.svg'; +import { ReactComponent as negev } from './weapons/negev.svg'; +import { ReactComponent as nova } from './weapons/nova.svg'; +import { ReactComponent as out } from './weapons/out.svg'; +import { ReactComponent as p250 } from './weapons/p250.svg'; +import { ReactComponent as p90 } from './weapons/p90.svg'; +import { ReactComponent as revolver } from './weapons/revolver.svg'; +import { ReactComponent as sawedoff } from './weapons/sawedoff.svg'; +import { ReactComponent as scar20 } from './weapons/scar20.svg'; +import { ReactComponent as sg556 } from './weapons/sg556.svg'; +import { ReactComponent as smokegrenade } from './weapons/smokegrenade.svg'; +import { ReactComponent as ssg08 } from './weapons/ssg08.svg'; +import { ReactComponent as taser } from './weapons/taser.svg'; +import { ReactComponent as tec9 } from './weapons/tec9.svg'; +import { ReactComponent as trigger_hurt } from './weapons/trigger_hurt.svg'; +import { ReactComponent as ump45 } from './weapons/ump45.svg'; +import { ReactComponent as usp_silencer } from './weapons/usp_silencer.svg'; +import { ReactComponent as usp_silencer_off } from './weapons/usp_silencer_off.svg'; +import { ReactComponent as world } from './weapons/world.svg'; +import { ReactComponent as xm1014 } from './weapons/xm1014.svg'; +export { + ak47, + aug, + awp, + bayonet, + bizon, + c4, + cz75a, + deagle, + decoy, + elite, + famas, + fiveseven, + flashbang, + g3sg1, + galilar, + glock, + hegrenade, + hkp2000, + incgrenade, + inferno, + knife, + knife_bayonet, + knife_butterfly, + knife_canis, + knife_cord, + knife_css, + knife_falchion, + knife_flip, + knife_gut, + knife_gypsy_jackknife, + knife_karambit, + knife_m9_bayonet, + knife_outdoor, + knife_push, + knife_skeleton, + knife_stiletto, + knife_survival_bowie, + knife_t, + knife_tactical, + knife_ursus, + knife_widowmaker, + m249, + m4a1, + m4a1_silencer, + m4a1_silencer_off, + mac10, + mag7, + molotov, + mp5sd, + mp7, + mp9, + negev, + nova, + out, + p250, + p90, + revolver, + sawedoff, + scar20, + sg556, + smokegrenade, + ssg08, + taser, + tec9, + trigger_hurt, + ump45, + usp_silencer, + usp_silencer_off, + world, + xm1014, +} \ No newline at end of file diff --git a/src/assets/bg.png b/src/assets/bg.png new file mode 100644 index 0000000..e01f481 Binary files /dev/null and b/src/assets/bg.png differ diff --git a/src/assets/flash_assist.png b/src/assets/flash_assist.png new file mode 100644 index 0000000..5314731 Binary files /dev/null and b/src/assets/flash_assist.png differ diff --git a/src/assets/flashed_kill.png b/src/assets/flashed_kill.png new file mode 100644 index 0000000..ecbc3fd Binary files /dev/null and b/src/assets/flashed_kill.png differ diff --git a/src/assets/headshot.png b/src/assets/headshot.png new file mode 100644 index 0000000..3f90d3c Binary files /dev/null and b/src/assets/headshot.png differ diff --git a/src/assets/images/bomb.svg b/src/assets/images/bomb.svg new file mode 100644 index 0000000..a1015f5 --- /dev/null +++ b/src/assets/images/bomb.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/flashed_kill.svg b/src/assets/images/flashed_kill.svg new file mode 100644 index 0000000..bb047a1 --- /dev/null +++ b/src/assets/images/flashed_kill.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/headshot.svg b/src/assets/images/headshot.svg new file mode 100644 index 0000000..d02d446 --- /dev/null +++ b/src/assets/images/headshot.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/icon_armor_full_default.svg b/src/assets/images/icon_armor_full_default.svg new file mode 100644 index 0000000..49baf9a --- /dev/null +++ b/src/assets/images/icon_armor_full_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_armor_half_default.svg b/src/assets/images/icon_armor_half_default.svg new file mode 100644 index 0000000..6797c4e --- /dev/null +++ b/src/assets/images/icon_armor_half_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_armor_half_helmet_default.svg b/src/assets/images/icon_armor_half_helmet_default.svg new file mode 100644 index 0000000..c1de3d0 --- /dev/null +++ b/src/assets/images/icon_armor_half_helmet_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_armor_helmet_default.svg b/src/assets/images/icon_armor_helmet_default.svg new file mode 100644 index 0000000..2b9370a --- /dev/null +++ b/src/assets/images/icon_armor_helmet_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_armor_none_default.svg b/src/assets/images/icon_armor_none_default.svg new file mode 100644 index 0000000..a9bd87f --- /dev/null +++ b/src/assets/images/icon_armor_none_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_blind.svg b/src/assets/images/icon_blind.svg new file mode 100644 index 0000000..55b6504 --- /dev/null +++ b/src/assets/images/icon_blind.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_bomb_default.svg b/src/assets/images/icon_bomb_default.svg new file mode 100644 index 0000000..e3d65f4 --- /dev/null +++ b/src/assets/images/icon_bomb_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_bomb_explosion_default.svg b/src/assets/images/icon_bomb_explosion_default.svg new file mode 100644 index 0000000..0b6ef6f --- /dev/null +++ b/src/assets/images/icon_bomb_explosion_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_bullets_default.svg b/src/assets/images/icon_bullets_default.svg new file mode 100644 index 0000000..ac64297 --- /dev/null +++ b/src/assets/images/icon_bullets_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_burning.svg b/src/assets/images/icon_burning.svg new file mode 100644 index 0000000..6cec928 --- /dev/null +++ b/src/assets/images/icon_burning.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_c4_default.svg b/src/assets/images/icon_c4_default.svg new file mode 100644 index 0000000..41c483b --- /dev/null +++ b/src/assets/images/icon_c4_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_defuse_default.svg b/src/assets/images/icon_defuse_default.svg new file mode 100644 index 0000000..559464a --- /dev/null +++ b/src/assets/images/icon_defuse_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_health_default.svg b/src/assets/images/icon_health_default.svg new file mode 100644 index 0000000..70b972b --- /dev/null +++ b/src/assets/images/icon_health_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_health_full_default.svg b/src/assets/images/icon_health_full_default.svg new file mode 100644 index 0000000..17b09be --- /dev/null +++ b/src/assets/images/icon_health_full_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_hourglass_default.svg b/src/assets/images/icon_hourglass_default.svg new file mode 100644 index 0000000..b75c4fc --- /dev/null +++ b/src/assets/images/icon_hourglass_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_microphone.svg b/src/assets/images/icon_microphone.svg new file mode 100644 index 0000000..6da5c55 --- /dev/null +++ b/src/assets/images/icon_microphone.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_pause_default.svg b/src/assets/images/icon_pause_default.svg new file mode 100644 index 0000000..a615edf --- /dev/null +++ b/src/assets/images/icon_pause_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_skull_default.svg b/src/assets/images/icon_skull_default.svg new file mode 100644 index 0000000..14598b4 --- /dev/null +++ b/src/assets/images/icon_skull_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icon_timer_default.svg b/src/assets/images/icon_timer_default.svg new file mode 100644 index 0000000..d43f4b7 --- /dev/null +++ b/src/assets/images/icon_timer_default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/images/logo_CT_default.png b/src/assets/images/logo_CT_default.png new file mode 100644 index 0000000..b0775b5 Binary files /dev/null and b/src/assets/images/logo_CT_default.png differ diff --git a/src/assets/images/logo_T_default.png b/src/assets/images/logo_T_default.png new file mode 100644 index 0000000..fbaacdc Binary files /dev/null and b/src/assets/images/logo_T_default.png differ diff --git a/src/assets/images/noscope.svg b/src/assets/images/noscope.svg new file mode 100644 index 0000000..1f7c3fd --- /dev/null +++ b/src/assets/images/noscope.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/smoke_kill.svg b/src/assets/images/smoke_kill.svg new file mode 100644 index 0000000..2cd9b90 --- /dev/null +++ b/src/assets/images/smoke_kill.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/suicide.svg b/src/assets/images/suicide.svg new file mode 100644 index 0000000..221dccf --- /dev/null +++ b/src/assets/images/suicide.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/wallbang.svg b/src/assets/images/wallbang.svg new file mode 100644 index 0000000..da98a12 --- /dev/null +++ b/src/assets/images/wallbang.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/noscope_kill.png b/src/assets/noscope_kill.png new file mode 100644 index 0000000..219f6aa Binary files /dev/null and b/src/assets/noscope_kill.png differ diff --git a/src/assets/smoke_kill.png b/src/assets/smoke_kill.png new file mode 100644 index 0000000..011a662 Binary files /dev/null and b/src/assets/smoke_kill.png differ diff --git a/src/assets/wallbang.png b/src/assets/wallbang.png new file mode 100644 index 0000000..b63740c Binary files /dev/null and b/src/assets/wallbang.png differ diff --git a/src/assets/weapons/ak47.svg b/src/assets/weapons/ak47.svg new file mode 100644 index 0000000..840fe3e --- /dev/null +++ b/src/assets/weapons/ak47.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/aug.svg b/src/assets/weapons/aug.svg new file mode 100644 index 0000000..4fe4f08 --- /dev/null +++ b/src/assets/weapons/aug.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/awp.svg b/src/assets/weapons/awp.svg new file mode 100644 index 0000000..8bbdeac --- /dev/null +++ b/src/assets/weapons/awp.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/bayonet.svg b/src/assets/weapons/bayonet.svg new file mode 100644 index 0000000..4d746e7 --- /dev/null +++ b/src/assets/weapons/bayonet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/bizon.svg b/src/assets/weapons/bizon.svg new file mode 100644 index 0000000..f8141a8 --- /dev/null +++ b/src/assets/weapons/bizon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/c4.svg b/src/assets/weapons/c4.svg new file mode 100644 index 0000000..18c9c93 --- /dev/null +++ b/src/assets/weapons/c4.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/cz75a.svg b/src/assets/weapons/cz75a.svg new file mode 100644 index 0000000..2c1727a --- /dev/null +++ b/src/assets/weapons/cz75a.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/deagle.svg b/src/assets/weapons/deagle.svg new file mode 100644 index 0000000..cbfbfd1 --- /dev/null +++ b/src/assets/weapons/deagle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/decoy.svg b/src/assets/weapons/decoy.svg new file mode 100644 index 0000000..e2c0c6b --- /dev/null +++ b/src/assets/weapons/decoy.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/elite.svg b/src/assets/weapons/elite.svg new file mode 100644 index 0000000..0424256 --- /dev/null +++ b/src/assets/weapons/elite.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/famas.svg b/src/assets/weapons/famas.svg new file mode 100644 index 0000000..831fcbb --- /dev/null +++ b/src/assets/weapons/famas.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/fiveseven.svg b/src/assets/weapons/fiveseven.svg new file mode 100644 index 0000000..45c0c20 --- /dev/null +++ b/src/assets/weapons/fiveseven.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/flashbang.svg b/src/assets/weapons/flashbang.svg new file mode 100644 index 0000000..5cb9d65 --- /dev/null +++ b/src/assets/weapons/flashbang.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/weapons/g3sg1.svg b/src/assets/weapons/g3sg1.svg new file mode 100644 index 0000000..537bc62 --- /dev/null +++ b/src/assets/weapons/g3sg1.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/galilar.svg b/src/assets/weapons/galilar.svg new file mode 100644 index 0000000..6fbd495 --- /dev/null +++ b/src/assets/weapons/galilar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/glock.svg b/src/assets/weapons/glock.svg new file mode 100644 index 0000000..ad9b29a --- /dev/null +++ b/src/assets/weapons/glock.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/hegrenade.svg b/src/assets/weapons/hegrenade.svg new file mode 100644 index 0000000..f4bc3ae --- /dev/null +++ b/src/assets/weapons/hegrenade.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/weapons/hkp2000.svg b/src/assets/weapons/hkp2000.svg new file mode 100644 index 0000000..260660b --- /dev/null +++ b/src/assets/weapons/hkp2000.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/incgrenade.svg b/src/assets/weapons/incgrenade.svg new file mode 100644 index 0000000..43e5d80 --- /dev/null +++ b/src/assets/weapons/incgrenade.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/inferno.svg b/src/assets/weapons/inferno.svg new file mode 100644 index 0000000..4992b74 --- /dev/null +++ b/src/assets/weapons/inferno.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife.svg b/src/assets/weapons/knife.svg new file mode 100644 index 0000000..3129f36 --- /dev/null +++ b/src/assets/weapons/knife.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_bayonet.svg b/src/assets/weapons/knife_bayonet.svg new file mode 100644 index 0000000..4d746e7 --- /dev/null +++ b/src/assets/weapons/knife_bayonet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_butterfly.svg b/src/assets/weapons/knife_butterfly.svg new file mode 100644 index 0000000..3657a53 --- /dev/null +++ b/src/assets/weapons/knife_butterfly.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_canis.svg b/src/assets/weapons/knife_canis.svg new file mode 100644 index 0000000..a347233 --- /dev/null +++ b/src/assets/weapons/knife_canis.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_cord.svg b/src/assets/weapons/knife_cord.svg new file mode 100644 index 0000000..7656eb7 --- /dev/null +++ b/src/assets/weapons/knife_cord.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_css.svg b/src/assets/weapons/knife_css.svg new file mode 100644 index 0000000..c0ab575 --- /dev/null +++ b/src/assets/weapons/knife_css.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_falchion.svg b/src/assets/weapons/knife_falchion.svg new file mode 100644 index 0000000..77d2222 --- /dev/null +++ b/src/assets/weapons/knife_falchion.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_flip.svg b/src/assets/weapons/knife_flip.svg new file mode 100644 index 0000000..7805419 --- /dev/null +++ b/src/assets/weapons/knife_flip.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_gut.svg b/src/assets/weapons/knife_gut.svg new file mode 100644 index 0000000..59ea74d --- /dev/null +++ b/src/assets/weapons/knife_gut.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_gypsy_jackknife.svg b/src/assets/weapons/knife_gypsy_jackknife.svg new file mode 100644 index 0000000..e6ae44a --- /dev/null +++ b/src/assets/weapons/knife_gypsy_jackknife.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_karambit.svg b/src/assets/weapons/knife_karambit.svg new file mode 100644 index 0000000..2d9b37a --- /dev/null +++ b/src/assets/weapons/knife_karambit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_m9_bayonet.svg b/src/assets/weapons/knife_m9_bayonet.svg new file mode 100644 index 0000000..524e56b --- /dev/null +++ b/src/assets/weapons/knife_m9_bayonet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_outdoor.svg b/src/assets/weapons/knife_outdoor.svg new file mode 100644 index 0000000..e5ff3f2 --- /dev/null +++ b/src/assets/weapons/knife_outdoor.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_push.svg b/src/assets/weapons/knife_push.svg new file mode 100644 index 0000000..d1f6c9b --- /dev/null +++ b/src/assets/weapons/knife_push.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_skeleton.svg b/src/assets/weapons/knife_skeleton.svg new file mode 100644 index 0000000..31e2076 --- /dev/null +++ b/src/assets/weapons/knife_skeleton.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_stiletto.svg b/src/assets/weapons/knife_stiletto.svg new file mode 100644 index 0000000..4ca01f7 --- /dev/null +++ b/src/assets/weapons/knife_stiletto.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_survival_bowie.svg b/src/assets/weapons/knife_survival_bowie.svg new file mode 100644 index 0000000..8abc713 --- /dev/null +++ b/src/assets/weapons/knife_survival_bowie.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_t.svg b/src/assets/weapons/knife_t.svg new file mode 100644 index 0000000..f284ac6 --- /dev/null +++ b/src/assets/weapons/knife_t.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_tactical.svg b/src/assets/weapons/knife_tactical.svg new file mode 100644 index 0000000..3474ad1 --- /dev/null +++ b/src/assets/weapons/knife_tactical.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_ursus.svg b/src/assets/weapons/knife_ursus.svg new file mode 100644 index 0000000..8810f74 --- /dev/null +++ b/src/assets/weapons/knife_ursus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/knife_widowmaker.svg b/src/assets/weapons/knife_widowmaker.svg new file mode 100644 index 0000000..63b58ce --- /dev/null +++ b/src/assets/weapons/knife_widowmaker.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/m249.svg b/src/assets/weapons/m249.svg new file mode 100644 index 0000000..3a896ce --- /dev/null +++ b/src/assets/weapons/m249.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/m4a1.svg b/src/assets/weapons/m4a1.svg new file mode 100644 index 0000000..2e65f85 --- /dev/null +++ b/src/assets/weapons/m4a1.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/m4a1_silencer.svg b/src/assets/weapons/m4a1_silencer.svg new file mode 100644 index 0000000..99c7c47 --- /dev/null +++ b/src/assets/weapons/m4a1_silencer.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/m4a1_silencer_off.svg b/src/assets/weapons/m4a1_silencer_off.svg new file mode 100644 index 0000000..99c7c47 --- /dev/null +++ b/src/assets/weapons/m4a1_silencer_off.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/mac10.svg b/src/assets/weapons/mac10.svg new file mode 100644 index 0000000..4dc0010 --- /dev/null +++ b/src/assets/weapons/mac10.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/mag7.svg b/src/assets/weapons/mag7.svg new file mode 100644 index 0000000..bb14624 --- /dev/null +++ b/src/assets/weapons/mag7.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/molotov.svg b/src/assets/weapons/molotov.svg new file mode 100644 index 0000000..4992b74 --- /dev/null +++ b/src/assets/weapons/molotov.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/mp5sd.svg b/src/assets/weapons/mp5sd.svg new file mode 100644 index 0000000..ee84c97 --- /dev/null +++ b/src/assets/weapons/mp5sd.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/mp7.svg b/src/assets/weapons/mp7.svg new file mode 100644 index 0000000..61007ae --- /dev/null +++ b/src/assets/weapons/mp7.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/mp9.svg b/src/assets/weapons/mp9.svg new file mode 100644 index 0000000..8867588 --- /dev/null +++ b/src/assets/weapons/mp9.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/negev.svg b/src/assets/weapons/negev.svg new file mode 100644 index 0000000..2ed063f --- /dev/null +++ b/src/assets/weapons/negev.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/nova.svg b/src/assets/weapons/nova.svg new file mode 100644 index 0000000..33e2027 --- /dev/null +++ b/src/assets/weapons/nova.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/out.svg b/src/assets/weapons/out.svg new file mode 100644 index 0000000..1b751ad --- /dev/null +++ b/src/assets/weapons/out.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/p250.svg b/src/assets/weapons/p250.svg new file mode 100644 index 0000000..a1b800e --- /dev/null +++ b/src/assets/weapons/p250.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/p90.svg b/src/assets/weapons/p90.svg new file mode 100644 index 0000000..df4f762 --- /dev/null +++ b/src/assets/weapons/p90.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/revolver.svg b/src/assets/weapons/revolver.svg new file mode 100644 index 0000000..da0001c --- /dev/null +++ b/src/assets/weapons/revolver.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/sawedoff.svg b/src/assets/weapons/sawedoff.svg new file mode 100644 index 0000000..b231ad8 --- /dev/null +++ b/src/assets/weapons/sawedoff.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/scar20.svg b/src/assets/weapons/scar20.svg new file mode 100644 index 0000000..729318c --- /dev/null +++ b/src/assets/weapons/scar20.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/sg556.svg b/src/assets/weapons/sg556.svg new file mode 100644 index 0000000..1661e5a --- /dev/null +++ b/src/assets/weapons/sg556.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/smokegrenade.svg b/src/assets/weapons/smokegrenade.svg new file mode 100644 index 0000000..9d3cf58 --- /dev/null +++ b/src/assets/weapons/smokegrenade.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/weapons/ssg08.svg b/src/assets/weapons/ssg08.svg new file mode 100644 index 0000000..aae9fcb --- /dev/null +++ b/src/assets/weapons/ssg08.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/taser.svg b/src/assets/weapons/taser.svg new file mode 100644 index 0000000..895fcf3 --- /dev/null +++ b/src/assets/weapons/taser.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/tec9.svg b/src/assets/weapons/tec9.svg new file mode 100644 index 0000000..00a5fe4 --- /dev/null +++ b/src/assets/weapons/tec9.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/trigger_hurt.svg b/src/assets/weapons/trigger_hurt.svg new file mode 100644 index 0000000..dd76282 --- /dev/null +++ b/src/assets/weapons/trigger_hurt.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/ump45.svg b/src/assets/weapons/ump45.svg new file mode 100644 index 0000000..0ea443a --- /dev/null +++ b/src/assets/weapons/ump45.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/usp_silencer.svg b/src/assets/weapons/usp_silencer.svg new file mode 100644 index 0000000..2e8c8cf --- /dev/null +++ b/src/assets/weapons/usp_silencer.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/usp_silencer_off.svg b/src/assets/weapons/usp_silencer_off.svg new file mode 100644 index 0000000..2e8c8cf --- /dev/null +++ b/src/assets/weapons/usp_silencer_off.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/world.svg b/src/assets/weapons/world.svg new file mode 100644 index 0000000..dd76282 --- /dev/null +++ b/src/assets/weapons/world.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/weapons/xm1014.svg b/src/assets/weapons/xm1014.svg new file mode 100644 index 0000000..1d8aaa5 --- /dev/null +++ b/src/assets/weapons/xm1014.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/fonts/Louis George Cafe.ttf b/src/fonts/Louis George Cafe.ttf new file mode 100644 index 0000000..e31d5fd Binary files /dev/null and b/src/fonts/Louis George Cafe.ttf differ diff --git a/src/fonts/Rounded_Elegance.ttf b/src/fonts/Rounded_Elegance.ttf new file mode 100644 index 0000000..056725b Binary files /dev/null and b/src/fonts/Rounded_Elegance.ttf differ diff --git a/src/fonts/Stratum2/Stratum2 Bold Regular.ttf b/src/fonts/Stratum2/Stratum2 Bold Regular.ttf new file mode 100644 index 0000000..e317d2c Binary files /dev/null and b/src/fonts/Stratum2/Stratum2 Bold Regular.ttf differ diff --git a/src/fonts/Stratum2/Stratum2 Bold Regular.woff b/src/fonts/Stratum2/Stratum2 Bold Regular.woff new file mode 100644 index 0000000..3bb4e06 Binary files /dev/null and b/src/fonts/Stratum2/Stratum2 Bold Regular.woff differ diff --git a/src/fonts/montserrat.css b/src/fonts/montserrat.css new file mode 100644 index 0000000..6511bbd --- /dev/null +++ b/src/fonts/montserrat.css @@ -0,0 +1,90 @@ + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 100; + src: url('./montserrat/Montserrat-Thin.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 200; + src: url('./montserrat/Montserrat-ExtraLight.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 300; + src: url('./montserrat/Montserrat-Light.ttf') format('truetype'); +} + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 400; + src: url('./montserrat/Montserrat-Regular.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 500; + src: url('./montserrat/Montserrat-Medium.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 600; + src: url('./montserrat/Montserrat-SemiBold.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 700; + src: url('./montserrat/Montserrat-Bold.ttf') format('truetype'); +} + +/* ITALIC */ + +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 100; + src: url('./montserrat/Montserrat-ThinItalic.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 200; + src: url('./montserrat/Montserrat-ExtraLightItalic.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 300; + src: url('./montserrat/Montserrat-LightItalic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 400; + src: url('./montserrat/Montserrat-MediumItalic.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 500; + src: url('./montserrat/Montserrat-MediumItalic.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 600; + src: url('./montserrat/Montserrat-SemiBoldItalic.ttf') format('truetype'); +} +@font-face { + font-family: 'Montserrat'; + font-style: italic; + font-weight: 700; + src: url('./montserrat/Montserrat-BoldItalic.ttf') format('truetype'); +} \ No newline at end of file diff --git a/src/fonts/montserrat/Montserrat-Black.ttf b/src/fonts/montserrat/Montserrat-Black.ttf new file mode 100644 index 0000000..437b115 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-Black.ttf differ diff --git a/src/fonts/montserrat/Montserrat-BlackItalic.ttf b/src/fonts/montserrat/Montserrat-BlackItalic.ttf new file mode 100644 index 0000000..5234835 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-BlackItalic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-Bold.ttf b/src/fonts/montserrat/Montserrat-Bold.ttf new file mode 100644 index 0000000..221819b Binary files /dev/null and b/src/fonts/montserrat/Montserrat-Bold.ttf differ diff --git a/src/fonts/montserrat/Montserrat-BoldItalic.ttf b/src/fonts/montserrat/Montserrat-BoldItalic.ttf new file mode 100644 index 0000000..9ae2bd2 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-BoldItalic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-ExtraBold.ttf b/src/fonts/montserrat/Montserrat-ExtraBold.ttf new file mode 100644 index 0000000..80ea806 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-ExtraBold.ttf differ diff --git a/src/fonts/montserrat/Montserrat-ExtraBoldItalic.ttf b/src/fonts/montserrat/Montserrat-ExtraBoldItalic.ttf new file mode 100644 index 0000000..6c961e1 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-ExtraBoldItalic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-ExtraLight.ttf b/src/fonts/montserrat/Montserrat-ExtraLight.ttf new file mode 100644 index 0000000..ca0bbb6 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-ExtraLight.ttf differ diff --git a/src/fonts/montserrat/Montserrat-ExtraLightItalic.ttf b/src/fonts/montserrat/Montserrat-ExtraLightItalic.ttf new file mode 100644 index 0000000..f3c1559 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-ExtraLightItalic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-Italic.ttf b/src/fonts/montserrat/Montserrat-Italic.ttf new file mode 100644 index 0000000..eb4232a Binary files /dev/null and b/src/fonts/montserrat/Montserrat-Italic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-Light.ttf b/src/fonts/montserrat/Montserrat-Light.ttf new file mode 100644 index 0000000..990857d Binary files /dev/null and b/src/fonts/montserrat/Montserrat-Light.ttf differ diff --git a/src/fonts/montserrat/Montserrat-LightItalic.ttf b/src/fonts/montserrat/Montserrat-LightItalic.ttf new file mode 100644 index 0000000..2096040 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-LightItalic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-Medium.ttf b/src/fonts/montserrat/Montserrat-Medium.ttf new file mode 100644 index 0000000..6e079f6 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-Medium.ttf differ diff --git a/src/fonts/montserrat/Montserrat-MediumItalic.ttf b/src/fonts/montserrat/Montserrat-MediumItalic.ttf new file mode 100644 index 0000000..0dc3ac9 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-MediumItalic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-Regular.ttf b/src/fonts/montserrat/Montserrat-Regular.ttf new file mode 100644 index 0000000..8d443d5 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-Regular.ttf differ diff --git a/src/fonts/montserrat/Montserrat-SemiBold.ttf b/src/fonts/montserrat/Montserrat-SemiBold.ttf new file mode 100644 index 0000000..f8a43f2 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-SemiBold.ttf differ diff --git a/src/fonts/montserrat/Montserrat-SemiBoldItalic.ttf b/src/fonts/montserrat/Montserrat-SemiBoldItalic.ttf new file mode 100644 index 0000000..336c56e Binary files /dev/null and b/src/fonts/montserrat/Montserrat-SemiBoldItalic.ttf differ diff --git a/src/fonts/montserrat/Montserrat-Thin.ttf b/src/fonts/montserrat/Montserrat-Thin.ttf new file mode 100644 index 0000000..b985875 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-Thin.ttf differ diff --git a/src/fonts/montserrat/Montserrat-ThinItalic.ttf b/src/fonts/montserrat/Montserrat-ThinItalic.ttf new file mode 100644 index 0000000..e488998 Binary files /dev/null and b/src/fonts/montserrat/Montserrat-ThinItalic.ttf differ diff --git a/src/fonts/stratum2.css b/src/fonts/stratum2.css new file mode 100644 index 0000000..4e9249e --- /dev/null +++ b/src/fonts/stratum2.css @@ -0,0 +1,6 @@ + +@font-face { + font-family: 'Stratum2'; + font-style: normal; + src: url('./Stratum2/Stratum2 Bold Regular.ttf') format('truetype'); +} \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..cef3b29 --- /dev/null +++ b/src/index.css @@ -0,0 +1,57 @@ +@import "fonts/montserrat.css"; +@import "fonts/stratum2.css"; +html, +body, +#root { + width: 100%; + height: 100%; +} + +body { + margin: 0; + font-family: 'Stratum2', 'Montserrat', 'Roboto', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /*background-image: url('./assets/bg.png');/**/ +} + +@font-face { + font-family: 'Louis George Cafe'; + src: local('Louis George Cafe'), url('./fonts/Louis George Cafe.ttf') format('truetype'); +} + +@font-face { + font-family: 'Rounded_Elegance'; + src: local('Rounded_Elegance'), url('./fonts/Rounded_Elegance.ttf') format('truetype'); +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +.layout { + width: 100%; + height: 100%; + perspective: 500px; +} + +:root { + --main-panel-color: rgba(12, 15, 18, 0.85); + --sub-panel-color: rgba(0, 0, 0, 0.83); + --white-dull: rgba(10, 5, 5, 0.25); + --white-full: rgba(250, 250, 250, 1); + --white-half: rgba(250, 250, 250, 0.5); + --color-gray: rgba(191, 191, 191, 1.0); + --color-moneys: #a7d32e; + --dev-purple: rgba(200, 0, 255, 1); + --color-t: #c19511; + --color-ct: #5788a8; + --color-new-t: #f0c941; + --color-new-ct: #5ab8f4; + --color-bomb: #f22222; + --color-defuse: #2222f2; +} + + + diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..919d652 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import "./index.css"; +import "./fonts/Louis George Cafe.ttf"; +import "./fonts/Rounded_Elegance.ttf"; +import App from "./App"; +declare global { + interface Window { + ipcApi: { + send: (channel: string, ...arg: any) => void; + receive: (channel: string, func: (...arg: any) => void) => void; + }; + } +} + +ReactDOM.render(, document.getElementById("root")); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d0c548f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "noFallthroughCasesInSwitch": true, + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": [ + "src" + ] +}