Continue script abstraction

This commit is contained in:
hydrant
2020-03-01 13:15:50 +01:00
parent 37f499a446
commit d7a76caf2a
14 changed files with 531 additions and 8 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "ReallifeGamemode.Client/libs/NativeUI"]
path = ReallifeGamemode.Client/libs/NativeUI
url = git@github.com:sprayzcs/RAGEMP-NativeUI.git

View File

@@ -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<T>(key: string): T {
var data = this.entity.getVariable(key);
if (typeof data !== 'undefined') {
return parseJson<T>(<string>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(<number>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,
}

View File

@@ -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(<VehicleMp>e);
break;
case 'player':
rE = new RagePlayer(<PlayerMp>e);
break;
}
callback(rE);
})
}
onPlayerEnterVehicle(callback: (vehicle: IVehicle, seat: VehicleSeat) => void): void {
mp.events.add("playerEnterVehicle", (rV: VehicleMp, rS: number) => {
callback(new RageVehicle(rV), <VehicleSeat>(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);
});
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<T>(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<TEntity> {
at(id: number): TEntity;
forEach(fn: (entity: TEntity) => void): void;
}
interface IPlayerPool extends IEntityPool<IPlayer> {
local: IPlayer;
}
interface IVehiclePool extends IEntityPool<IVehicle> {
}
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
}

View File

@@ -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;

View File

@@ -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<TData>(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 <TData>JSON.parse(json, dateTimeReviver);
}
interface MenuMap {
[key: string]: UIMenuItem;
}
export {
createMenuItem,
parseJson,
createMenu,
MenuMap
}

View File

@@ -17,12 +17,13 @@ namespace ReallifeGamemode.Server.Core.Commands
private readonly ILogger logger = LogManager.GetLogger<CommandHandler>();
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);

View File

@@ -20,14 +20,18 @@ namespace ReallifeGamemode.Server.Core
private readonly ILogger logger = LogManager.GetLogger<Main>();
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);

View File

@@ -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)
{

View File

@@ -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<CommandAttribute>().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);

View File

@@ -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