From d7a76caf2a8f54fe295947f4bdb594db16e22583 Mon Sep 17 00:00:00 2001 From: hydrant Date: Sun, 1 Mar 2020 13:15:50 +0100 Subject: [PATCH] Continue script abstraction --- .gitmodules | 3 + .../core/rage-mp/entities.ts | 116 +++++++++++++++ .../core/rage-mp/events.ts | 97 +++++++++++++ ReallifeGamemode.Client/core/rage-mp/game.ts | 20 +++ ReallifeGamemode.Client/core/rage-mp/ui.ts | 73 ++++++++++ ReallifeGamemode.Client/game.ts | 134 ++++++++++++++++++ ReallifeGamemode.Client/index.ts | 10 ++ ReallifeGamemode.Client/libs/NativeUI | 1 + ReallifeGamemode.Client/util.ts | 48 +++++++ .../Commands/CommandHandler.cs | 10 +- ReallifeGamemode.Server.Core/Main.cs | 12 +- ReallifeGamemode.Server/Gangwar/Gangwar.cs | 2 +- ReallifeGamemode.Server/Main.cs | 12 +- ReallifeGamemode.sln | 1 + 14 files changed, 531 insertions(+), 8 deletions(-) create mode 100644 .gitmodules create mode 100644 ReallifeGamemode.Client/core/rage-mp/entities.ts create mode 100644 ReallifeGamemode.Client/core/rage-mp/events.ts create mode 100644 ReallifeGamemode.Client/core/rage-mp/game.ts create mode 100644 ReallifeGamemode.Client/core/rage-mp/ui.ts create mode 100644 ReallifeGamemode.Client/game.ts create mode 160000 ReallifeGamemode.Client/libs/NativeUI create mode 100644 ReallifeGamemode.Client/util.ts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..c7bd0506 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ReallifeGamemode.Client/libs/NativeUI"] + path = ReallifeGamemode.Client/libs/NativeUI + url = git@github.com:sprayzcs/RAGEMP-NativeUI.git diff --git a/ReallifeGamemode.Client/core/rage-mp/entities.ts b/ReallifeGamemode.Client/core/rage-mp/entities.ts new file mode 100644 index 00000000..87962d10 --- /dev/null +++ b/ReallifeGamemode.Client/core/rage-mp/entities.ts @@ -0,0 +1,116 @@ +import { IEntity, IPlayer, IPlayerPool, IVehicle, IVehiclePool, VehicleSeat, EntityType } from "../../game"; +import { parseJson } from "../../util"; + +class RageEntity implements IEntity { + private entity: EntityMp; + + get id(): number { + return this.entity.id; + } + + constructor(entity: EntityMp) { + this.entity = entity; + } + + get type(): EntityType { + switch (this.entity.type) { + case 'vehicle': return EntityType.Vehicle; + case 'player': return EntityType.Player; + default: return EntityType.Unknown; + } + } + + getSharedData(key: string): T { + var data = this.entity.getVariable(key); + if (typeof data !== 'undefined') { + return parseJson(data); + } + + return null; + } +} + +class RagePlayer extends RageEntity implements IPlayer { + private player: PlayerMp; + + get name(): string { + return this.player.name; + } + + get vehicle(): IVehicle { + var veh: VehicleMp = this.player.vehicle; + return veh ? new RageVehicle(veh) : null; + } + + get inVehicle(): boolean { + return this.player.isInAnyVehicle(false); + } + + constructor(player: PlayerMp) { + super(player); + this.player = player; + } +} + +class RagePlayerPool implements IPlayerPool { + get local(): IPlayer { + return new RagePlayer(mp.players.local); + } + + at(id: number): IPlayer { + return new RagePlayer(mp.players.atRemoteId(Number(id))); + } + + forEach(fn: (entity: IPlayer) => void): void { + mp.players.forEach(e => { + fn(new RagePlayer(e)); + }); + } +} + +class RageVehicle extends RageEntity implements IVehicle { + private vehicle: VehicleMp; + + constructor(vehicle: VehicleMp) { + super(vehicle); + this.vehicle = vehicle; + } + + isSeatFree(seat: VehicleSeat): boolean { + return this.vehicle.isSeatFree(seat - 1); + } + + setEngineStatus(status: boolean, instantly: boolean, otherwise: boolean) { + this.vehicle.setEngineOn(status, instantly, otherwise); + } + + setUndriveable(status: boolean) { + this.vehicle.setUndriveable(status); + } + + setDoorsLocked(state: boolean) { + this.vehicle.setDoorsLocked(state ? 2 : 1); + } +} + +class RageVehiclePool implements IVehiclePool { + at(id: number): IVehicle { + return new RageVehicle(mp.vehicles.atRemoteId(Number(id))); + } + + forEach(fn: (entity: IVehicle) => void): void { + mp.vehicles.forEach(e => { + fn(new RageVehicle(e)); + }) + } + + +} + +export { + RageEntity, + RagePlayer, + RagePlayerPool, + RageVehicle, + RageVehiclePool, +} diff --git a/ReallifeGamemode.Client/core/rage-mp/events.ts b/ReallifeGamemode.Client/core/rage-mp/events.ts new file mode 100644 index 00000000..f3197cea --- /dev/null +++ b/ReallifeGamemode.Client/core/rage-mp/events.ts @@ -0,0 +1,97 @@ +import { IEvents, EventName, Key, IEntity, IVehicle, VehicleSeat } from "../../game"; +import game from "../../index"; +import { RageEntity, RageVehicle, RagePlayer } from "./entities"; + +export default class RageEvents implements IEvents { + boundKeys: Key[] = []; + + translateEventName(event: EventName): string { + switch (event) { + case EventName.PlayerCommand: return "playerCommand"; + case EventName.Tick: return "render"; + case EventName.EntityStreamIn: return "entityStreamIn"; + } + } + + add(event: EventName | string, callback: (...args: any[]) => void): void { + var eventName: string; + + if (typeof (event) === 'string') { + eventName = event.toString(); + } else { + eventName = game.events.translateEventName(event); + } + + mp.events.add(eventName, callback); + } + + addCef(event: string, callback: (args: any[]) => void): void { + mp.events.add('CEF:' + event, (cefArgs: string) => { + callback(JSON.parse(cefArgs)); + }); + } + + callServer(event: string, args?: any[] | any): void { + if (args) { + if (typeof args === 'object') { + args.push(event); + } else { + args = [args, event]; + } + } else { + args = [event]; + } + mp.events.callRemote('CLIENT:Event', JSON.stringify(args)); + } + + bindKey(key: Key, hold: boolean, callback: Function) { + if (this.boundKeys.indexOf(key) === -1) { + mp.keys.bind(key, hold, callback); + this.boundKeys.push(key); + } + } + + unbindKey(key: Key, hold: boolean, callback?: Function) { + var index: number = this.boundKeys.indexOf(key); + if (index !== -1) { + mp.keys.unbind(key, hold, callback); + this.boundKeys.splice(index, 1); + } + } + + + onEntityStreamIn(callback: (entity: IEntity) => void): void { + mp.events.add("entityStreamIn", (e: EntityMp) => { + var rE: RageEntity; + switch (e.type) { + case 'vehicle': + rE = new RageVehicle(e); + break; + case 'player': + rE = new RagePlayer(e); + break; + } + + callback(rE); + }) + } + + onPlayerEnterVehicle(callback: (vehicle: IVehicle, seat: VehicleSeat) => void): void { + mp.events.add("playerEnterVehicle", (rV: VehicleMp, rS: number) => { + callback(new RageVehicle(rV), (rS + 1)); + }); + } + + onPlayerExitVehicle(callback: () => void): void { + mp.events.add("playerLeaveVehicle", (rV: VehicleMp) => { + callback(); + }); + } + + + onPlayerCommand(callback: (cmd: string) => void) { + mp.events.add("playerCommand", (cmd: string) => { + callback(cmd); + }); + } +} diff --git a/ReallifeGamemode.Client/core/rage-mp/game.ts b/ReallifeGamemode.Client/core/rage-mp/game.ts new file mode 100644 index 00000000..68cbd072 --- /dev/null +++ b/ReallifeGamemode.Client/core/rage-mp/game.ts @@ -0,0 +1,20 @@ +import { IGame, IUi, IEvents, IPlayerPool, IVehiclePool } from "../../game"; +import RageEvents from "./events"; +import RageUi from "./ui"; +import { RagePlayerPool, RageVehiclePool } from "./entities"; + +export default class RageGame implements IGame { + players: IPlayerPool = new RagePlayerPool(); + vehicles: IVehiclePool = new RageVehiclePool(); + + events: IEvents = new RageEvents; + ui: IUi = new RageUi; + + wait(ms: number): void { + mp.game.wait(ms); + } + + disableDefaultEngineBehaviour(): void { + mp.game.vehicle.defaultEngineBehaviour = false; + } +} \ No newline at end of file diff --git a/ReallifeGamemode.Client/core/rage-mp/ui.ts b/ReallifeGamemode.Client/core/rage-mp/ui.ts new file mode 100644 index 00000000..52ff1e11 --- /dev/null +++ b/ReallifeGamemode.Client/core/rage-mp/ui.ts @@ -0,0 +1,73 @@ +import { IUi, IBrowser } from "../../game"; +import { Menu } from "../../libs/NativeUI/index"; + +export default class RageUi implements IUi { + private _inMenu: boolean = false; + private _activeMenu: Menu = null; + + inChat: boolean = false; + + get activeMenu(): Menu { + return this._activeMenu; + } + + set activeMenu(value: Menu) { + if (this.activeMenu && this.activeMenu.Visible) { + this.activeMenu.Close(true); + } + + this._activeMenu = value; + + if (this.activeMenu) { + this.activeMenu.Open(); + } + + this.inMenu = this.activeMenu != null; + } + + get inMenu() { + return this._inMenu; + } + + set inMenu(value: boolean) { + this._inMenu = value; + this.toggleChat(!value); + } + + toggleChat(toggle: boolean) { + mp.gui.chat.show(toggle); + } + + openBrower(path: string): IBrowser { + return new RageBrowser(path); + } + + sendChatMessage(message: string) { + mp.gui.chat.push(message); + } + + setCursor(freeze: boolean, show: boolean): void { + mp.gui.cursor.show(freeze, show); + } +} + +export class RageBrowser implements IBrowser { + private rageBrowser: BrowserMp; + + constructor(path: string) { + this.rageBrowser = mp.browsers.new(path); + } + + close(): void { + if (this.rageBrowser) { + this.rageBrowser.destroy(); + this.rageBrowser = null; + } + } + + executeJs(script: string) { + if (this.rageBrowser) { + this.rageBrowser.execute(script); + } + } +} \ No newline at end of file diff --git a/ReallifeGamemode.Client/game.ts b/ReallifeGamemode.Client/game.ts new file mode 100644 index 00000000..3976caf4 --- /dev/null +++ b/ReallifeGamemode.Client/game.ts @@ -0,0 +1,134 @@ +interface IGame { + wait(ms: number); + events: IEvents; + ui: IUi; + + players: IPlayerPool; + vehicles: IVehiclePool; + + disableDefaultEngineBehaviour(): void; +} + +interface IEvents { + translateEventName(event: EventName): string; + add(event: EventName | string, callback: (...args: any[]) => void): void; + addCef(event: string, callback: (args: any[]) => void): void; + callServer(event: string, args?: any[] | any): void; + bindKey(key: Key, hold: boolean, callback: Function); + unbindKey(key: Key, hold: boolean, callback?: Function); + + onEntityStreamIn(callback: (entity: IEntity) => void): void; + onPlayerEnterVehicle(callback: (entity: IVehicle, seat: VehicleSeat) => void): void; + onPlayerExitVehicle(callback: () => void): void; + onPlayerCommand(callback: (cmd: string) => void); +} + +interface IUi { + sendChatMessage(message: string); + openBrower(path: string): IBrowser; + setCursor(freeze: boolean, show: boolean): void; + toggleChat(toggle: boolean): void; + inMenu: boolean; + inChat: boolean; +} + +interface IBrowser { + close(): void; + executeJs(script: string); +} + +interface IEntity { + id: number; + type: EntityType; + getSharedData(key: string): T; +} + +interface IPlayer extends IEntity { + inVehicle: boolean; + name: string; + vehicle: IVehicle; +} + +interface IVehicle extends IEntity { + isSeatFree(seat: VehicleSeat): boolean; + setEngineStatus(status: boolean, instantly: boolean, otherwise: boolean); + setUndriveable(status: boolean); + setDoorsLocked(state: boolean); +} + +interface IEntityPool { + at(id: number): TEntity; + forEach(fn: (entity: TEntity) => void): void; +} + +interface IPlayerPool extends IEntityPool { + local: IPlayer; +} + +interface IVehiclePool extends IEntityPool { + +} + +enum EntityType { + Unknown, + Player, + Vehicle +} + +interface AccountData { + Username: string; + CreatedAt: Date; + + GroupName: string, + GroupRank: string, +} + +interface VehicleData { + EngineState: boolean; + Locked: boolean; +} + +enum EventName { + PlayerCommand, + Tick, + EntityStreamIn +} + +enum Key { + ENTER = 0x0D, + M = 0x4D, + T = 0x54, + X = 0x58 +} + +enum VehicleSeat { + Driver, + CoDriver, + LeftPassenger, + RightPassenger, + ExtraSeat1, + ExtraSeat2, + ExtraSeat3, + ExtraSeat4 +} + +export { + IGame, + IEvents, + IUi, + IBrowser, + + IPlayer, + IVehicle, + IEntity, + IEntityPool, + IPlayerPool, + IVehiclePool, + + EventName, + Key, + VehicleSeat, + EntityType, + AccountData, + VehicleData +} \ No newline at end of file diff --git a/ReallifeGamemode.Client/index.ts b/ReallifeGamemode.Client/index.ts index ca47b675..d64a02e1 100644 --- a/ReallifeGamemode.Client/index.ts +++ b/ReallifeGamemode.Client/index.ts @@ -4,6 +4,9 @@ * @copyright (c) 2008 - 2018 Life of German **/ +import { IGame } from './game'; +import RageGame from './core/rage-mp/game'; + let globalData: GlobalData = { InTuning: false, HideGui: false, @@ -21,6 +24,13 @@ let globalData: GlobalData = { } }; +const game: IGame = new RageGame(); +export default game; + +game.events.onPlayerCommand((cmd) => { + game.events.callServer("Command", cmd.split(' ')); +}); + var inMenu = false; mp.game.vehicle.defaultEngineBehaviour = false; diff --git a/ReallifeGamemode.Client/libs/NativeUI b/ReallifeGamemode.Client/libs/NativeUI new file mode 160000 index 00000000..be44f86e --- /dev/null +++ b/ReallifeGamemode.Client/libs/NativeUI @@ -0,0 +1 @@ +Subproject commit be44f86e1da12ad3d33a32ff216d3b382f17c1dc diff --git a/ReallifeGamemode.Client/util.ts b/ReallifeGamemode.Client/util.ts new file mode 100644 index 00000000..c505d3a7 --- /dev/null +++ b/ReallifeGamemode.Client/util.ts @@ -0,0 +1,48 @@ +import { UIMenuItem, Point, Menu } from "./libs/NativeUI/index"; + +function createMenuItem(title: string, description?: string, fn?: (menuItem: UIMenuItem) => void): UIMenuItem { + var item: UIMenuItem = new UIMenuItem(title, description || ""); + if (fn) { + fn(item); + } + + return item; +} + +function createMenu(title: string, subtitle: string, pos: Point, items: MenuMap): Menu { + var menu: Menu = new Menu(title, subtitle, pos); + + Object.entries(items).forEach((value, index, array) => { + menu.AddItem(items[value[0]]); + }); + + menu.Close(true); + + return menu; +} + +function parseJson(json: string): TData { + var dateTimeReviver = function (key: string, value: any) { + var a; + if (typeof value === 'string') { + a = /\/Date\((\d*)\)\//.exec(value); + if (a) { + return new Date(+a[1]); + } + } + return value; + } + + return JSON.parse(json, dateTimeReviver); +} + +interface MenuMap { + [key: string]: UIMenuItem; +} + +export { + createMenuItem, + parseJson, + createMenu, + MenuMap +} \ No newline at end of file diff --git a/ReallifeGamemode.Server.Core/Commands/CommandHandler.cs b/ReallifeGamemode.Server.Core/Commands/CommandHandler.cs index 05b992a2..2477a2f0 100644 --- a/ReallifeGamemode.Server.Core/Commands/CommandHandler.cs +++ b/ReallifeGamemode.Server.Core/Commands/CommandHandler.cs @@ -17,12 +17,13 @@ namespace ReallifeGamemode.Server.Core.Commands private readonly ILogger logger = LogManager.GetLogger(); private readonly IAPI api; - + private readonly string[] legacyCommands; private Random random = new Random(); - public CommandHandler(IAPI api) + public CommandHandler(IAPI api, string[] registeredCommands) { this.api = api; + legacyCommands = registeredCommands; Main.EventHandler.RegisterClientEvent("Command", OnPlayerCommand); } @@ -32,6 +33,11 @@ namespace ReallifeGamemode.Server.Core.Commands logger.LogInformation("Player '{Name}' executed command '{command}'", player.Name, command); + if(legacyCommands.Contains(command)) + { + return; + } + if (!registeredCommands.ContainsKey(command)) { player.SendMessage($"Der Befehl ~b~/{command}~s~ existiert nicht", ChatPrefix.Error); diff --git a/ReallifeGamemode.Server.Core/Main.cs b/ReallifeGamemode.Server.Core/Main.cs index f53c9fb4..7db2182b 100644 --- a/ReallifeGamemode.Server.Core/Main.cs +++ b/ReallifeGamemode.Server.Core/Main.cs @@ -20,14 +20,18 @@ namespace ReallifeGamemode.Server.Core private readonly ILogger logger = LogManager.GetLogger
(); - public Main(IAPI api, Events.EventHandler eventHandler) + public Main(IAPI api, Events.EventHandler eventHandler, string[] registeredCommands) { logger.LogInformation("Loading scripts..."); API = api; EventHandler = eventHandler; + API.DisableDefaultCommandErrorMessages(); + API.DisableDefaultSpawnBehavior(); + API.SetGlobalChatEnabled(false); + LoadScript(); - LoadCommands(); + LoadCommands(registeredCommands); } private void LoadScript() @@ -43,9 +47,9 @@ namespace ReallifeGamemode.Server.Core } } - private void LoadCommands() + private void LoadCommands(string[] registeredCommands) { - CommandHandler commandHandler = new CommandHandler(API); + CommandHandler commandHandler = new CommandHandler(API, registeredCommands); var allTypes = Assembly.GetExecutingAssembly().GetTypes(); var commandTypes = allTypes.Where(t => t.IsSubclassOf(typeof(Command)) && !t.IsAbstract); diff --git a/ReallifeGamemode.Server/Gangwar/Gangwar.cs b/ReallifeGamemode.Server/Gangwar/Gangwar.cs index 630081a9..7ecd7fc9 100644 --- a/ReallifeGamemode.Server/Gangwar/Gangwar.cs +++ b/ReallifeGamemode.Server/Gangwar/Gangwar.cs @@ -99,7 +99,7 @@ namespace ReallifeGamemode.Server.Gangwar } } } - + [RemoteEvent("SERVER:SetTurf")] public void RmtEvent_SetTurf(Player client, string jsonX, string jsonY, string jsonRot, string jsonRange, string Name) { diff --git a/ReallifeGamemode.Server/Main.cs b/ReallifeGamemode.Server/Main.cs index 1c0ac0e4..f26c6e99 100644 --- a/ReallifeGamemode.Server/Main.cs +++ b/ReallifeGamemode.Server/Main.cs @@ -13,6 +13,8 @@ using ReallifeGamemode.Server.Core.RageMP; using ReallifeGamemode.Server.Core.Events; using System.Collections.Generic; using ReallifeGamemode.Server.Common; +using System.Reflection; +using System.Linq; using ReallifeGamemode.Server.Job; /** @@ -35,10 +37,18 @@ namespace ReallifeGamemode.Server [ServerEvent(Event.ResourceStart)] public void OnResourceStart() { + var methods = Assembly.GetExecutingAssembly() + .GetTypes() + .SelectMany(t => t.GetMethods()) + .Where(m => m.GetCustomAttributes(typeof(CommandAttribute), false).Length > 0) + .ToArray(); + + var registeredCommands = methods.Select(c => c.GetCustomAttribute().CommandString); + IAPI apiInstance = new RageAPI(); eventHandler = new EventHandler(apiInstance); - new Core.Main(apiInstance, eventHandler); + new Core.Main(apiInstance, eventHandler, registeredCommands.ToArray()); NAPI.Server.SetGlobalServerChat(false); diff --git a/ReallifeGamemode.sln b/ReallifeGamemode.sln index a349d8c7..231bc3da 100644 --- a/ReallifeGamemode.sln +++ b/ReallifeGamemode.sln @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitattributes = .gitattributes .gitignore = .gitignore .gitlab-ci.yml = .gitlab-ci.yml + .gitmodules = .gitmodules postbuild.config.xml = postbuild.config.xml postbuild.ps1 = postbuild.ps1 readme.md = readme.md