├── clientside ├── index.js ├── src │ ├── core │ │ ├── index.ts │ │ ├── Application.ts │ │ └── ErrorHandler.ts │ ├── declarations │ │ ├── shared.d.ts │ │ ├── types.d.ts │ │ ├── ragemp.d.ts │ │ ├── enums.d.ts │ │ └── interfaces.d.ts │ ├── utils │ │ ├── index.ts │ │ ├── Tickable.ts │ │ ├── print.ts │ │ └── decorators.ts │ ├── errors │ │ ├── LogErrors.ts │ │ └── PlayerErrors.ts │ ├── app.ts │ ├── bootstrap.ts │ ├── entities │ │ ├── Dummy.ts │ │ ├── Zone.ts │ │ └── Route.ts │ ├── hud │ │ ├── Gamemode.ts │ │ ├── Controls.ts │ │ ├── effects │ │ │ ├── Death.ts │ │ │ └── RoundStop.ts │ │ ├── Position.ts │ │ ├── Hud.ts │ │ ├── SpectateViewers.ts │ │ ├── SpectateCurrent.ts │ │ └── TeamSelecting.ts │ └── managers │ │ ├── index.ts │ │ ├── dummies │ │ ├── DummyRoundStatManager.ts │ │ └── DummyLanguageManager.ts │ │ ├── TeamManager.ts │ │ ├── GameMapManager.ts │ │ ├── ZoneManager.ts │ │ ├── EventManager.ts │ │ ├── WeaponManager.ts │ │ ├── DialogManager.ts │ │ └── RpcManager.ts ├── dist │ └── .gitignore ├── webpack.prod.js ├── webpack.dev.js ├── webpack.common.js ├── package.json └── tsconfig.json ├── serverside ├── src │ ├── entities │ │ ├── GameMap.ts │ │ ├── validators │ │ │ ├── SharedDataValidator.ts │ │ │ ├── PlayerDataValidator.ts │ │ │ ├── MapEditorDataValidator.ts │ │ │ └── Validator.ts │ │ ├── SharedData.ts │ │ └── Dummy.ts │ ├── declarations │ │ ├── shared.d.ts │ │ ├── override.ts │ │ ├── ragemp.d.ts │ │ ├── types.d.ts │ │ └── interfaces.d.ts │ ├── app.ts │ ├── utils │ │ ├── index.ts │ │ └── decorators.ts │ ├── errors │ │ ├── LogErrors.ts │ │ └── PlayerErrors.ts │ ├── db │ │ ├── entity │ │ │ └── DomainConverter.ts │ │ └── repos │ │ │ ├── ProfileRepository.ts │ │ │ └── RoundRepository.ts │ ├── bootstrap.ts │ ├── validators │ │ ├── operationFactory.ts │ │ └── validatorFactory.ts │ ├── managers │ │ ├── DbManager.ts │ │ ├── CommonManager.ts │ │ ├── dummies │ │ │ ├── DummyRoundStatManager.ts │ │ │ ├── DummyConfigManager.ts │ │ │ └── DummyLanguageManager.ts │ │ ├── BotManager.ts │ │ └── WeaponManager.ts │ └── core │ │ ├── ErrorHandler.ts │ │ └── Config.ts ├── dist │ └── .gitignore ├── index.js ├── assets │ └── maps.json ├── tsconfig.json └── package.json ├── cef ├── public │ ├── robots.txt │ ├── assets │ │ └── weapons │ │ │ ├── mg.webp │ │ │ ├── bat.webp │ │ │ ├── smg.webp │ │ │ ├── bottle.webp │ │ │ ├── crowbar.webp │ │ │ ├── dagger.webp │ │ │ ├── hammer.webp │ │ │ ├── hatchet.webp │ │ │ ├── knife.webp │ │ │ ├── knuckle.webp │ │ │ ├── machete.webp │ │ │ ├── minismg.webp │ │ │ ├── musket.webp │ │ │ ├── pistol.webp │ │ │ ├── poolcue.webp │ │ │ ├── skull.webp │ │ │ ├── smg-mk2.webp │ │ │ ├── stungun.webp │ │ │ ├── unarmed.webp │ │ │ ├── wrench.webp │ │ │ ├── battleaxe.webp │ │ │ ├── combatmg.webp │ │ │ ├── combatpdw.webp │ │ │ ├── dbshotgun.webp │ │ │ ├── flaregun.webp │ │ │ ├── golfclub.webp │ │ │ ├── gusenberg.webp │ │ │ ├── microsmg.webp │ │ │ ├── pistol50.webp │ │ │ ├── revolver.webp │ │ │ ├── snspistol.webp │ │ │ ├── assaultrifle.webp │ │ │ ├── assaultsmg.webp │ │ │ ├── autoshotgun.webp │ │ │ ├── bullpuprifle.webp │ │ │ ├── carbinerifle.webp │ │ │ ├── combatmg-mk2.webp │ │ │ ├── combatpistol.webp │ │ │ ├── compactrifle.webp │ │ │ ├── doubleaction.webp │ │ │ ├── flashlight.webp │ │ │ ├── heavypistol.webp │ │ │ ├── heavyshotgun.webp │ │ │ ├── heavysniper.webp │ │ │ ├── nightstick.webp │ │ │ ├── pistol-mk2.webp │ │ │ ├── pumpshotgun.webp │ │ │ ├── raycarbine.webp │ │ │ ├── revolver_mk2.webp │ │ │ ├── sniperrifle.webp │ │ │ ├── switchblade.webp │ │ │ ├── advancedrifle.webp │ │ │ ├── assaultshotgun.webp │ │ │ ├── bullpupshotgun.webp │ │ │ ├── machinepistol.webp │ │ │ ├── marksmanpistol.webp │ │ │ ├── marksmanrifle.webp │ │ │ ├── sawnoffshotgun.webp │ │ │ ├── snspistol-mk2.webp │ │ │ ├── specialcarbine.webp │ │ │ ├── stone_hatchet.webp │ │ │ ├── vintagepistol.webp │ │ │ ├── assaultrifle-mk2.webp │ │ │ ├── bullpuprifle-mk2.webp │ │ │ ├── carbinerifle-mk2.webp │ │ │ ├── heavysniper-mk2.webp │ │ │ ├── marksmanrifle-mk2.webp │ │ │ ├── pumpshotgun-mk2.webp │ │ │ └── specialcarbine-mk2.webp │ ├── manifest.json │ └── index.html ├── src │ ├── setupTests.ts │ ├── Theme │ │ └── Dark │ │ │ ├── TabComponents.tsx │ │ │ ├── ButtonComponents.tsx │ │ │ ├── TableComponents.tsx │ │ │ ├── DialogComponents.tsx │ │ │ └── AccordionComponents.tsx │ ├── App │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ └── App.css │ ├── Effects │ │ ├── Effects.tsx │ │ ├── ExpEffect.tsx │ │ ├── DeathEffect.tsx │ │ └── WinEffect.tsx │ ├── GameMenu │ │ ├── Vote │ │ │ ├── VoteWrapper.tsx │ │ │ ├── VoteCard.tsx │ │ │ └── Vote.tsx │ │ ├── Credits │ │ │ ├── CreditsWrapper.tsx │ │ │ ├── Credits.tsx │ │ │ ├── Changes040.tsx │ │ │ ├── Changes020.tsx │ │ │ ├── Changes030.tsx │ │ │ └── Changelog.tsx │ │ ├── Players │ │ │ ├── PlayersWrapper.tsx │ │ │ ├── TopPlayersWrapper.tsx │ │ │ ├── PlayerDetail.tsx │ │ │ └── Players.tsx │ │ ├── Profile │ │ │ ├── ProfileWrapper.tsx │ │ │ └── Profile.tsx │ │ ├── History │ │ │ ├── HistoryWrapper.tsx │ │ │ ├── HistoryDetailWrapper.tsx │ │ │ └── History.tsx │ │ ├── TabPanel.tsx │ │ ├── Reducer.ts │ │ └── Actions.ts │ ├── react-app-env.d.ts │ ├── index.tsx │ ├── Common │ │ ├── RefreshButton.tsx │ │ ├── MainControls.tsx │ │ └── Controls.tsx │ ├── InfoPanel │ │ ├── DirectionsRunIconCustom.tsx │ │ └── InfoPanelWrapper.tsx │ ├── WeaponDialog │ │ ├── WeaponList.tsx │ │ └── WeaponImgChoose.tsx │ ├── Scoreboard │ │ └── ScoreboardData.tsx │ ├── Notify │ │ └── NotifyNotistack.tsx │ ├── MapEditor │ │ ├── Header.tsx │ │ ├── PointList.tsx │ │ ├── SpawnVectorList.tsx │ │ ├── Body.tsx │ │ └── MapEditor.tsx │ ├── lib │ │ ├── Language.ts │ │ └── utils.ts │ ├── Spectate │ │ ├── SpectateViewers.tsx │ │ ├── SpectateCurrent.tsx │ │ └── Spectate.tsx │ ├── Deathlog │ │ ├── Deathlog.tsx │ │ └── DeathlogList.tsx │ └── events.ts ├── tsconfig.json ├── package.json └── README.md ├── .gitignore ├── LICENSE ├── package.json └── README.md /clientside/index.js: -------------------------------------------------------------------------------- 1 | require('./league/bundle') -------------------------------------------------------------------------------- /clientside/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Application' -------------------------------------------------------------------------------- /serverside/src/entities/GameMap.ts: -------------------------------------------------------------------------------- 1 | export type GameMap = SHARED.TYPES.GameMap 2 | -------------------------------------------------------------------------------- /clientside/src/declarations/shared.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /serverside/src/declarations/shared.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /cef/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /clientside/dist/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /serverside/dist/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /serverside/src/app.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | import { app } from './bootstrap' 3 | 4 | app.start() -------------------------------------------------------------------------------- /clientside/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vector2' 2 | export * from './print' 3 | export * from './functions' -------------------------------------------------------------------------------- /cef/public/assets/weapons/mg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/mg.webp -------------------------------------------------------------------------------- /serverside/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | require('./dist/app') 3 | } catch (err) { 4 | console.log('something has gone wrong', err) 5 | } -------------------------------------------------------------------------------- /cef/public/assets/weapons/bat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/bat.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/smg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/smg.webp -------------------------------------------------------------------------------- /clientside/src/errors/LogErrors.ts: -------------------------------------------------------------------------------- 1 | export class ConsoleError extends Error {} 2 | export class IsNotExistsError extends ConsoleError {} -------------------------------------------------------------------------------- /cef/public/assets/weapons/bottle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/bottle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/crowbar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/crowbar.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/dagger.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/dagger.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/hammer.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/hammer.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/hatchet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/hatchet.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/knife.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/knife.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/knuckle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/knuckle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/machete.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/machete.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/minismg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/minismg.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/musket.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/musket.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/pistol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/pistol.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/poolcue.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/poolcue.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/skull.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/skull.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/smg-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/smg-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/stungun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/stungun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/unarmed.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/unarmed.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/wrench.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/wrench.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/battleaxe.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/battleaxe.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/combatmg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/combatmg.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/combatpdw.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/combatpdw.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/dbshotgun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/dbshotgun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/flaregun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/flaregun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/golfclub.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/golfclub.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/gusenberg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/gusenberg.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/microsmg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/microsmg.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/pistol50.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/pistol50.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/revolver.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/revolver.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/snspistol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/snspistol.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/assaultrifle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/assaultrifle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/assaultsmg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/assaultsmg.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/autoshotgun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/autoshotgun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/bullpuprifle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/bullpuprifle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/carbinerifle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/carbinerifle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/combatmg-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/combatmg-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/combatpistol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/combatpistol.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/compactrifle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/compactrifle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/doubleaction.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/doubleaction.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/flashlight.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/flashlight.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/heavypistol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/heavypistol.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/heavyshotgun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/heavyshotgun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/heavysniper.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/heavysniper.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/nightstick.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/nightstick.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/pistol-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/pistol-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/pumpshotgun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/pumpshotgun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/raycarbine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/raycarbine.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/revolver_mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/revolver_mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/sniperrifle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/sniperrifle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/switchblade.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/switchblade.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/advancedrifle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/advancedrifle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/assaultshotgun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/assaultshotgun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/bullpupshotgun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/bullpupshotgun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/machinepistol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/machinepistol.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/marksmanpistol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/marksmanpistol.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/marksmanrifle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/marksmanrifle.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/sawnoffshotgun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/sawnoffshotgun.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/snspistol-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/snspistol-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/specialcarbine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/specialcarbine.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/stone_hatchet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/stone_hatchet.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/vintagepistol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/vintagepistol.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/assaultrifle-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/assaultrifle-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/bullpuprifle-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/bullpuprifle-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/carbinerifle-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/carbinerifle-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/heavysniper-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/heavysniper-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/marksmanrifle-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/marksmanrifle-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/pumpshotgun-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/pumpshotgun-mk2.webp -------------------------------------------------------------------------------- /cef/public/assets/weapons/specialcarbine-mk2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/READYTOMASSACRE/league-gamemode/HEAD/cef/public/assets/weapons/specialcarbine-mk2.webp -------------------------------------------------------------------------------- /clientside/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const common = require('./webpack.common') 3 | 4 | module.exports = merge(common, { 5 | mode: 'production', 6 | }) -------------------------------------------------------------------------------- /serverside/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators' 2 | export * from './functions' 3 | export * from './vector2' 4 | export const cmdName = '{{cmdName}}' 5 | export const groupname = '{{groupName}}' -------------------------------------------------------------------------------- /clientside/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const common = require('./webpack.common') 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'inline-source-map', 7 | }) -------------------------------------------------------------------------------- /clientside/src/app.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | import { print } from './utils' 3 | import { app } from './bootstrap' 4 | 5 | try { 6 | print.reset() 7 | app.start() 8 | } catch (err) { 9 | print.error(err.stack) 10 | } -------------------------------------------------------------------------------- /cef/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [], 5 | "start_url": ".", 6 | "display": "standalone", 7 | "theme_color": "#000000", 8 | "background_color": "#ffffff" 9 | } 10 | -------------------------------------------------------------------------------- /cef/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /cef/src/Theme/Dark/TabComponents.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@material-ui/core/styles'; 2 | import Tabs from '@material-ui/core/Tabs' 3 | 4 | export const GameMenuTabs = withStyles({ 5 | root: { 6 | backgroundColor: 'black', 7 | }, 8 | indicator: { 9 | backgroundColor: 'white', 10 | } 11 | })(Tabs) -------------------------------------------------------------------------------- /clientside/src/errors/PlayerErrors.ts: -------------------------------------------------------------------------------- 1 | export class PlayerNotifyError extends Error { 2 | public readonly args: string[] 3 | constructor(message?: string, ...args: string[]) { 4 | super(message) 5 | 6 | this.args = args 7 | } 8 | } 9 | 10 | export class NotFoundNotifyError extends PlayerNotifyError {} -------------------------------------------------------------------------------- /cef/src/App/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /cef/src/Effects/Effects.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // import ExpEffect from './ExpEffect' 3 | import WinEffect from './WinEffect' 4 | import DeathEffect from './DeathEffect' 5 | 6 | export default function Effects() { 7 | return ( 8 | 9 | 10 | 11 | {/* */} 12 | 13 | ) 14 | } -------------------------------------------------------------------------------- /cef/src/Theme/Dark/ButtonComponents.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@material-ui/core/styles' 2 | import Button from '@material-ui/core/Button' 3 | 4 | export const StyledButton = withStyles({ 5 | root: { 6 | color: 'white', 7 | borderColor: 'white', 8 | '&:disabled': { 9 | color: '#353535', 10 | borderColor: '#353535', 11 | } 12 | }, 13 | })(Button) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *build 3 | *node_modules 4 | *yarn-error.log 5 | *npm-debug.log* 6 | *yarn-debug.log* 7 | *yarn-error.log* 8 | 9 | !/**/.gitkeep 10 | 11 | .DS_Store 12 | 13 | # server-side 14 | serverside/assets/savepositions.json 15 | 16 | # cef 17 | cef/.pnp 18 | cef/.env.local 19 | cef/.env.development.local 20 | cef/.env.test.local 21 | cef/.env.production.local 22 | .pnp.js 23 | # cef testing 24 | /cef/coverage 25 | -------------------------------------------------------------------------------- /cef/src/GameMenu/Vote/VoteWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Vote from './Vote' 3 | import { GAMEMENU, GameMenuContext } from '../Reducer' 4 | 5 | /** 6 | * Wrapper of vote component to handle with state from context 7 | */ 8 | export default function VoteWrapper() { 9 | const { state: { 10 | [GAMEMENU.VOTE]: props 11 | } } = React.useContext(GameMenuContext) 12 | 13 | return 14 | } -------------------------------------------------------------------------------- /clientside/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { Application } from './core' 2 | import { container } from 'tsyringe' 3 | import { ErrorHandler } from './core/ErrorHandler' 4 | 5 | /** @todo take then result from config */ 6 | export const DEBUG = true 7 | 8 | // resolve the errHandler 9 | export const errorHandler: ErrorHandler = container.resolve(ErrorHandler) 10 | 11 | // make an app 12 | export const app: Application = new Application() -------------------------------------------------------------------------------- /cef/src/GameMenu/Credits/CreditsWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { GAMEMENU, GameMenuContext } from '../Reducer' 3 | import Credits from './Credits' 4 | 5 | /** 6 | * Wrapper of players component to handle with state from context 7 | */ 8 | export default function CreditsWrapper() { 9 | const { state: { 10 | [GAMEMENU.CREDITS]: props 11 | } } = React.useContext(GameMenuContext) 12 | 13 | return 14 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/Players/PlayersWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Players from './Players' 3 | import { GAMEMENU, GameMenuContext } from '../Reducer' 4 | 5 | /** 6 | * Wrapper of players component to handle with state from context 7 | */ 8 | export default function PlayersWrapper() { 9 | const { state: { 10 | [GAMEMENU.PLAYERS]: props 11 | } } = React.useContext(GameMenuContext) 12 | 13 | return 14 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/Profile/ProfileWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Profile from './Profile' 3 | import { GAMEMENU, GameMenuContext } from '../Reducer' 4 | 5 | /** 6 | * Wrapper of profile component to handle with state from context 7 | */ 8 | export default function ProfileWrapper() { 9 | const { state: { 10 | [GAMEMENU.PROFILE]: props 11 | } } = React.useContext(GameMenuContext) 12 | 13 | return 14 | } -------------------------------------------------------------------------------- /serverside/src/entities/validators/SharedDataValidator.ts: -------------------------------------------------------------------------------- 1 | import { Validator } from './Validator' 2 | 3 | /** 4 | * @inheritdoc 5 | */ 6 | class SharedDataValidator extends Validator> { 7 | /** 8 | * @inheritdoc 9 | */ 10 | protected validators: KeyValueCollection = { 11 | lang: "string", 12 | state: "number", 13 | teamId: "string", 14 | spectate: "number", 15 | } 16 | } 17 | 18 | export { SharedDataValidator } -------------------------------------------------------------------------------- /serverside/assets/maps.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 0, 4 | "code": "zero_arena", 5 | "area": [[-1389.8885498046875, 165.0953826904297], [-1386.8018798828125, 7.266519546508789], 6 | [-1299.7457275390625, 16.754438400268555], [-1309.6605224609375, 174.69664001464844]], 7 | "spawnPoints": { 8 | "ATTACKERS": [[-1367.40576171875, 150.63015747070312, 55.960227966308594]], 9 | "DEFENDERS": [[-1350.4813232421875, 17.191625595092773, 53.26210403442383]] 10 | } 11 | } 12 | ] -------------------------------------------------------------------------------- /serverside/src/errors/LogErrors.ts: -------------------------------------------------------------------------------- 1 | export class LogError extends Error { 2 | constructor(message?: string, public readonly colorFunc?: Function) { 3 | super(message) 4 | } 5 | } 6 | 7 | export class ConsoleError extends LogError {} 8 | export class ServerError extends ConsoleError {} 9 | export class NullError extends ConsoleError {} 10 | export class IsNotExistsError extends ConsoleError {} 11 | export class InvalidArgument extends ConsoleError {} 12 | export class InvalidTypeError extends ConsoleError {} -------------------------------------------------------------------------------- /cef/src/Theme/Dark/TableComponents.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@material-ui/core/styles' 2 | import TableRow from '@material-ui/core/TableRow' 3 | import TableCell from '@material-ui/core/TableCell' 4 | 5 | export const StyledTableCell = withStyles({ 6 | root: { 7 | borderBottom: 'none', 8 | }, 9 | })(TableCell) 10 | 11 | export const StyledTableRow = withStyles({ 12 | root: { 13 | '&:nth-of-type(odd)': { 14 | backgroundColor: 'rgb(101 101 101 / 54%)', 15 | }, 16 | } 17 | })(TableRow) -------------------------------------------------------------------------------- /serverside/src/entities/validators/PlayerDataValidator.ts: -------------------------------------------------------------------------------- 1 | import { Validator } from './Validator' 2 | 3 | type PlayerEditableData = Pick 4 | 5 | /** 6 | * @inheritdoc 7 | */ 8 | class PlayerDataValidator extends Validator { 9 | /** 10 | * @inheritdoc 11 | */ 12 | protected validators: KeyValueCollection = { 13 | model: "number", 14 | position: "vector", 15 | dimension: "number", 16 | health: "number", 17 | } 18 | } 19 | 20 | export { PlayerDataValidator } -------------------------------------------------------------------------------- /clientside/src/entities/Dummy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Dummy entity which synchronized with server-side 3 | */ 4 | class Dummy { 5 | readonly dummy: DummyEntity 6 | readonly type: T 7 | 8 | public data: SHARED.TYPES.DummyTypes[T] 9 | 10 | constructor(type: T, dummyEntity: DummyEntity) { 11 | this.type = type 12 | this.dummy = dummyEntity 13 | 14 | this.data = new Proxy({} as SHARED.TYPES.DummyTypes[T], { 15 | get: (_, key) => this.dummy.getVariable(key.toString()) 16 | }) 17 | } 18 | } 19 | 20 | export { Dummy } -------------------------------------------------------------------------------- /cef/src/Theme/Dark/DialogComponents.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@material-ui/core/styles' 2 | import Dialog from '@material-ui/core/Dialog' 3 | import DialogContent from '@material-ui/core/DialogContent' 4 | 5 | export const StyledDialogComponent = withStyles({ 6 | paper: { 7 | color: 'white', 8 | backgroundColor: 'rgb(0 0 0 / 75%)', 9 | } 10 | })(Dialog) 11 | 12 | export const StyledDialogContent = withStyles({ 13 | dividers: { 14 | borderTop: '1px solid rgb(255 255 255 / 43%)', 15 | borderBottom: '1px solid rgb(255 255 255 / 43%)', 16 | } 17 | })(DialogContent) -------------------------------------------------------------------------------- /cef/src/GameMenu/History/HistoryWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import History from './History' 3 | import { GAMEMENU, GameMenuContext } from '../Reducer' 4 | 5 | interface HistoryWrapperProps { 6 | onRefresh?: (...args: any[]) => void 7 | } 8 | /** 9 | * Wrapper of history component to handle with state from context 10 | */ 11 | export default function HistoryWrapper({ onRefresh }: HistoryWrapperProps) { 12 | const { state: { 13 | [GAMEMENU.HISTORY]: props 14 | } } = React.useContext(GameMenuContext) 15 | 16 | return 17 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/Players/TopPlayersWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Players from './Players' 3 | import { GAMEMENU, GameMenuContext } from '../Reducer' 4 | 5 | interface TopPlayersWrapperProps { 6 | onRefresh?: (...args: any[]) => void 7 | } 8 | 9 | /** 10 | * Wrapper of players component to handle with state from context 11 | */ 12 | export default function TopPlayersWrapper({ onRefresh }: TopPlayersWrapperProps) { 13 | const { state: { 14 | [GAMEMENU.TOP]: props 15 | } } = React.useContext(GameMenuContext) 16 | 17 | return 18 | } -------------------------------------------------------------------------------- /cef/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /cef/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | interface EventMpPool { 5 | add(eventName: RageEnums.EventKey | string, callback: (...args: any[]) => void): void; 6 | add(events: ({ [name: string]: (...args: any[]) => void; })): void; 7 | call(eventName: string, ...args: any[]): void; 8 | remove(eventName: string, handler?: (...args: any[]) => void): void; 9 | remove(eventNames: string[]): void; 10 | } 11 | 12 | interface Mp { 13 | events: EventMpPool; 14 | trigger(eventName: string, params: any): void; 15 | } 16 | 17 | declare const mp: Mp; -------------------------------------------------------------------------------- /clientside/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const outputPath = path.resolve("./dist") 5 | 6 | // check if directory exists 7 | fs.statSync(outputPath) 8 | 9 | module.exports = { 10 | entry: "./src/app.ts", 11 | target: 'web', 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.tsx?$/, 16 | use: 'ts-loader' 17 | }, 18 | ], 19 | }, 20 | resolve: { 21 | extensions: ['.tsx', '.ts', '.js', '.d.ts'], 22 | mainFields: ["main"], 23 | }, 24 | output: { 25 | path: outputPath, 26 | filename: "bundle.js" 27 | }, 28 | } -------------------------------------------------------------------------------- /serverside/src/db/entity/DomainConverter.ts: -------------------------------------------------------------------------------- 1 | export interface Type extends Function { 2 | new (...args: any[]): T 3 | } 4 | 5 | /** 6 | * Class to convert object from and to DTO 7 | */ 8 | export class DomainConverter { 9 | /** 10 | * Convert a dto to the domain 11 | * @param {Type} domain 12 | * @param {any} dto 13 | */ 14 | static fromDto(domain: Type, dto: any): T { 15 | return new domain(dto) 16 | } 17 | 18 | /** 19 | * Convert a domain to the dto 20 | * @param {Type} domain 21 | */ 22 | static toDto(domain: any): T { 23 | return domain.state 24 | } 25 | } -------------------------------------------------------------------------------- /cef/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App/App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | import { registerGlobalEvents } from './events' 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.register(); 18 | registerGlobalEvents(); 19 | -------------------------------------------------------------------------------- /cef/src/Theme/Dark/AccordionComponents.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from '@material-ui/core/styles' 2 | import AccordionSummary from '@material-ui/core/AccordionSummary' 3 | import AccordionDetails from '@material-ui/core/AccordionDetails' 4 | 5 | export const StyledAccordionSummary = withStyles({ 6 | root: { 7 | backgroundColor: 'rgb(51 51 51 / 54%)', 8 | borderBottom: '1px solid rgba(0, 0, 0, .125)', 9 | }, 10 | expandIcon: { 11 | color: 'white', 12 | }, 13 | })(AccordionSummary) 14 | 15 | export const StyledAccordionDetails = withStyles({ 16 | root: { 17 | backgroundColor: 'rgb(51 51 51 / 54%)', 18 | } 19 | })(AccordionDetails) -------------------------------------------------------------------------------- /clientside/src/declarations/types.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace TYPES { 2 | type GameMap = Pick & { 3 | area: SHARED.TYPES.Vector2[] 4 | spawnPoints: { [key in SHARED.TEAMS]: Vector3Mp[] } 5 | } 6 | 7 | type PlayerTeam = { 8 | ID: SHARED.TEAMS 9 | NAME: string 10 | SKINS: string[] 11 | COLOR: string 12 | } 13 | 14 | type PlayerCustomData = { 15 | rollbackVector?: SHARED.TYPES.Vector2 16 | rollbackPosition?: Vector3Mp 17 | isSelecting: boolean 18 | isSpectating: boolean 19 | assist: { [key: number]: number } 20 | } 21 | 22 | type KeyNumberCollection = { [key: string]: number } 23 | } -------------------------------------------------------------------------------- /serverside/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "esnext", 5 | "noImplicitAny": true, 6 | "removeComments": true, 7 | "preserveConstEnums": true, 8 | "sourceMap": true, 9 | "outDir": "./dist", 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictPropertyInitialization": true, 14 | "noImplicitThis": false, 15 | "experimentalDecorators": true, 16 | "emitDecoratorMetadata": true, 17 | "allowJs": true, 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["node_modules", "**/*.spec.ts"] 21 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/TabPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Box from '@material-ui/core/Box' 3 | 4 | interface TabPanelProps { 5 | children?: React.ReactNode; 6 | index: any; 7 | value: any; 8 | } 9 | 10 | /** 11 | * Tab panel component 12 | * @param {TabPanelProps} props 13 | */ 14 | export default function TabPanel(props: TabPanelProps) { 15 | const { children, value, index } = props 16 | return ( 17 | 27 | ) 28 | } -------------------------------------------------------------------------------- /clientside/src/hud/Gamemode.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "./Hud" 2 | 3 | /** 4 | * Hud element - Gamemode 5 | */ 6 | class Gamemode extends Hud { 7 | /** 8 | * @inheritdoc 9 | */ 10 | start(): void { 11 | mp.events.add(RageEnums.EventKey.RENDER, this.render) 12 | } 13 | 14 | /** 15 | * @inheritdoc 16 | */ 17 | stop(): void { 18 | mp.events.remove(RageEnums.EventKey.RENDER, this.render) 19 | } 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | render(): void { 25 | const name = this.dummyConfig.getGamemode() 26 | const version = this.dummyConfig.getGamemodeVersion() 27 | 28 | mp.game.graphics.drawText(`${name} ${version}`, [0.2, 0.95], this.textParams) 29 | } 30 | } 31 | 32 | export { Gamemode } -------------------------------------------------------------------------------- /serverside/src/declarations/override.ts: -------------------------------------------------------------------------------- 1 | import { logWrapper } from '../utils' 2 | import { DEBUG } from '../bootstrap' 3 | import { magenta } from 'colors' 4 | 5 | console.debug = function () { return DEBUG && console.log(...[magenta('[DEBUG]'), ...arguments]) } 6 | 7 | /** 8 | * override ragemp calls if app in debug mode 9 | */ 10 | if (DEBUG) { 11 | mp.events.add = logWrapper(mp.events.add, 'mp.events') 12 | mp.events.call = logWrapper(mp.events.call, 'mp.events.call') 13 | mp.players.call = logWrapper(mp.players.call, 'mp.players.call') 14 | mp.dummies.new = logWrapper(mp.dummies.new, 'mp.dummies.new') 15 | mp.events.add("playerJoin", (player: PlayerMp) => player.call = logWrapper(player.call, `player[${player.name}:${player.id}].call`)) 16 | } -------------------------------------------------------------------------------- /serverside/src/entities/validators/MapEditorDataValidator.ts: -------------------------------------------------------------------------------- 1 | import { Validator } from './Validator' 2 | 3 | export type Point = { 4 | name: string 5 | coord: Vector3Mp 6 | } 7 | 8 | export type SpawnVectorState = { 9 | [SHARED.TEAMS.ATTACKERS]: Point[] 10 | [SHARED.TEAMS.DEFENDERS]: Point[] 11 | } 12 | 13 | export interface MapEditorState { 14 | path: Point[] 15 | spawn: SpawnVectorState 16 | mapName: string 17 | } 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | class MapEditorDataValidator extends Validator { 23 | /** 24 | * @inheritdoc 25 | */ 26 | protected validators: KeyValueCollection = { 27 | mapName: "string", 28 | path: "pointArray", 29 | spawn: "spawn", 30 | } 31 | } 32 | 33 | export { MapEditorDataValidator } -------------------------------------------------------------------------------- /clientside/src/declarations/ragemp.d.ts: -------------------------------------------------------------------------------- 1 | interface PlayerMp { 2 | customData: TYPES.PlayerCustomData 3 | sharedData: SHARED.TYPES.SharedData 4 | vector2: SHARED.TYPES.Vector2 5 | isTypingInTextChat: boolean 6 | } 7 | 8 | interface String { 9 | padding(n: number, c?: string): string 10 | } 11 | 12 | interface DummyEntityMpPool extends EntityMpPool { 13 | forEachByType(type: number, fn: (dummyEntity: DummyEntity) => void): void; 14 | } 15 | 16 | interface DummyEntity { 17 | [key: string]: any 18 | } 19 | 20 | interface EntityMp { 21 | setAlpha(alphaLevel: number, skin: boolean): void; 22 | } 23 | 24 | declare namespace RageEnums { 25 | const enum EventKey { 26 | PLAYER_READY = "playerReady", 27 | } 28 | } 29 | 30 | interface Mp { 31 | _events: any 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, RAGEKAKAO (readytomassacre) 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /clientside/src/managers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BrowserManager' 2 | export * from './DialogManager' 3 | export * from './GameMapManager' 4 | export * from './PlayerManager' 5 | export * from './RoundManager' 6 | export * from './ZoneManager' 7 | export * from './dummies/DummyLanguageManager' 8 | export * from './dummies/DummyConfigManager' 9 | export * from './dummies/DummyMapManager' 10 | export * from './dummies/DummyPlayerStatManager' 11 | export * from './dummies/DummyRoundStatManager' 12 | export * from './InteractionManager' 13 | export * from './TeamManager' 14 | export * from './EventManager' 15 | export * from './RpcManager' 16 | export * from './RoundStatManager' 17 | export * from './ScoreboardManager' 18 | export * from './HudManager' 19 | export * from './WeaponManager' 20 | export * from './MechanicsManager' -------------------------------------------------------------------------------- /serverside/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "league", 3 | "version": "0.5.0", 4 | "license": "ISC", 5 | "keywords": [], 6 | "private": true, 7 | "devDependencies": { 8 | "@types/colors": "^1.2.1", 9 | "@types/glob": "^7.1.2", 10 | "@types/node": "^14.0.11", 11 | "@types/ragemp-s": "github:CocaColaBear/types-ragemp-s#v1.0" 12 | }, 13 | "scripts": { 14 | "start": "tsc --watch", 15 | "build": "tsc" 16 | }, 17 | "dependencies": { 18 | "colors": "^1.4.0", 19 | "glob": "^7.1.6", 20 | "mongodb": "^3.6.0", 21 | "rage-decorators": "^1.1.3", 22 | "rage-rpc": "^0.4.0", 23 | "reflect-metadata": "^0.1.13", 24 | "tsyringe": "^4.3.0", 25 | "typeorm": "^0.2.25", 26 | "util": "^0.12.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /clientside/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "league-clientside", 3 | "version": "0.5.0", 4 | "license": "ISC", 5 | "private": true, 6 | "devDependencies": { 7 | "@types/node": "^14.0.12", 8 | "@types/ragemp-c": "github:CocaColaBear/types-ragemp-c#v1.0", 9 | "ts-loader": "^7.0.5", 10 | "typescript": "^3.9.5", 11 | "webpack": "^4.43.0", 12 | "webpack-cli": "^3.3.11", 13 | "webpack-merge": "^4.2.2" 14 | }, 15 | "scripts": { 16 | "start": "npx webpack --watch --config webpack.dev.js", 17 | "build": "npx webpack --config webpack.prod.js" 18 | }, 19 | "dependencies": { 20 | "deepmerge": "^4.2.2", 21 | "rage-decorators": "^1.1.3", 22 | "rage-rpc": "^0.4.0", 23 | "reflect-metadata": "^0.1.13", 24 | "tsyringe": "^4.3.0", 25 | "util": "^0.12.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /clientside/src/utils/Tickable.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from "../core/ErrorHandler" 2 | import { EventEmitter } from "events" 3 | 4 | abstract class Tickable extends EventEmitter { 5 | protected stopped: boolean = true 6 | 7 | constructor(readonly errHandler: ErrorHandler) { 8 | super() 9 | 10 | this.tick = this.tick.bind(this) 11 | } 12 | 13 | /** 14 | * An update function which will call from the HudManager if it necessary 15 | * Invoke all tickable functions will be written in HudManager 16 | */ 17 | tick(...args: any[]): void {} 18 | 19 | /** 20 | * Start invoking the tick method 21 | */ 22 | startTick(): void { 23 | this.stopped = false 24 | } 25 | /** 26 | * Stop invoking the tick method 27 | */ 28 | stopTick(): void { 29 | this.stopped = true 30 | } 31 | 32 | get isStopped(): boolean { 33 | return this.stopped 34 | } 35 | } 36 | 37 | export { Tickable } -------------------------------------------------------------------------------- /cef/src/GameMenu/Credits/Credits.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | import Changelog from './Changelog'; 4 | 5 | export interface CreditsProps { 6 | gamemode: string 7 | version: string 8 | } 9 | /** 10 | * Credits component 11 | */ 12 | export default function Credits(props: CreditsProps) { 13 | const { gamemode, version } = props 14 | 15 | return ( 16 | 17 | 18 | {gamemode}, {version} 19 | 20 | TDM gamemode for clan matches 21 | Author: ragecacao (readytomassacre) 22 | https://discord.gg/pwTZ6SS 23 | 24 | 25 | ) 26 | } -------------------------------------------------------------------------------- /cef/src/Common/RefreshButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import RefreshIcon from '@material-ui/icons/Refresh' 3 | import { StyledButton } from '../Theme/Dark/ButtonComponents' 4 | import { lang } from '../lib/Language' 5 | import { MSG } from '../messages' 6 | import { ButtonProps as MuiButtonProps } from '@material-ui/core/Button' 7 | import { useThrottle } from '../lib/utils' 8 | 9 | interface RefreshButtonProps { 10 | onClick: (...args: any[]) => void 11 | ms?: number 12 | } 13 | 14 | export default function RefreshButton(props: RefreshButtonProps & Omit) { 15 | const { onClick, ms = 1000, ...other } = props 16 | const [ onClickThrottled ] = useThrottle(onClick, ms) 17 | 18 | if (typeof onClickThrottled !== 'function') return <> 19 | 20 | return ( 21 | onClickThrottled()} {...other}>{lang.get(MSG.GAMEMENU_REFRESH_BTN)} 22 | ) 23 | } -------------------------------------------------------------------------------- /serverside/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { getProjectDir, getJsonFromFileSync } from './utils/index' 3 | import { container } from 'tsyringe' 4 | 5 | // first init the debug param 6 | const params: any = getJsonFromFileSync(resolve(getProjectDir(), "assets", "config.json")) 7 | export const DEBUG: boolean = params.DEBUG 8 | 9 | // then makes all overrides 10 | import './declarations/override' 11 | 12 | import { Config } from './core/Config' 13 | import { Application } from './core/Application' 14 | import { ErrorHandler } from './core/ErrorHandler' 15 | 16 | // resolve errHandler to handle static errors 17 | export const errorHandler: ErrorHandler = container.resolve(ErrorHandler) 18 | // resolve Config 19 | export const config: any = new Config(params) 20 | container.register(Config, { useValue: config }) 21 | 22 | // resolve the App 23 | export const app: Application = container.resolve(Application) 24 | container.register("app", { useValue: app }) -------------------------------------------------------------------------------- /cef/src/InfoPanel/DirectionsRunIconCustom.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SvgIcon from '@material-ui/core/SvgIcon'; 3 | 4 | const DirectionsRunIconCustom = (props: any) => { 5 | const gradientId = `direction-linear-gradient-${props.gradientid}` 6 | return ( 7 | 8 | 9 | {props.healthgradient.map((color: any, key: number) => ( 10 | 11 | ))} 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | export default DirectionsRunIconCustom -------------------------------------------------------------------------------- /serverside/src/declarations/ragemp.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend entity mp interface 3 | */ 4 | interface EntityMp { 5 | sharedData: SHARED.TYPES.SharedData 6 | playingTime?: number 7 | muted: number 8 | } 9 | 10 | /** 11 | * Extend string interface 12 | */ 13 | interface String { 14 | padding(n: number, c?: string): string 15 | } 16 | 17 | /** 18 | * Correct dummies pool interface 19 | */ 20 | interface DummyEntityMpPool { 21 | "new"(dummyEntityType: number, dimension: number, sharedVariables: KeyValueCollection): DummyEntityMp; 22 | } 23 | 24 | /** 25 | * Correct dummy interface 26 | */ 27 | interface DummyEntityMp { 28 | setVariable: (key: string, value: any) => void 29 | setVariables: (object: any) => void 30 | getVariable: (key: string) => any 31 | [key: string]: any 32 | } 33 | 34 | interface PedMp extends PlayerMp {} 35 | 36 | /** 37 | * Extend Rage enums 38 | */ 39 | declare namespace RageEnums { 40 | const enum EventKey { 41 | PACKAGES_LOADED = 'packagesLoaded' 42 | } 43 | } -------------------------------------------------------------------------------- /clientside/src/hud/Controls.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "./Hud" 2 | import { DummyConfigManager, DummyLanguageManager, DialogManager } from "../managers" 3 | import { ErrorHandler } from "../core/ErrorHandler" 4 | import { print } from "../utils" 5 | 6 | /** 7 | * Hud element - Controls 8 | */ 9 | class Controls extends Hud { 10 | private _controls: any = {} 11 | 12 | constructor( 13 | readonly dummyConfig : DummyConfigManager, 14 | readonly lang : DummyLanguageManager, 15 | readonly errHandler : ErrorHandler, 16 | readonly dialogManager : DialogManager, 17 | ) { 18 | super(dummyConfig, lang, errHandler) 19 | } 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | start(): void { 25 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_CONTROLS_TOGGLE, true) 26 | } 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | stop(): void { 32 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_CONTROLS_TOGGLE, false) 33 | } 34 | } 35 | 36 | export { Controls } -------------------------------------------------------------------------------- /serverside/src/declarations/types.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace TYPES { 2 | /** 3 | * Interface of Vector2 4 | */ 5 | type Vector2 = { 6 | new([x, y]?: [number, number]): Vector2 7 | 8 | x: number 9 | y: number 10 | xy: [number, number] 11 | 12 | at(index: number): number 13 | reset(): void 14 | copy(dest?: Vector2): Vector2 15 | negate(dest?: Vector2): Vector2 16 | equals(vector: Vector2, threshold?: number): boolean 17 | length(): number 18 | squaredLength(): number 19 | add(vector: Vector2): Vector2 20 | subtract(vector: Vector2): Vector2 21 | multiply(vector: Vector2): Vector2 22 | divide(vector: Vector2): Vector2 23 | scale(value: number, dest?: Vector2): Vector2 24 | normalize(dest?: Vector2): Vector2 25 | } 26 | 27 | /** 28 | * Command callable type 29 | */ 30 | type CommandCallable = (player: PlayerMp, fullText: string, ...args: string[]) => void 31 | 32 | /** 33 | * Types of records 34 | */ 35 | type RoundStatRecord = SHARED.TYPES.RoundStatDTO & { id: number } 36 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "league", 3 | "version": "0.5.0", 4 | "description": "League tdm gamemode for clan matches", 5 | "keywords": [ 6 | "league", 7 | "rage-mp", 8 | "rage", 9 | "gta", 10 | "v", 11 | "clanwarscript" 12 | ], 13 | "scripts": { 14 | "postinstall": "yarn postinstall:clientside && yarn postinstall:serverside && yarn postinstall:cef", 15 | "postinstall:clientside": "yarn --cwd clientside/", 16 | "postinstall:cef": "yarn --cwd cef/", 17 | "postinstall:serverside": "yarn --cwd serverside/", 18 | "build": "yarn build:clientside && yarn build:serverside && yarn build:cef && yarn build:make", 19 | "build:clientside": "yarn --cwd clientside build", 20 | "build:cef": "yarn --cwd cef build", 21 | "build:serverside": "yarn --cwd serverside build", 22 | "build:make": "node build.js make" 23 | }, 24 | "author": "READYTOMASSACRE", 25 | "license": "ISC", 26 | "dependencies": { 27 | "commander": "^6.0.0", 28 | "inquirer": "^7.3.2", 29 | "ncp": "^2.0.0", 30 | "rimraf": "^3.0.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cef/src/App/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import WeaponDialog from '../WeaponDialog/WeaponDialog'; 4 | import ScoreboardData from '../Scoreboard/ScoreboardData'; 5 | import InfoPanelWrapper from '../InfoPanel/InfoPanelWrapper'; 6 | import NotifyNotistack from '../Notify/NotifyNotistack'; 7 | import GameMenu from '../GameMenu/GameMenu'; 8 | import DeathlogList from '../Deathlog/DeathlogList'; 9 | import Effects from '../Effects/Effects'; 10 | import Spectate from '../Spectate/Spectate'; 11 | import MainControls from '../Common/MainControls'; 12 | import MapEditor from '../MapEditor/MapEditor'; 13 | 14 | function App() { 15 | return ( 16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | ); 31 | } 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /cef/src/Effects/ExpEffect.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEvent } from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Typography from '@material-ui/core/Typography' 4 | import clsx from 'clsx' 5 | 6 | const useStyles = makeStyles({ 7 | root: { 8 | position: 'absolute', 9 | animationDuration: '.3s', 10 | animationFillMode: 'both', 11 | textShadow: '0px 0px 5px rgba(255,190,0,1)', 12 | color: 'rgba(255,190,0,1)', 13 | } 14 | }) 15 | 16 | interface ExpEffect { 17 | message?: string 18 | on?: number 19 | } 20 | 21 | const Text = (props: ExpEffect) => { 22 | const classes = useStyles() 23 | const exp = props.message || "+120 xp" 24 | const rootClass = clsx(classes.root, props.on ? 'bounceIn' : 'bounceOut') 25 | 26 | return {exp} 27 | } 28 | 29 | export default function ExpEffect(props: ExpEffect) { 30 | const [on, set] = React.useState(0) 31 | const handleClick = (e: MouseEvent) => set(on => +!on) 32 | 33 | return ( 34 | 35 | ) 36 | } -------------------------------------------------------------------------------- /serverside/src/db/repos/ProfileRepository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, MongoRepository, FindAndModifyWriteOpResultObject } from "typeorm" 2 | import { Profile } from "../entity/Profile" 3 | 4 | /** 5 | * Player stats repostiroy class 6 | */ 7 | @EntityRepository(Profile) 8 | class ProfileRepository extends MongoRepository { 9 | /** 10 | * Get a player by player rgscId 11 | * @param {PlayerMp} player 12 | */ 13 | async getPlayerById(player: PlayerMp): Promise { 14 | return this.findOne({ 15 | where: { rgscId: player.rgscId } 16 | }) 17 | } 18 | 19 | async getTopPlayers(take: number = 100): Promise { 20 | return await this.find({ 21 | order: { 'state.mmr': -1 }, 22 | take, 23 | }) 24 | } 25 | 26 | /** 27 | * Save a player's profile 28 | * @param {Profile} profile 29 | */ 30 | async saveProfile(profile: Profile): Promise { 31 | return this.findOneAndUpdate({ rgscId: profile.rgscId }, { $set: profile }, { upsert: true }) 32 | } 33 | } 34 | 35 | export { ProfileRepository } -------------------------------------------------------------------------------- /serverside/src/declarations/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace INTERFACES { 2 | /** 3 | * Manager interface 4 | */ 5 | interface Manager { 6 | /** 7 | * Load manager into application 8 | * @param {any} params (optional) 9 | */ 10 | load(params?: any): void 11 | } 12 | 13 | /** 14 | * Interface of the entity 15 | */ 16 | interface EntityManager { 17 | initData(entity: E): void 18 | 19 | getTeam(entity: E): SHARED.TEAMS 20 | setTeam(entity: E, teamId: SHARED.TEAMS): boolean 21 | 22 | spawn(entity: E, vector: Vector3Mp): boolean 23 | 24 | getState(entity: E): SHARED.STATE 25 | setState(entity: E, state: SHARED.STATE): boolean 26 | hasState(entity: E, state: SHARED.STATE | SHARED.STATE[]): boolean 27 | 28 | getEntitiesWithState(state: SHARED.STATE | SHARED.STATE[], players?: E[]) : E[] 29 | 30 | call(entity: E, eventName: string, args: any[]): void 31 | } 32 | 33 | /** 34 | * DTO validator interface 35 | */ 36 | interface DtoValidator { 37 | valid(value: any): boolean 38 | getValue(value: any): any 39 | } 40 | } -------------------------------------------------------------------------------- /clientside/src/managers/dummies/DummyRoundStatManager.ts: -------------------------------------------------------------------------------- 1 | import { Dummy } from "../../entities/Dummy" 2 | import { singleton, injectable } from "tsyringe" 3 | 4 | /** 5 | * Class to manage round stats through the dummy 6 | */ 7 | @singleton() 8 | @injectable() 9 | class DummyRoundStatManager { 10 | private readonly type = SHARED.ENTITIES.ROUND_STAT 11 | private _dummy?: Dummy 12 | 13 | /** 14 | * Register all existing dummies 15 | */ 16 | registerDummies(): void { 17 | mp.dummies.forEachByType(this.type, entity => { 18 | this._dummy = new Dummy(this.type, entity) 19 | }) 20 | } 21 | 22 | /** 23 | * Get the score by team id 24 | * @param {Exclude} teamId 25 | */ 26 | getScore(teamId: Exclude): number { 27 | return this.dummy.data[teamId].score 28 | } 29 | 30 | /** 31 | * Get round stat dummy 32 | */ 33 | get dummy() { 34 | if (!this._dummy) throw new ReferenceError(SHARED.MSG.ERR_NOT_FOUND) 35 | 36 | return this._dummy 37 | } 38 | } 39 | 40 | export { DummyRoundStatManager } -------------------------------------------------------------------------------- /serverside/src/errors/PlayerErrors.ts: -------------------------------------------------------------------------------- 1 | export class PlayerNotifyError extends Error { 2 | public args: string[] 3 | constructor(message?: string, public readonly player?: PlayerMp | PlayerMp[], ...args: string[]) { 4 | super(message) 5 | 6 | this.args = args 7 | } 8 | } 9 | 10 | export class RoundIsNotRunningError extends PlayerNotifyError {} 11 | export class RoundIsRunningError extends PlayerNotifyError {} 12 | export class RoundIsPausedError extends PlayerNotifyError {} 13 | export class RoundIsNotPausedError extends PlayerNotifyError {} 14 | 15 | export class RoundStatGetError extends PlayerNotifyError {} 16 | export class RoundStatUpdateError extends PlayerNotifyError {} 17 | 18 | export class PlayerStatUpdateError extends PlayerNotifyError {} 19 | 20 | export class VoteError extends PlayerNotifyError {} 21 | export class VoteAddError extends PlayerNotifyError {} 22 | 23 | export class NotFoundNotifyError extends PlayerNotifyError {} 24 | export class InvalidArgumentNotify extends PlayerNotifyError {} 25 | export class InvalidLoginGroup extends PlayerNotifyError {} 26 | export class InvalidAccessNotify extends PlayerNotifyError {} -------------------------------------------------------------------------------- /clientside/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 5 | "outDir": "./dist", /* Redirect output structure to the directory. */ 6 | "strict": true, /* Enable all strict type-checking options. */ 7 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 8 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 9 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 10 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 11 | } 12 | } -------------------------------------------------------------------------------- /cef/src/WeaponDialog/WeaponList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import List from '@material-ui/core/List' 4 | import WeaponItem from './WeaponItem' 5 | 6 | // compile css styles 7 | const useStyles = makeStyles({ 8 | root: { 9 | display: 'flex', 10 | flexWrap: 'wrap', 11 | width: '100%', 12 | justifyContent: 'center', 13 | }, 14 | }) 15 | 16 | interface Props { 17 | list: string[] 18 | handleClick: Function 19 | choice: string[] 20 | } 21 | 22 | function WeaponList(props: Props) { 23 | const classes = useStyles() 24 | return ( 25 | 26 | {props.list.map((value: string, index: number) => { 27 | const weaponName = value.replace(/weapon_/, '') 28 | const src = `/assets/weapons/${weaponName}.webp` 29 | const isDisabled = props.choice.indexOf(value) !== -1 30 | const handleClick = () => props.handleClick(value) 31 | 32 | return {weaponName} 33 | })} 34 | 35 | ) 36 | } 37 | 38 | export default WeaponList -------------------------------------------------------------------------------- /cef/src/Scoreboard/ScoreboardData.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Scoreboard from './Scoreboard' 3 | import { register } from 'rage-rpc' 4 | import { RPC_DIALOG } from '../events' 5 | 6 | const defaultTeam = { 7 | name: "Unknown team", 8 | color: "white", 9 | players: [], 10 | score: 0, 11 | } 12 | 13 | const defaultProps = { 14 | motd: "Unknown", 15 | team: { 16 | ATTACKERS: defaultTeam, 17 | DEFENDERS: defaultTeam, 18 | } 19 | } 20 | 21 | export default function ScoreboardData() { 22 | // register setState handlers 23 | const [open, setOpen] = React.useState(false) 24 | const [render, setRender] = React.useState(false) 25 | const [props, setProps] = React.useState({}) 26 | 27 | const initData = () => { 28 | if (render) return 29 | 30 | setRender(true) 31 | register(RPC_DIALOG.CLIENT_SCOREBOARD_TOGGLE, ([ toggle ]) => setOpen(toggle)) 32 | register(RPC_DIALOG.CLIENT_SCOREBOARD_DATA, ([ props ]) => setProps(props)) 33 | } 34 | 35 | // effect hook to register rpc calls 36 | React.useEffect(initData, [render]) 37 | 38 | // render Scoreboard 39 | return 40 | } -------------------------------------------------------------------------------- /clientside/src/utils/print.ts: -------------------------------------------------------------------------------- 1 | import { format } from 'util' 2 | 3 | const enum LOG_TYPE { 4 | INFO = "logInfo", 5 | WARN = "logWarning", 6 | ERROR = "logError", 7 | FATAL = "logFatal", 8 | CLEAR = "clear", 9 | RESET = "reset", 10 | } 11 | 12 | /** 13 | * Print a message into the Rage console 14 | * @param {LOG_TYPE} TYPE 15 | * @param {any[]} args - the args which will be passed into the console 16 | */ 17 | function message(TYPE: LOG_TYPE, ...args: any[]) { 18 | if (mp.console) { 19 | try { 20 | return mp.console[TYPE](format('', ...args) + "\n") 21 | } catch(err) { 22 | return mp.console.logError(err.stack + "\n") 23 | } 24 | } else { 25 | return console.log(...args) 26 | } 27 | } 28 | 29 | export const print = { 30 | info: (...params: any[]) => message(LOG_TYPE.INFO, ...params), 31 | warn: (...params: any[]) => message(LOG_TYPE.WARN, ...params), 32 | error: (...params: any[]) => message(LOG_TYPE.ERROR, ...params), 33 | fatal: (...params: any[]) => message(LOG_TYPE.FATAL, ...params), 34 | clear: (...params: any[]) => message(LOG_TYPE.CLEAR, ...params), 35 | reset: (...params: any[]) => message(LOG_TYPE.RESET, ...params), 36 | } -------------------------------------------------------------------------------- /cef/src/Common/MainControls.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Controls, { Control } from './Controls' 3 | import { MSG } from '../messages' 4 | import { register } from 'rage-rpc' 5 | import { RPC_DIALOG } from '../events' 6 | 7 | const controls: Control[] = [ 8 | { 9 | input: ['`'], 10 | label: MSG.CONTROL_SCOREBOARD 11 | }, 12 | { 13 | input: ['F2'], 14 | label: MSG.CONTROL_GAMEMENU 15 | }, 16 | { 17 | input: ['F4'], 18 | label: MSG.CONTROL_TEAMCHANGE 19 | }, 20 | ] 21 | 22 | /** 23 | * Main control hud element 24 | */ 25 | export default function MainControls() { 26 | const [render, setRender] = React.useState(false) 27 | const [open, setOpen] = React.useState(false) 28 | 29 | React.useEffect(() => { 30 | setRender(true) 31 | if (!render) { 32 | register(RPC_DIALOG.CLIENT_CONTROLS_TOGGLE, ([ toggle ]) => setOpen(toggle)) 33 | } 34 | }, [render, open]) 35 | 36 | return 48 | } -------------------------------------------------------------------------------- /clientside/src/utils/decorators.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | 3 | import { inspect } from 'util' 4 | import { print } from './print' 5 | 6 | export function logMethod(debug?: boolean) { 7 | return function ( 8 | target: Object, 9 | propertyName: string, 10 | propertyDesciptor: PropertyDescriptor): PropertyDescriptor { 11 | 12 | if (debug) { 13 | const method = propertyDesciptor.value 14 | 15 | propertyDesciptor.value = function (...args: any[]) { 16 | 17 | // convert list of greet arguments to string 18 | const params = args.map(a => inspect(a)).join() 19 | 20 | // invoke greet() and get its return value 21 | const result = method.apply(this, args) 22 | 23 | // convert result to string 24 | const r = JSON.stringify(result) 25 | 26 | // display in console the function call details 27 | const methodName: string = `${target.constructor.name}::${propertyName}(${params})` 28 | print.info('[DEBUG]', `${methodName.padding(100)} => ${r}`) 29 | // return the result of invoking the method 30 | return result 31 | } 32 | } 33 | 34 | return propertyDesciptor 35 | } 36 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/Profile/Profile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { lang } from '../../lib/Language' 3 | import Table from '@material-ui/core/Table' 4 | import TableBody from '@material-ui/core/TableBody' 5 | import { StyledTableRow, StyledTableCell } from '../../Theme/Dark/TableComponents' 6 | import { MSG } from '../../messages' 7 | 8 | export interface ProfileProps { 9 | [key: string]: string | number 10 | } 11 | 12 | /** 13 | * Profile table component 14 | * @param {ProfileProps} props 15 | */ 16 | export default function Profile(props: ProfileProps) { 17 | const arrayProps = Object.entries(props) 18 | const name = props.name || "" 19 | 20 | return ( 21 | 22 | {lang.get(MSG.GAMEMENU_PROFILE_TITLE, name.toString())} 23 | 24 | 25 | {arrayProps.map(([propName, propValue], index) => ( 26 | 27 | {lang.get(propName)} 28 | {propValue} 29 | 30 | ))} 31 | 32 |
33 |
34 | ) 35 | } -------------------------------------------------------------------------------- /serverside/src/entities/SharedData.ts: -------------------------------------------------------------------------------- 1 | export const SHARED_DATA = "sharedData" 2 | 3 | /** 4 | * Helper class to store shared data 5 | */ 6 | class SharedData { 7 | constructor(handler: ProxyHandler) { 8 | const proxy = new Proxy(this, handler) 9 | 10 | return proxy 11 | } 12 | } 13 | 14 | /** 15 | * Makes the proxy to getting data from an entity 16 | * @param {any} entity 17 | */ 18 | export const getSharedData = (entity: any): any => { 19 | return function(target: any, key: any) { 20 | if (typeof target[key] === 'function') return target[key] 21 | 22 | const sharedData = entity.getVariable(SHARED_DATA) 23 | 24 | return sharedData ? sharedData[key] : undefined 25 | } 26 | } 27 | 28 | /** 29 | * Makes the proxy to setting data to an entity 30 | * @param {any} entity 31 | */ 32 | export const setSharedData = (entity: any) => { 33 | return function(target: any, key: string | number | symbol, value: any): boolean { 34 | if (typeof target[key] === 'function') return true 35 | 36 | const sharedData = entity.getVariable(SHARED_DATA) 37 | entity.setVariable(SHARED_DATA, { ...sharedData, [key]: value }) 38 | 39 | return true 40 | } 41 | } 42 | 43 | export { SharedData } -------------------------------------------------------------------------------- /cef/src/GameMenu/Players/PlayerDetail.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { callServer } from 'rage-rpc' 3 | import { RPC } from '../../events' 4 | import Profile from '../Profile/Profile' 5 | import { StyledButton } from '../../Theme/Dark/ButtonComponents' 6 | import ArrowBackIcon from '@material-ui/icons/ArrowBack' 7 | 8 | interface PlayerDetailProps { 9 | id: number 10 | onClick: (event: React.MouseEvent, id?: number) => void 11 | } 12 | 13 | /** 14 | * Player detail component 15 | * @param {PlayerDetailProps} props 16 | */ 17 | export default function PlayerDetail(props: PlayerDetailProps) { 18 | const { onClick, id } = props 19 | 20 | const [profileProps, setProps] = React.useState({}) 21 | const [render, setRender] = React.useState(false) 22 | 23 | React.useEffect(() => { 24 | if (!render) { 25 | setRender(true) 26 | 27 | callServer(RPC.CEF_GAMEMENU_PROFILE, id) 28 | .then(props => setProps(props)) 29 | } 30 | }, [render, id]) 31 | 32 | return ( 33 | 34 | } style={{ margin: 8 }} variant="outlined" onClick={onClick}>Back 35 | 36 | 37 | ) 38 | } -------------------------------------------------------------------------------- /serverside/src/utils/decorators.ts: -------------------------------------------------------------------------------- 1 | import { magenta } from 'colors' 2 | import { inspect, formatWithOptions } from 'util' 3 | 4 | export function logMethod(debug?: boolean) { 5 | return function ( 6 | target: Object, 7 | propertyName: string, 8 | propertyDesciptor: PropertyDescriptor): PropertyDescriptor { 9 | 10 | if (debug) { 11 | const method = propertyDesciptor.value 12 | 13 | propertyDesciptor.value = function (...args: any[]) { 14 | 15 | // convert list of greet arguments to string 16 | const params = args.map(a => formatWithOptions({colors: true}, '%j', a)).join() 17 | 18 | // invoke greet() and get its return value 19 | const result = method.apply(this, args) 20 | 21 | // convert result to string 22 | const r = JSON.stringify(result) 23 | 24 | // display in console the function call details 25 | const methodName: string = `${target.constructor.name}::${magenta(propertyName)}(${params})` 26 | console.log(magenta('[DEBUG]'), `${methodName.padding(100)} => ${r}`) 27 | // return the result of invoking the method 28 | return result 29 | } 30 | } 31 | 32 | return propertyDesciptor 33 | } 34 | } -------------------------------------------------------------------------------- /cef/src/InfoPanel/InfoPanelWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import InfoPanel from './InfoPanel' 3 | import { RPC_DIALOG } from '../events' 4 | import { register } from 'rage-rpc' 5 | 6 | const defaultTeam = { 7 | name : 'unknown', 8 | color : 'white', 9 | players : [], 10 | score : 0, 11 | } 12 | const defaultTeams = { 13 | ATTACKERS: defaultTeam, 14 | DEFENDERS: defaultTeam, 15 | } 16 | 17 | const defaultProps = { 18 | team: defaultTeams, 19 | } 20 | 21 | /** 22 | * Wrapper for top round panel 23 | */ 24 | export default function InfoPanelWrapper() { 25 | // register setState handlers 26 | const [open, setOpen] = React.useState(false) 27 | const [render, setRender] = React.useState(false) 28 | const [props, setProps] = React.useState({}) 29 | 30 | const initWrapper = () => { 31 | if (render) return 32 | 33 | setRender(true) 34 | register(RPC_DIALOG.CLIENT_INFOPANEL_TOGGLE, ([ toggle ]) => setOpen(toggle)) 35 | register(RPC_DIALOG.CLIENT_INFOPANEL_DATA, ([ props ]) => setProps(props)) 36 | } 37 | 38 | // effect hook to register rpc calls 39 | React.useEffect(initWrapper, [render]) 40 | 41 | // render Infopanel 42 | return 43 | } -------------------------------------------------------------------------------- /clientside/src/core/Application.ts: -------------------------------------------------------------------------------- 1 | import * as Managers from '../managers' 2 | import { container } from "tsyringe" 3 | 4 | /** 5 | * Core class of an application 6 | */ 7 | export class Application { 8 | private managers: INTERFACES.Manager[] = [] 9 | /** 10 | * Start the application 11 | */ 12 | start(): void { 13 | this.loadManagers() 14 | } 15 | /** 16 | * Load all managers by factory config params 17 | */ 18 | loadManagers(): void { 19 | Object.entries(Managers).forEach(([packageName, packageItem]: [string, any]) => { 20 | if ( 21 | packageName.match(/manager/gi) 22 | && typeof packageItem === 'function' 23 | ) { 24 | this.managers.push(container.resolve(packageItem) as INTERFACES.Manager) 25 | } 26 | }) 27 | 28 | this.managers.forEach(manager => { 29 | if (manager.load) manager.load() 30 | }) 31 | } 32 | 33 | /** 34 | * Get a manager 35 | * @note recommended using dependency injection instead of this method 36 | * 37 | * @param Manager - class of a manager 38 | */ 39 | getManager(Manager: TManager | any): TManager { 40 | return this.managers.find(manager => manager instanceof Manager) as TManager 41 | } 42 | } -------------------------------------------------------------------------------- /cef/src/WeaponDialog/WeaponImgChoose.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ButtonBase, { ButtonBaseProps as MuiButtonBaseProps} from '@material-ui/core/ButtonBase'; 3 | import { Omit } from '@material-ui/types'; 4 | import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'; 5 | 6 | interface Props { 7 | thumbUrl: string 8 | } 9 | 10 | // compile css styles 11 | const useStyles = makeStyles((theme: Theme) => 12 | createStyles({ 13 | image: { 14 | position: 'relative', 15 | height: 42, 16 | width: 42, 17 | margin: '2px 2px 0', 18 | '& disabled': { 19 | opacity: 0.5 20 | } 21 | }, 22 | }), 23 | ); 24 | 25 | function WeaponImgChoose(props: Props & Omit) { 26 | const { thumbUrl, ...other } = props 27 | const classes = useStyles(props) 28 | 29 | return ( 30 | 41 | 42 | ) 43 | } 44 | 45 | export default WeaponImgChoose -------------------------------------------------------------------------------- /cef/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "league-cef", 3 | "version": "0.5.0", 4 | "license": "ISC", 5 | "private": true, 6 | "dependencies": { 7 | "@material-ui/core": "^4.11.0", 8 | "@material-ui/icons": "^4.9.1", 9 | "@testing-library/jest-dom": "^4.2.4", 10 | "@testing-library/react": "^9.3.2", 11 | "@testing-library/user-event": "^7.1.2", 12 | "@types/jest": "^24.0.0", 13 | "@types/node": "^12.0.0", 14 | "@types/react": "^16.9.0", 15 | "@types/react-dom": "^16.9.0", 16 | "node-sass": "^4.14.1", 17 | "notistack": "^0.9.17", 18 | "rage-rpc": "^0.4.0", 19 | "react": "^16.13.1", 20 | "react-dom": "^16.13.1", 21 | "react-scripts": "3.4.1", 22 | "typescript": "~3.7.2" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": "react-app" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "devDependencies": {} 46 | } 47 | -------------------------------------------------------------------------------- /clientside/src/hud/effects/Death.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "../Hud" 2 | import { DummyConfigManager, DummyLanguageManager, DialogManager } from "../../managers" 3 | import { ErrorHandler } from "../../core/ErrorHandler" 4 | 5 | /** 6 | * An effect - Death 7 | */ 8 | class Death extends Hud { 9 | private timeoutId?: NodeJS.Timeout 10 | 11 | constructor( 12 | readonly dummyConfig : DummyConfigManager, 13 | readonly lang : DummyLanguageManager, 14 | readonly errHandler : ErrorHandler, 15 | readonly dialogManager : DialogManager, 16 | ) { 17 | super(dummyConfig, lang, errHandler) 18 | } 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | start(): Death { 24 | if (this.timeoutId) this.stop() 25 | 26 | const { PLAYING_SECONDS } = this.dummyConfig.getDeathEffect() 27 | 28 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_NOTIFY_DEATH, this.lang.get(SHARED.MSG.DEATH_TEXT)) 29 | this.timeoutId = setTimeout(() => this.stop(), PLAYING_SECONDS * 1000) 30 | 31 | return this 32 | } 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | stop(): void { 38 | if (this.timeoutId) { 39 | clearTimeout(this.timeoutId) 40 | this.timeoutId = undefined 41 | } 42 | 43 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_NOTIFY_DEATH) 44 | } 45 | } 46 | 47 | export { Death } -------------------------------------------------------------------------------- /clientside/src/declarations/enums.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace ENUMS { 2 | /** 3 | * An enum of CEF packages 4 | */ 5 | const enum CEF { 6 | MAIN = 'package://cef/index.html' 7 | // MAIN = 'http://localhost:3000' 8 | // MAIN = 'http://192.168.0.100:3000/' 9 | } 10 | 11 | /** 12 | * An enum of keycodes 13 | */ 14 | const enum KEYCODES { 15 | VK_LEFT = 0x25, 16 | VK_UP = 0x26, 17 | VK_RIGHT = 0x27, 18 | VK_DOWN = 0x28, 19 | VK_RETURN = 0x0D, 20 | W = 0x57, 21 | A = 0x41, 22 | S = 0x53, 23 | D = 0x44, 24 | VK_F2 = 0x71, 25 | VK_F3 = 0x72, 26 | VK_F4 = 0x73, 27 | VK_F7 = 0x76, 28 | VK_TILDE = 0xC0, 29 | VK_1 = 0x31, 30 | VK_2 = 0x32, 31 | VK_3 = 0x33, 32 | VK_4 = 0x34, 33 | VK_5 = 0x35, 34 | VK_E = 0x45, 35 | VK_R = 0x52, 36 | VK_X = 0x58, 37 | VK_C = 0x43, 38 | VK_F = 0x46, 39 | VK_T = 0x54, 40 | } 41 | 42 | const enum BONES { 43 | IK_Head = 12844, 44 | SKEL_L_Foot = 14201, 45 | } 46 | 47 | const enum CONTROLS { 48 | F5 = 327, 49 | W = 32, 50 | S = 33, 51 | A = 34, 52 | D = 35, 53 | Space = 321, 54 | LCtrl = 326, 55 | LShift = 21, 56 | LAlt = 19, 57 | } 58 | } -------------------------------------------------------------------------------- /cef/src/Notify/NotifyNotistack.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SnackbarProvider, VariantType, useSnackbar } from 'notistack' 3 | import { RPC_DIALOG } from '../events' 4 | import { register } from 'rage-rpc' 5 | 6 | const validateVariant = (variant: VariantType = 'default'): VariantType => { 7 | variant = ['default', 'error', 'success', 'warning', 'info'].indexOf(variant) !== -1 8 | ? variant 9 | : 'default' 10 | 11 | return variant 12 | } 13 | 14 | /** 15 | * Notify component 16 | */ 17 | const Notify = () => { 18 | // set state handlers 19 | const [render, setRender] = React.useState(false) 20 | const { enqueueSnackbar } = useSnackbar() 21 | 22 | // set effects 23 | React.useEffect(() => { 24 | if (render) return 25 | setRender(true) 26 | register(RPC_DIALOG.CLIENT_NOTIFY_NOTISTACK, ([message, variant]) => ( 27 | enqueueSnackbar(message.replace(/!{.*}/g, ''), { variant: validateVariant(variant) }) 28 | )) 29 | }, [render, enqueueSnackbar]) 30 | 31 | return 32 | } 33 | 34 | export default function NotifyNotistack() { 35 | const vertical = 'bottom', 36 | horizontal = 'center' 37 | 38 | return ( 39 | 44 | 45 | 46 | ) 47 | } -------------------------------------------------------------------------------- /cef/src/MapEditor/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | import TextField from '@material-ui/core/TextField' 4 | import { MapEditorState } from './MapEditor' 5 | import { lang } from '../lib/Language' 6 | import { MSG } from '../messages' 7 | 8 | interface Props { 9 | className: string 10 | mapName: string 11 | setState: React.Dispatch> 12 | disabled: boolean 13 | } 14 | /** 15 | * The header part of map editor's form 16 | * @param {Props} props 17 | */ 18 | export default function Header(props: Props) { 19 | const { className, mapName, setState, disabled } = props 20 | 21 | // handler to edit a name of the map 22 | const handleEdit = (mapName: string) => setState(state => ({ ...state, mapName })) 23 | // handler to toggle a focus on the input element 24 | const toggleFocus = (toggle: boolean) => setState(state => ({ ...state, focus: toggle })) 25 | 26 | return ( 27 | <> 28 | {lang.get(MSG.MAP_EDITOR_HEADER)} 29 | handleEdit(e.target.value)} 32 | onFocus={() => toggleFocus(true)} 33 | onBlur={() => toggleFocus(false)} 34 | id="standard-basic" 35 | label={lang.get(MSG.MAP_EDITOR_NAME_LABEL)} 36 | value={mapName} 37 | /> 38 | 39 | ) 40 | } -------------------------------------------------------------------------------- /clientside/src/managers/TeamManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton, injectable } from "tsyringe" 2 | import { DummyConfigManager } from "./dummies/DummyConfigManager" 3 | 4 | /** 5 | * Class to manage the teams 6 | * @todo guess may this class worth delete, need to revise 7 | */ 8 | @injectable() 9 | @singleton() 10 | class TeamManager { 11 | constructor(readonly dummyConfigManager: DummyConfigManager) {} 12 | 13 | /** 14 | * Normalize data 15 | * @param dummyTeams 16 | */ 17 | getTeams(dummyTeams?: SHARED.TYPES.Teams): TYPES.PlayerTeam[] { 18 | if (!dummyTeams) dummyTeams = this.getDummyTeams() 19 | 20 | const teams: TYPES.PlayerTeam[] = [ 21 | { 22 | ID: SHARED.TEAMS.ATTACKERS, 23 | NAME: dummyTeams.ATTACKERS.NAME, 24 | SKINS: dummyTeams.ATTACKERS.SKINS, 25 | COLOR: dummyTeams.ATTACKERS.COLOR, 26 | }, 27 | { 28 | ID: SHARED.TEAMS.DEFENDERS, 29 | NAME: dummyTeams.DEFENDERS.NAME, 30 | SKINS: dummyTeams.DEFENDERS.SKINS, 31 | COLOR: dummyTeams.DEFENDERS.COLOR, 32 | }, 33 | { 34 | ID: SHARED.TEAMS.SPECTATORS, 35 | NAME: dummyTeams.SPECTATORS.NAME, 36 | SKINS: dummyTeams.SPECTATORS.SKINS, 37 | COLOR: dummyTeams.SPECTATORS.COLOR, 38 | } 39 | ] 40 | 41 | return teams 42 | } 43 | 44 | private getDummyTeams(): SHARED.TYPES.Teams { 45 | return this.dummyConfigManager.dummy.data.TEAMS 46 | } 47 | } 48 | 49 | export { TeamManager } -------------------------------------------------------------------------------- /clientside/src/managers/GameMapManager.ts: -------------------------------------------------------------------------------- 1 | import { CustomRoute } from "../entities/Route" 2 | import { singleton } from "tsyringe" 3 | import { event, eventable } from "rage-decorators" 4 | 5 | /** 6 | * Class to manage the gamemap functions 7 | */ 8 | @singleton() 9 | @eventable() 10 | class GameMapManager { 11 | private area: SHARED.TYPES.Vector2[] = [] 12 | private route: INTERFACES.Route 13 | 14 | constructor() { 15 | this.route = new CustomRoute() 16 | 17 | this.drawZone = this.drawZone.bind(this) 18 | this.clearZone = this.clearZone.bind(this) 19 | this.onRender = this.onRender.bind(this) 20 | } 21 | 22 | /** 23 | * Draw a zone with vectors 24 | * @param {CW.Map} area An array of the map vectors 25 | */ 26 | @event(SHARED.EVENTS.SERVER_MAP_DRAW) 27 | drawZone(map: TYPES.GameMap): void { 28 | this.area = map.area 29 | 30 | this.route.clear() 31 | this.route.start() 32 | 33 | mp.events.add(RageEnums.EventKey.RENDER, this.onRender) 34 | } 35 | 36 | /** 37 | * Stop drawing zone 38 | */ 39 | @event(SHARED.EVENTS.SERVER_MAP_CLEAR) 40 | clearZone(): void { 41 | mp.events.remove(RageEnums.EventKey.RENDER, this.onRender) 42 | this.route.clear() 43 | } 44 | 45 | /** 46 | * Update route params 47 | */ 48 | private onRender(): void { 49 | this.area.forEach((vector: SHARED.TYPES.Vector2) => this.route.addPoint(vector)) 50 | this.route.setRender(true) 51 | } 52 | } 53 | 54 | export { GameMapManager } -------------------------------------------------------------------------------- /serverside/src/db/repos/RoundRepository.ts: -------------------------------------------------------------------------------- 1 | import { singleton, injectable, inject } from "tsyringe" 2 | import { ErrorHandler } from "../../core/ErrorHandler" 3 | import { Round } from "../entity/Round" 4 | import { EntityRepository, AbstractRepository, MongoRepository, ObjectID } from "typeorm" 5 | 6 | /** 7 | * Round stats repostiroy class 8 | */ 9 | @EntityRepository(Round) 10 | class RoundRepository extends MongoRepository { 11 | /** 12 | * Get match by id 13 | * @param {number} id 14 | */ 15 | async getMatchById(id: ObjectID): Promise { 16 | return this.findOne(id) 17 | } 18 | 19 | /** 20 | * Get all matches in the last week 21 | */ 22 | async getMatchesLastWeek(rgscId: string): Promise { 23 | const startkey = (+this.getLastWeek()) 24 | const endkey = Date.now() 25 | 26 | return this.find({ 27 | where: { 28 | created_at: { 29 | $gt: startkey, 30 | $lt: endkey, 31 | }, 32 | $or: [ 33 | {'state.ATTACKERS.rgscId': { $eq: rgscId }}, 34 | {'state.DEFENDERS.rgscId': { $eq: rgscId }}, 35 | ], 36 | }, 37 | order: { created_at: -1 }, 38 | }) 39 | } 40 | 41 | /** 42 | * Get last week date 43 | */ 44 | private getLastWeek(): Date { 45 | var today = new Date() 46 | var lastWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7) 47 | return lastWeek 48 | } 49 | } 50 | 51 | export { RoundRepository } -------------------------------------------------------------------------------- /cef/src/GameMenu/Vote/VoteCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Card from '@material-ui/core/Card' 4 | import CardContent from '@material-ui/core/CardContent' 5 | import Button from '@material-ui/core/Button' 6 | import Typography from '@material-ui/core/Typography' 7 | import { lang } from '../../lib/Language' 8 | import { MSG } from '../../messages' 9 | import { callServer } from 'rage-rpc' 10 | import { RPC } from '../../events' 11 | 12 | const useStyles = makeStyles({ 13 | root: { 14 | maxWidth: 200, 15 | margin: 5, 16 | border: '2px solid white', 17 | }, 18 | }); 19 | 20 | export interface VoteCardProps { 21 | id: number 22 | code: string 23 | } 24 | 25 | /** 26 | * Vote card component 27 | * @param {VoteCardProps} props 28 | */ 29 | export default function VoteCard(props: VoteCardProps) { 30 | const classes = useStyles() 31 | 32 | const handleClick = (_: React.MouseEvent, id: number) => callServer(RPC.CEF_GAMEMENU_VOTE_NOMINATE, id) 33 | 34 | return ( 35 | 36 | 37 | 38 | #{props.id} {props.code} 39 | 46 | 47 | 48 | 49 | ) 50 | } -------------------------------------------------------------------------------- /serverside/src/validators/operationFactory.ts: -------------------------------------------------------------------------------- 1 | type Result = T 2 | type Operation = (accumulator: any, value: any) => Result 3 | type Value> = ReturnType 4 | 5 | /** 6 | * Operation factory configure 7 | * @param operations 8 | */ 9 | const configure = >>(operations: O) => { 10 | return (key: K) => { 11 | return (accumulator: any, value: any): Value => { 12 | const operation = operations[key] 13 | return operation(accumulator, value) 14 | } 15 | } 16 | } 17 | 18 | const add = (data: number, additional: number) => data + additional 19 | const subtract = (data: number, additional: number) => data - additional 20 | const replace = (_: any, value: any) => value 21 | const addObject = (data: any, additional: any) => { 22 | Object.keys(additional).forEach(key => { 23 | if (typeof data[key] === 'undefined') data[key] = 0 24 | data[key] = add(data[key], additional[key]) 25 | }) 26 | 27 | return data 28 | } 29 | const subtractObject = (data: any, additional: any) => { 30 | Object.keys(additional).forEach(key => { 31 | if (typeof data[key] === 'undefined') data[key] = 0 32 | data[key] = subtract(data[key], additional[key]) 33 | }) 34 | 35 | return data 36 | } 37 | 38 | /** 39 | * Operation factory 40 | */ 41 | export const operationFactory = configure({ 42 | add, 43 | subtract, 44 | addObject, 45 | subtractObject, 46 | replace, 47 | }) 48 | -------------------------------------------------------------------------------- /clientside/src/hud/Position.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "./Hud" 2 | 3 | interface TextParams { 4 | font: number 5 | centre: boolean 6 | color: RGBA 7 | scale: Array2d 8 | outline: boolean 9 | } 10 | 11 | interface HealthParams { 12 | width: number 13 | height: number 14 | border: number 15 | } 16 | 17 | /** 18 | * Hud element - Position 19 | */ 20 | class Position extends Hud { 21 | /** 22 | * @inheritdoc 23 | */ 24 | start(): void { 25 | mp.events.add(RageEnums.EventKey.RENDER, this.render) 26 | } 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | stop(): void { 32 | mp.events.remove(RageEnums.EventKey.RENDER, this.render) 33 | } 34 | 35 | /** 36 | * Render method 37 | */ 38 | render(): void { 39 | try { 40 | const { x, y, z } = mp.players.local.position 41 | mp.game.graphics.drawText(JSON.stringify({ x: x.toFixed(2), y: y.toFixed(2), z: z.toFixed(2) }), [0.2, 0.5], this.textParams) 42 | } catch (err) { 43 | this.stop() 44 | if (!this.errHandler.handle(err)) throw err 45 | } 46 | } 47 | 48 | get textParams(): TextParams { 49 | if (!this._textParams) { 50 | const { NICKNAME: { FONT, CENTRE, OUTLINE } } = this.dummyConfig.getNameTagConfig() 51 | 52 | this._textParams = { 53 | font : FONT, 54 | centre : CENTRE, 55 | color : [255, 255, 255, 255], 56 | scale : [0.35, 0.35], 57 | outline : OUTLINE, 58 | } 59 | } 60 | 61 | return this._textParams 62 | } 63 | } 64 | 65 | export { Position } -------------------------------------------------------------------------------- /cef/src/lib/Language.ts: -------------------------------------------------------------------------------- 1 | import { register } from "rage-rpc" 2 | import { RPC } from "../events" 3 | import { MSG } from "../messages" 4 | 5 | /** 6 | * Language class 7 | */ 8 | export class Language { 9 | private messages: Map = new Map() 10 | 11 | constructor(language?: any) { 12 | this.load = this.load.bind(this) 13 | register(RPC.CLIENT_LANGUAGE, this.load) 14 | 15 | if (language) this.load(language) 16 | } 17 | 18 | /** 19 | * Set up app language 20 | * 21 | */ 22 | load(data: any[]): void { 23 | try { 24 | const [ messages ] = data 25 | this.messages = new Map() 26 | 27 | Object 28 | .entries(messages) 29 | .forEach(([id, msg]) => this.messages.set(id, msg as string)) 30 | } catch (err) { 31 | throw new Error(this.get(MSG.ERR_LANG_NOT_FOUND)) 32 | } 33 | } 34 | 35 | /** 36 | * Get message by id 37 | * 38 | * @param id - message id 39 | * @param args - (optional) arguments to format message 40 | */ 41 | get(id: string, ...args: string[]): string { 42 | const message = this.messages.get(id) 43 | 44 | if (!message) return id 45 | 46 | return args.length 47 | ? this.format(message, ...args) 48 | : message 49 | } 50 | 51 | private format(message: string, ...args: string[]): string { 52 | if (args.length) { 53 | args.forEach(argument => { 54 | message = message.replace('%s', argument) 55 | }) 56 | } 57 | 58 | return message 59 | } 60 | } 61 | 62 | export const lang: Language = new Language() -------------------------------------------------------------------------------- /cef/src/MapEditor/PointList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import List from '@material-ui/core/List' 3 | import ListItem from '@material-ui/core/ListItem' 4 | import ListItemText from '@material-ui/core/ListItemText' 5 | import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' 6 | import IconButton from '@material-ui/core/IconButton' 7 | import DeleteIcon from '@material-ui/icons/Delete' 8 | import { callClient } from 'rage-rpc' 9 | import { RPC_DIALOG } from '../events' 10 | import { Point } from './MapEditor' 11 | 12 | interface Props { 13 | className: string 14 | disabled: boolean 15 | points: Point[] 16 | } 17 | /** 18 | * Point list component 19 | * @param {Props} props 20 | */ 21 | export default function PointList(props: Props) { 22 | const { className, disabled, points } = props 23 | 24 | if (!points.length) return <> 25 | 26 | const removePointRequest = (index: number) => { 27 | callClient(RPC_DIALOG.CEF_MAP_EDITOR_REMOVE_POINT, index) 28 | } 29 | 30 | return ( 31 | 32 | {points.map((point, index) => ( 33 | 34 | 35 | 36 | removePointRequest(index)} disabled={disabled} edge="end" aria-label="delete"> 37 | 38 | 39 | 40 | 41 | ))} 42 | 43 | ) 44 | } -------------------------------------------------------------------------------- /serverside/src/entities/Dummy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Dummy wrapper class 3 | */ 4 | class Dummy { 5 | readonly dummy: DummyEntityMp 6 | readonly type: T 7 | 8 | public data: SHARED.TYPES.DummyTypes[T] 9 | private keys: string[] 10 | 11 | constructor(type: T, data: SHARED.TYPES.DummyTypes[T], dimension: number = 0) { 12 | this.type = type 13 | this.dummy = mp.dummies.new(type, data) 14 | this.keys = Object.keys(data) 15 | 16 | this.data = new Proxy({} as SHARED.TYPES.DummyTypes[T], { 17 | get: (_, key) => { 18 | const value = this.dummy.getVariable(key.toString()) 19 | return value !== null ? value : undefined 20 | }, 21 | set: (_, key: string, value: any) => { 22 | this.dummy.setVariable(key.toString(), value) 23 | return true 24 | }, 25 | ownKeys: () => this.keys, 26 | getOwnPropertyDescriptor: () => ({ enumerable: true, configurable: true }) 27 | }) 28 | 29 | this.update(data) 30 | } 31 | 32 | /** 33 | * Update all data in the dummy 34 | * @param {SHARED.TYPES.DummyTypes[T]} data - data which should be updated in the dummy 35 | */ 36 | update(data: SHARED.TYPES.DummyTypes[T]): void { 37 | for (let key in data) this.data[key] = data[key] 38 | } 39 | 40 | /** 41 | * Get all data in the dummy 42 | */ 43 | getData(): SHARED.TYPES.DummyTypes[T] { 44 | let data: any = {} 45 | for (let key in this.data) data[key] = this.data[key] 46 | 47 | return data 48 | } 49 | } 50 | 51 | export { Dummy } -------------------------------------------------------------------------------- /cef/src/Spectate/SpectateViewers.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import { lang } from '../lib/Language' 5 | import { MSG } from '../messages' 6 | 7 | const useStyles = makeStyles({ 8 | root: { 9 | position: 'absolute', 10 | left: '2vmin', 11 | top: '60%', 12 | textAlign: 'left', 13 | color: 'white', 14 | textShadow: '0px 0px 5px rgba(255 255 255 / 50%)', 15 | }, 16 | spectate: { 17 | display: 'flex', 18 | flexDirection: 'column', 19 | height: 100, 20 | flexWrap: 'wrap', 21 | '& p': { 22 | marginRight: 5, 23 | } 24 | } 25 | }) 26 | interface Information { 27 | players: string[] 28 | } 29 | 30 | interface SpectateViewersProps { 31 | open?: boolean 32 | data?: Information 33 | } 34 | 35 | /** 36 | * Spectate viewers hud element 37 | */ 38 | export default function SpectateViewers(props: SpectateViewersProps) { 39 | const classes = useStyles() 40 | const { open, data } = props 41 | 42 | if ( 43 | !open 44 | || typeof data === 'undefined' 45 | || !Array.isArray(data.players) 46 | || !data.players.length 47 | ) { 48 | return <> 49 | } 50 | 51 | return ( 52 |
53 | {lang.get(MSG.SPECTATE_CURRENT_TEXT)} 54 |
55 | {data.players.map((nickname, index) => ( 56 | {nickname} 57 | ))} 58 |
59 |
60 | ) 61 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/History/HistoryDetailWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { callServer } from 'rage-rpc' 3 | import { RPC } from '../../events' 4 | import { StyledButton } from '../../Theme/Dark/ButtonComponents' 5 | import ArrowBackIcon from '@material-ui/icons/ArrowBack' 6 | import HistoryDetail from './HistoryDetail' 7 | 8 | interface HistoryDetailProps { 9 | id: string 10 | onClick: (event: React.MouseEvent, id?: string) => void 11 | } 12 | 13 | /** 14 | * Player detail component 15 | * @param {HistoryDetailProps} props 16 | */ 17 | export default function HistoryDetailWrapper(props: HistoryDetailProps) { 18 | const { onClick, id } = props 19 | 20 | const [matchProps, setProps] = React.useState({}) 21 | const [render, setRender] = React.useState(false) 22 | 23 | React.useEffect(() => { 24 | if (!render) { 25 | setRender(true) 26 | 27 | callServer(RPC.CEF_GAMEMENU_HISTORY, id) 28 | .then(props => setProps(props)) 29 | } 30 | }, [render, id]) 31 | 32 | const { ATTACKERS, DEFENDERS } = matchProps 33 | 34 | let content = <> 35 | if ( 36 | !ATTACKERS 37 | || !DEFENDERS 38 | || !Array.isArray(ATTACKERS.players) 39 | || !Array.isArray(DEFENDERS.players) 40 | ) { 41 | content = Data corrupted. 42 | } else { 43 | content = 44 | } 45 | 46 | return ( 47 | 48 | } style={{ margin: 8 }} variant="outlined" onClick={onClick}>Back 49 | {content} 50 | 51 | ) 52 | } -------------------------------------------------------------------------------- /cef/src/Effects/DeathEffect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import Fade from '@material-ui/core/Fade' 5 | import { RPC_DIALOG } from '../events' 6 | import { register } from 'rage-rpc' 7 | 8 | const useStyles = makeStyles({ 9 | root: { 10 | position: 'absolute', 11 | fontSize: '12vmin', 12 | color: '#f5392a', 13 | background: 'linear-gradient(0deg, rgba(255,255,255,0) 0%, rgba(5,5,5,0.87) 20%, rgba(0,0,0,0.87) 50%, rgba(0,0,0,0.87) 80%, rgba(255,255,255,0) 100%)', 14 | width: '100%', 15 | textShadow: '0px 0px 5px #ff1515, 5px 0px 0px #ff000085, -5px 0px 0px #ff00007a', 16 | } 17 | }) 18 | 19 | interface DeathEffectState { 20 | open: boolean 21 | text?: string 22 | } 23 | 24 | export default function DeathEffect() { 25 | const [render, setRender] = React.useState(false) 26 | const [state, setState] = React.useState({ open: false, text: '' }) 27 | const classes = useStyles() 28 | 29 | React.useEffect(() => { 30 | setRender(true) 31 | if (!render) { 32 | register(RPC_DIALOG.CLIENT_NOTIFY_DEATH, ([text]) => { 33 | if (typeof text === 'string') { 34 | setState({ open: true, text }) 35 | } else { 36 | setState(state => ({ ...state, open: false })) 37 | } 38 | }) 39 | } 40 | }, [render, state]) 41 | 42 | return ( 43 | 44 | {state.text} 45 | 46 | ) 47 | } -------------------------------------------------------------------------------- /clientside/src/core/ErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { PlayerNotifyError } from "../errors/PlayerErrors" 2 | import { ConsoleError } from "../errors/LogErrors" 3 | import { singleton } from "tsyringe" 4 | import { print } from "../utils" 5 | 6 | /** 7 | * Class of error handling 8 | */ 9 | @singleton() 10 | class ErrorHandler { 11 | /** 12 | * Handle error 13 | * @param {Error} err - the object of an error 14 | */ 15 | handle(err: E): boolean { 16 | if (err instanceof PlayerNotifyError) { 17 | return this.playerNotifyError(err) 18 | } else if (err instanceof ConsoleError) { 19 | return this.logError(err) 20 | } else if (err instanceof Error) { 21 | return this.error(err) 22 | } else { 23 | throw err 24 | } 25 | } 26 | 27 | /** 28 | * Notify player about an error 29 | * @param {PlayerNotifyError} err - the object of an error 30 | */ 31 | private playerNotifyError(err: E): boolean { 32 | mp.gui.chat.push(err.message) 33 | 34 | return true 35 | } 36 | 37 | /** 38 | * Log an error 39 | * @param {LogError} err - the object of an error 40 | */ 41 | private logError(err: E): boolean { 42 | print.error('[ERR]', err.stack) 43 | 44 | return true 45 | } 46 | 47 | /** 48 | * Log an error, but notice that is not handled by any types 49 | * @param {LogError} err - the object of an error 50 | */ 51 | private error(err: E): boolean { 52 | print.error('[NOT_HANDLED_ERR]', err.stack) 53 | 54 | return true 55 | } 56 | } 57 | 58 | export { ErrorHandler } -------------------------------------------------------------------------------- /cef/src/Deathlog/Deathlog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Typography from '@material-ui/core/Typography' 4 | import Fade from '@material-ui/core/Fade' 5 | import Collapse from '@material-ui/core/Collapse' 6 | 7 | const useStyles = makeStyles({ 8 | root: { 9 | display: 'flex', 10 | flexDirection: 'row', 11 | alignItems: 'center', 12 | padding: 5, 13 | borderRadius: 3, 14 | height: 25, 15 | backgroundColor: 'rgba(0 0 0 / 60%)', 16 | border: '1px solid rgba(0 0 0 / 15%)', 17 | margin: 5, 18 | }, 19 | img: { 20 | maxHeight: 24, 21 | margin: 5, 22 | filter: 'brightness(0) invert(1) drop-shadow(1px 1px 1px rgba(0 0 0 / 25%))', 23 | }, 24 | }) 25 | 26 | export interface DeathlogProps { 27 | checked: boolean 28 | killer: { 29 | name: string 30 | color: string 31 | } 32 | victim: { 33 | name: string 34 | color: string 35 | } 36 | weapon: string 37 | } 38 | 39 | /** 40 | * Deathlog 41 | */ 42 | export default function Deathlog(props: DeathlogProps) { 43 | const { checked, killer, victim, weapon } = props 44 | 45 | const classes = useStyles() 46 | const src = `/assets/weapons/${weapon}.webp` 47 | 48 | return ( 49 | 50 | 51 |
52 | {killer.name} 53 | 54 | {victim.name} 55 |
56 |
57 |
58 | ) 59 | } -------------------------------------------------------------------------------- /clientside/src/entities/Zone.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to control of zone 3 | */ 4 | export class Zone { 5 | constructor(public map: TYPES.GameMap) {} 6 | 7 | /** 8 | * Check if vector in zone 9 | * @param {SHARED.TYPES.Vector2} vector 10 | */ 11 | in(vector: SHARED.TYPES.Vector2): boolean { 12 | let deltaIndex: number = this.map.area.length - 1 13 | 14 | let inPolygon: boolean = false 15 | this.map.area.forEach((currVector: SHARED.TYPES.Vector2, index: number) => { 16 | const prevVector: SHARED.TYPES.Vector2 = this.map.area[deltaIndex] 17 | 18 | const cond1: boolean = (currVector.y > vector.y) != (prevVector.y > vector.y) 19 | const delta: number = (prevVector.x - currVector.x) * (vector.y - currVector.y) / (prevVector.y - currVector.y) + currVector.x 20 | const cond3: boolean = vector.x < delta 21 | 22 | if (cond1 && cond3) inPolygon = !inPolygon 23 | 24 | deltaIndex = index 25 | }) 26 | 27 | return inPolygon 28 | } 29 | 30 | /** 31 | * Check if vector out of zone 32 | * @param {SHARED.TYPES.Vector2} vector 33 | */ 34 | out(vector: SHARED.TYPES.Vector2) : boolean { 35 | return !!!this.in(vector) 36 | } 37 | 38 | /** 39 | * Return a center vector of map 40 | * @return {SHARED.TYPES.Vector2} 41 | */ 42 | center(): Vector3Mp { 43 | const x = this.map.area.map(vector => vector.x) 44 | const y = this.map.area.map(vector => vector.y) 45 | 46 | const centerX = (Math.min(...x) + Math.max(...x)) / 2 47 | const centerY = (Math.min(...y) + Math.max(...y)) / 2 48 | 49 | const { z } = this.map.spawnPoints.ATTACKERS[0] 50 | 51 | return new mp.Vector3(centerX, centerY, z) 52 | } 53 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/Credits/Changes040.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | 4 | export default function Changes040() { 5 | return ( 6 | 7 | + Removed pouchdb from the gamemode because it is not compatitable with linux server
8 | + Added mongodb database
9 | + Rewriting repositories and models for new database
10 | + Added installing instruction to README.md in the github repository
11 | + Added in-game effects
12 | Round start notify
13 | Winning round notify
14 | Death notify
15 | Damage notify
16 | + Added refresh button to history, to player's top
17 | + Added player's top page in the gamemenu
18 | + Added detail history page
19 | + Added pagination to players, history, top players pages in the gamemenu
20 | + Updated info panel, from this moment shows current score of teams when round is not running
21 | + Updated weapon dialog design
22 | + Fix deathlog bug, when a new message erase others messages
23 | + Refactoring hud manager
24 | + Refactoring abstract class hud
25 | + Optimize setInterval calls (reduce huds' count)
26 | + Fixed isVisible method in nametag hud element (thanks to hromik)
27 | + Fixed freeze player method
28 | + Fixed round states (Added prepare state)
29 | + Fixed saving stats when a player has changed a team
30 | + Added configurable damage from config
31 | + Added autologin option
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /cef/src/Spectate/SpectateCurrent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import Controls, { Control } from '../Common/Controls' 5 | import { MSG } from '../messages' 6 | 7 | const useStyles = makeStyles({ 8 | root: { 9 | position: 'absolute', 10 | top: '80%', 11 | color: 'white', 12 | textShadow: '0px 0px 5px rgba(255 255 255 / 50%)', 13 | }, 14 | nickname: { 15 | fontSize: 32, 16 | lineHeight: 1, 17 | } 18 | }) 19 | 20 | interface Information { 21 | id: number 22 | nickname: string 23 | kill: number 24 | death: number 25 | assist: number 26 | } 27 | 28 | interface SpectateCurrentProps { 29 | open?: boolean 30 | data?: Information 31 | } 32 | 33 | const controls: Control[] = [ 34 | { 35 | label: MSG.SPECTATE_CONTROL_LABEL, 36 | }, 37 | { 38 | input: ['A'], 39 | label: MSG.SPECTATE_CONTROL_LEFT, 40 | }, 41 | { 42 | input: ['D'], 43 | label: MSG.SPECTATE_CONTROL_RIGHT, 44 | }, 45 | ] 46 | 47 | /** 48 | * Spectating for the current player component 49 | * @param {SpectateCurrentProps} props 50 | */ 51 | export default function SpectateCurrent(props: SpectateCurrentProps) { 52 | const classes = useStyles() 53 | const { open, data } = props 54 | 55 | if (!open || typeof data === 'undefined') return <> 56 | 57 | return ( 58 |
59 | {data.nickname} 60 | id: {data.id} 61 | K:{data.kill} D:{data.death} A:{data.assist} 62 | 63 |
64 | ) 65 | } -------------------------------------------------------------------------------- /clientside/src/hud/Hud.ts: -------------------------------------------------------------------------------- 1 | import { DummyConfigManager, DummyLanguageManager, DialogManager } from "../managers" 2 | import { ErrorHandler } from "../core/ErrorHandler" 3 | import { Tickable } from "../utils/Tickable" 4 | 5 | /** 6 | * Base class of a hud element 7 | */ 8 | abstract class Hud extends Tickable implements INTERFACES.HudElement { 9 | protected _textParams?: INTERFACES.TextParams 10 | 11 | constructor( 12 | readonly dummyConfig: DummyConfigManager, 13 | readonly lang: DummyLanguageManager, 14 | readonly errHandler: ErrorHandler, 15 | ) { 16 | super(errHandler) 17 | 18 | this.render = this.render.bind(this) 19 | 20 | mp.events.add(SHARED.EVENTS.CLIENT_DUMMIES_READY, () => this.prepare()) 21 | } 22 | 23 | /** 24 | * Event 25 | * 26 | * Fires when all dummies has registered and hud element has created 27 | */ 28 | protected prepare() {} 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | abstract start(...args: any[]): void 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | abstract stop(...args: any[]): void 39 | 40 | /** 41 | * Render a hud element 42 | */ 43 | render(...args: any[]): void {} 44 | 45 | /** 46 | * Get default text params for the function mp.game.graphics.drawText 47 | */ 48 | get textParams(): INTERFACES.TextParams { 49 | if (!this._textParams) { 50 | const { TEXT: { FONT, CENTRE, COLOR, SCALE, OUTLINE } } = this.dummyConfig.getGlobalHudConfig() 51 | 52 | this._textParams = { 53 | font: FONT, 54 | centre: CENTRE, 55 | color: COLOR || [255, 255, 255, 255], 56 | scale: SCALE, 57 | outline: OUTLINE, 58 | } 59 | } 60 | 61 | return this._textParams 62 | } 63 | } 64 | 65 | export { Hud } -------------------------------------------------------------------------------- /serverside/src/managers/DbManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton, injectable, container } from "tsyringe" 2 | import { Config } from "../core/Config" 3 | import { statSync } from "fs" 4 | import { resolve } from 'path' 5 | import { createConnection, Connection } from 'typeorm' 6 | import { ErrorHandler } from "../core/ErrorHandler" 7 | import { logMethod } from "../utils" 8 | import { DEBUG } from "../bootstrap" 9 | 10 | /** 11 | * Class to manage database connection 12 | */ 13 | @singleton() 14 | @injectable() 15 | class DbManager implements INTERFACES.Manager { 16 | constructor( 17 | readonly config: Config, 18 | readonly errHandler: ErrorHandler, 19 | ) {} 20 | 21 | /** 22 | * Load the database 23 | */ 24 | @logMethod(DEBUG) 25 | async load(): Promise { 26 | try { 27 | const dbConfig = this.config.get('DB') 28 | if (dbConfig) { 29 | const connection = await createConnection({ 30 | type: 'mongodb', 31 | host: dbConfig.HOSTNAME, 32 | port: dbConfig.PORT, 33 | username: dbConfig.USERNAME, 34 | password: dbConfig.PASSWORD, 35 | database: dbConfig.DATABASE, 36 | entities: [ this.getEntitiesFolder() ], 37 | useUnifiedTopology: true, 38 | synchronize: true, 39 | }) 40 | 41 | container.register(Connection, { useValue: connection }) 42 | } 43 | } catch (err) { 44 | if (!this.errHandler.handle(err)) throw err 45 | } 46 | 47 | return true 48 | } 49 | 50 | /** 51 | * Get an absolute path to database 52 | */ 53 | private getEntitiesFolder(): string { 54 | const path = resolve(__dirname, "..", "db", "entity") 55 | 56 | statSync(path) 57 | 58 | return path + "/*.js" 59 | } 60 | } 61 | 62 | export { DbManager } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # League 2 | **The TDM gamemode**, based on the GTA V [RAGE:MP](http://rage.mp/) 3 | **Discord**: [League, RAGE:MP Gamemode](https://discord.gg/pwTZ6SS) 4 | 5 | ### Requirements 6 | 7 | 1. [MongoDB Community server](https://www.mongodb.com/try/download/community) v4.4.0 or above 8 | 2. Setup the connection config.json (./serverside/assets/config.json) 9 | For example 10 | ```json 11 | { 12 | "DB": { 13 | "HOSTNAME": "localhost", 14 | "PORT": "27017", 15 | "USERNAME": "root", 16 | "PASSWORD": "root", 17 | "DATABASE": "league" 18 | } 19 | } 20 | ``` 21 | ### Installation 22 | 23 | If you don't have typescript compiler 24 | ```bash 25 | $ yarn add global typescript 26 | ``` 27 | 28 | Install dependecies 29 | ```bash 30 | $ yarn 31 | ``` 32 | 33 | ### Build the gamemode 34 | 35 | ```bash 36 | $ yarn build 37 | ``` 38 | 39 | ### Flexibility with watch mode 40 | 41 | For **development** with the watch mode it's recommended to change the build paths in cef/clientside/serverside to your server's folders for more flexibility. 42 | For example (in the clientside), change the `webpack.common.js` to something like this: 43 | ```js 44 | const path = require('path') 45 | const fs = require('fs') 46 | 47 | // Let's imagine that GIT folder in the %RAGEMP%/server-files/league 48 | // And we are in the %RAGEMP%/server-files/league/clientside 49 | // So make the path to the server client_packages 50 | // To avoid the build project any time when files are changed 51 | const outputPath = path.resolve("..", "..", "client_packages", "league") 52 | 53 | // check if directory exists 54 | fs.statSync(outputPath) 55 | 56 | // other code in webpack.common.js... 57 | ``` 58 | 59 | 60 | ### Roadmap 61 | 62 | [Roadmap](./roadmap.md) (ru) 63 | 64 | ### License 65 | 66 | [ISC LICENSE](./LICENSE) -------------------------------------------------------------------------------- /cef/src/GameMenu/Reducer.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ProfileProps } from './Profile/Profile' 3 | import { PlayersProps } from './Players/Players' 4 | import { HistoryProps } from './History/History' 5 | import { VoteProps } from './Vote/Vote' 6 | import { CreditsProps } from './Credits/Credits' 7 | 8 | export enum GAMEMENU { 9 | PROFILE, 10 | PLAYERS, 11 | HISTORY, 12 | VOTE, 13 | TOP, 14 | CREDITS, 15 | } 16 | 17 | export type GameMenuState = { 18 | [GAMEMENU.PROFILE] : ProfileProps, 19 | [GAMEMENU.PLAYERS] : PlayersProps, 20 | [GAMEMENU.HISTORY] : HistoryProps, 21 | [GAMEMENU.VOTE] : VoteProps, 22 | [GAMEMENU.TOP] : PlayersProps, 23 | [GAMEMENU.CREDITS] : CreditsProps, 24 | } 25 | 26 | export type GameMenuAction = { 27 | type: GAMEMENU 28 | payload: any 29 | } 30 | 31 | /** 32 | * Initial state 33 | */ 34 | export const initialState: GameMenuState = { 35 | [GAMEMENU.PROFILE] : { name: '' }, 36 | [GAMEMENU.PLAYERS] : { players: [] }, 37 | [GAMEMENU.HISTORY] : { matches: [] }, 38 | [GAMEMENU.VOTE] : { maps: [] }, 39 | [GAMEMENU.TOP] : { players: [] }, 40 | [GAMEMENU.CREDITS] : { gamemode: '', version: '' }, 41 | } 42 | 43 | /** 44 | * Context of reducer state 45 | */ 46 | export const GameMenuContext = React.createContext<{ 47 | state: typeof initialState 48 | dispatch: (action: GameMenuAction) => void 49 | }>({ 50 | state: initialState, 51 | dispatch: () => {} 52 | }) 53 | 54 | /** 55 | * Reducer of GameMenu 56 | * @param {GameMenuState} state 57 | * @param {GameMenuAction} action 58 | */ 59 | export const reducer = (state: GameMenuState = initialState, action: GameMenuAction) => { 60 | const payload = { [action.type]: action.payload } 61 | 62 | return { ...state, ...payload} 63 | } -------------------------------------------------------------------------------- /cef/src/Spectate/Spectate.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SpectateCurrent from './SpectateCurrent' 3 | import SpectateViewers from './SpectateViewers' 4 | import { register } from 'rage-rpc' 5 | import { RPC_DIALOG } from '../events' 6 | 7 | const action = (state: any, field: string, value: any) => ({ 8 | ...state, 9 | [field]: value 10 | }) 11 | 12 | /** 13 | * Spectate hud component 14 | */ 15 | export default function Spectate() { 16 | const [render, setRender] = React.useState(false) 17 | const [state, setState] = React.useState({ 18 | currentOpen: false, 19 | currentData: undefined, 20 | viewersOpen: false, 21 | viewersData: undefined, 22 | }) 23 | 24 | // register side effects (toggle current/viewers hud, updating current/viewers data) 25 | React.useEffect(() => { 26 | setRender(true) 27 | if (!render) { 28 | 29 | register(RPC_DIALOG.CLIENT_SPECTATE_CURRENT_TOGGLE, ([ toggle ]) => 30 | setState(state => action(state, 'currentOpen', toggle)) 31 | ) 32 | 33 | register(RPC_DIALOG.CLIENT_SPECTATE_VIEWERS_TOGGLE, ([ toggle ]) => 34 | setState(state => action(state, 'viewersOpen', toggle)) 35 | ) 36 | 37 | register(RPC_DIALOG.CLIENT_SPECTATE_CURRENT, ([ data ]) => { 38 | setState(state => action(state, 'currentData', data)) 39 | }) 40 | 41 | register(RPC_DIALOG.CLIENT_SPECTATE_VIEWERS, ([ data ]) => { 42 | setState(state => action(state, 'viewersData', data)) 43 | }) 44 | } 45 | }, [render, state]) 46 | 47 | const { currentOpen, currentData, viewersOpen, viewersData } = state 48 | 49 | return ( 50 | <> 51 | 52 | 53 | 54 | ) 55 | } -------------------------------------------------------------------------------- /clientside/src/managers/dummies/DummyLanguageManager.ts: -------------------------------------------------------------------------------- 1 | import { Dummy } from "../../entities/Dummy" 2 | import { format } from "util" 3 | import { IsNotExistsError } from "../../errors/LogErrors" 4 | import { singleton } from "tsyringe" 5 | 6 | /** 7 | * Class to manage languages 8 | */ 9 | @singleton() 10 | class DummyLanguageManager { 11 | private readonly type = SHARED.ENTITIES.LANGUAGE 12 | private _dummy?: Dummy 13 | private player: PlayerMp = mp.players.local 14 | 15 | /** 16 | * Register all existing dummies 17 | */ 18 | registerDummies(): void { 19 | mp.dummies.forEachByType(this.type, entity => { 20 | this._dummy = new Dummy(this.type, entity) 21 | }) 22 | } 23 | 24 | /** 25 | * Get message by message id 26 | * 27 | * @param id - message id 28 | * @param args - (optional) arguments to format message 29 | */ 30 | get(id: string, ...args: string[]): string { 31 | const message = this.getMessages()[id] 32 | 33 | if (!message) return id 34 | 35 | return args.length && format(message, ...args) || message 36 | } 37 | 38 | /** 39 | * Get all messages by a language 40 | * @param {string} lang - langauge id 41 | */ 42 | getMessages(lang?: string): any { 43 | const messages = this.dummy.data[lang || this.lang] 44 | 45 | if (typeof messages === 'undefined') { 46 | throw new IsNotExistsError(`Language ${this.lang} is not exists`) 47 | } 48 | 49 | return messages 50 | } 51 | 52 | /** 53 | * Get current language 54 | */ 55 | private get lang(): string { 56 | return this.player.sharedData.lang 57 | } 58 | 59 | /** 60 | * Get language dummy 61 | */ 62 | get dummy() { 63 | if (!this._dummy) throw new ReferenceError(SHARED.MSG.ERR_NOT_FOUND) 64 | 65 | return this._dummy 66 | } 67 | } 68 | 69 | export { DummyLanguageManager } -------------------------------------------------------------------------------- /serverside/src/managers/CommonManager.ts: -------------------------------------------------------------------------------- 1 | import { getProjectDir } from "../utils" 2 | import { command, commandable } from "rage-decorators" 3 | import { resolve } from "path" 4 | import { readFileSync, writeFileSync } from "fs" 5 | import { singleton } from "tsyringe" 6 | 7 | /** 8 | * Manager to register and store any common cmds 9 | */ 10 | @singleton() 11 | @commandable() 12 | class CommonManager { 13 | 14 | /** 15 | * Command 16 | * 17 | * Getting the player's pos 18 | * @note Command will be deleted in the future 19 | * @param {PlayerMp} player 20 | */ 21 | @command('pos') 22 | sendPlayerPos(player: PlayerMp): void { 23 | const {x, y, z} = player.position 24 | 25 | player.notify(`x:${x} y:${y} z:${z}`) 26 | } 27 | 28 | /** 29 | * Command 30 | * 31 | * Save the player's position 32 | * @note Command will be deleted in the future 33 | * @param {PlayerMp} 34 | * @param {string} cmdDesc 35 | * @param {string} pointName - (optional) name of the point 36 | */ 37 | @command('savepos', { desc: SHARED.MSG.CMD_SAVE_POS }) 38 | savePlayerPos(player: PlayerMp, _: string, pointName?: string): void { 39 | const {x, y, z} = player.position 40 | 41 | try { 42 | const path = resolve(getProjectDir(), 'assets', 'savepositions.json') 43 | 44 | const positions: any[] = JSON.parse(readFileSync(path).toString()) 45 | positions.push({ point: [x, y, z], name: pointName || ""}) 46 | 47 | writeFileSync(path, JSON.stringify(positions)) 48 | 49 | player.notify(`${pointName || "Point"} saved!`) 50 | } catch (err) { 51 | console.error(err) 52 | } 53 | } 54 | 55 | /** 56 | * Command 57 | * 58 | * Make the suicide 59 | * @param {PlayerMp} 60 | */ 61 | @command('kill') 62 | kill(player: PlayerMp): void { 63 | player.health = 0 64 | } 65 | } 66 | 67 | export { CommonManager } -------------------------------------------------------------------------------- /cef/src/App/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: "Roboto", -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | -webkit-user-select: none; 9 | -khtml-user-select: none; 10 | -moz-user-select: none; 11 | -o-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | code { 16 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 17 | monospace; 18 | } 19 | 20 | .App { 21 | text-align: center; 22 | } 23 | 24 | .App-header { 25 | background-color:transparent; 26 | min-height: 100vh; 27 | display: flex; 28 | flex-direction: column; 29 | align-items: center; 30 | justify-content: center; 31 | font-size: calc(10px + 2vmin); 32 | } 33 | 34 | .App-font { 35 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 36 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 37 | sans-serif; 38 | } 39 | 40 | .bounceIn { 41 | -webkit-animation-name: bounceIn; 42 | animation-name: bounceIn; 43 | } 44 | 45 | .bounceOut { 46 | -webkit-animation-name: bounceOut; 47 | animation-name: bounceOut; 48 | } 49 | 50 | @keyframes bounceIn { 51 | 0% { 52 | opacity: 0; 53 | transform: scale(.3); 54 | } 55 | 50% { 56 | opacity: .75; 57 | transform: scale(1.05); 58 | } 59 | 70% { 60 | opacity: 1; 61 | transform: scale(.9); 62 | } 63 | 100% { 64 | transform: scale(1); 65 | } 66 | } 67 | 68 | @keyframes bounceOut { 69 | 0% { 70 | opacity: 1; 71 | transform: scale(1); 72 | } 73 | 50% { 74 | transform: scale(.9); 75 | opacity: .75; 76 | } 77 | 70% { 78 | transform: scale(1.05); 79 | } 80 | 100% { 81 | opacity: 0; 82 | transform: scale(.3); 83 | } 84 | } -------------------------------------------------------------------------------- /clientside/src/hud/effects/RoundStop.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "../Hud" 2 | import { DummyConfigManager, DummyLanguageManager, DialogManager } from "../../managers" 3 | import { ErrorHandler } from "../../core/ErrorHandler" 4 | 5 | /** 6 | * An effect - Round stop 7 | */ 8 | class RoundStop extends Hud { 9 | private timeoutId?: NodeJS.Timeout 10 | private params?: { text: string, color: string } 11 | 12 | constructor( 13 | readonly dummyConfig : DummyConfigManager, 14 | readonly lang : DummyLanguageManager, 15 | readonly errHandler : ErrorHandler, 16 | readonly dialogManager : DialogManager, 17 | ) { 18 | super(dummyConfig, lang, errHandler) 19 | } 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | start(winner?: SHARED.TEAMS): RoundStop { 25 | if (this.timeoutId) this.stop() 26 | 27 | const { PLAYING_END_SECONDS } = this.dummyConfig.getRoundStartEffect() 28 | 29 | if (winner) { 30 | const { NAME, COLOR } = this.dummyConfig.getTeam(winner) 31 | this.params = { 32 | text: this.lang.get(SHARED.MSG.ROUND_WINNING_TEXT, NAME), 33 | color: COLOR, 34 | } 35 | } else { 36 | this.params = { 37 | text: this.lang.get(SHARED.MSG.ROUND_STOP_MESSAGE), 38 | color: '#fff', 39 | } 40 | } 41 | 42 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_NOTIFY_ROUND_END, 'in', this.params) 43 | this.timeoutId = setTimeout(() => this.stop(), PLAYING_END_SECONDS * 1000) 44 | 45 | return this 46 | } 47 | 48 | /** 49 | * @inheritdoc 50 | */ 51 | stop(): void { 52 | if (this.timeoutId) { 53 | clearTimeout(this.timeoutId) 54 | this.timeoutId = undefined 55 | } 56 | 57 | if (this.params) { 58 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_NOTIFY_ROUND_END, 'out', this.params) 59 | this.params = undefined 60 | } 61 | } 62 | } 63 | 64 | export { RoundStop } -------------------------------------------------------------------------------- /clientside/src/hud/SpectateViewers.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "./Hud" 2 | import { DummyConfigManager, DummyLanguageManager, DialogManager, PlayerManager } from "../managers" 3 | import { ErrorHandler } from "../core/ErrorHandler" 4 | 5 | export interface SpectateViewersInformation { 6 | id: number 7 | nickname: string 8 | kill: number 9 | death: number 10 | assist: number 11 | } 12 | /** 13 | * Hud element - spectate 14 | */ 15 | class SpectateViewers extends Hud { 16 | private player: PlayerMp = mp.players.local 17 | 18 | constructor( 19 | readonly dummyConfig : DummyConfigManager, 20 | readonly lang : DummyLanguageManager, 21 | readonly errHandler : ErrorHandler, 22 | readonly dialogManager : DialogManager, 23 | readonly playerManager : PlayerManager, 24 | ) { 25 | super(dummyConfig, lang, errHandler) 26 | } 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | start(): void { 32 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_SPECTATE_VIEWERS_TOGGLE, true) 33 | this.player = mp.players.local 34 | this.startTick() 35 | } 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | stop(): void { 41 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_SPECTATE_VIEWERS_TOGGLE, false) 42 | this.stopTick() 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | tick(): void { 49 | try { 50 | const players = this.playerManager.getPlayerSpectates(this.player) 51 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_SPECTATE_VIEWERS, { players: players.map(ppl => ppl.name) }) 52 | 53 | } catch (err) { 54 | if (this.errHandler.handle(err)) throw err 55 | this.stop() 56 | } 57 | } 58 | 59 | /** 60 | * Update hud information 61 | * @param {PlayerMp} player 62 | */ 63 | update(player: PlayerMp): void { 64 | this.player = player 65 | this.tick() 66 | } 67 | } 68 | 69 | export { SpectateViewers } -------------------------------------------------------------------------------- /cef/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState, useCallback } from 'react' 2 | 3 | export function useInterval(callback: Function, delay: number) { 4 | const savedCallback = useRef() 5 | 6 | // Remember the latest callback. 7 | useEffect(() => { 8 | savedCallback.current = callback 9 | }, [callback]) 10 | 11 | // Set up the interval. 12 | useEffect(() => { 13 | function tick() { 14 | if (savedCallback.current) 15 | savedCallback.current() 16 | } 17 | if (delay !== null) { 18 | let id = setInterval(tick, delay) 19 | return () => clearInterval(id) 20 | } 21 | }, [delay]) 22 | } 23 | 24 | /** 25 | * Escape a regexp pattern 26 | * @param {string} string 27 | */ 28 | export function escapeRegExp(string: string) { 29 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string 30 | } 31 | 32 | /** 33 | * Get a random int 34 | * @param {number} max 35 | */ 36 | export function getRandomInt(max: number): number { 37 | return Math.floor(Math.random() * Math.floor(max)) 38 | } 39 | 40 | export function useThrottle(fn: Function, timeout: number = 300) { 41 | const [ready, setReady] = useState(true); 42 | const timerRef = useRef>() 43 | 44 | if (!fn || typeof fn !== "function") { 45 | throw new Error( 46 | "As a first argument, you need to pass a function to useThrottle hook." 47 | ); 48 | } 49 | 50 | const throttledFn = useCallback( 51 | (...args) => { 52 | if (!ready) { 53 | return; 54 | } 55 | 56 | setReady(false); 57 | fn(...args) 58 | }, 59 | [ready, fn] 60 | ); 61 | 62 | useEffect(() => { 63 | if (!ready) { 64 | timerRef.current = setTimeout(() => { 65 | setReady(true) 66 | }, timeout) 67 | return () => timerRef.current && clearTimeout(timerRef.current) 68 | } 69 | }, [ready, timeout]) 70 | return [throttledFn, ready] 71 | } -------------------------------------------------------------------------------- /cef/src/Effects/WinEffect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import Typography from '@material-ui/core/Typography' 5 | import { register } from 'rage-rpc' 6 | import { RPC_DIALOG } from '../events' 7 | 8 | const useStyles = makeStyles({ 9 | root: { 10 | position: 'absolute', 11 | animationDuration: '.75s', 12 | animationFillMode: 'both', 13 | fontSize: 54, 14 | color: '#03a9f4', 15 | textShadow: '0px 0px 15px #03a9f4', 16 | textDecoration: 'dotted', 17 | top: '15%', 18 | } 19 | }) 20 | 21 | interface WinState { 22 | way: false | string 23 | text?: string 24 | color?: string 25 | } 26 | 27 | /** 28 | * Play a win effect after round is end 29 | */ 30 | export default function WinEffect() { 31 | const [render, setRender] = React.useState(false) 32 | const [state, setState] = React.useState({ way: false, color: '#03a9f4' }) 33 | const classes = useStyles() 34 | 35 | React.useEffect(() => { 36 | setRender(true) 37 | if (!render) { 38 | register(RPC_DIALOG.CLIENT_NOTIFY_ROUND_END, ([way, params]) => { 39 | if (['in', 'out'].indexOf(way) !== -1) { 40 | const { text = "", color = "#fff" } = params || {} 41 | setState({ way, text, color }) 42 | } else { 43 | setState(state => ({ 44 | ...state, 45 | way: false, 46 | })) 47 | } 48 | }) 49 | } 50 | }, [render, state]) 51 | 52 | if (state.way === false) return <> 53 | 54 | const animation = state.way === 'in' ? 'bounceIn' : 'bounceOut' 55 | 56 | return ( 57 | 66 | {state.text} 67 | 68 | ) 69 | } -------------------------------------------------------------------------------- /serverside/src/core/ErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { PlayerNotifyError } from "../errors/PlayerErrors" 2 | import { LogError, ConsoleError } from "../errors/LogErrors" 3 | import { red } from "colors" 4 | import { singleton } from "tsyringe" 5 | 6 | /** 7 | * Class of error handling 8 | */ 9 | @singleton() 10 | class ErrorHandler { 11 | /** 12 | * Handle error 13 | * @param {Error} err - the object of an error 14 | */ 15 | handle(err: E): boolean { 16 | if (err instanceof PlayerNotifyError) { 17 | return this.playerNotifyError(err) 18 | } else if (err instanceof LogError) { 19 | return this.logError(err) 20 | } else { 21 | return this.error(err) 22 | } 23 | } 24 | 25 | /** 26 | * Notify player about an error 27 | * @param {PlayerNotifyError} err - the object of an error 28 | */ 29 | private playerNotifyError(err: E): boolean { 30 | if (err.player) { 31 | const players = Array.isArray(err.player) && err.player || [err.player] 32 | 33 | players.forEach(player => { 34 | if (mp.players.exists(player)) { 35 | 36 | const args = [err.constructor.name, err.message, ...(err.args || [])] 37 | player.call(SHARED.EVENTS.SERVER_NOTIFY_ERROR, args) 38 | } 39 | }) 40 | 41 | return true 42 | } else { 43 | return this.error(err) 44 | } 45 | } 46 | 47 | /** 48 | * Log an error 49 | * @param {LogError} err - the object of an error 50 | */ 51 | private logError(err: E): boolean { 52 | if (err instanceof ConsoleError) { 53 | const colorFunc = typeof err.colorFunc === 'function' && err.colorFunc || red 54 | console.error(colorFunc('[ERR]'), err.stack) 55 | 56 | return true 57 | } 58 | 59 | return false 60 | } 61 | 62 | private error(err: E): boolean { 63 | console.error(red('[NOT_HANDLED_ERR]'), err.stack) 64 | return true 65 | } 66 | } 67 | 68 | export { ErrorHandler } -------------------------------------------------------------------------------- /clientside/src/hud/SpectateCurrent.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "./Hud" 2 | import { DummyConfigManager, DummyLanguageManager, DialogManager, DummyPlayerStatManager } from "../managers" 3 | import { ErrorHandler } from "../core/ErrorHandler" 4 | 5 | export interface SpectateCurrentInformation { 6 | id: number 7 | nickname: string 8 | kill: number 9 | death: number 10 | assist: number 11 | } 12 | /** 13 | * Hud element - spectate 14 | */ 15 | class SpectateCurrent extends Hud { 16 | constructor( 17 | readonly dummyConfig : DummyConfigManager, 18 | readonly lang : DummyLanguageManager, 19 | readonly errHandler : ErrorHandler, 20 | readonly dialogManager : DialogManager, 21 | readonly playerStatManager : DummyPlayerStatManager, 22 | ) { 23 | super(dummyConfig, lang, errHandler) 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | start(): void { 30 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_SPECTATE_CURRENT_TOGGLE, true) 31 | } 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | stop(): void { 37 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_SPECTATE_CURRENT_TOGGLE, false) 38 | } 39 | 40 | /** 41 | * Update hud information 42 | * @param {PlayerMp} player 43 | */ 44 | update(player: PlayerMp): void { 45 | try { 46 | const payload: SpectateCurrentInformation = { 47 | id: player.remoteId, 48 | nickname: player.name, 49 | kill: 0, 50 | death: 0, 51 | assist: 0 52 | } 53 | 54 | const kda = this.playerStatManager.getPlayerKDA(player) 55 | if (typeof kda !== 'undefined') { 56 | payload.kill = kda.kill 57 | payload.death = kda.death 58 | payload.assist = kda.assist 59 | } 60 | 61 | this.dialogManager.call(SHARED.RPC_DIALOG.CLIENT_SPECTATE_CURRENT, payload) 62 | } catch (err) { 63 | if (!this.errHandler.handle(err)) throw err 64 | this.stop() 65 | } 66 | } 67 | } 68 | 69 | export { SpectateCurrent } -------------------------------------------------------------------------------- /serverside/src/managers/dummies/DummyRoundStatManager.ts: -------------------------------------------------------------------------------- 1 | import { IsNotExistsError, InvalidTypeError } from "../../errors/LogErrors" 2 | import { Dummy } from "../../entities/Dummy" 3 | import { singleton } from "tsyringe" 4 | 5 | /** 6 | * Class to manage round stats through the dummy 7 | */ 8 | @singleton() 9 | class DummyRoundStatManager implements INTERFACES.Manager { 10 | private _dummy?: Dummy 11 | 12 | /** 13 | * Load dummy 14 | */ 15 | load(): void { 16 | const roundStatDTO: SHARED.TYPES.RoundStatDummyDTO = { 17 | ATTACKERS: { 18 | score: 0 19 | }, 20 | DEFENDERS: { 21 | score: 0 22 | } 23 | } 24 | 25 | this._dummy = new Dummy(SHARED.ENTITIES.ROUND_STAT, roundStatDTO) 26 | 27 | this.update(this.toDto()) 28 | } 29 | 30 | /** 31 | * Return roundstat dto 32 | */ 33 | toDto(): SHARED.TYPES.RoundStatDummyDTO { 34 | return this.dummy.getData() 35 | } 36 | 37 | /** 38 | * Replace round stat by passing a new dto 39 | * @param {SHARED.TYPES.RoundStatDummyDTO} dto 40 | */ 41 | update(dto: SHARED.TYPES.RoundStatDummyDTO): void { 42 | this.dummy.update(dto) 43 | } 44 | 45 | /** 46 | * Set the team winner by team id 47 | * @param { SHARED.TEAMS | false} teamId 48 | */ 49 | setWinner(teamId: SHARED.TEAMS | false): void { 50 | if (teamId === false) return 51 | 52 | if (teamId === SHARED.TEAMS.SPECTATORS) { 53 | throw new InvalidTypeError("Wrong team id") 54 | } 55 | 56 | const dto = this.toDto() 57 | 58 | dto[teamId].score++ 59 | 60 | this.update(dto) 61 | } 62 | 63 | getTeamScore(teamId: SHARED.TEAMS): number { 64 | if (teamId === SHARED.TEAMS.SPECTATORS) { 65 | throw new InvalidTypeError("Wrong team id") 66 | } 67 | 68 | return this.dummy.data[teamId].score 69 | } 70 | 71 | get dummy() { 72 | if (!this._dummy) throw new IsNotExistsError("Dummy not found") 73 | 74 | return this._dummy 75 | } 76 | } 77 | 78 | export { DummyRoundStatManager } -------------------------------------------------------------------------------- /serverside/src/managers/BotManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton, injectable } from "tsyringe" 2 | import { DummyConfigManager } from "./dummies/DummyConfigManager" 3 | import { EntityBase } from "../entities/EntityBase" 4 | 5 | interface IPedArguments { 6 | modelHash: number 7 | position: Vector3Mp 8 | teamId?: SHARED.TEAMS 9 | } 10 | 11 | /** 12 | * @todo will be commented 13 | * At this moment this class has no effects 14 | */ 15 | @injectable() 16 | @singleton() 17 | class BotManager extends EntityBase { 18 | static readonly watchTimeout = 100 19 | 20 | private readonly collection: Map = new Map() 21 | private watchInterval?: NodeJS.Timeout 22 | 23 | constructor(readonly dummyConfigManager: DummyConfigManager) { 24 | super() 25 | } 26 | 27 | get(id: number): PedMp | undefined { 28 | return this.collection.get(id) 29 | } 30 | 31 | addBot(teamId: SHARED.TEAMS): PedMp { 32 | return this.add({ 33 | modelHash: mp.joaat(this.dummyConfigManager.getRandomSkin(teamId)), 34 | position: new mp.Vector3(0, 0, 0), 35 | teamId, 36 | }) 37 | } 38 | 39 | removeBot(botId: number): boolean { 40 | return this.remove(botId) 41 | } 42 | 43 | getBot(botId: number) { 44 | return this.collection.get(botId) 45 | } 46 | 47 | toArray(): PedMp[] { 48 | const peds: PedMp[] = [] 49 | this.collection.forEach(ped => peds.push(ped)) 50 | 51 | return peds 52 | } 53 | 54 | private add(params: IPedArguments): PedMp { 55 | const { modelHash, position, teamId = SHARED.TEAMS.ATTACKERS } = params 56 | const ped = mp.peds.new(modelHash, position) 57 | 58 | this.initData(ped) 59 | ped.sharedData.teamId = teamId 60 | this.collection.set(ped.id, ped) 61 | 62 | return ped 63 | } 64 | 65 | private remove(botId: number): boolean { 66 | const ped = this.collection.get(botId) 67 | 68 | if (!ped) return false 69 | 70 | ped.destroy() 71 | this.collection.delete(ped.id) 72 | 73 | return true 74 | } 75 | } 76 | 77 | export { BotManager } -------------------------------------------------------------------------------- /clientside/src/managers/ZoneManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton } from 'tsyringe' 2 | import { Zone } from '../entities/Zone' 3 | 4 | export const MAX_ROLLBACK_ATTEMPTS = 10 5 | export const DELAY_INSPECT = 100 6 | 7 | /** 8 | * Class to manage the zones 9 | */ 10 | @singleton() 11 | class ZoneManager { 12 | private zone?: Zone 13 | private attempts: number = 0 14 | private player: PlayerMp = mp.players.local 15 | private delay: number = Date.now() 16 | 17 | constructor() { 18 | this.inspectHandler = this.inspectHandler.bind(this) 19 | this.inspect = this.inspect.bind(this) 20 | this.stopInspect = this.stopInspect.bind(this) 21 | } 22 | 23 | /** 24 | * Inspect the zone 25 | * @param {Zone} zone 26 | */ 27 | inspect(zone: Zone): void { 28 | this.zone = zone 29 | mp.events.add(RageEnums.EventKey.RENDER, this.inspectHandler) 30 | } 31 | 32 | /** 33 | * Stop inspect the zone 34 | */ 35 | stopInspect(): void { 36 | mp.events.remove(RageEnums.EventKey.RENDER, this.inspectHandler) 37 | } 38 | 39 | /** 40 | * Inspect lifecycle 41 | */ 42 | inspectHandler(): void { 43 | this.outOfZone() && this.rollback() || this.commit() 44 | } 45 | 46 | /** 47 | * Check if the local player out of a zone 48 | */ 49 | private outOfZone(): boolean { 50 | return !!(this.zone && this.zone.out(this.player.vector2)) 51 | } 52 | 53 | /** 54 | * Change a position of the local player 55 | */ 56 | private rollback(): void { 57 | const delay = Date.now() 58 | if (delay - this.delay >= DELAY_INSPECT && this.player.customData.rollbackPosition) { 59 | this.delay = delay 60 | this.player.position = this.player.customData.rollbackPosition 61 | } 62 | } 63 | 64 | /** 65 | * Commit a new rollback position 66 | */ 67 | private commit(): void { 68 | if (this.zone && this.zone.in(this.player.vector2)) { 69 | this.player.customData.rollbackPosition = this.player.position 70 | this.player.customData.rollbackVector = this.player.vector2 71 | } 72 | } 73 | } 74 | 75 | export { ZoneManager } -------------------------------------------------------------------------------- /clientside/src/entities/Route.ts: -------------------------------------------------------------------------------- 1 | export const HUD_COLOR: number = 6 2 | export const DISPLAY_ON_FOOT: boolean = true 3 | export const FOLLOW_PLAYER: boolean = false 4 | export const RADAR_THICKNESS: number = 8 5 | export const MAP_THICKNESS: number = 8 6 | 7 | /** 8 | * Makes the GPS path by params 9 | */ 10 | export abstract class Route implements INTERFACES.Route { 11 | readonly CLEAR_HASH: string = "" 12 | readonly START_HASH: string = "" 13 | readonly ADD_POINT_HASH: string = "" 14 | readonly SET_RENDER_HASH: string = "" 15 | 16 | /** 17 | * @inheritdoc 18 | */ 19 | clear(): void { 20 | mp.game.invoke(this.CLEAR_HASH) 21 | } 22 | /** 23 | * @inheritdoc 24 | */ 25 | start(hudColor?: number, displayOnFoot?: boolean, followPlayer?: boolean): void { 26 | mp.game.invoke(this.START_HASH, hudColor || HUD_COLOR, displayOnFoot || DISPLAY_ON_FOOT, followPlayer || FOLLOW_PLAYER) 27 | } 28 | /** 29 | * @inheritdoc 30 | */ 31 | addPoint(vector: SHARED.TYPES.Vector2): void { 32 | mp.game.invoke(this.ADD_POINT_HASH, vector.x, vector.y, 0) 33 | } 34 | /** 35 | * @inheritdoc 36 | */ 37 | setRender(toggle: boolean, radarThickness?: number, mapThickness?: number): void { 38 | mp.game.invoke(this.SET_RENDER_HASH, toggle, radarThickness || RADAR_THICKNESS, mapThickness || MAP_THICKNESS) 39 | } 40 | } 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | export class CustomRoute extends Route implements INTERFACES.Route { 46 | readonly CLEAR_HASH: string = "0xE6DE0561D9232A64" 47 | readonly START_HASH: string = "0xDB34E8D56FC13B08" 48 | readonly ADD_POINT_HASH: string = "0x311438A071DD9B1A" 49 | readonly SET_RENDER_HASH: string = "0x900086F371220B6F" 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | export class MultiRoute extends Route implements INTERFACES.Route { 56 | readonly CLEAR_HASH: string = "0x67EEDEA1B9BAFD94" 57 | readonly START_HASH: string = "0x3D3D15AF7BCAAF83" 58 | readonly ADD_POINT_HASH: string = "0xA905192A6781C41B" 59 | readonly SET_RENDER_HASH: string = "0x3DDA37128DD1ACA8" 60 | } -------------------------------------------------------------------------------- /clientside/src/managers/EventManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton, autoInjectable } from 'tsyringe' 2 | import { DummyMapManager } from './dummies/DummyMapManager' 3 | import { DummyConfigManager } from './dummies/DummyConfigManager' 4 | import { event, eventable } from 'rage-decorators' 5 | import { DummyPlayerStatManager } from './dummies/DummyPlayerStatManager' 6 | import { ErrorHandler } from '../core/ErrorHandler' 7 | import { PlayerManager } from './PlayerManager' 8 | import { DummyRoundStatManager } from './dummies/DummyRoundStatManager' 9 | import { DummyLanguageManager } from './dummies/DummyLanguageManager' 10 | 11 | /** 12 | * Class to invoke complex managers by some events 13 | */ 14 | @singleton() 15 | @eventable() 16 | @autoInjectable() 17 | class EventManager { 18 | constructor( 19 | readonly dummyMapManager: DummyMapManager, 20 | readonly dummyConfigManager: DummyConfigManager, 21 | readonly dummyStatManager: DummyPlayerStatManager, 22 | readonly dummyRoundStatManager: DummyRoundStatManager, 23 | readonly dummyLanguageManager: DummyLanguageManager, 24 | readonly playerManager: PlayerManager, 25 | readonly errHandler: ErrorHandler, 26 | ) { 27 | this.serverPlayerReady = this.serverPlayerReady.bind(this) 28 | } 29 | 30 | /** 31 | * Event 32 | * 33 | * Fires when the server says that player is ready 34 | */ 35 | @event(SHARED.EVENTS.SERVER_PLAYER_READY) 36 | serverPlayerReady(): void { 37 | // register dummies 38 | this.dummyLanguageManager.registerDummies() 39 | this.dummyMapManager.registerDummies() 40 | this.dummyConfigManager.registerDummies() 41 | this.dummyStatManager.registerDummies() 42 | this.dummyRoundStatManager.registerDummies() 43 | 44 | // init shareddata 45 | this.playerManager.initData() 46 | 47 | // init language 48 | const defaultLanguage = this.dummyConfigManager.getDefaultLanguage() 49 | this.playerManager.loadLanguage(defaultLanguage) 50 | 51 | // call en event that all dummies are registered 52 | mp.events.call(SHARED.EVENTS.CLIENT_DUMMIES_READY) 53 | } 54 | } 55 | 56 | export { EventManager } -------------------------------------------------------------------------------- /clientside/src/managers/WeaponManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton, injectable } from "tsyringe" 2 | import { DummyConfigManager } from "./dummies/DummyConfigManager" 3 | import { ErrorHandler } from "../core/ErrorHandler" 4 | import { weapons } from "../declarations/weapons" 5 | 6 | @singleton() 7 | @injectable() 8 | class WeaponManager { 9 | constructor ( 10 | readonly configManager : DummyConfigManager, 11 | readonly errHandler : ErrorHandler, 12 | ) {} 13 | 14 | /** 15 | * Calculates a damage from config 16 | * @param {RageEnums.Hashes.Weapon} weapon 17 | */ 18 | calculateDamage(weapon: RageEnums.Hashes.Weapon): number | false { 19 | try { 20 | const weaponName = this.getWeaponName(weapon) 21 | if (!weaponName) return false 22 | 23 | const config = this.configManager.getDamageConfig() 24 | 25 | if (typeof config.SPECIFIC[weaponName] !== 'undefined') { 26 | return config.SPECIFIC[weaponName] 27 | } 28 | 29 | const category = this.getWeaponCategory(weaponName) 30 | if (typeof config.GROUP[category] !== 'undefined') { 31 | return config.GROUP[category] 32 | } 33 | 34 | return false 35 | } catch (err) { 36 | if (!this.errHandler.handle(err)) throw err 37 | 38 | return false 39 | } 40 | } 41 | 42 | /** 43 | * Get a weapon name, if exists 44 | * 45 | * @param {RageEnums.Hashes.Weapon} weapon 46 | */ 47 | getWeaponName(weapon: RageEnums.Hashes.Weapon): string | undefined { 48 | const weaponName = weapons[weapon] 49 | if (!weaponName) return undefined 50 | 51 | return 'weapon_' + weaponName.replace('weapon_', '') 52 | } 53 | 54 | /** 55 | * Get a category of the weapon 56 | * @param {string} weaponName - a weapon's name 57 | */ 58 | getWeaponCategory(weaponName: string): string { 59 | const weapons = this.configManager.getWeapons() 60 | 61 | const [[ category ]] = Object 62 | .entries(weapons) 63 | .filter(([category, weaponSets]) => weaponSets.indexOf(weaponName) !== -1) 64 | 65 | return category 66 | } 67 | } 68 | 69 | export { WeaponManager } -------------------------------------------------------------------------------- /cef/src/GameMenu/Credits/Changes020.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | 4 | export default function Changes020() { 5 | return ( 6 | 7 | + Changed language system
8 | + Moved lang from clientside to serverside
9 | Now available adding new language(s) without build gamemode.
10 | Just create a file in the lang folder calls e.g. "custom.json" and set up config with the new language "custom"
11 | + HUD for team selecting (params are editable in config)
12 | + HUD for vote map (params are editable in config)
13 | + HUD for Nametag with team color (params are editable in config)
14 | + HUD for round info panel
15 | + HUD for any notify messages
16 | + Text chat
17 | + Added RCON password in config.json
18 | You should change rcon password otherwise you will have an error
19 | + Add groups system [USER, MODERATORS, ADMIN, ROOT]
20 | Commands:
21 | /g rcon [password] - grant super privileges
22 | /g login [password] - login as admin or moderator
23 | /g addadm [id] [password] - (root only) add a new admin by id
24 | /g addmod [id] [password] - (root and admin) add a new moderator by id
25 | /g pwd [password] - change a password for login
26 | /g user [id] - set a user group for player by id
27 | + Added admin/root access to commands:
28 | /roundstart
29 | /roundend
30 | + Fixed cmdlist command
31 | + Refactoring some parts of code in clientside, serverside
32 | + Added kick command
33 | /kick [idOrName] [reason] (root and admin and moderator only) - Kick player by reason
34 | + Added mute command (root and admin and moderator only) - Mute player by reason
35 | /mute [idOrName] [minutes] [reason]
36 | + Added unmute command (root and admin and moderator only) - Unmute player
37 | /unmute [idOrName]
38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /cef/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 15 | 16 | 25 | 26 | React App 27 | 28 | 29 | 30 |
31 | 41 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /cef/src/Deathlog/DeathlogList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Box from '@material-ui/core/Box' 4 | import Deathlog, { DeathlogProps } from './Deathlog' 5 | import { useInterval } from '../lib/utils' 6 | import { register } from 'rage-rpc' 7 | import { RPC } from '../events' 8 | 9 | const useStyles = makeStyles({ 10 | root: { 11 | position: 'absolute', 12 | right: 0, 13 | marginRight: 5, 14 | top: '5%', 15 | color: 'white', 16 | display: 'flex', 17 | flexDirection: 'column', 18 | alignItems: 'flex-end', 19 | }, 20 | }) 21 | 22 | const alive = 5000 23 | 24 | export type DeathlogItem = DeathlogProps & { id: number, created: number } 25 | 26 | /** 27 | * Deathlog list component 28 | */ 29 | export default function DeathlogList() { 30 | const [items, setItems] = React.useState([]) 31 | const [render, setRender] = React.useState(false) 32 | 33 | const classes = useStyles() 34 | 35 | React.useEffect(() => { 36 | if (!render) { 37 | setRender(true) 38 | 39 | register(RPC.CLIENT_DEATHLOG, ([ deathlog ]) => { 40 | const created = Date.now() 41 | const newItem: DeathlogItem = {...deathlog, ...{ id: created, created, checked: true }} 42 | setItems(items => items.concat(newItem)) 43 | }) 44 | } 45 | }, [render, items]) 46 | 47 | useInterval(() => { 48 | if (items.length) { 49 | let changed = 0 50 | const newItems = items.map(item => { 51 | if (item.checked && Date.now() - item.created > alive) { 52 | item.checked = false 53 | changed++ 54 | } 55 | 56 | return item 57 | }) 58 | 59 | if (changed) { 60 | setItems(newItems) 61 | } else { 62 | const visibleItems = items.filter(item => item.checked) 63 | if (visibleItems.length !== items.length) { 64 | setItems(items => items.filter(item => item.checked)) 65 | } 66 | } 67 | } 68 | }, 1000) 69 | 70 | return ( 71 | 72 | {items.map((item) => )} 73 | 74 | ) 75 | } -------------------------------------------------------------------------------- /serverside/src/entities/validators/Validator.ts: -------------------------------------------------------------------------------- 1 | import { validatorFactory } from '../../validators/validatorFactory' 2 | import { InvalidArgument } from '../../errors/LogErrors' 3 | import { keys } from '../../utils' 4 | import { DEBUG } from '../../bootstrap' 5 | 6 | /** 7 | * Class to validate a data which passed in the player 8 | */ 9 | class Validator { 10 | /** 11 | * Key value collection to flag a validator function from the factory 12 | */ 13 | protected validators: KeyValueCollection = {} 14 | /** 15 | * Storage the validated values 16 | */ 17 | protected _validated: Partial = {} 18 | 19 | constructor(private readonly dto: T) {} 20 | 21 | /** 22 | * Validate all values which passed into the class 23 | */ 24 | isValid(): boolean { 25 | keys(this.dto) 26 | .filter(key => { 27 | // get a validator name 28 | const validatorName = this.validators[key as string] 29 | 30 | if (typeof validatorName === 'undefined') { 31 | throw new InvalidArgument(`Key validator ${key} doesn't exists in ${keys(this.validators).join(', ')}`) 32 | } 33 | 34 | // make an validator function 35 | const validator = validatorFactory(validatorName) 36 | 37 | if (typeof validator !== 'function') { 38 | throw new InvalidArgument(`Validator ${validatorName} is not a function`) 39 | } 40 | 41 | // grab the a data by key 42 | const value = this.dto[key] 43 | 44 | // check if the data is correct 45 | const isValidValue = (typeof value !== 'undefined') && validatorFactory(validatorName)(value) 46 | 47 | // print to console if data is invalid and we're in debug mode 48 | if (DEBUG && !isValidValue) { 49 | console.debug(this.constructor.name, 'INVALID_DATA', key, value, validatorName) 50 | } 51 | 52 | return isValidValue 53 | }) 54 | .forEach(key => this.validated[key] = this.dto[key]) 55 | 56 | return !!keys(this.validated).length 57 | } 58 | 59 | /** 60 | * Return the validated state 61 | */ 62 | get validated(): Partial { 63 | return this._validated 64 | } 65 | } 66 | 67 | export { Validator } -------------------------------------------------------------------------------- /serverside/src/core/Config.ts: -------------------------------------------------------------------------------- 1 | import { sync } from 'glob' 2 | import { resolve } from 'path' 3 | import { red } from 'colors' 4 | import { logMethod, getJsonFromFileSync, getSrcDir } from '../utils/index' 5 | import { DEBUG } from '../bootstrap' 6 | import { singleton } from 'tsyringe' 7 | 8 | /** 9 | * Config class of the app 10 | */ 11 | @singleton() 12 | class Config extends Object { 13 | [key: string]: any 14 | globPatterns: any = { 15 | "managers": "**/*Manager.js", 16 | "repositories": "**/*Repo.js", 17 | } 18 | 19 | constructor(configPath?: string | object) { 20 | super() 21 | try { 22 | this.load(configPath) 23 | this.resolveGlobPatterns(this.globPatterns) 24 | } catch (err) { 25 | console.warn(red('[WARNING]'), "CONFIG NOT LOADED: ", err) 26 | } 27 | } 28 | 29 | /** 30 | * Load config json 31 | * 32 | * @param configPath may be path of config or being json object 33 | */ 34 | @logMethod(DEBUG) 35 | load(configPath?: string | object): void { 36 | const params = typeof configPath === 'string' && getJsonFromFileSync(configPath) || configPath 37 | Object.entries(params).forEach(([key, value]) => this.set(key, value)) 38 | } 39 | 40 | /** 41 | * Get config value 42 | * @param {string} key key of config value 43 | */ 44 | @logMethod(DEBUG) 45 | get(key: string): any { 46 | return this.hasOwnProperty(key) && this[key] 47 | } 48 | 49 | @logMethod(DEBUG) 50 | private set(key: string, value: any): void { 51 | Object.defineProperty(this, key, { value, enumerable: true }) 52 | } 53 | 54 | /** 55 | * Resolve self glob patterns 56 | * @param {any} patterns 57 | */ 58 | @logMethod(DEBUG) 59 | private resolveGlobPatterns(patterns: any): void { 60 | const srcDir: string = getSrcDir() 61 | 62 | /* resolve globPatterns and fill the object by resolved patterns */ 63 | this.globPatterns = Object 64 | .entries(patterns) 65 | .reduce((carry: any, currentValue) => { 66 | const [key, pattern]: [string, any] = currentValue 67 | 68 | carry[key] = sync(resolve(srcDir, pattern)) 69 | 70 | return carry 71 | }, {}) 72 | } 73 | } 74 | 75 | export { Config } -------------------------------------------------------------------------------- /cef/src/GameMenu/Actions.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'react' 2 | import { GAMEMENU, GameMenuAction } from "./Reducer" 3 | import { callServer, callClient } from "rage-rpc" 4 | import { RPC } from "../events" 5 | 6 | export type Action = object | Function 7 | export type Actions = { [key in GAMEMENU]: Action } 8 | 9 | /** 10 | * Async dispatch handler 11 | * @param {GAMEMENU} type 12 | * @param {Dispatch} dispatch 13 | * @param {Promise | any} payload 14 | */ 15 | const asyncDispatch = async (type: GAMEMENU, dispatch: Dispatch, payload: Promise | any) => { 16 | if (payload instanceof Promise) payload = await Promise.resolve(payload) 17 | 18 | return dispatch({ type, payload }) 19 | } 20 | 21 | /* register requests as action to dispatch a state */ 22 | export const profileRequest = (dispatch: Dispatch, id?: number) => asyncDispatch(GAMEMENU.PROFILE, dispatch, callServer(RPC.CEF_GAMEMENU_PROFILE, id)) 23 | export const playersRequest = (dispatch: Dispatch) => asyncDispatch(GAMEMENU.PLAYERS, dispatch, callServer(RPC.CEF_GAMEMENU_PLAYERS)) 24 | export const historyRequest = (dispatch: Dispatch) => asyncDispatch(GAMEMENU.HISTORY, dispatch, callServer(RPC.CEF_GAMEMENU_HISTORY)) 25 | export const voteRequest = (dispatch: Dispatch) => asyncDispatch(GAMEMENU.VOTE, dispatch, callClient(RPC.CEF_GAMEMENU_VOTE)) 26 | export const topRequest = (dispatch: Dispatch) => asyncDispatch(GAMEMENU.TOP, dispatch, callServer(RPC.CEF_GAMEMENU_TOP)) 27 | export const creditsRequest = (dispatch: Dispatch) => asyncDispatch(GAMEMENU.CREDITS, dispatch, callClient(RPC.CEF_GAMEMENU_CREDITS)) 28 | 29 | /* list of refreshing actions (update per click on refresh button) */ 30 | export const refreshActions: Partial = { 31 | [GAMEMENU.HISTORY] : historyRequest, 32 | [GAMEMENU.TOP] : topRequest, 33 | } 34 | 35 | /* list of change actions (update per change a tab in tab panel) */ 36 | export const changeActions: Partial = { 37 | [GAMEMENU.PROFILE] : profileRequest, 38 | [GAMEMENU.PLAYERS] : playersRequest, 39 | [GAMEMENU.VOTE] : voteRequest, 40 | } -------------------------------------------------------------------------------- /cef/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /cef/src/MapEditor/SpawnVectorList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import List from '@material-ui/core/List' 3 | import ListItem from '@material-ui/core/ListItem' 4 | import ListItemText from '@material-ui/core/ListItemText' 5 | import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' 6 | import IconButton from '@material-ui/core/IconButton' 7 | import DeleteIcon from '@material-ui/icons/Delete' 8 | import { callClient } from 'rage-rpc' 9 | import { RPC_DIALOG } from '../events' 10 | import { SpawnVector } from './MapEditor' 11 | 12 | interface Props { 13 | className: string 14 | disabled: boolean 15 | points: SpawnVector 16 | } 17 | /** 18 | * SpawnVector list component 19 | * @param {Props} props 20 | */ 21 | export default function SpawnVectorList(props: Props) { 22 | const { className, disabled, points: { 23 | ATTACKERS, 24 | DEFENDERS, 25 | } } = props 26 | 27 | if (!ATTACKERS.length && !DEFENDERS.length) return <> 28 | 29 | const removePointRequest = (team: 'ATTACKERS' | 'DEFENDERS', index: number) => { 30 | callClient(RPC_DIALOG.CEF_MAP_EDITOR_REMOVE_SPAWN_POINT, [team, index]) 31 | } 32 | 33 | return ( 34 | 35 | {ATTACKERS.map((point, index) => ( 36 | 37 | 38 | 39 | removePointRequest('ATTACKERS', index)} disabled={disabled} edge="end" aria-label="delete"> 40 | 41 | 42 | 43 | 44 | ))} 45 | {DEFENDERS.map((point, index) => ( 46 | 47 | 48 | 49 | removePointRequest('DEFENDERS', index)} disabled={disabled} edge="end" aria-label="delete"> 50 | 51 | 52 | 53 | 54 | ))} 55 | 56 | ) 57 | } -------------------------------------------------------------------------------- /clientside/src/managers/DialogManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton, injectable } from "tsyringe" 2 | import { BrowserManager } from "./BrowserManager" 3 | import { ErrorHandler } from "../core/ErrorHandler" 4 | 5 | /** 6 | * Class to manage UI dialogs 7 | */ 8 | @singleton() 9 | @injectable() 10 | class DialogManager { 11 | private toggleMap: { [key: string]: boolean } = {} 12 | 13 | constructor( 14 | readonly browserManager: BrowserManager, 15 | readonly errHandler: ErrorHandler, 16 | ) {} 17 | 18 | /** 19 | * Call the dialog by RPC_OPEN_DIALOG with args 20 | * @param {SHARED.RPC_DIALOG} RPC_OPEN_DIALOG 21 | * @param {any[]} args - params which whill be passed in to the dialog 22 | */ 23 | call(RPC_OPEN_DIALOG: SHARED.RPC_DIALOG, ...args: any[]): Promise | undefined { 24 | try { 25 | return this.browserManager.callBrowser(ENUMS.CEF.MAIN, RPC_OPEN_DIALOG, ...args) 26 | } catch (err) { 27 | if (!this.errHandler.handle(err)) throw err 28 | } 29 | } 30 | 31 | /** 32 | * Toggle the dialog by RPC_TOGGLE_DIALOG 33 | */ 34 | toggle(RPC_TOGGLE_DIALOG: SHARED.RPC_DIALOG) { 35 | try { 36 | if (typeof this.toggleMap[RPC_TOGGLE_DIALOG] === 'undefined') { 37 | this.toggleMap[RPC_TOGGLE_DIALOG] = false 38 | } 39 | 40 | this.toggleMap[RPC_TOGGLE_DIALOG] = !this.toggleMap[RPC_TOGGLE_DIALOG] 41 | 42 | if (this.toggleMap[RPC_TOGGLE_DIALOG]) { 43 | this.onOpen() 44 | } else { 45 | this.onClose() 46 | } 47 | 48 | this.browserManager.callBrowser(ENUMS.CEF.MAIN, RPC_TOGGLE_DIALOG, this.toggleMap[RPC_TOGGLE_DIALOG]) 49 | } catch (err) { 50 | if (!this.errHandler.handle(err)) throw err 51 | } 52 | } 53 | 54 | /** 55 | * @todo change it when the callProc will available in prerelase or above version 56 | * 57 | * RPC 58 | * 59 | * Fires from CEF when the dialog is opened 60 | */ 61 | onOpen(): void { 62 | mp.gui.cursor.visible = true 63 | } 64 | 65 | /** 66 | * @todo change it when the callProc will available in prerelase or above version 67 | * 68 | * RPC 69 | * 70 | * Fires from CEF when the dialog is closed 71 | */ 72 | onClose(): void { 73 | mp.gui.cursor.visible = false 74 | } 75 | } 76 | 77 | export { DialogManager } -------------------------------------------------------------------------------- /clientside/src/managers/RpcManager.ts: -------------------------------------------------------------------------------- 1 | import { injectable, singleton } from 'tsyringe' 2 | import { DialogManager } from './DialogManager' 3 | import { register } from 'rage-rpc' 4 | import { DummyMapManager } from './dummies/DummyMapManager' 5 | import { DummyConfigManager } from './dummies/DummyConfigManager' 6 | import { MapEditor } from './rpcs/MapEditor' 7 | import { MechanicsManager } from './MechanicsManager' 8 | import { ErrorHandler } from '../core/ErrorHandler' 9 | import { DummyLanguageManager } from './dummies/DummyLanguageManager' 10 | 11 | /** 12 | * Class to manage rpc calss 13 | */ 14 | @injectable() 15 | @singleton() 16 | class RpcManager { 17 | private mapEditorHandler: MapEditor 18 | 19 | constructor( 20 | readonly dialogManager: DialogManager, 21 | readonly dummyMapManager: DummyMapManager, 22 | readonly dummyConfigManager: DummyConfigManager, 23 | readonly mechanicsManager: MechanicsManager, 24 | readonly errHandler: ErrorHandler, 25 | readonly lang: DummyLanguageManager 26 | ) { 27 | this.mapEditorHandler = new MapEditor(mechanicsManager, errHandler, dialogManager, lang) 28 | } 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | load(): void { 34 | // dialogManager 35 | register(SHARED.RPC_DIALOG.CLIENT_DIALOG_OPEN, this.dialogManager.onOpen) 36 | register(SHARED.RPC_DIALOG.CLIENT_DIALOG_CLOSE, this.dialogManager.onClose) 37 | 38 | // CEF GameMenu 39 | register(SHARED.RPC.CEF_GAMEMENU_VOTE, this.dummyMapManager.cefVoteRequest) 40 | register(SHARED.RPC.CEF_GAMEMENU_CREDITS, this.dummyConfigManager.cefCreditsRequest) 41 | 42 | // map editor 43 | register(SHARED.RPC_DIALOG.CEF_MAP_EDITOR_RESET, this.mapEditorHandler.resetRequest) 44 | register(SHARED.RPC_DIALOG.CEF_MAP_EDITOR_SAVE, this.mapEditorHandler.saveRequest) 45 | register(SHARED.RPC_DIALOG.CEF_MAP_EDITOR_ADD_POINT, this.mapEditorHandler.addPointRequest) 46 | register(SHARED.RPC_DIALOG.CEF_MAP_EDITOR_REMOVE_POINT, this.mapEditorHandler.removePointRequest) 47 | register(SHARED.RPC_DIALOG.CEF_MAP_EDITOR_ADD_SPAWN_POINT, this.mapEditorHandler.addSpawnPointRequest) 48 | register(SHARED.RPC_DIALOG.CEF_MAP_EDITOR_REMOVE_SPAWN_POINT, this.mapEditorHandler.removeSpawnPointRequest) 49 | register(SHARED.RPC_DIALOG.CEF_MAP_EDITOR_UPDATE_CLIENT, this.mapEditorHandler.updateStateRequest) 50 | } 51 | } 52 | 53 | export { RpcManager } -------------------------------------------------------------------------------- /cef/src/GameMenu/Credits/Changes030.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typography from '@material-ui/core/Typography' 3 | 4 | export default function Changes030() { 5 | return ( 6 | 7 | + Fixed teamselector bug with double binding keys
8 | + Fixed bug when round timer was not being synced with client/server
9 | + Added Controls hud element
10 | + Added Gamemode hud element
11 | + Added Deathlog hud element
12 | + Added round pause and unpause commands:
13 | /pause - Pause a round when the round is running
14 | /unpause - Unpause a round when the round is running
15 | + Added change team command:
16 | /ct [id|nickname] [att|def|spec] - Change a team by player id/nickname, alias /changeteam
17 | + Added add/remove in/from a round:
18 | /add [id|nickname] - Add a player to the round
19 | /remove [id|nickname] - Remove a player from the round
20 | + Added Gamemenu tabs:
21 | 1. Profile tab - a tab with profile information of player
22 | 2. Players tab - a tab with list of current players (clickable to a player to get a profile)
23 | 3. History tab - a tab with history of matches (at this moment not clickable, soon)
24 | 4. Vote tab - a tab with voting for the map
25 | 5. Top tab - a tab with top of players, sorted by mmr (at this moment no finished, soon)
26 | 6. Credits tab - a tab with information about gamemode author, changelog
27 | Gamemenu available by F2 button
28 | + Rework round players' statistic, moved from round players teamId to sharedData
29 | Breaking changes: round stats now saved with another json object which looks like:
30 | winner, created_at, ATTACKERS: [], DEFENDERS: []
31 | + Fixed teamId state in Scoreboard tab
32 | + Fixed changelang description
33 | + Fixed error on client when round is running stats has been saving
34 | + Fixed assist calculation on clientside
35 | + Fixed bug with checking state of a player (typeof undefined)
36 | + Added player join/quit notifications in chat
37 | + Fixed vote bug, when player has voted but at finished time round has not started
38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /clientside/src/declarations/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace INTERFACES { 2 | /** 3 | * Implemenation to manage the gamemap functions 4 | */ 5 | interface Manager { 6 | /** 7 | * Register all events from the server 8 | */ 9 | load(): void 10 | } 11 | 12 | interface Route { 13 | /** 14 | * Clear all points and stop render a route 15 | */ 16 | clear(): void 17 | /** 18 | * Start describe process to show a route 19 | * @param {number} hudColor The HUD color of the GPS path 20 | * @param {boolean} displayOnFoot Draws the path regardless if the player is in a vehicle or not. 21 | * @param {boolean} followPlayer Draw the path partially between the previous and next point based on the players position between them. When false, the GPS appears to not disappear after the last leg is completed. 22 | */ 23 | start(hudColor?: number, displayOnFoot?: boolean, followPlayer?: boolean): void 24 | /** 25 | * Add points to the GPS path 26 | * @param {CW.TYPES.Vector2} vector 27 | */ 28 | addPoint(vector: SHARED.TYPES.Vector2): void 29 | /** 30 | * Set render on the map and minimap the GPS path 31 | * @param {boolean} toggle 32 | * @param {number} radarThickness 33 | * @param {number} mapThickness 34 | */ 35 | setRender(toggle: boolean, radarThickness?: number, mapThickness?: number): void 36 | } 37 | 38 | interface Render2DParamsFullScreen { 39 | color?: RGBA 40 | } 41 | 42 | interface Render2DParams { 43 | color?: RGBA 44 | position: { x: number, y: number } 45 | size?: { w: number, h: number } 46 | p9?: number 47 | } 48 | 49 | interface Render3DParams { 50 | position: Vector3Mp 51 | rotation: Vector3Mp 52 | scale: Vector3Mp 53 | color?: RGB 54 | p13?: number 55 | } 56 | 57 | interface Interaction { 58 | start(listener ?: Function): void 59 | stop(listener ?: Function): void 60 | } 61 | 62 | interface HudElement { 63 | /** 64 | * Start render the hud element 65 | * @param args - (optional) args to pass in the start method 66 | */ 67 | start(...args: any[]): void 68 | /** 69 | * Stop render the hud element 70 | * @param args - (optional) args to pass in the stop method 71 | */ 72 | stop(...args: any[]): void 73 | } 74 | 75 | interface TextParams { 76 | font: number 77 | centre: boolean 78 | color: RGBA 79 | scale: Array2d 80 | outline: boolean 81 | } 82 | } -------------------------------------------------------------------------------- /serverside/src/validators/validatorFactory.ts: -------------------------------------------------------------------------------- 1 | import { Point, SpawnVectorState } from "../entities/validators/MapEditorDataValidator" 2 | 3 | type Success = [true, T] 4 | type Failure = [false, any] 5 | type Result = Success | Failure 6 | type Validator = (x: any) => Result 7 | type Value> = Extract, [true, any]>[1] 8 | 9 | /** 10 | * Validators factory configure 11 | * @param validators 12 | */ 13 | const configure = >>(validators: V) => { 14 | return (key: K) => { 15 | return (x: any): x is Value => { 16 | const validator = validators[key] 17 | const [valid] = validator(x) 18 | return valid 19 | } 20 | } 21 | } 22 | 23 | const isArray3d = (x: Array3d) => x.filter(vec => typeof vec === 'number').length === 3 24 | const isVector = (x: any) => { 25 | console.debug('Is vector', x) 26 | return typeof x.x === 'number' 27 | && typeof x.y === 'number' 28 | && typeof x.z === 'number' 29 | && typeof x.toArray === 'function' 30 | && isArray3d(x.toArray()) 31 | } 32 | const isNumberObject = (x: any) => Object.values(x).filter(value => typeof value === 'number').length === Object.values(x).length 33 | 34 | const isPoint = (x: Point) => typeof x === 'object' && isVector(x.coord) && typeof x.name === 'string' 35 | const isPointArray = (x: Point[]) => x.filter(point => isPoint(point)).length === x.length 36 | const isSpawnVector = (x: SpawnVectorState) => ( 37 | typeof x[SHARED.TEAMS.ATTACKERS] !== 'undefined' 38 | && typeof x[SHARED.TEAMS.DEFENDERS] !== 'undefined' 39 | && Array.isArray(x[SHARED.TEAMS.ATTACKERS]) 40 | && Array.isArray(x[SHARED.TEAMS.DEFENDERS]) 41 | && isPointArray(x[SHARED.TEAMS.ATTACKERS]) 42 | && isPointArray(x[SHARED.TEAMS.DEFENDERS]) 43 | ) 44 | /** 45 | * Validators factory 46 | */ 47 | export const validatorFactory = configure({ 48 | number: (x: any) => typeof x === 'number' ? [true, x] : [false, 'Invalid!'], 49 | string: (x: any) => typeof x === 'string' ? [true, x]: [false, 'Invalid'], 50 | vector: (x: any) => isVector(x) ? [true, x]: [false, 'Invalid!'], 51 | array3d: (x: Array3d) => isArray3d(x) ? [true, x] : [false, 'Invalid!'], 52 | numberObject: (x: any) => isNumberObject(x) ? [true, x] : [false, 'Invalid!'], 53 | point: (x: Point) => isPoint(x) ? [true, x] : [false, 'Invalid!'], 54 | pointArray: (x: Point[]) => isPointArray(x) ? [true, x] : [false, 'Invalid'], 55 | spawn: (x: SpawnVectorState) => isSpawnVector(x) ? [true, x] : [false, 'Invalid!'], 56 | }) -------------------------------------------------------------------------------- /serverside/src/managers/dummies/DummyConfigManager.ts: -------------------------------------------------------------------------------- 1 | import { singleton, injectable, inject } from "tsyringe" 2 | import { Dummy } from "../../entities/Dummy" 3 | import { Config } from "../../core/Config" 4 | import { WeaponManager } from "../WeaponManager" 5 | import { getRandomInt } from "../../utils" 6 | import { IsNotExistsError } from "../../errors/LogErrors" 7 | import { Application } from "../../core/Application" 8 | 9 | /** 10 | * Class to manage config through the dummy 11 | */ 12 | @injectable() 13 | @singleton() 14 | class DummyConfigManager implements INTERFACES.Manager { 15 | private _dummy?: Dummy 16 | 17 | constructor( 18 | @inject(Config) private readonly config: Config, 19 | private readonly weaponManager: WeaponManager, 20 | ) {} 21 | 22 | /** 23 | * Load config to players from the server 24 | */ 25 | load(): void { 26 | this._dummy = new Dummy(SHARED.ENTITIES.CONFIG, { 27 | SERVER_NAME : mp.config.name, 28 | LOBBY : this.config.get('LOBBY'), 29 | TEAMS : this.config.get('TEAMS'), 30 | WEAPONS : this.config.get('WEAPONS'), 31 | WEAPON_SET : this.weaponManager.weaponSet, 32 | WEAPON_DAMAGE : this.config.get('WEAPON_DAMAGE'), 33 | TEAM_SELECTOR : this.config.get('TEAM_SELECTOR'), 34 | LANGUAGE : this.config.get('LANGUAGE'), 35 | ROUND_TIME_INTERVAL : this.config.get('ROUND_TIME_INTERVAL_MINUTES'), 36 | VOTE : this.config.get('VOTE'), 37 | HUD : this.config.get('HUD'), 38 | GAMEMODE : Application.GAMEMODE, 39 | VERSION : Application.VERSION, 40 | EFFECTS : this.config.get('EFFECTS'), 41 | }) 42 | } 43 | 44 | /** 45 | * Getting random skin by teamId 46 | * @param {SHARED.TEAMS} teamId 47 | */ 48 | getRandomSkin(teamId: SHARED.TEAMS): string { 49 | const skins = this.dummy.data.TEAMS[teamId].SKINS 50 | 51 | return skins[getRandomInt(skins.length)] 52 | } 53 | 54 | /** 55 | * Getting team data by teamId 56 | * @param {SHARED.TEAMS} teamId 57 | */ 58 | getTeamData(teamId: T): SHARED.TYPES.Teams[T] { 59 | return this.dummy.data.TEAMS[teamId] 60 | } 61 | 62 | get dummy() { 63 | if (!this._dummy) throw new IsNotExistsError("Dummy not found") 64 | 65 | return this._dummy 66 | } 67 | 68 | /** 69 | * Get an effects 70 | */ 71 | getEffects(): SHARED.TYPES.EffectsConfig { 72 | return this.dummy.data.EFFECTS 73 | } 74 | } 75 | 76 | export { DummyConfigManager } -------------------------------------------------------------------------------- /cef/src/GameMenu/Credits/Changelog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Theme, createStyles, makeStyles } from '@material-ui/core/styles' 3 | import Typography from '@material-ui/core/Typography' 4 | import Accordion from '@material-ui/core/Accordion' 5 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore' 6 | import { StyledAccordionSummary, StyledAccordionDetails } from '../../Theme/Dark/AccordionComponents' 7 | import Changes020 from './Changes020' 8 | import Changes030 from './Changes030' 9 | import Changes040 from './Changes040' 10 | import Changes050 from './Changes050' 11 | 12 | const useStyles = makeStyles((theme: Theme) => 13 | createStyles({ 14 | root: { 15 | width: '100%', 16 | '& .MuiPaper-root': { 17 | backgroundColor: 'transparent', 18 | color: 'white', 19 | }, 20 | }, 21 | heading: { 22 | fontSize: theme.typography.pxToRem(15), 23 | fontWeight: theme.typography.fontWeightRegular, 24 | }, 25 | }), 26 | ) 27 | 28 | /** 29 | * Changelog component 30 | */ 31 | export default function Changelog() { 32 | const classes = useStyles() 33 | 34 | return ( 35 |
36 | 37 | } 39 | > 40 | 0.5.0 changes 41 | 42 | 43 | 44 | 45 | 46 | 47 | } 49 | > 50 | 0.4.0 changes 51 | 52 | 53 | 54 | 55 | 56 | 57 | } 59 | > 60 | 0.3.0 changes 61 | 62 | 63 | 64 | 65 | 66 | 67 | } 69 | > 70 | 0.2.0 changes 71 | 72 | 73 | 74 | 75 | 76 |
77 | ) 78 | } -------------------------------------------------------------------------------- /serverside/src/managers/dummies/DummyLanguageManager.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | import { sync } from 'glob' 3 | import { getProjectDir, getJsonFromFileSync } from "../../utils" 4 | import { Dummy } from "../../entities/Dummy" 5 | import { IsNotExistsError } from "../../errors/LogErrors" 6 | import { format } from "util" 7 | import { singleton } from "tsyringe" 8 | 9 | /** 10 | * Class to manage languages 11 | */ 12 | @singleton() 13 | class DummyLanguageManager implements INTERFACES.Manager { 14 | static readonly LANGUAGE_FOLDER = resolve(getProjectDir(), 'lang') 15 | 16 | private _dummy?: Dummy 17 | private loaded: boolean = false 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | load(): void { 23 | const languages = this.loadLanguages() 24 | 25 | this._dummy = new Dummy(SHARED.ENTITIES.LANGUAGE, languages) 26 | this.loaded = true 27 | } 28 | 29 | /** 30 | * Get message by lang and message id 31 | * 32 | * @param lang - language id 33 | * @param id - message id 34 | * @param args - (optional) arguments to format message 35 | */ 36 | get(lang: string, id: string, ...args: any[]): string { 37 | if (!this.loaded) { 38 | throw new Error("Dummy Language is not ready") 39 | } 40 | 41 | const messages = this.getMessages(lang) 42 | 43 | if (typeof messages === 'undefined') { 44 | throw new IsNotExistsError(`Language ${lang} is not exists`) 45 | } 46 | 47 | const message = messages[id] 48 | 49 | if (!message) return id 50 | 51 | return args.length && format(message, ...args) || message 52 | } 53 | 54 | /** 55 | * Get all messages by a language 56 | * @param {string} lang - langauge id 57 | */ 58 | getMessages(lang: string): any { 59 | return this.dummy.data[lang] 60 | } 61 | 62 | /** 63 | * Load all languages from folder LANGUAGE_FOLDER 64 | */ 65 | private loadLanguages(): KeyValueCollection { 66 | const pattern = '*.json' 67 | 68 | const languages: KeyValueCollection = {} 69 | 70 | const files = sync(resolve(DummyLanguageManager.LANGUAGE_FOLDER, pattern)) 71 | 72 | const filepathRegexp = /^.*[\\\/]/ 73 | files.forEach(fullpath => { 74 | const languageId = fullpath 75 | .replace(filepathRegexp, '') 76 | .replace('.json', '') 77 | 78 | const messages = getJsonFromFileSync(fullpath) 79 | 80 | languages[languageId] = messages 81 | }) 82 | 83 | return languages 84 | } 85 | 86 | get dummy(): Dummy { 87 | if (!this._dummy) throw new TypeError("Invalid language dummy") 88 | 89 | return this._dummy 90 | } 91 | } 92 | 93 | export { DummyLanguageManager } -------------------------------------------------------------------------------- /cef/src/GameMenu/Vote/Vote.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles, withStyles } from '@material-ui/core/styles' 3 | import Box from '@material-ui/core/Box' 4 | import VoteCard, { VoteCardProps } from './VoteCard' 5 | import TextField from '@material-ui/core/TextField'; 6 | import { lang } from '../../lib/Language'; 7 | import { MSG } from '../../messages'; 8 | import { escapeRegExp } from '../../lib/utils'; 9 | 10 | const CssTextFiled = withStyles({ 11 | root: { 12 | '& label': { 13 | color: 'gray', 14 | }, 15 | '& label.Mui-focused': { 16 | color: 'white', 17 | }, 18 | '& .MuiInput-underline:after': { 19 | borderBottomColor: 'white', 20 | }, 21 | '& .MuiOutlinedInput-root': { 22 | color: 'white', 23 | '& fieldset': { 24 | borderColor: 'gray', 25 | }, 26 | '&:hover fieldset': { 27 | borderColor: 'white', 28 | }, 29 | '&.Mui-focused fieldset': { 30 | borderColor: 'white', 31 | }, 32 | }, 33 | } 34 | })(TextField) 35 | 36 | const useStyles = makeStyles({ 37 | card: { 38 | display: 'flex' 39 | } 40 | }) 41 | 42 | export interface VoteProps { 43 | maps: VoteCardProps[] 44 | } 45 | 46 | /** 47 | * Vote main component 48 | * @param {VoteProps} props 49 | */ 50 | export default function Vote(props: VoteProps) { 51 | const initialMapState = props.maps || [] 52 | 53 | const [input, setInput] = React.useState("") 54 | const [maps, setMaps] = React.useState(initialMapState) 55 | const classes = useStyles() 56 | 57 | const handleChange = (event: React.ChangeEvent) => setInput(event.target.value) 58 | 59 | React.useEffect(() => { 60 | if (input.length) { 61 | const regexp = new RegExp(escapeRegExp(input), 'gi') 62 | const result = initialMapState.filter(map => map.code.match(regexp) || map.id.toString() === input) 63 | setMaps(result) 64 | } else { 65 | setMaps(initialMapState) 66 | } 67 | }, [input, initialMapState]) 68 | 69 | return ( 70 | 71 |
72 | 85 | 86 |
87 | {maps.map((mapProps, index) => ( 88 | 89 | ))} 90 |
91 |
92 | ) 93 | } -------------------------------------------------------------------------------- /cef/src/events.ts: -------------------------------------------------------------------------------- 1 | import { register } from "rage-rpc" 2 | 3 | export enum RPC { 4 | CLIENT_DEATHLOG = 'client.deathlog', 5 | CLIENT_WEAPON_REQUEST = 'CEF.weaponDialog.request', 6 | CLIENT_CONSOLE = 'CEF.console', 7 | CLIENT_LANGUAGE = 'CEF.language', 8 | 9 | CEF_GAMEMENU_PROFILE = 'CEF.gamemenu.profile', 10 | CEF_GAMEMENU_PLAYERS = 'CEF.gamemenu.players', 11 | CEF_GAMEMENU_HISTORY = 'CEF.gamemenu.history', 12 | CEF_GAMEMENU_VOTE = 'CEF.gamemenu.vote', 13 | CEF_GAMEMENU_VOTE_NOMINATE = 'CEF.gamemenu.vote.nominate', 14 | CEF_GAMEMENU_TOP = 'CEF.gamemenu.top', 15 | CEF_GAMEMENU_CREDITS = 'CEF.gamemenu.credits', 16 | } 17 | 18 | export enum RPC_DIALOG { 19 | CLIENT_DIALOG_OPEN = 'CEF.dialog.open', 20 | CLIENT_DIALOG_CLOSE = 'CEF.dialog.close', 21 | CLIENT_WEAPON_DIALOG_OPEN = 'CEF.weaponDialog.open', 22 | CLIENT_WEAPON_DIALOG_CLOSE = 'CEF.weaponDialog.close', 23 | CLIENT_SCOREBOARD_TOGGLE = 'CEF.scoreboard.toggle', 24 | CLIENT_SCOREBOARD_DATA = 'CEF.scoreboard.data', 25 | CLIENT_INFOPANEL_TOGGLE = 'CEF.infopanel.toggle', 26 | CLIENT_INFOPANEL_DATA = 'CEF.infopanel.data', 27 | CLIENT_NOTIFY_NOTISTACK = 'CEF.notify.notistack', 28 | CLIENT_GAMEMENU_TOGGLE = 'CEF.gamemenu.toggle', 29 | CLIENT_NOTIFY_ROUND_END = 'CEF.notify.round.end', 30 | CLIENT_NOTIFY_DEATH = 'CEF.notify.death', 31 | CLIENT_SPECTATE_CURRENT = 'CEF.spectate.current', 32 | CLIENT_SPECTATE_CURRENT_TOGGLE = 'CEF.spectate.current.toggle', 33 | CLIENT_SPECTATE_VIEWERS = 'CEF.spectate.viewers', 34 | CLIENT_SPECTATE_VIEWERS_TOGGLE = 'CEF.spectate.viewers.toggle', 35 | CLIENT_CONTROLS_TOGGLE = 'CEF.controls.toggle', 36 | CLIENT_MAP_EDITOR_TOGGLE = 'CEF.mapeditor.toggle', 37 | CLIENT_MAP_EDITOR_UPDATE = 'CEF.mapeditor.update', 38 | 39 | CEF_MAP_EDITOR_ADD_POINT = 'CEF.mapeditor.add.point', 40 | CEF_MAP_EDITOR_REMOVE_POINT = 'CEF.mapeditor.remove.point', 41 | CEF_MAP_EDITOR_ADD_SPAWN_POINT = 'CEF.mapeditor.add.spawnpoint', 42 | CEF_MAP_EDITOR_REMOVE_SPAWN_POINT = 'CEF.mapeditor.remove.spawnpoint', 43 | CEF_MAP_EDITOR_START = 'CEF.mapeditor.start', 44 | CEF_MAP_EDITOR_STOP = 'CEF.mapeditor.stop', 45 | CEF_MAP_EDITOR_RESET = 'CEF.mapeditor.reset', 46 | CEF_MAP_EDITOR_SAVE = 'CEF.mapeditor.save', 47 | CEF_MAP_EDITOR_UPDATE_CLIENT = 'CEF.mapeditor.client.update', 48 | } 49 | 50 | export const registerGlobalEvents = () => { 51 | register(RPC.CLIENT_CONSOLE, (args: any) => console.log(args)) 52 | } -------------------------------------------------------------------------------- /clientside/src/hud/TeamSelecting.ts: -------------------------------------------------------------------------------- 1 | import { Hud } from "./Hud" 2 | import { hex2rgba } from "../utils" 3 | import { Scaleform } from "../entities/Scaleform" 4 | 5 | /** 6 | * Hud element - Team selecting 7 | */ 8 | class TeamSelecting extends Hud { 9 | private teamName : string = 'unknown' 10 | private color : RGBA = [255, 255, 255, 255] 11 | private buttons? : Scaleform 12 | 13 | /** 14 | * @inheritdoc 15 | */ 16 | protected async prepare(): Promise { 17 | try { 18 | this.buttons = new Scaleform("INSTRUCTIONAL_BUTTONS") 19 | 20 | this.buttons.callFunction("CLEAR_ALL") 21 | this.buttons.callFunction("TOGGLE_MOUSE_BUTTONS", 0) 22 | this.buttons.callFunction("SET_CLEAR_SPACE", 100) 23 | 24 | const changeTeam = this.lang.get(SHARED.MSG.TEAM_SELECTOR_CHANGE_TEAM) 25 | const changeSkin = this.lang.get(SHARED.MSG.TEAM_SELECTOR_CHANGE_SKIN) 26 | const submit = this.lang.get(SHARED.MSG.TEAM_SELECTOR_SUBMIT) 27 | 28 | const vk_w_s = mp.game.controls.getControlActionName(2, RageEnums.Controls.FLY_UP_DOWN, true), 29 | vk_a_d = mp.game.controls.getControlActionName(2, RageEnums.Controls.FLY_LEFT_RIGHT, true), 30 | vk_submit = mp.game.controls.getControlActionName(2, RageEnums.Controls.FRONTEND_RDOWN, true) 31 | 32 | // we should wait before scaleforms will be initialized 33 | await new Promise(resolve => setTimeout(() => resolve(), 1000)) 34 | 35 | this.buttons.callFunction('SET_DATA_SLOT', 0, vk_submit, submit) 36 | this.buttons.callFunction('SET_DATA_SLOT', 1, vk_w_s, changeSkin) 37 | this.buttons.callFunction('SET_DATA_SLOT', 2, vk_a_d, changeTeam) 38 | 39 | this.buttons.callFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1) 40 | } catch (err) { 41 | if (!this.errHandler.handle(err)) throw err 42 | } 43 | } 44 | /** 45 | * @inheritdoc 46 | */ 47 | start(): void { 48 | mp.events.add(RageEnums.EventKey.RENDER, this.render) 49 | } 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | stop(): void { 55 | mp.events.remove(RageEnums.EventKey.RENDER, this.render) 56 | } 57 | 58 | /** 59 | * Change a team data for the render method 60 | * @param {SHARED.TEAMS} teamId 61 | */ 62 | setTeamData(teamId: SHARED.TEAMS): void { 63 | const team = this.dummyConfig.getTeam(teamId) 64 | this.teamName = team.NAME 65 | this.color = hex2rgba(team.COLOR) 66 | } 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | render(): void { 72 | try { 73 | mp.game.graphics.drawText(this.teamName, [0.5, 0.65], {...this.textParams, color: this.color }) 74 | if (this.buttons) this.buttons.render2DFullScreen() 75 | } catch (err) { 76 | if (!this.errHandler.handle(err)) throw err 77 | } 78 | } 79 | } 80 | 81 | export { TeamSelecting } -------------------------------------------------------------------------------- /cef/src/MapEditor/Body.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Grid from '@material-ui/core/Grid' 3 | import Typography from '@material-ui/core/Typography' 4 | import PointList from './PointList' 5 | import Button from '@material-ui/core/Button' 6 | import AddIcon from '@material-ui/icons/Add' 7 | import Select from '@material-ui/core/Select' 8 | import MenuItem from '@material-ui/core/MenuItem' 9 | import { MapEditorState } from './MapEditor' 10 | import { lang } from '../lib/Language' 11 | import { MSG } from '../messages' 12 | import { callClient } from 'rage-rpc' 13 | import { RPC_DIALOG } from '../events' 14 | import SpawnVectorList from './SpawnVectorList' 15 | 16 | interface Props { 17 | classes: any 18 | state: MapEditorState 19 | setState: React.Dispatch> 20 | } 21 | 22 | /** 23 | * The body part of map editor's form 24 | * 25 | * @param {Props} props 26 | */ 27 | export default function Body(props: Props) { 28 | const { classes, state: { 29 | editing, 30 | path, 31 | spawn, 32 | team, 33 | }, setState } = props 34 | 35 | // handler to change the current team flag 36 | const changeTeam = (team: 'ATTACKERS' | 'DEFENDERS') => setState(state => ({ ...state, team })) 37 | 38 | // handler to add a new point of map coordinates 39 | const addPointRequest = (type: 'spawn' | 'coords', team?: string) => { 40 | if (type === 'coords') { 41 | callClient(RPC_DIALOG.CEF_MAP_EDITOR_ADD_POINT) 42 | } else { 43 | callClient(RPC_DIALOG.CEF_MAP_EDITOR_ADD_SPAWN_POINT, team) 44 | } 45 | } 46 | 47 | return ( 48 | <> 49 | 50 | {lang.get(MSG.MAP_EDITOR_COORD_LABEL)} 51 | 52 | 53 | 54 | 55 | {lang.get(MSG.MAP_EDITOR_SPAWN_LABEL)} 56 | 57 | 58 | 59 | 69 | 70 | 71 | 72 | ) 73 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/History/History.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEvent } from 'react' 2 | import Table from '@material-ui/core/Table' 3 | import TableHead from '@material-ui/core/TableHead' 4 | import TableBody from '@material-ui/core/TableBody' 5 | import TableRow from '@material-ui/core/TableRow' 6 | import TablePagination from '@material-ui/core/TablePagination' 7 | import TableContainer from '@material-ui/core/TableContainer' 8 | import { StyledTableRow, StyledTableCell } from '../../Theme/Dark/TableComponents' 9 | import { lang } from '../../lib/Language' 10 | import { MSG } from '../../messages' 11 | import HistoryDetailWrapper from './HistoryDetailWrapper' 12 | import RefreshButton from '../../Common/RefreshButton' 13 | 14 | interface RoundProps { 15 | id: string 16 | result: string 17 | date: string 18 | kda: string 19 | } 20 | 21 | export interface HistoryProps { 22 | matches: RoundProps[] 23 | onRefresh?: (...args: any[]) => void 24 | } 25 | 26 | /** 27 | * Round history component 28 | * @param {HistoryProps} props 29 | */ 30 | export default function History(props: HistoryProps) { 31 | const rowsPerPage = 15 32 | const [page, setPage] = React.useState(0) 33 | const [id, setId] = React.useState(undefined) 34 | const handleChangePage = (event: unknown, newPage: number) => setPage(newPage) 35 | const handleClick = (_: MouseEvent, id?: string) => setId(id) 36 | 37 | if (typeof id === 'string') return 38 | 39 | const matches = props.matches.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) 40 | const { onRefresh } = props 41 | 42 | return ( 43 | <> 44 | {onRefresh && } 45 | 46 | 47 | 48 | 49 | {lang.get(MSG.GAMEMENU_HISTORY_TD_RESULT)} 50 | {lang.get(MSG.GAMEMENU_HISTORY_TD_DATE)} 51 | {lang.get(MSG.GAMEMENU_HISTORY_TD_KDA)} 52 | 53 | 54 | 55 | {matches.map((match, index) => ( 56 | handleClick(event, match.id)} key={index}> 57 | {match.result} 58 | {match.date} 59 | {match.kda} 60 | 61 | ))} 62 | 63 |
64 |
65 | 74 | 75 | ) 76 | } -------------------------------------------------------------------------------- /cef/src/GameMenu/Players/Players.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEvent } from 'react' 2 | import Table from '@material-ui/core/Table' 3 | import TableHead from '@material-ui/core/TableHead' 4 | import TableBody from '@material-ui/core/TableBody' 5 | import TableRow from '@material-ui/core/TableRow' 6 | import TablePagination from '@material-ui/core/TablePagination' 7 | import { StyledTableRow, StyledTableCell } from '../../Theme/Dark/TableComponents' 8 | import AccountBoxIcon from '@material-ui/icons/AccountBox' 9 | import { lang } from '../../lib/Language' 10 | import { MSG } from '../../messages' 11 | import PlayerDetail from './PlayerDetail' 12 | import RefreshButton from '../../Common/RefreshButton' 13 | 14 | interface PlayerProps { 15 | id: number 16 | name: string 17 | mmr: number 18 | } 19 | 20 | export interface PlayersProps { 21 | players: PlayerProps[] 22 | onRefresh?: (...args: any[]) => void 23 | } 24 | 25 | /** 26 | * Players table component 27 | * @param {PlayersProps} props 28 | */ 29 | export default function Players(props: PlayersProps) { 30 | const rowsPerPage = 12 31 | const [id, setId] = React.useState(undefined) 32 | const [page, setPage] = React.useState(0) 33 | const handleClick = (_: MouseEvent, id?: number) => setId(id) 34 | const handleChangePage = (event: unknown, newPage: number) => setPage(newPage) 35 | 36 | if (typeof id === 'number') return 37 | 38 | const players = props.players.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) 39 | const { onRefresh } = props 40 | 41 | return ( 42 | <> 43 | {onRefresh && } 44 | 45 | 46 | 47 | {lang.get(MSG.GAMEMENU_PLAYERS_TD_ID)} 48 | {lang.get(MSG.GAMEMENU_PLAYERS_TD_NAME)} 49 | {lang.get(MSG.GAMEMENU_PLAYERS_TD_MMR)} 50 | {lang.get(MSG.GAMEMENU_PLAYERS_TD_ACTIONS)} 51 | 52 | 53 | 54 | {players.map((player, index) => ( 55 | handleClick(event, player.id)} key={index}> 56 | {player.id} 57 | {player.name} 58 | {player.mmr} 59 | 60 | 61 | ))} 62 | 63 |
64 | 73 | 74 | ) 75 | } -------------------------------------------------------------------------------- /cef/src/Common/Controls.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Typography from '@material-ui/core/Typography' 4 | import { lang } from '../lib/Language' 5 | import clsx from 'clsx' 6 | 7 | const useStyles = makeStyles({ 8 | root: { 9 | display: 'flex', 10 | flexWrap: 'wrap', 11 | textShadow: 'none', 12 | }, 13 | horizontal: {}, 14 | vertical: { 15 | flexDirection: 'column', 16 | }, 17 | group: { 18 | display: 'inherit', 19 | margin: 2, 20 | alignItems: 'center', 21 | color: 'white', 22 | borderRadius: 3, 23 | }, 24 | medium: { 25 | padding: 5, 26 | }, 27 | small: { 28 | '& $span': { 29 | padding: 5, 30 | } 31 | }, 32 | span: { 33 | border: '1px solid white', 34 | borderRadius: 'inherit', 35 | padding: 'inherit', 36 | fontSize: 14, 37 | marginRight: 5, 38 | }, 39 | default: { 40 | background: 'rgba(0 0 0 / 55%)', 41 | }, 42 | outlined: { 43 | border: '1px solid rgba(0 0 0 / 55%)' 44 | }, 45 | bordered: { 46 | textShadow: '1px 1px 1px rgba(0 0 0 / 55%)', 47 | margin: 0, 48 | '& $span': { 49 | textShadow: '1px 1px 0px rgba(255 255 255 / 40%)', 50 | background: 'rgba(255 255 255 / 70%)', 51 | color: 'rgb(0 0 0 / 80%)', 52 | fontWeight: 600, 53 | boxShadow: '1px 1px 0px 2px rgb(0 0 0 / 55%)', 54 | border: 'none', 55 | padding: 2, 56 | } 57 | }, 58 | disabled: { 59 | opacity: .1, 60 | } 61 | }) 62 | 63 | export interface Control { 64 | input?: string[] 65 | label?: string 66 | disabled?: boolean 67 | } 68 | 69 | export interface ControlProps { 70 | controls?: Control[] 71 | direction?: 'horizontal' | 'vertical' 72 | variant?: 'default' | 'outlined' | 'bordered' 73 | size?: 'medium' | 'small' 74 | style?: any 75 | open?: boolean 76 | } 77 | 78 | function renderInputs(inputs: string[], className: string) { 79 | return ( 80 | <> 81 | {inputs.map((input, index) => {lang.get(input)})} 82 | 83 | ) 84 | } 85 | 86 | /** 87 | * Control component 88 | * 89 | * Showing control information 90 | * @param {ControlProps} props 91 | */ 92 | export default function Controls(props: ControlProps) { 93 | const classes = useStyles() 94 | const { controls, direction = 'horizontal', variant = 'default', style = {}, size = 'medium', open = true } = props 95 | 96 | if (typeof controls === 'undefined' || open === false) return <> 97 | 98 | return ( 99 |
100 | {controls.map(({ input, label, disabled }, index) => ( 101 |
102 | {input && renderInputs(input, classes.span)} 103 | {label && {lang.get(label)}} 104 |
105 | ))} 106 |
107 | ) 108 | } -------------------------------------------------------------------------------- /serverside/src/managers/WeaponManager.ts: -------------------------------------------------------------------------------- 1 | import { app } from "../bootstrap" 2 | import { singleton, inject, injectable, delay } from "tsyringe" 3 | import { Config } from "../core/Config" 4 | import { NotFoundNotifyError } from "../errors/PlayerErrors" 5 | 6 | /** 7 | * Class to manage the weapons 8 | */ 9 | @injectable() 10 | @singleton() 11 | class WeaponManager { 12 | static readonly MAX_AMMO: number = app.getConfig().get('MAX_AMMO') || 1000 13 | static readonly MAX_WEAPON_SLOTS: number = app.getConfig().get('MAX_WEAPON_SLOTS') || 3 14 | 15 | private weapons: any 16 | private weaponsSetCategories: string[][] 17 | public readonly weaponSet: string[][] 18 | 19 | constructor(@inject(Config) readonly config: Config) { 20 | 21 | this.weaponRequest = this.weaponRequest.bind(this) 22 | 23 | this.weapons = this.config.get("WEAPONS") 24 | this.weaponsSetCategories = this.config.get('WEAPONS_SET') 25 | this.weaponSet = this.getWeaponSet() 26 | } 27 | 28 | /** 29 | * Rpc call 30 | * 31 | * Fires when the player has made the choice 32 | * @param {string[]} choice 33 | * @param {rpc.ProcedureListenerInfo} param1 34 | */ 35 | weaponRequest(choice: string[], { player }: rpc.ProcedureListenerInfo): void { 36 | if (this.isCorrectChoice(choice)) { 37 | this.giveWeapons(player as PlayerMp, choice) 38 | } else { 39 | throw new NotFoundNotifyError(SHARED.MSG.ERR_WEAPON_NOT_FOUND, player as PlayerMp) 40 | } 41 | } 42 | 43 | /** 44 | * Give the weapons to the player 45 | * @param {PlayerMp} player 46 | * @param {string[]} choice - array of weapon hashes 47 | */ 48 | giveWeapons(player: PlayerMp, choice: string[]) : void { 49 | player.removeAllWeapons() 50 | choice.forEach(hash => player.giveWeapon(mp.joaat(hash), WeaponManager.MAX_AMMO)) 51 | } 52 | 53 | /** 54 | * Check if the choice is correct 55 | * @param {string[]} choice - array of weapon hashes 56 | */ 57 | isCorrectChoice(choice: string[]) : boolean { 58 | const list = this.getList() 59 | 60 | return choice.length <= WeaponManager.MAX_WEAPON_SLOTS && choice.every(hash => list.indexOf(hash) !== -1) 61 | } 62 | 63 | /** 64 | * Get weapon list from the config 65 | */ 66 | getList(): string[] { 67 | return Object 68 | .entries(this.weapons) 69 | .reduce((carry, currentValue) => { 70 | const [_, weapons]: [any, any] = currentValue 71 | 72 | return [...carry, ...weapons] as any 73 | }, []) 74 | } 75 | 76 | /** 77 | * Set the weapon list 78 | */ 79 | private getWeaponSet(): string[][] { 80 | const weaponSet: string[][] = [] 81 | 82 | if (Array.isArray(this.weaponsSetCategories) && typeof this.weapons === 'object') { 83 | this.weaponsSetCategories.forEach((packs, index) => { 84 | if (weaponSet[index] === undefined) weaponSet[index] = [] 85 | packs.forEach(pack => { 86 | if (Array.isArray(this.weapons[pack])) weaponSet[index] = [...weaponSet[index], ...this.weapons[pack]] 87 | }) 88 | }) 89 | } 90 | 91 | return weaponSet 92 | } 93 | } 94 | 95 | export { WeaponManager } -------------------------------------------------------------------------------- /cef/src/MapEditor/MapEditor.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Paper from '@material-ui/core/Paper' 4 | import Grid from '@material-ui/core/Grid' 5 | import Header from './Header' 6 | import Body from './Body' 7 | import Footer from './Footer' 8 | import { register, callClient } from 'rage-rpc' 9 | import { RPC_DIALOG } from '../events' 10 | 11 | const useStyles = makeStyles({ 12 | root: { 13 | borderTop: '10px solid black', 14 | position: 'absolute', 15 | right: '5vmin', 16 | flexGrow: 1, 17 | padding: 15, 18 | maxWidth: 460, 19 | background: 'rgba(255 255 255 / 75%)', 20 | '&:focus': { 21 | outline: 'none', 22 | }, 23 | }, 24 | header: { 25 | fontWeight: 600, 26 | fontSize: 24, 27 | textDecoration: 'underline', 28 | textDecorationStyle: 'dotted', 29 | textDecorationColor: 'crimson', 30 | }, 31 | title: { 32 | marginTop: 15, 33 | }, 34 | list: { 35 | maxHeight: 100, 36 | overflow: 'auto', 37 | } 38 | }) 39 | 40 | export interface Point { 41 | name: string 42 | coord: any 43 | } 44 | export type SpawnVector = { 45 | 'ATTACKERS': Point[] 46 | 'DEFENDERS': Point[] 47 | } 48 | 49 | export interface MapEditorState { 50 | open: boolean 51 | editing: boolean 52 | path: Point[] 53 | spawn: SpawnVector 54 | team: 'ATTACKERS' | 'DEFENDERS' 55 | mapName: string 56 | focus: boolean 57 | } 58 | 59 | export const initialMapEditorState: MapEditorState = { 60 | open: false, 61 | editing: false, 62 | path: [], 63 | spawn: { 64 | 'ATTACKERS': [], 65 | 'DEFENDERS': [], 66 | }, 67 | team: 'ATTACKERS', 68 | mapName: '', 69 | focus: false, 70 | } 71 | 72 | /** 73 | * The map editor form 74 | */ 75 | export default function MapEditor() { 76 | const [render, setRender] = React.useState(false) 77 | const [state, setState] = React.useState(initialMapEditorState) 78 | 79 | const classes = useStyles() 80 | 81 | // register side effects (toggling event and state update event) 82 | React.useEffect(() => { 83 | if (!render) { 84 | setRender(true) 85 | register(RPC_DIALOG.CLIENT_MAP_EDITOR_TOGGLE, ([ toggle ]) => { 86 | setState(state => ({ ...state, open: toggle })) 87 | }) 88 | 89 | register(RPC_DIALOG.CLIENT_MAP_EDITOR_UPDATE, ([ newState ]) => { 90 | setState(state => ({ ...state, ...newState })) 91 | }) 92 | } 93 | }, [render, state]) 94 | 95 | // register side effects (update state on the client) 96 | React.useEffect(() => { 97 | callClient(RPC_DIALOG.CEF_MAP_EDITOR_UPDATE_CLIENT, state) 98 | }, [state]) 99 | 100 | if (state.open === false) return <> 101 | 102 | return ( 103 | 104 | 105 |
106 | 107 |