import { Command } from '../commands/command'; import { CommandReceiver } from '../commands/command-receiver'; import { Id } from '../communication/id'; import { serializable } from '../serialization/serializable'; @serializable export class RemoteCall { constructor( public readonly functionName: string, public readonly args: Array, ) {} public toArray(): Array { return [this.functionName, this.args]; } } @serializable export class UpdatePropertyCommand extends Command { constructor( public readonly propertyKey: string, public readonly propertyValue: any, public readonly rateOfChange: any, ) { super(); } public toArray(): Array { return [this.propertyKey, this.propertyValue, this.rateOfChange]; } } @serializable export class PropertyUpdatesForObject { constructor( public readonly id: Id, public readonly updates: Array, ) {} public toArray(): Array { return [this.id, this.updates]; } } export abstract class GameObject extends CommandReceiver { private remoteCalls: Array = []; // The only methods a peer may invoke over the wire. processRemoteCalls // dispatches by a raw string taken straight off the network, so without this // gate a malformed or hostile packet could call ANY method on the object // (toArray, resetRemoteCalls, even prototype methods). Keep in sync with the // remoteCall() emitters on the *-physical classes. private static readonly allowedRemoteCalls: ReadonlySet = new Set([ 'onShoot', 'onLeap', 'onDie', 'onHitConfirmed', 'onKillConfirmed', 'setHealth', 'setKillCount', 'onFlipped', 'setContested', 'generatedPoints', 'setLight', ]); constructor(public readonly id: Id) { super(); } public processRemoteCalls(remoteCalls: Array) { remoteCalls.forEach((r) => { if (!GameObject.allowedRemoteCalls.has(r.functionName)) { console.warn(`Dropped disallowed remote call: ${r.functionName}`); return; } const fn = this[r.functionName as keyof this]; if (typeof fn === 'function') { (fn as (...args: Array) => unknown).apply(this, r.args); } }); } public getPropertyUpdates(): PropertyUpdatesForObject | void {} public getRemoteCalls(): Array { return this.remoteCalls; } public resetRemoteCalls() { this.remoteCalls = []; } protected remoteCall(name: string & keyof this, ...args: Array) { this.remoteCalls.push(new RemoteCall(name, args)); } }