UNPKG

starblast-modding

Version:

A powerful library for interacting with the Starblast Modding API

218 lines (189 loc) 5.8 kB
'use strict'; const runMod = require("../utils/runMod.js"); const deepFreeze = require("../utils/deepFreeze.js"); class ModdingAPI { constructor(game, options) { this.game = game; this.cacheECPKey = !!options?.cacheECPKey; this.cacheOptions = !!options?.cacheOptions; this.cacheEvents = !!options?.cacheEvents; this.compressWSMessages = !!options?.compressWSMessages; this.extendedMode = !!options?.extendedMode; this.gameClient = new (require("../clients/GameClient.js"))(this.game, this); this.events = require("../resources/Events.js"); this.handlers = { create: new Map(), destroy: new Map() } this.configuration = {}; this.create_requests = []; this.mod_data = {}; this.stopHandlers = []; this.instanced = {}; this.clientReset(this.game); this.stopped = false; } clear () { return this.set() } setOptions (options) { let setup = (this.processStarted ? this.instanced : this.configuration); return setup.options = options; } setRegion (region) { let setup = (this.processStarted ? this.instanced : this.configuration); return setup.region = region; } setECPKey (ECPKey) { let setup = (this.processStarted ? this.instanced : this.configuration); return setup.ECPKey = ECPKey; } getRequestOptions () { return this.configuration.options } getRegion () { return this.configuration.region } reset (skipStop = false) { this.started = false; this.stopped = true; this.processStarted = false; this.preflight_requests = []; this.gameClient.socket = null; this.clear(); if (!this.cacheECPKey && !skipStop) delete this.configuration.ECPKey; else this.configuration.ECPKey = this.instanced.ECPKey; if (!this.cacheOptions && !skipStop) delete this.configuration.options; else this.configuration.options = this.instanced.options; this.configuration.region = this.instanced.region; if (!this.cacheEvents && !skipStop) this.game.removeAllListeners(); while (this.stopHandlers.length > 0) { let { resolve } = this.stopHandlers.shift(); resolve?.(this.game); } } async start () { try { this.configuration.options = deepFreeze(JSON.parse(JSON.stringify(this.configuration.options ?? {}))); this.encodeOptionsError = false } catch (e) { this.configuration.options = deepFreeze({}); this.encodeOptionsError = true } this.processStarted = true; this.stopTriggered = false; Object.assign(this.instanced, { region: this.configuration.region, options: this.configuration.options, ECPKey: this.configuration.ECPKey }); try { return await runMod(this); } catch (e) { this.reset(true); throw e; } } stop () { return new Promise(function(resolve, reject) { this.stopHandlers.push({resolve, reject}); this.name("stop").send(null, "stop") }.bind(this)) } name (name) { return this.prop("name", name); } set (data) { this.pending_request = Object.assign(Object.create(null), data); return this } prop (name, data) { this.pending_request[name] = data; return this } data (...data) { let pData = data[0] ?? {}; Object.assign(pData, ...data.slice(1)); return this.prop("data", pData) } clientMessage (id, name, data) { this.name("client_message"); data = Object.assign(Object.create(null), data, { name }); return this.data({id, data}) } globalMessage (name, data) { return this.clientMessage(null, name, data) } send (uuid, action) { let pr = this.pending_request; if (this.started) try { this.socket.send(JSON.stringify(pr)); if ("string" == typeof pr.name && pr.name.match(/^add_(alien|asteroid|collectible)$/)) this.create_requests.push(pr.data.uuid) } catch(e) { if (arguments.length > 0) { let error = new Error("Failed to encode request"), globalMessage; switch (action) { case "create": case "destroy": { let handler = this.handlers[action], reject = handler.get(uuid)?.reject; handler.delete(uuid); this.game.findStructureByUUID(uuid)?.markAsInactive?.(); reject?.(error); break; } case "stop": { while (this.stopHandlers.length > 0) { let { reject } = this.stopHandlers.shift(); reject?.(error); } break; } default: globalMessage = 1; } if (globalMessage) this.game.emit('error', error, this.game) } } else this.preflight_requests.push(pr); return this.clear() } clientReset (client) { /** * Custom object served for assigning data by the user * @name ModdingClient.prototype.custom * @type {object} */ client.custom = {}; let stopError = new Error("Mod had stopped before the action could be completed"); for (let key of ["create", "destroy"]) { let handlers = [...this.handlers[key].entries()]; this.handlers[key].clear(); for (let handler of handlers) handler[1]?.reject?.(stopError) } this.create_requests.splice(0); Object.assign(this.mod_data, { aliens: new (require("../managers/AlienManager.js"))(client, this), asteroids: new (require("../managers/AsteroidManager.js"))(client, this), collectibles: new (require("../managers/CollectibleManager.js"))(client, this), ships: new (require("../managers/ShipManager.js"))(client, this), objects: new (require("../managers/ObjectManager.js"))(client, this), timer: new (require("../managers/TimeManager.js"))(client, this), teams: null, options: null, step: -1 }); this.reset(); } triggerStopEvent () { if (this.stopTriggered) return; this.stopTriggered = true; let isStarted = this.game.started; if (!isStarted) this.lastRejectHandler?.call?.(this.game, new Error("Failed to run the mod")); this.game.emit(this.events.MOD_STOPPED, this.game); this.clientReset(this.game); } } module.exports = ModdingAPI