omegga 1.9.3 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/brickadia/server.js +23 -1
- package/dist/brickadia/server.js.map +1 -1
- package/dist/omegga/matchers/version.js +32 -0
- package/dist/omegga/matchers/version.js.map +1 -1
- package/dist/omegga/server.js +8 -0
- package/dist/omegga/server.js.map +1 -1
- package/dist/omegga/wrapper.js +4 -0
- package/dist/omegga/wrapper.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
- Future features go here
|
|
6
6
|
|
|
7
|
+
## 1.10.0 - 2026-07-17
|
|
8
|
+
|
|
9
|
+
This is a minor version bump because it may break existing plugins that rely on the `version` event
|
|
10
|
+
|
|
11
|
+
- Resolve the game version from the server binary before startup
|
|
12
|
+
|
|
7
13
|
## 1.9.3 - 2026-07-16
|
|
8
14
|
|
|
9
15
|
- Plugins: `readSaveData` now reconstructs a legacy save from `.brz` prefabs on EA3, matching `getSaveData`
|
package/dist/brickadia/server.js
CHANGED
|
@@ -110,6 +110,28 @@ class BrickadiaServer extends EventEmitter {
|
|
|
110
110
|
];
|
|
111
111
|
return candidates.find(({ file }) => file && this.worldExists(file)) ?? null;
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolve the path to the game server binary for steam/override installs.
|
|
115
|
+
* Returns null for launcher-managed installs, where the binary lives in a
|
|
116
|
+
* branch directory the launcher owns and isn't known statically here.
|
|
117
|
+
*/
|
|
118
|
+
getGameBinaryPath() {
|
|
119
|
+
const overrideBinary = softconfig.getOverrideGameBinary();
|
|
120
|
+
if (overrideBinary) return overrideBinary;
|
|
121
|
+
const isSteam = !this.config.server.branch;
|
|
122
|
+
if (!isSteam) return null;
|
|
123
|
+
const steamBeta = this.config.server.steambeta ?? "main";
|
|
124
|
+
return path.join(
|
|
125
|
+
softconfig.getSteamInstallDir(),
|
|
126
|
+
// steam install directory
|
|
127
|
+
steamBeta,
|
|
128
|
+
// steam beta branch (or main)
|
|
129
|
+
softconfig.getSteamGameDir(),
|
|
130
|
+
// Brickadia
|
|
131
|
+
softconfig.GAME_BIN_PATH
|
|
132
|
+
// path to binary
|
|
133
|
+
);
|
|
134
|
+
}
|
|
113
135
|
// start the server child process
|
|
114
136
|
start() {
|
|
115
137
|
const {
|
|
@@ -140,7 +162,7 @@ class BrickadiaServer extends EventEmitter {
|
|
|
140
162
|
softconfig.GAME_BIN_PATH
|
|
141
163
|
// path to binary
|
|
142
164
|
);
|
|
143
|
-
let gameBinary = steamBinary;
|
|
165
|
+
let gameBinary = this.getGameBinaryPath() ?? steamBinary;
|
|
144
166
|
if (overrideBinary) {
|
|
145
167
|
if (!fs.existsSync(overrideBinary)) {
|
|
146
168
|
logger.default.error(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../../src/brickadia/server.ts"],"sourcesContent":["/*\n Brickadia Server Wrapper\n Manages IO with the game server\n*/\n\nimport Logger from '@/logger';\nimport {\n ACTIVE_WORLD_FILE,\n CONFIG_SAVED_DIR,\n GAME_BIN_PATH,\n getOverrideGameBinary,\n getSteamGameDir,\n getSteamInstallDir,\n} from '@/softconfig';\nimport { getGlobalToken } from '@cli/auth';\nimport { IConfig } from '@config/types';\nimport { checkWsl } from '@util/wsl';\nimport 'colors';\nimport { ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { env } from 'node:process';\nimport readline from 'readline';\nimport stripAnsi from 'strip-ansi';\n\n// list of errors that can be solved by yelling at the user\nconst knownErrors: {\n name: string;\n solution?: string;\n match: RegExp;\n message?: string;\n}[] = [\n {\n name: 'MISSING_LIBGL',\n solution: 'apt-get install libgl1-mesa-glx libglib2.0-0',\n match:\n /error while loading shared libraries: libGL\\.so\\.1: cannot open shared object file/,\n },\n {\n name: 'MISSING_GLIB',\n solution: 'apt-get install libgl1-mesa-glx libglib2.0-0',\n match:\n /error while loading shared libraries: libgthread-2\\.0\\.so\\.0: cannot open shared object file/,\n },\n];\n\n/** Start a brickadia server */\nexport default class BrickadiaServer extends EventEmitter {\n #child: ChildProcessWithoutNullStreams = null;\n #errInterface: readline.Interface = null;\n #outInterface: readline.Interface = null;\n\n config: IConfig;\n path: string;\n\n constructor(dataPath: string, config: IConfig) {\n super();\n\n this.config = config;\n // use the data path if it's absolute, otherwise build an absolute path\n this.path =\n path.isAbsolute(dataPath) || dataPath.startsWith('/')\n ? dataPath\n : path.join(process.cwd(), dataPath);\n\n this.lineListener = this.lineListener.bind(this);\n this.errorListener = this.errorListener.bind(this);\n this.exitListener = this.exitListener.bind(this);\n }\n\n getActiveWorldFile(): string {\n return path.join(this.path, ACTIVE_WORLD_FILE);\n }\n\n /** A world specified by the active world file */\n getActiveWorld(): string | null {\n const activeWorldFile = this.getActiveWorldFile();\n if (existsSync(activeWorldFile)) {\n try {\n return readFileSync(activeWorldFile, 'utf8').trim();\n } catch (err) {\n Logger.errorp(\n 'Failed to read active world file',\n activeWorldFile.yellow,\n err,\n );\n return null;\n }\n }\n return null;\n }\n\n /** Set the world to use next startup */\n setActiveWorld(world: string | null): boolean {\n const activeWorldFile = this.getActiveWorldFile();\n if (!world || world === null) {\n if (existsSync(activeWorldFile)) {\n Logger.verbose('Removing active world file', activeWorldFile.yellow);\n unlinkSync(activeWorldFile);\n }\n return true;\n }\n\n if (!this.worldExists(world)) {\n Logger.verbose(\n 'Cannot set active world to',\n world.yellow,\n 'as it does not exist',\n );\n return false;\n }\n\n Logger.verbose('Setting active world to', world.yellow);\n try {\n writeFileSync(activeWorldFile, world, 'utf8');\n return true;\n } catch (err) {\n Logger.errorp(\n 'Failed to write active world file',\n activeWorldFile.yellow,\n err,\n );\n return false;\n }\n }\n\n /** A world specified by the config */\n getConfigWorld(): string | null {\n return this.config?.server?.world ?? null;\n }\n\n /** A world specified by the BRICKADIA_WORLD env variable */\n getEnvWorld(): string | null {\n return env.BRICKADIA_WORLD || null;\n }\n\n /** Check if a world exists */\n worldExists(world: string): boolean {\n const savedDir = this.config?.server?.savedDir ?? CONFIG_SAVED_DIR;\n const worldPath = path.join(this.path, savedDir, 'Worlds', world + '.brdb');\n return existsSync(worldPath);\n }\n\n /** Get the world that will be used next startup */\n getNextWorld() {\n const candidates = [\n { source: 'file', file: this.getActiveWorld() },\n { source: 'config', file: this.getConfigWorld() },\n { source: 'env', file: this.getEnvWorld() },\n ];\n\n return (\n candidates.find(({ file }) => file && this.worldExists(file)) ?? null\n );\n }\n\n // start the server child process\n start() {\n const {\n email,\n password,\n token: confToken,\n }: { email?: string; password?: string; token?: string } = this.config\n .credentials || {};\n\n const token = confToken || process.env.BRICKADIA_TOKEN || getGlobalToken();\n\n if (token) {\n Logger.verbose('Starting server with hosting token');\n } else {\n Logger.verbose(\n 'Starting server',\n (!email && !password ? 'without' : 'with').yellow,\n 'credentials',\n );\n }\n\n const isSteam = !this.config.server.branch;\n const steamBeta = this.config.server.steambeta ?? 'main';\n const overrideBinary = getOverrideGameBinary();\n const steamBinary = path.join(\n getSteamInstallDir(), // steam install directory\n steamBeta, // steam beta branch (or main)\n getSteamGameDir(), // Brickadia\n GAME_BIN_PATH, // path to binary\n );\n\n let gameBinary = steamBinary;\n\n if (overrideBinary) {\n if (!existsSync(overrideBinary)) {\n Logger.error(\n 'Override binary',\n overrideBinary.yellow,\n 'does not exist!',\n );\n throw new Error(`Override binary ${overrideBinary} does not exist!`);\n }\n\n Logger.verbose(\n 'Using override binary',\n overrideBinary.yellow,\n 'instead of',\n steamBinary.yellow,\n );\n gameBinary = overrideBinary;\n } else if (isSteam) {\n Logger.verbose('Using steam binary', steamBeta.yellow);\n } else {\n Logger.verbose(\n 'Running',\n (this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia launcher'\n ).yellow,\n );\n if (typeof this.config.server.branch === 'string')\n Logger.verbose('Using branch', this.config.server.branch.yellow);\n }\n\n // handle local launcher support\n const launchArgs = isSteam\n ? [gameBinary]\n : [\n this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia',\n this.config.server.branch && `--branch=${this.config.server.branch}`,\n '--server',\n '--',\n ];\n\n const world = this.getNextWorld();\n if (world) {\n Logger.verbose(\n 'Using world',\n world.file.yellow,\n 'from',\n world.source.yellow,\n );\n } else if (this.config.server.map) {\n Logger.verbose('Using map', this.config.server.map.yellow, 'from config');\n }\n\n const params = [\n '--output=L',\n '--',\n ...launchArgs,\n !world &&\n this.config.server.map &&\n `-Environment=\"${this.config.server.map}\"`,\n world && `-World=\"${world.file}\"`,\n '-NotInstalled',\n '-log',\n checkWsl() === 1 ? '-OneThread' : null,\n this.path ? `-UserDir=\"${this.path}\"` : null,\n token ? `-Token=\"${token}\"` : null, // remove token argument if not provided\n !token && email ? `-User=\"${email}\"` : null, // remove email argument if not provided or token is provided\n !token && password ? `-Password=\"${password}\"` : null, // remove password argument if not provided or token is provided\n `-port=\"${this.config.server.port}\"`,\n this.config.server.launchArgs,\n ].filter(Boolean); // remove unused arguments\n Logger.verbose(\n 'Params for spawn',\n params\n .join(' ')\n .replace(/-User=\".*?\"/, '-User=\"<hidden>\"')\n .replace(/-Password=\".*?\"/, '-Password=\"<hidden>\"')\n .replace(/-Token=\".*?\"/, '-Token=\"<hidden>\"')\n .replace(/-Cookie=\".*?\"/, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/, '-Cookie=<hidden>'),\n );\n\n // Either unbuffer or stdbuf must be used because brickadia's output is buffered\n // this means that multiple lines can be bundled together if the output buffer is not full\n // unfortunately without stdbuf or unbuffer, the output would not happen immediately\n this.#child = spawn('stdbuf', params);\n\n Logger.verbose(\n 'Spawn process',\n this.#child ? this.#child.pid : 'failed'.red,\n );\n\n this.#child.stdin.setDefaultEncoding('utf8');\n this.#outInterface = readline.createInterface({\n input: this.#child.stdout,\n terminal: false,\n });\n this.#errInterface = readline.createInterface({\n input: this.#child.stderr,\n terminal: false,\n });\n this.attachListeners();\n Logger.verbose('Attached listeners');\n }\n\n // write a string to the child process\n write(line: string) {\n if (line.length >= 512) {\n // show a warning\n Logger.warn(\n 'WARNING'.yellow,\n 'The following line was called and is',\n 'longer than allowed limit'.red,\n );\n Logger.warn(line.replace(/\\n$/, ''));\n // throw a fake error to get the line number\n try {\n throw new Error('Console Line Too Long');\n } catch (err) {\n Logger.warn(err);\n }\n return;\n }\n if (this.#child) {\n Logger.verbose('WRITE'.green, line.replace(/\\n$/, ''));\n this.#child.stdin.write(line);\n }\n }\n\n // write a line to the child process\n writeln(line: string) {\n this.write(line + '\\n');\n }\n\n // forcibly kills the server\n stop() {\n if (!this.#child) {\n Logger.verbose('Cannot stop server as no subprocess exists');\n return;\n }\n\n Logger.verbose('Forcibly stopping server');\n // kill the process\n this.#child.kill('SIGINT');\n\n // ...kill it again just to make sure it's dead\n spawn('kill', ['-9', this.#child.pid + '']);\n }\n\n // detaches listeners\n cleanup() {\n if (!this.#child) return;\n\n Logger.verbose('Cleaning up brickadia server');\n\n // detach listener\n this.detachListeners();\n\n this.#child = null;\n this.#outInterface = null;\n this.#errInterface = null;\n }\n\n // attaches proxy event listeners\n attachListeners() {\n this.#outInterface.on('line', this.lineListener);\n this.#errInterface.on('line', this.errorListener);\n this.#child.on('exit', this.exitListener);\n this.#child.on('close', () => {});\n }\n\n // removes previously attached proxy event listeners\n detachListeners() {\n this.#outInterface.off('line', this.lineListener);\n this.#errInterface.off('line', this.errorListener);\n this.#child.off('exit', this.exitListener);\n this.#child.removeAllListeners('close');\n }\n\n // -- listeners for basic events (line, err, exit)\n errorListener(line: string) {\n Logger.verbose('ERROR'.red, line);\n this.emit('err', line);\n for (const { match, solution, name, message } of knownErrors) {\n if (line.match(match)) {\n Logger.error(\n `Encountered ${name.red}. ${\n solution ? 'Known fix:\\n ' + solution : message || 'Unknown error.'\n }`,\n );\n }\n }\n }\n\n exitListener(...args: any[]) {\n Logger.verbose('Exit listener fired');\n this.emit('closed', ...args);\n this.cleanup();\n }\n\n lineListener(line: string) {\n this.emit('line', stripAnsi(line));\n }\n}\n"],"names":["ACTIVE_WORLD_FILE","existsSync","readFileSync","Logger","unlinkSync","writeFileSync","env","CONFIG_SAVED_DIR","getGlobalToken","getOverrideGameBinary","getSteamInstallDir","getSteamGameDir","GAME_BIN_PATH","checkWsl","spawn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../../src/brickadia/server.ts"],"sourcesContent":["/*\n Brickadia Server Wrapper\n Manages IO with the game server\n*/\n\nimport Logger from '@/logger';\nimport {\n ACTIVE_WORLD_FILE,\n CONFIG_SAVED_DIR,\n GAME_BIN_PATH,\n getOverrideGameBinary,\n getSteamGameDir,\n getSteamInstallDir,\n} from '@/softconfig';\nimport { getGlobalToken } from '@cli/auth';\nimport { IConfig } from '@config/types';\nimport { checkWsl } from '@util/wsl';\nimport 'colors';\nimport { ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { env } from 'node:process';\nimport readline from 'readline';\nimport stripAnsi from 'strip-ansi';\n\n// list of errors that can be solved by yelling at the user\nconst knownErrors: {\n name: string;\n solution?: string;\n match: RegExp;\n message?: string;\n}[] = [\n {\n name: 'MISSING_LIBGL',\n solution: 'apt-get install libgl1-mesa-glx libglib2.0-0',\n match:\n /error while loading shared libraries: libGL\\.so\\.1: cannot open shared object file/,\n },\n {\n name: 'MISSING_GLIB',\n solution: 'apt-get install libgl1-mesa-glx libglib2.0-0',\n match:\n /error while loading shared libraries: libgthread-2\\.0\\.so\\.0: cannot open shared object file/,\n },\n];\n\n/** Start a brickadia server */\nexport default class BrickadiaServer extends EventEmitter {\n #child: ChildProcessWithoutNullStreams = null;\n #errInterface: readline.Interface = null;\n #outInterface: readline.Interface = null;\n\n config: IConfig;\n path: string;\n\n constructor(dataPath: string, config: IConfig) {\n super();\n\n this.config = config;\n // use the data path if it's absolute, otherwise build an absolute path\n this.path =\n path.isAbsolute(dataPath) || dataPath.startsWith('/')\n ? dataPath\n : path.join(process.cwd(), dataPath);\n\n this.lineListener = this.lineListener.bind(this);\n this.errorListener = this.errorListener.bind(this);\n this.exitListener = this.exitListener.bind(this);\n }\n\n getActiveWorldFile(): string {\n return path.join(this.path, ACTIVE_WORLD_FILE);\n }\n\n /** A world specified by the active world file */\n getActiveWorld(): string | null {\n const activeWorldFile = this.getActiveWorldFile();\n if (existsSync(activeWorldFile)) {\n try {\n return readFileSync(activeWorldFile, 'utf8').trim();\n } catch (err) {\n Logger.errorp(\n 'Failed to read active world file',\n activeWorldFile.yellow,\n err,\n );\n return null;\n }\n }\n return null;\n }\n\n /** Set the world to use next startup */\n setActiveWorld(world: string | null): boolean {\n const activeWorldFile = this.getActiveWorldFile();\n if (!world || world === null) {\n if (existsSync(activeWorldFile)) {\n Logger.verbose('Removing active world file', activeWorldFile.yellow);\n unlinkSync(activeWorldFile);\n }\n return true;\n }\n\n if (!this.worldExists(world)) {\n Logger.verbose(\n 'Cannot set active world to',\n world.yellow,\n 'as it does not exist',\n );\n return false;\n }\n\n Logger.verbose('Setting active world to', world.yellow);\n try {\n writeFileSync(activeWorldFile, world, 'utf8');\n return true;\n } catch (err) {\n Logger.errorp(\n 'Failed to write active world file',\n activeWorldFile.yellow,\n err,\n );\n return false;\n }\n }\n\n /** A world specified by the config */\n getConfigWorld(): string | null {\n return this.config?.server?.world ?? null;\n }\n\n /** A world specified by the BRICKADIA_WORLD env variable */\n getEnvWorld(): string | null {\n return env.BRICKADIA_WORLD || null;\n }\n\n /** Check if a world exists */\n worldExists(world: string): boolean {\n const savedDir = this.config?.server?.savedDir ?? CONFIG_SAVED_DIR;\n const worldPath = path.join(this.path, savedDir, 'Worlds', world + '.brdb');\n return existsSync(worldPath);\n }\n\n /** Get the world that will be used next startup */\n getNextWorld() {\n const candidates = [\n { source: 'file', file: this.getActiveWorld() },\n { source: 'config', file: this.getConfigWorld() },\n { source: 'env', file: this.getEnvWorld() },\n ];\n\n return (\n candidates.find(({ file }) => file && this.worldExists(file)) ?? null\n );\n }\n\n /**\n * Resolve the path to the game server binary for steam/override installs.\n * Returns null for launcher-managed installs, where the binary lives in a\n * branch directory the launcher owns and isn't known statically here.\n */\n getGameBinaryPath(): string | null {\n const overrideBinary = getOverrideGameBinary();\n if (overrideBinary) return overrideBinary;\n\n // a configured branch means the launcher manages the install, not steam\n const isSteam = !this.config.server.branch;\n if (!isSteam) return null;\n\n const steamBeta = this.config.server.steambeta ?? 'main';\n return path.join(\n getSteamInstallDir(), // steam install directory\n steamBeta, // steam beta branch (or main)\n getSteamGameDir(), // Brickadia\n GAME_BIN_PATH, // path to binary\n );\n }\n\n // start the server child process\n start() {\n const {\n email,\n password,\n token: confToken,\n }: { email?: string; password?: string; token?: string } = this.config\n .credentials || {};\n\n const token = confToken || process.env.BRICKADIA_TOKEN || getGlobalToken();\n\n if (token) {\n Logger.verbose('Starting server with hosting token');\n } else {\n Logger.verbose(\n 'Starting server',\n (!email && !password ? 'without' : 'with').yellow,\n 'credentials',\n );\n }\n\n const isSteam = !this.config.server.branch;\n const steamBeta = this.config.server.steambeta ?? 'main';\n const overrideBinary = getOverrideGameBinary();\n const steamBinary = path.join(\n getSteamInstallDir(), // steam install directory\n steamBeta, // steam beta branch (or main)\n getSteamGameDir(), // Brickadia\n GAME_BIN_PATH, // path to binary\n );\n\n let gameBinary = this.getGameBinaryPath() ?? steamBinary;\n\n if (overrideBinary) {\n if (!existsSync(overrideBinary)) {\n Logger.error(\n 'Override binary',\n overrideBinary.yellow,\n 'does not exist!',\n );\n throw new Error(`Override binary ${overrideBinary} does not exist!`);\n }\n\n Logger.verbose(\n 'Using override binary',\n overrideBinary.yellow,\n 'instead of',\n steamBinary.yellow,\n );\n gameBinary = overrideBinary;\n } else if (isSteam) {\n Logger.verbose('Using steam binary', steamBeta.yellow);\n } else {\n Logger.verbose(\n 'Running',\n (this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia launcher'\n ).yellow,\n );\n if (typeof this.config.server.branch === 'string')\n Logger.verbose('Using branch', this.config.server.branch.yellow);\n }\n\n // handle local launcher support\n const launchArgs = isSteam\n ? [gameBinary]\n : [\n this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia',\n this.config.server.branch && `--branch=${this.config.server.branch}`,\n '--server',\n '--',\n ];\n\n const world = this.getNextWorld();\n if (world) {\n Logger.verbose(\n 'Using world',\n world.file.yellow,\n 'from',\n world.source.yellow,\n );\n } else if (this.config.server.map) {\n Logger.verbose('Using map', this.config.server.map.yellow, 'from config');\n }\n\n const params = [\n '--output=L',\n '--',\n ...launchArgs,\n !world &&\n this.config.server.map &&\n `-Environment=\"${this.config.server.map}\"`,\n world && `-World=\"${world.file}\"`,\n '-NotInstalled',\n '-log',\n checkWsl() === 1 ? '-OneThread' : null,\n this.path ? `-UserDir=\"${this.path}\"` : null,\n token ? `-Token=\"${token}\"` : null, // remove token argument if not provided\n !token && email ? `-User=\"${email}\"` : null, // remove email argument if not provided or token is provided\n !token && password ? `-Password=\"${password}\"` : null, // remove password argument if not provided or token is provided\n `-port=\"${this.config.server.port}\"`,\n this.config.server.launchArgs,\n ].filter(Boolean); // remove unused arguments\n Logger.verbose(\n 'Params for spawn',\n params\n .join(' ')\n .replace(/-User=\".*?\"/, '-User=\"<hidden>\"')\n .replace(/-Password=\".*?\"/, '-Password=\"<hidden>\"')\n .replace(/-Token=\".*?\"/, '-Token=\"<hidden>\"')\n .replace(/-Cookie=\".*?\"/, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/, '-Cookie=<hidden>'),\n );\n\n // Either unbuffer or stdbuf must be used because brickadia's output is buffered\n // this means that multiple lines can be bundled together if the output buffer is not full\n // unfortunately without stdbuf or unbuffer, the output would not happen immediately\n this.#child = spawn('stdbuf', params);\n\n Logger.verbose(\n 'Spawn process',\n this.#child ? this.#child.pid : 'failed'.red,\n );\n\n this.#child.stdin.setDefaultEncoding('utf8');\n this.#outInterface = readline.createInterface({\n input: this.#child.stdout,\n terminal: false,\n });\n this.#errInterface = readline.createInterface({\n input: this.#child.stderr,\n terminal: false,\n });\n this.attachListeners();\n Logger.verbose('Attached listeners');\n }\n\n // write a string to the child process\n write(line: string) {\n if (line.length >= 512) {\n // show a warning\n Logger.warn(\n 'WARNING'.yellow,\n 'The following line was called and is',\n 'longer than allowed limit'.red,\n );\n Logger.warn(line.replace(/\\n$/, ''));\n // throw a fake error to get the line number\n try {\n throw new Error('Console Line Too Long');\n } catch (err) {\n Logger.warn(err);\n }\n return;\n }\n if (this.#child) {\n Logger.verbose('WRITE'.green, line.replace(/\\n$/, ''));\n this.#child.stdin.write(line);\n }\n }\n\n // write a line to the child process\n writeln(line: string) {\n this.write(line + '\\n');\n }\n\n // forcibly kills the server\n stop() {\n if (!this.#child) {\n Logger.verbose('Cannot stop server as no subprocess exists');\n return;\n }\n\n Logger.verbose('Forcibly stopping server');\n // kill the process\n this.#child.kill('SIGINT');\n\n // ...kill it again just to make sure it's dead\n spawn('kill', ['-9', this.#child.pid + '']);\n }\n\n // detaches listeners\n cleanup() {\n if (!this.#child) return;\n\n Logger.verbose('Cleaning up brickadia server');\n\n // detach listener\n this.detachListeners();\n\n this.#child = null;\n this.#outInterface = null;\n this.#errInterface = null;\n }\n\n // attaches proxy event listeners\n attachListeners() {\n this.#outInterface.on('line', this.lineListener);\n this.#errInterface.on('line', this.errorListener);\n this.#child.on('exit', this.exitListener);\n this.#child.on('close', () => {});\n }\n\n // removes previously attached proxy event listeners\n detachListeners() {\n this.#outInterface.off('line', this.lineListener);\n this.#errInterface.off('line', this.errorListener);\n this.#child.off('exit', this.exitListener);\n this.#child.removeAllListeners('close');\n }\n\n // -- listeners for basic events (line, err, exit)\n errorListener(line: string) {\n Logger.verbose('ERROR'.red, line);\n this.emit('err', line);\n for (const { match, solution, name, message } of knownErrors) {\n if (line.match(match)) {\n Logger.error(\n `Encountered ${name.red}. ${\n solution ? 'Known fix:\\n ' + solution : message || 'Unknown error.'\n }`,\n );\n }\n }\n }\n\n exitListener(...args: any[]) {\n Logger.verbose('Exit listener fired');\n this.emit('closed', ...args);\n this.cleanup();\n }\n\n lineListener(line: string) {\n this.emit('line', stripAnsi(line));\n }\n}\n"],"names":["ACTIVE_WORLD_FILE","existsSync","readFileSync","Logger","unlinkSync","writeFileSync","env","CONFIG_SAVED_DIR","getOverrideGameBinary","getSteamInstallDir","getSteamGameDir","GAME_BIN_PATH","getGlobalToken","checkWsl","spawn"],"mappings":"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"}
|
|
@@ -4,12 +4,43 @@ const logger = require("../../logger.js");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const versionRegExp = /Brickadia (?<branchName>.+?) \(.+-CL(?<version>\d+)\), Engine (?<engineVersion>.+)/;
|
|
7
|
+
const CL_NEEDLE = Buffer.from("-CL", "utf16le");
|
|
8
|
+
const PAREN_LO = ")".charCodeAt(0);
|
|
9
|
+
const ZERO = "0".charCodeAt(0);
|
|
10
|
+
const NINE = "9".charCodeAt(0);
|
|
11
|
+
const MAX_BINARY_SCANS = 1e5;
|
|
12
|
+
function readBinaryVersion(binPath) {
|
|
13
|
+
try {
|
|
14
|
+
if (!binPath || !fs.existsSync(binPath)) return null;
|
|
15
|
+
const buf = fs.readFileSync(binPath);
|
|
16
|
+
const found = /* @__PURE__ */ new Set();
|
|
17
|
+
let i = buf.indexOf(CL_NEEDLE);
|
|
18
|
+
let scans = 0;
|
|
19
|
+
while (i !== -1 && scans++ < MAX_BINARY_SCANS) {
|
|
20
|
+
let p = i + CL_NEEDLE.length;
|
|
21
|
+
let digits = "";
|
|
22
|
+
while (p + 1 < buf.length && buf[p + 1] === 0 && buf[p] >= ZERO && buf[p] <= NINE) {
|
|
23
|
+
digits += String.fromCharCode(buf[p]);
|
|
24
|
+
p += 2;
|
|
25
|
+
}
|
|
26
|
+
if (digits && buf[p] === PAREN_LO && buf[p + 1] === 0) {
|
|
27
|
+
found.add(Number(digits));
|
|
28
|
+
}
|
|
29
|
+
i = buf.indexOf(CL_NEEDLE, i + 2);
|
|
30
|
+
}
|
|
31
|
+
return found.size === 1 ? [...found][0] : null;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
logger.default.verbose("Failed to read version from binary", binPath, err);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
7
37
|
const version = (omegga) => {
|
|
8
38
|
const LOG_PATH = path.join(omegga.dataPath, "Saved/Logs/Brickadia.log");
|
|
9
39
|
return {
|
|
10
40
|
pattern(line, _logMatch) {
|
|
11
41
|
if (!line.startsWith("LogPakFile")) return;
|
|
12
42
|
if (line !== "LogPakFile: Initializing PakPlatformFile") return;
|
|
43
|
+
if (omegga.version > 0) return;
|
|
13
44
|
if (!fs.existsSync(LOG_PATH)) {
|
|
14
45
|
logger.default.warnp(
|
|
15
46
|
"Log file not found",
|
|
@@ -60,4 +91,5 @@ const version = (omegga) => {
|
|
|
60
91
|
};
|
|
61
92
|
};
|
|
62
93
|
exports.default = version;
|
|
94
|
+
exports.readBinaryVersion = readBinaryVersion;
|
|
63
95
|
//# sourceMappingURL=version.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sources":["../../../src/omegga/matchers/version.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { MatchGenerator } from './types';\nimport path from 'path';\nimport { createReadStream, existsSync } from 'node:fs';\n\nconst versionRegExp =\n /Brickadia (?<branchName>.+?) \\(.+-CL(?<version>\\d+)\\), Engine (?<engineVersion>.+)/;\n\nconst version: MatchGenerator<number> = omegga => {\n const LOG_PATH = path.join(omegga.dataPath, 'Saved/Logs/Brickadia.log');\n\n return {\n pattern(line, _logMatch) {\n if (!line.startsWith('LogPakFile')) return;\n if (line !== 'LogPakFile: Initializing PakPlatformFile') return;\n\n // The version line\n // Brickadia Release-EA1 (PC-Shipping-CL11633), Engine 444a09f18f48\n // is not printed in the game logs.. only the log file...\n\n if (!existsSync(LOG_PATH)) {\n Logger.warnp(\n 'Log file not found',\n LOG_PATH.yellow + '. Cannot check version.',\n );\n return;\n }\n\n // The version line lives only in the log file (not stdout), near the very\n // top. We trigger off a stdout line, but the file may not be flushed to\n // disk that exact instant - so read the first 100 chars and, if the\n // version line isn't there yet, retry a couple times to win that race.\n const tryReadVersion = async (): Promise<number | null> => {\n const stream = createReadStream(LOG_PATH, {\n encoding: 'utf8',\n start: 0,\n end: 100,\n });\n\n let data = '';\n try {\n for await (const chunk of stream) {\n data += chunk;\n }\n } catch {\n return null;\n }\n\n for (const line of data.split('\\n')) {\n const match = line.match(versionRegExp);\n if (match) return Number(match.groups?.version);\n }\n return null;\n };\n\n (async () => {\n // initial attempt + 2 retries; the version is at the start of the log\n for (let attempt = 0; attempt < 3; attempt++) {\n const version = await tryReadVersion();\n if (version != null) {\n omegga.emit('version', version);\n Logger.verbose('Brickadia Version', version);\n omegga.version = version;\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 250));\n }\n Logger.warnp(\n 'Could not determine Brickadia version from',\n LOG_PATH.yellow,\n );\n })();\n\n return 1;\n },\n callback(_version) {},\n };\n};\n\nexport default version;\n"],"names":["existsSync","Logger","createReadStream","line","version"],"mappings":";;;;;AAKA,MAAM,gBACJ;
|
|
1
|
+
{"version":3,"file":"version.js","sources":["../../../src/omegga/matchers/version.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { MatchGenerator } from './types';\nimport path from 'path';\nimport { createReadStream, existsSync, readFileSync } from 'node:fs';\n\nconst versionRegExp =\n /Brickadia (?<branchName>.+?) \\(.+-CL(?<version>\\d+)\\), Engine (?<engineVersion>.+)/;\n\n// UTF-16LE bytes for \"-CL\" (2D 00 43 00 4C 00). UE bakes the game version into\n// the server binary as a UTF-16LE string literal of the form\n// `<Branch> (<Platform>-<Config>-CL<changelist>)`, e.g. `Release-EA3 (PC-Shipping-CL14860)`.\nconst CL_NEEDLE = Buffer.from('-CL', 'utf16le');\n\n// Low bytes of the UTF-16LE ')' terminator and '0'/'9' digit bounds (the high\n// byte is 0 for ASCII, checked separately).\nconst PAREN_LO = ')'.charCodeAt(0);\nconst ZERO = '0'.charCodeAt(0);\nconst NINE = '9'.charCodeAt(0);\n\n// Safety cap on needle scans so a corrupt/unexpected binary can never stall\n// startup. Real binaries contain a single match; this is orders of magnitude\n// above anything legitimate.\nconst MAX_BINARY_SCANS = 100_000;\n\n/**\n * Extract the Brickadia changelist (game version) directly from the server\n * binary, without launching it. This lets `omegga.version` be populated before\n * plugins hit `init` (e.g. calling `readSaveData`), instead of staying `-1`\n * until the game boots and writes its log.\n *\n * UE bakes the version in as a UTF-16LE literal like `Release-EA3 (PC-Shipping-CL14860)`\n * (`BRANCH_NAME`, platform/config, and `BUILT_FROM_CHANGELIST` concatenated at\n * preprocessor time). We scan the raw bytes for the `-CL<digits>)` needle.\n * Verified across pre-steam launcher installs and EA1-EA3+ steam builds, both\n * Shipping and Test configs.\n *\n * @param binPath absolute path to `BrickadiaServer-Linux-Shipping` (a null path,\n * e.g. an unresolved launcher install, simply yields null)\n * @returns the changelist number, or null if it can't be determined (missing\n * file, read error, no match, or an ambiguous multi-match)\n */\nexport function readBinaryVersion(binPath: string | null): number | null {\n try {\n if (!binPath || !existsSync(binPath)) return null;\n const buf = readFileSync(binPath);\n const found = new Set<number>();\n\n let i = buf.indexOf(CL_NEEDLE);\n let scans = 0;\n while (i !== -1 && scans++ < MAX_BINARY_SCANS) {\n // read the following UTF-16LE ASCII digits until a non-digit\n let p = i + CL_NEEDLE.length;\n let digits = '';\n while (\n p + 1 < buf.length &&\n buf[p + 1] === 0 &&\n buf[p] >= ZERO &&\n buf[p] <= NINE\n ) {\n digits += String.fromCharCode(buf[p]);\n p += 2;\n }\n // require the UTF-16LE ')' terminator so we skip the `%s-CL-%u` format\n // string (and any `-CL-` variant that isn't the parenthesized literal)\n if (digits && buf[p] === PAREN_LO && buf[p + 1] === 0) {\n found.add(Number(digits));\n }\n i = buf.indexOf(CL_NEEDLE, i + 2);\n }\n\n // only trust an unambiguous single match\n return found.size === 1 ? [...found][0] : null;\n } catch (err) {\n Logger.verbose('Failed to read version from binary', binPath, err);\n return null;\n }\n}\n\nconst version: MatchGenerator<number> = omegga => {\n const LOG_PATH = path.join(omegga.dataPath, 'Saved/Logs/Brickadia.log');\n\n return {\n pattern(line, _logMatch) {\n if (!line.startsWith('LogPakFile')) return;\n if (line !== 'LogPakFile: Initializing PakPlatformFile') return;\n\n // The version is normally resolved up-front from the server binary\n // ({@link readBinaryVersion}, in Omegga.start). This log-file read is only\n // a fallback for when that fails (e.g. launcher installs whose binary path\n // isn't resolved, or an unreadable binary). If we already have it, skip.\n if (omegga.version > 0) return;\n\n // The version line\n // Brickadia Release-EA1 (PC-Shipping-CL11633), Engine 444a09f18f48\n // is not printed in the game logs.. only the log file...\n\n if (!existsSync(LOG_PATH)) {\n Logger.warnp(\n 'Log file not found',\n LOG_PATH.yellow + '. Cannot check version.',\n );\n return;\n }\n\n // The version line lives only in the log file (not stdout), near the very\n // top. We trigger off a stdout line, but the file may not be flushed to\n // disk that exact instant - so read the first 100 chars and, if the\n // version line isn't there yet, retry a couple times to win that race.\n const tryReadVersion = async (): Promise<number | null> => {\n const stream = createReadStream(LOG_PATH, {\n encoding: 'utf8',\n start: 0,\n end: 100,\n });\n\n let data = '';\n try {\n for await (const chunk of stream) {\n data += chunk;\n }\n } catch {\n return null;\n }\n\n for (const line of data.split('\\n')) {\n const match = line.match(versionRegExp);\n if (match) return Number(match.groups?.version);\n }\n return null;\n };\n\n (async () => {\n // initial attempt + 2 retries; the version is at the start of the log\n for (let attempt = 0; attempt < 3; attempt++) {\n const version = await tryReadVersion();\n if (version != null) {\n omegga.emit('version', version);\n Logger.verbose('Brickadia Version', version);\n omegga.version = version;\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 250));\n }\n Logger.warnp(\n 'Could not determine Brickadia version from',\n LOG_PATH.yellow,\n );\n })();\n\n return 1;\n },\n callback(_version) {},\n };\n};\n\nexport default version;\n"],"names":["existsSync","readFileSync","Logger","createReadStream","line","version"],"mappings":";;;;;AAKA,MAAM,gBACJ;AAKF,MAAM,YAAY,OAAO,KAAK,OAAO,SAAS;AAI9C,MAAM,WAAW,IAAI,WAAW,CAAC;AACjC,MAAM,OAAO,IAAI,WAAW,CAAC;AAC7B,MAAM,OAAO,IAAI,WAAW,CAAC;AAK7B,MAAM,mBAAmB;AAmBlB,SAAS,kBAAkB,SAAuC;AACvE,MAAI;AACF,QAAI,CAAC,WAAW,CAACA,GAAAA,WAAW,OAAO,EAAG,QAAO;AAC7C,UAAM,MAAMC,GAAAA,aAAa,OAAO;AAChC,UAAM,4BAAY,IAAA;AAElB,QAAI,IAAI,IAAI,QAAQ,SAAS;AAC7B,QAAI,QAAQ;AACZ,WAAO,MAAM,MAAM,UAAU,kBAAkB;AAE7C,UAAI,IAAI,IAAI,UAAU;AACtB,UAAI,SAAS;AACb,aACE,IAAI,IAAI,IAAI,UACZ,IAAI,IAAI,CAAC,MAAM,KACf,IAAI,CAAC,KAAK,QACV,IAAI,CAAC,KAAK,MACV;AACA,kBAAU,OAAO,aAAa,IAAI,CAAC,CAAC;AACpC,aAAK;AAAA,MACP;AAGA,UAAI,UAAU,IAAI,CAAC,MAAM,YAAY,IAAI,IAAI,CAAC,MAAM,GAAG;AACrD,cAAM,IAAI,OAAO,MAAM,CAAC;AAAA,MAC1B;AACA,UAAI,IAAI,QAAQ,WAAW,IAAI,CAAC;AAAA,IAClC;AAGA,WAAO,MAAM,SAAS,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI;AAAA,EAC5C,SAAS,KAAK;AACZC,WAAAA,QAAO,QAAQ,sCAAsC,SAAS,GAAG;AACjE,WAAO;AAAA,EACT;AACF;AAEA,MAAM,UAAkC,CAAA,WAAU;AAChD,QAAM,WAAW,KAAK,KAAK,OAAO,UAAU,0BAA0B;AAEtE,SAAO;AAAA,IACL,QAAQ,MAAM,WAAW;AACvB,UAAI,CAAC,KAAK,WAAW,YAAY,EAAG;AACpC,UAAI,SAAS,2CAA4C;AAMzD,UAAI,OAAO,UAAU,EAAG;AAMxB,UAAI,CAACF,GAAAA,WAAW,QAAQ,GAAG;AACzBE,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,SAAS,SAAS;AAAA,QAAA;AAEpB;AAAA,MACF;AAMA,YAAM,iBAAiB,YAAoC;AACzD,cAAM,SAASC,GAAAA,iBAAiB,UAAU;AAAA,UACxC,UAAU;AAAA,UACV,OAAO;AAAA,UACP,KAAK;AAAA,QAAA,CACN;AAED,YAAI,OAAO;AACX,YAAI;AACF,2BAAiB,SAAS,QAAQ;AAChC,oBAAQ;AAAA,UACV;AAAA,QACF,QAAQ;AACN,iBAAO;AAAA,QACT;AAEA,mBAAWC,SAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,gBAAM,QAAQA,MAAK,MAAM,aAAa;AACtC,cAAI,MAAO,QAAO,OAAO,MAAM,QAAQ,OAAO;AAAA,QAChD;AACA,eAAO;AAAA,MACT;AAEA,OAAC,YAAY;AAEX,iBAAS,UAAU,GAAG,UAAU,GAAG,WAAW;AAC5C,gBAAMC,WAAU,MAAM,eAAA;AACtB,cAAIA,YAAW,MAAM;AACnB,mBAAO,KAAK,WAAWA,QAAO;AAC9BH,2BAAO,QAAQ,qBAAqBG,QAAO;AAC3C,mBAAO,UAAUA;AACjB;AAAA,UACF;AACA,gBAAM,IAAI,QAAQ,CAAA,YAAW,WAAW,SAAS,GAAG,CAAC;AAAA,QACvD;AACAH,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,SAAS;AAAA,QAAA;AAAA,MAEb,GAAA;AAEA,aAAO;AAAA,IACT;AAAA,IACA,SAAS,UAAU;AAAA,IAAC;AAAA,EAAA;AAExB;;;"}
|
package/dist/omegga/server.js
CHANGED
|
@@ -15,6 +15,7 @@ const path = require("path");
|
|
|
15
15
|
const commandInjector = require("./commandInjector.js");
|
|
16
16
|
const commands = require("./commands.js");
|
|
17
17
|
const index$1 = require("./matchers/index.js");
|
|
18
|
+
const version$1 = require("./matchers/version.js");
|
|
18
19
|
const plugin = require("./plugin.js");
|
|
19
20
|
const wrapper = require("./wrapper.js");
|
|
20
21
|
const MISSING_CMD = '"Command not found. Type <color=\\"ffff00\\">/help</> for a list of commands or <color=\\"ffff00\\">/plugins</> for plugin information."';
|
|
@@ -269,6 +270,13 @@ class Omegga extends wrapper.default {
|
|
|
269
270
|
//
|
|
270
271
|
async start() {
|
|
271
272
|
this.starting = true;
|
|
273
|
+
if (this.version < 0) {
|
|
274
|
+
const binVersion = version$1.readBinaryVersion(this.getGameBinaryPath());
|
|
275
|
+
if (binVersion != null) {
|
|
276
|
+
this.version = binVersion;
|
|
277
|
+
logger.default.verbose("Brickadia Version (from binary)", binVersion);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
272
280
|
if (this.webserver) await this.webserver.start();
|
|
273
281
|
if (this.pluginLoader) {
|
|
274
282
|
logger.default.verbose("Scanning for plugins");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../../src/omegga/server.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { OmeggaLike, OmeggaPlayer, PluginInterop } from '@/plugin';\nimport {\n BRICKADIA_AUTH_FILES,\n CONFIG_AUTH_DIR,\n CONFIG_HOME,\n CONFIG_SAVED_DIR,\n DATA_PATH,\n} from '@/softconfig';\nimport { VERSION } from '@/version';\nimport { EnvironmentPreset } from '@brickadia/presets';\nimport {\n BRBanList,\n BRPlayerNameCache,\n BRRoleAssignments,\n BRRoleSetup,\n} from '@brickadia/types';\nimport { IConfig } from '@config/types';\nimport { map as mapUtils, pattern, uuid } from '@util';\nimport { readBrdbRevisions } from '@util/brdb';\nimport { copyFiles, mkdir, readWatchedJSON } from '@util/file';\nimport Webserver from '@webserver/backend';\nimport brs, {\n WorldReader,\n writeBrzLegacy,\n type ReadSaveObject,\n type WriteSaveObject,\n} from 'brs-js';\nimport 'colors';\nimport glob from 'glob';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { basename, join } from 'path';\nimport { AutoRestartConfig } from '..';\nimport commandInjector from './commandInjector';\nimport {\n ConsoleCommands,\n EA2_VERSION,\n PREFAB_VERSION,\n resolveConsoleCommands,\n} from './commands';\nimport MATCHERS from './matchers';\nimport Player from './player';\nimport { PluginLoader } from './plugin';\nimport {\n IGamemode,\n ILogMinigame,\n IMinigameList,\n IOmeggaOptions,\n IPlayerPositions,\n IServerStatus,\n} from './types';\nimport OmeggaWrapper from './wrapper';\n\nconst MISSING_CMD =\n '\"Command not found. Type <color=\\\\\"ffff00\\\\\">/help</> for a list of commands or <color=\\\\\"ffff00\\\\\">/plugins</> for plugin information.\"';\n\n// Prefab.SaveRegion requires a region; when saving the whole world we pass a\n// maximal extent centered on the origin to capture everything.\nconst WHOLE_WORLD_EXTENT = 1_000_000_000;\n\n// These helpers are module-level (not class methods) on purpose: safe plugins\n// call these methods through a ProxyOmegga whose prototype steals Omegga's\n// implementations (see injectOmeggaPrototypes). A `#private` method would fail\n// the brand check when `this` is a ProxyOmegga (\"Receiver must be an instance\n// of class Omegga\"), so anything a stolen method depends on must not be\n// `#private`. Module functions keep working regardless of the receiver.\n\n/**\n * Whether the running game version has removed the legacy .brs bricks console\n * commands (Bricks.Save/Load/ClearAll/ClearRegion, World.LoadAdditive) in\n * favor of the prefab (`br.Prefab.*`) and world (`br.World.Clear*`) commands.\n * Removed at Brickadia CL{@link PREFAB_VERSION}.\n */\nfunction brsRemoved(version: number): boolean {\n return version > 0 && version >= PREFAB_VERSION;\n}\n\n/**\n * Warn that a method backed by a removed console command is a no-op on the\n * running game version, and return whether the caller should bail.\n * @param version the running game version\n * @param method the omegga method being called (for the message)\n * @param since CL version the underlying command was removed at\n * @param release human release name for that version (e.g. `EA2`, `EA3`)\n * @param replacement suggested replacement method/API\n */\nfunction warnRemoved(\n version: number,\n method: string,\n since: number,\n release: string,\n replacement: string,\n): boolean {\n if (!(version > 0 && version >= since)) return false;\n Logger.warnp(\n `omegga.${method}() uses a console command removed in Brickadia ` +\n `${release}. This call has no effect - use ${replacement} instead.`,\n );\n return true;\n}\n\n/**\n * Write a save to a temporary `.brz` prefab in the prefab directory (EA3).\n * @returns the bare path ref for `br.Prefab.*` commands (no `Prefabs/` prefix\n * or `.brz` extension) and the absolute file path for cleanup.\n */\nfunction writeTempPrefab(\n omegga: {\n prefabPath: string;\n _tempSavePrefix: string;\n _tempCounter: { save: number };\n },\n saveData: WriteSaveObject,\n): { ref: string; file: string } {\n const ref =\n omegga._tempSavePrefix + Date.now() + '_' + omegga._tempCounter.save++;\n const file = join(omegga.prefabPath, ref + '.brz');\n if (!file.startsWith(omegga.prefabPath))\n throw 'prefab file not in Saved/Prefabs directory';\n mkdir(omegga.prefabPath);\n writeFileSync(file, new Uint8Array(writeBrzLegacy(saveData)));\n return { ref, file };\n}\n\n// TODO: safe broadcast parsing\n\nexport default class Omegga extends OmeggaWrapper implements OmeggaLike {\n /** The save counter prevents omegga from saving over the same file */\n _tempCounter = { save: 0, environment: 0 };\n /** The save prefix is prepended to all temporary saves */\n _tempSavePrefix = 'omegga_temp_';\n\n // pluginloader is not private so plugins can potentially add more formats\n pluginLoader: PluginLoader = undefined;\n webserver: Webserver;\n\n verbose: boolean;\n savePath: string;\n worldPath: string;\n prefabPath: string;\n presetPath: string;\n configPath: string;\n options: IOmeggaOptions;\n\n version: number;\n\n /** memoized version-resolved console commands ({@link Console}) */\n #console: { version: number; commands: ConsoleCommands };\n\n /**\n * version-resolved Brickadia console command names, nested by namespace.\n * e.g. `omegga.Console.Bricks.Clear` -> \"Bricks.Clear\" or \"br.Bricks.Clear\"\n * depending on the running game version.\n */\n get Console(): ConsoleCommands {\n if (this.#console?.version !== this.version)\n this.#console = {\n version: this.version,\n commands: resolveConsoleCommands(this.version),\n };\n return this.#console.commands;\n }\n\n host?: { id: string; name: string };\n players: OmeggaPlayer[];\n\n started = false;\n starting = false;\n stopping = false;\n crashDetected = false;\n currentMap: string;\n\n getServerStatus: () => Promise<IServerStatus>;\n listMinigames: () => Promise<IMinigameList>;\n getAllPlayerPositions: () => Promise<IPlayerPositions>;\n getMinigames: () => Promise<ILogMinigame[]>;\n getGamemode: () => Promise<IGamemode | null>;\n\n /**\n * Omegga instance\n */\n constructor(serverPath: string, cfg: IConfig, options: IOmeggaOptions = {}) {\n super(serverPath, cfg);\n this.verbose = Logger.VERBOSE;\n\n Logger.verbose('Running omegga', `v${VERSION}`.green);\n Logger.verbose('Versions', process.versions);\n Logger.verbose('Config', {\n ...cfg,\n credentials: cfg.credentials\n ? Object.fromEntries(\n Object.entries(cfg.credentials).map(([k, v]) => [k, v ? '***' : v]),\n )\n : cfg.credentials,\n server: {\n ...cfg.server,\n ...(cfg.server.password && { password: '***' }),\n ...(cfg.server.steambetaPassword && { steambetaPassword: '***' }),\n ...(cfg.server.launchArgs && {\n launchArgs: cfg.server.launchArgs\n .replace(/-Cookie=\".*?\"/g, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/g, '-Cookie=<hidden>'),\n }),\n },\n });\n\n // inject commands\n Logger.verbose('Setting up command injector');\n commandInjector(this, this.logWrangler);\n\n // launch options (disabling webserver)\n this.options = options;\n const savedDir = cfg.server.savedDir ?? CONFIG_SAVED_DIR;\n\n // path to save files\n this.savePath = join(this.path, DATA_PATH, savedDir, 'Builds');\n this.worldPath = join(this.path, DATA_PATH, savedDir, 'Worlds');\n this.prefabPath = join(this.path, DATA_PATH, savedDir, 'Prefabs');\n\n this.presetPath = join(this.path, DATA_PATH, savedDir, 'Presets');\n\n // path to config files\n this.configPath = join(this.path, DATA_PATH, savedDir, 'Server');\n\n // create dir folders\n Logger.verbose('Creating directories');\n mkdir(this.savePath);\n mkdir(this.configPath);\n\n // ignore auth file copy\n if (!options.noauth) {\n Logger.verbose('Copying auth files');\n this.copyAuthFiles();\n }\n\n // create the webserver if it's enabled\n // the web interface provides access to server information while the server is running\n // and lets you view chat logs, disable plugins, etc\n if (!options.noweb) {\n Logger.verbose('Creating webserver');\n this.webserver = new Webserver(cfg.omegga, this);\n }\n\n if (!options.noplugin) {\n Logger.verbose('Creating plugin loader');\n this.pluginLoader = new PluginLoader(this.path, this);\n }\n\n /** @type {Array<Player>}list of online players */\n this.players = [];\n\n /** host player info `{id: uuid, name: player name}` */\n this.host = undefined;\n\n /** @type {String} current game version - may later be turned into CL#### versions */\n this.version = -1;\n\n /** @type {Boolean} whether server has started */\n this.started = false;\n /** @type {Boolean} whether server is starting up */\n this.starting = false;\n\n /** @type {String} current map */\n this.currentMap = '';\n\n // add all the matchers to the server\n Logger.verbose('Adding matchers');\n for (const matcher of MATCHERS) {\n const { pattern, callback } = matcher(this);\n this.addMatcher(pattern, callback);\n }\n\n process.on('uncaughtException', async err => {\n Logger.verbose('Uncaught exception', err);\n this.emit('error', err);\n\n // publish stop to database\n this.webserver?.database?.addChatLog('server', {}, 'Server error');\n\n try {\n await this.stop();\n } catch (e) {\n Logger.error(e);\n }\n process.exit();\n });\n\n // when brickadia starts, mark the server as started\n this.on('start', ({ map }) => {\n this.started = true;\n this.starting = false;\n this.currentMap = map;\n this.writeln(`${this.Console.Chat.MessageForUnknownCommands} 0`);\n\n this.restoreServer();\n });\n\n // detect engine crash from stderr or stdout\n const crashHandler = (line: string) => {\n if (\n !this.crashDetected &&\n (/Engine crash handling finished; re-raising signal \\d+ for the default handler\\. Good bye\\./.test(\n line,\n ) ||\n /LogCore: === Critical error: ===/.test(line))\n ) {\n Logger.error('Engine crash detected!');\n this.crashDetected = true;\n }\n };\n this.on('err', crashHandler);\n this.on('line', crashHandler);\n\n // when brickadia exits, stop omegga\n this.on('exit', () => {\n this.stop();\n });\n\n // when the process closes, emit the exit signal and stop\n this.on('closed', () => {\n // capture crash state before 'exit' handler triggers stop()\n const wasCrash = this.crashDetected;\n this.crashDetected = false;\n if (this.started) this.emit('exit');\n const doRestart = async () => {\n if (!wasCrash) return;\n try {\n const config = await this.webserver?.database?.getAutoRestartConfig();\n if (config?.crashRestartEnabled) {\n Logger.logp('Restarting server after crash...');\n this.webserver?.database?.addChatLog(\n 'server',\n {},\n 'Server crashed, restarting...',\n );\n await this.start();\n }\n } catch (err) {\n Logger.error('Error restarting after crash', err);\n }\n };\n if (!this.stopping) {\n this.stop().then(doRestart);\n } else {\n // stop() already in progress from 'exit' handler - wait for it to finish\n this.once('server:stopped', () => doRestart());\n }\n });\n\n // detect when the game reports a command does not exist\n this.on('unknownCommand', (name: string, cmd: string) => {\n // if it's not registered to a plugin, send the missing command message\n if (!this.pluginLoader || !this.pluginLoader.isCommand(cmd)) {\n this.whisper(name, MISSING_CMD);\n }\n });\n }\n\n /** attempt to save server state */\n async saveServer(config: AutoRestartConfig) {\n if (config.players && this.players.length > 0) {\n Logger.logp('Getting player positions...');\n const players = await this.getAllPlayerPositions();\n Logger.logp(`Saving ${players.length} player positions...`);\n const data = players\n .filter(p => !p.isDead && p.pos)\n .map(p => ({ position: p.pos, id: p.player.id }));\n if (players.length > 0)\n writeFileSync(\n join(this.path, DATA_PATH, 'omegga_temp_players.json'),\n JSON.stringify(data),\n );\n }\n\n if (config.saveWorld) {\n Logger.logp('Saving world...');\n await this.saveWorld();\n }\n }\n\n async restartServer() {\n if (this.starting || this.stopping) return;\n if (!this.started) return await this.start();\n\n const nextWorld = this.getNextWorld();\n if (nextWorld) {\n Logger.logp('Loading world', nextWorld.file.yellow);\n Logger.verbose('Next world configured from', nextWorld.source.yellow);\n this.loadWorld(nextWorld.file);\n } else {\n this.changeMap(this.currentMap);\n }\n\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('Restart result:', res);\n }\n\n /** attempt to restore the server's state */\n async restoreServer() {\n const tempPlayersFile = join(\n this.path,\n DATA_PATH,\n 'omegga_temp_players.json',\n );\n if (!existsSync(tempPlayersFile)) return;\n\n try {\n Logger.logp('Loading previous player positions...');\n\n // player positions are an array to address multi-clienting\n const players: { position: number[]; id: string }[] = JSON.parse(\n readFileSync(tempPlayersFile).toString(),\n );\n\n // restore player position on join\n const callback = (player: OmeggaPlayer) => {\n const index = players.findIndex(p => p.id === player.id);\n if (index > -1) {\n const { position } = players[index];\n this.writeln(\n `${this.Console.Chat.Command} /TP \"${player.name}\" ${position.join(' ')} 0`,\n );\n\n // remove the entry\n players[index] = players[players.length - 1];\n players.pop();\n }\n };\n this.on('join', callback);\n\n let timeout = setTimeout(() => {\n try {\n this.off('join', callback);\n if (existsSync(tempPlayersFile)) unlinkSync(tempPlayersFile);\n } catch (err) {\n Logger.error('Error removing omegga_temp_players.json', err);\n }\n }, 10000);\n this.once('changemap', () => {\n clearTimeout(timeout);\n this.off('join', callback);\n });\n } catch (err) {\n Logger.error('Error restoring previous server state', err);\n }\n }\n\n /**\n * start webserver, load plugins, start the brickadia server\n * this should not be called by a plugin\n */\n //\n async start(): Promise<any> {\n this.starting = true;\n if (this.webserver) await this.webserver.start();\n if (this.pluginLoader) {\n // scan for plugins\n Logger.verbose('Scanning for plugins');\n await this.pluginLoader.scan();\n\n // load the plugins\n Logger.verbose('Loading plugins');\n await this.pluginLoader.reload();\n }\n\n Logger.verbose('Starting Brickadia');\n super.start();\n this.emit('server:starting');\n }\n\n /**\n * unload plugins and stop the server\n * this should not be called by a plugin\n */\n async stop() {\n if (!this.started && !this.starting) {\n Logger.verbose(\"Stop called while server wasn't started or was starting\");\n return;\n }\n\n if (this.stopping) {\n Logger.verbose('Stop called while server was starting');\n return;\n }\n\n this.stopping = true;\n this.emit('server:stopping');\n if (this.pluginLoader) {\n Logger.verbose('Unloading plugins');\n await this.pluginLoader.unload();\n }\n Logger.verbose('Stopping server');\n super.stop();\n\n const res = await Promise.race([\n new Promise(resolve => this.once('exit', () => resolve('exit'))),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n\n Logger.verbose('Stop result:', res);\n if (this.stopping) this.emit('server:stopped');\n this.stopping = false;\n this.started = false;\n this.starting = false;\n this.players = [];\n }\n\n /**\n * Copies auth files from home config dir\n * this should never be called by a plugin\n */\n copyAuthFiles() {\n const authDir = this.config.server.authDir ?? CONFIG_AUTH_DIR;\n const savedDir = this.config.server.savedDir ?? CONFIG_SAVED_DIR;\n const authPath = join(this.path, DATA_PATH, savedDir, authDir);\n const homeAuthPath = join(\n CONFIG_HOME,\n (savedDir !== CONFIG_SAVED_DIR ? savedDir : '') + authDir,\n );\n\n copyFiles(homeAuthPath, authPath, BRICKADIA_AUTH_FILES);\n }\n\n // TODO: split messages that longer than 512 characters\n // TODO: delete characters that are known to crash the game\n broadcast(...messages: string[]) {\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m => this.writeln(`${this.Console.Chat.Broadcast} ${m}`));\n }\n\n whisper(target: string | OmeggaPlayer, ...messages: string[]) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // player may have left before the message could be sent\n if (!target) return;\n\n // whisper the messages to that player\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m =>\n this.writeln(\n `${this.Console.Chat.Whisper} \"${(target as { name: string }).name}\" ${m}`,\n ),\n );\n }\n\n middlePrint(target: string | OmeggaPlayer, message: string) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // player may have left before the message could be sent\n if (!target) return;\n\n // whisper the messages to that player\n if (message.length > 512) return;\n this.writeln(\n `${this.Console.Chat.StatusMessage} \"${(target as { name: string }).name}\" ${message}`,\n );\n }\n\n getPlayers(): {\n id: string;\n name: string;\n displayName: string;\n controller: string;\n state: string;\n }[] {\n return this.players.map(p => ({ ...p }));\n }\n\n getRoleSetup(): BRRoleSetup {\n // Read RoleSetup2, fallback to old RoleSetup if it doesn't exist\n return (readWatchedJSON(join(this.configPath, 'RoleSetup2.json')) ??\n readWatchedJSON(join(this.configPath, 'RoleSetup.json'))) as BRRoleSetup;\n }\n\n getRoleAssignments(): BRRoleAssignments {\n return readWatchedJSON(\n join(this.configPath, 'RoleAssignments.json'),\n ) as BRRoleAssignments;\n }\n\n getBanList(): BRBanList {\n return readWatchedJSON(join(this.configPath, 'BanList.json')) as BRBanList;\n }\n\n getNameCache(): BRPlayerNameCache {\n return readWatchedJSON(\n join(this.configPath, 'PlayerNameCache.json'),\n ) as BRPlayerNameCache;\n }\n\n getPlayer(target: string): OmeggaPlayer {\n return this.players.find(\n p =>\n p.name === target ||\n p.id === target ||\n p.controller === target ||\n p.state === target,\n );\n }\n\n findPlayerByName(name: string): OmeggaPlayer {\n name = name.toLowerCase();\n const exploded = pattern.explode(name);\n return (\n this.players.find(p => p.name === name || p.displayName === name) || // find by exact match\n this.players.find(\n p => p.name.indexOf(name) > -1 || p.displayName.indexOf(name) > -1,\n ) || // find by rough match\n this.players.find(\n p => p.name.match(exploded) || p.displayName.match(exploded),\n ) // find by exploded regex match (ck finds cake, tbp finds TheBlackParrot)\n );\n }\n\n getHostId(): string {\n return this.host?.id ?? '';\n }\n\n saveMinigame(index: number, name: string) {\n this.writeln(\n `${this.Console.Server.Minigames.SavePreset} ${index} \"${name}\"`,\n );\n }\n\n deleteMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Delete} ${index}`);\n }\n\n resetMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Reset} ${index}`);\n }\n\n nextRoundMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.NextRound} ${index}`);\n }\n\n loadMinigame(presetName: string, owner = '') {\n this.writeln(\n `${this.Console.Server.Minigames.LoadPreset} \"${presetName}\" ${owner ? `\"${owner}\"` : ''}`,\n );\n }\n\n getMinigamePresets(): string[] {\n const presetPath = join(this.presetPath, 'Minigame');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n resetEnvironment() {\n this.writeln(`${this.Console.Server.Environment.Reset}`);\n }\n\n async saveEnvironment(presetName: string): Promise<void> {\n await this.addWatcher(/Environment preset saved.$/, {\n // request the pawn for this player's controller (should only be one)\n exec: () =>\n this.writeln(\n `${this.Console.Server.Environment.SavePreset} \"${presetName}\"`,\n ),\n timeoutDelay: 100,\n });\n }\n\n async getEnvironmentData(): Promise<EnvironmentPreset> {\n const saveName =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n await this.saveEnvironment(saveName);\n const data = this.readEnvironmentData(saveName);\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n if (existsSync(file)) unlinkSync(file);\n\n return data;\n }\n\n readEnvironmentData(saveName: string): EnvironmentPreset {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readEnvironmentData';\n\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n try {\n if (existsSync(file)) return JSON.parse(readFileSync(file).toString());\n } catch (err) {\n Logger.verbose('Error parsing save data in readEnvironmentData', err);\n }\n return null;\n }\n\n loadEnvironment(presetName: string) {\n this.writeln(`${this.Console.Server.Environment.LoadPreset} ${presetName}`);\n }\n\n loadEnvironmentData(\n preset: EnvironmentPreset | EnvironmentPreset['data']['groups'],\n ) {\n if ('data' in preset) preset = preset.data.groups;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n const path = join(this.presetPath, 'Environment', saveFile + '.bp');\n\n writeFileSync(\n path,\n JSON.stringify({\n formatVersion: '1',\n presetVersion: '1',\n type: 'Environment',\n data: {\n groups: {\n ...preset,\n },\n },\n }),\n );\n\n this.loadEnvironment(saveFile);\n\n // this is lazy, but environments should load much faster than builds\n // do, so it's not really worth keeping track of logs for this\n setTimeout(() => unlinkSync(path), 5000);\n }\n\n getEnvironmentPresets(): string[] {\n const presetPath = join(this.presetPath, 'Environment');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n clearBricks(target: string | { id: string }, quiet = false) {\n // target is a player object, just use that id\n if (typeof target === 'object' && target.id) target = target.id;\n // if the target isn't a uuid already, find the player by name or controller and use that uuid\n else if (typeof target === 'string' && !uuid.match(target)) {\n // only set the target if the player exists\n const player = this.getPlayer(target);\n target = player && player.id;\n }\n\n if (!target) return;\n\n this.writeln(`${this.Console.Bricks.Clear} ${target} ${quiet ? 1 : ''}`);\n }\n\n clearRegion(\n region: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n options?: {\n target?: string | OmeggaPlayer;\n /** clear bricks in the region (default true) */\n bricks?: boolean;\n /** also clear entities in the region (default false, EA3 only) */\n entities?: boolean;\n },\n ) {\n // resolve the optional owner filter (player object, uuid, or name) to a uuid\n let target = '';\n const rawTarget = options?.target;\n if (rawTarget) {\n if (typeof rawTarget === 'object') target = rawTarget.id;\n else if (uuid.match(rawTarget)) target = rawTarget;\n else target = this.getPlayer(rawTarget)?.id ?? '';\n }\n\n const center = region.center.join(' ');\n const extent = region.extent.join(' ');\n\n if (brsRemoved(this.version)) {\n // br.World.ClearRegion <Center> <Extent> [ClearBricks] [ClearEntities] [FilterUserId]\n const bricks = (options?.bricks ?? true) ? 1 : 0;\n const entities = (options?.entities ?? false) ? 1 : 0;\n this.writeln(\n `${this.Console.World.ClearRegion} ${center} ${extent} ${bricks} ${entities}${\n target ? ' ' + target : ''\n }`,\n );\n return;\n }\n\n // legacy .brs region clear (bricks only)\n this.writeln(\n `${this.Console.Bricks.ClearRegion} ${center} ${extent}${\n target ? ' ' + target : ''\n }`,\n );\n }\n\n clearAllBricks(\n options:\n | boolean\n | { quiet?: boolean; bricks?: boolean; entities?: boolean } = {},\n ) {\n // backwards compat: a bare boolean is the legacy `quiet` argument\n const {\n quiet = false,\n bricks = true,\n entities = false,\n } = typeof options === 'boolean' ? { quiet: options } : options;\n\n if (brsRemoved(this.version)) {\n // br.World.ClearAll [ClearBricks] [ClearEntities] [Silent]\n this.writeln(\n `${this.Console.World.ClearAll} ${bricks ? 1 : 0} ${\n entities ? 1 : 0\n } ${quiet ? 1 : 0}`,\n );\n return;\n }\n // legacy Bricks.ClearAll only ever cleared bricks\n this.writeln(`${this.Console.Bricks.ClearAll} ${quiet ? 1 : ''}`);\n }\n\n saveBricks(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ) {\n if (\n warnRemoved(\n this.version,\n 'saveBricks',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefab',\n )\n )\n return;\n if (!saveName) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n if (region?.center && region?.extent)\n this.writeln(\n `${this.Console.Bricks.SaveRegion} ${saveName} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`,\n );\n else this.writeln(`${this.Console.Bricks.Save} ${saveName}`);\n }\n\n async saveBricksAsync(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ): Promise<void> {\n if (\n warnRemoved(\n this.version,\n 'saveBricksAsync',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefabAsync',\n )\n )\n return;\n if (!saveName) return;\n\n let saveNameClean = saveName;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveNameClean = `\"${saveName}\"`;\n\n const command =\n region?.center && region?.extent\n ? `${this.Console.Bricks.SaveRegion} ${saveNameClean} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`\n : `${this.Console.Bricks.Save} ${saveNameClean}`;\n\n // wait for the server to save the file\n await this.watchLogChunk(command, /^(LogBrickSerializer|LogTemp): (.+)$/, {\n first: match => match[0].endsWith(saveName + '.brs...'),\n last: match =>\n Boolean(\n match[2].match(\n /Saved .+ bricks and .+ components from .+ owners|Error: No bricks in grid!|Error: No bricks selected to save!/,\n ),\n ),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n });\n }\n\n loadBricks(\n saveName: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricks',\n PREFAB_VERSION,\n 'EA3',\n 'loadPrefab',\n )\n )\n return;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.Load} ${saveName} ${offX} ${offY} ${offZ} ${\n quiet ? 1 : 0\n } ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`,\n );\n }\n\n loadBricksOnPlayer(\n saveName: string,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricksOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.LoadTemplate} ${saveName} ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n );\n }\n\n getSaves(): string[] {\n return existsSync(this.savePath)\n ? glob.sync(this.savePath + '/**/*.brs')\n : [];\n }\n\n getWorlds(): string[] {\n return existsSync(this.worldPath)\n ? glob.sync(this.worldPath + '/**/*.brdb')\n : [];\n }\n\n getPrefabs(): string[] {\n return existsSync(this.prefabPath)\n ? glob.sync(this.prefabPath + '/**/*.brz')\n : [];\n }\n\n getSavePath(saveName: string) {\n const file = join(\n this.savePath,\n saveName.endsWith('.brs') ? saveName : saveName + '.brs',\n );\n return existsSync(file) ? file : undefined;\n }\n\n getPrefabPath(prefabName: string) {\n const file = join(\n this.prefabPath,\n prefabName.endsWith('.brz') ? prefabName : prefabName + '.brz',\n );\n return existsSync(file) ? file : undefined;\n }\n\n getWorldPath(worldName: string) {\n const file = join(\n this.worldPath,\n worldName.endsWith('.brdb') ? worldName : worldName + '.brdb',\n );\n return existsSync(file) ? file : undefined;\n }\n\n async getWorldRevisions(worldName: string) {\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n const path = this.getWorldPath(worldName);\n if (!worldName || !path) {\n throw new Error(`World \"${worldName}\" does not exist`);\n }\n\n // Revisions are read directly from the .brdb bundle (SQLite), so this no\n // longer requires the server to be running or a console round-trip. The\n // brdb revision indices/notes match the game's World.ListRevisions output.\n return readBrdbRevisions(path) ?? [];\n }\n\n async loadWorld(worldName: string): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n this.writeln(`${this.Console.World.Load} \"${worldName}\"`);\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async loadWorldRevision(\n worldName: string,\n revision: number,\n ): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n if (typeof revision !== 'number' || revision < 1) {\n throw new Error(`Invalid revision number: ${revision}`);\n }\n this.writeln(\n `${this.Console.World.LoadRevision} \"${worldName}\" ${revision}`,\n );\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async saveWorldAs(worldName: string) {\n if (!worldName) return false;\n if (this.stopping || this.starting || !this.started) return false;\n\n if (this.getWorldPath(worldName)) {\n return false;\n }\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(\n /^Error: (World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.SaveAs} \"${worldName}\"`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async saveWorld(): Promise<boolean> {\n // Don't allow saving while the server is starting or stopping\n if (this.stopping || this.starting || !this.started) return false;\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(/^Error: (World has not been saved\\.)?/);\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.Save} 0`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async createEmptyWorld(\n worldName: string,\n map: 'Plate' | 'Space' | 'Studio' | 'Peaks' = 'Plate',\n ): Promise<boolean> {\n if (!worldName) return;\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err = match.groups.data.match(\n /^Error: (Invalid preset|World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(\n `${this.Console.World.CreateEmpty} \"${worldName}\" ${map}`,\n );\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n writeSaveData(saveName: string, saveData: WriteSaveObject) {\n if (typeof saveName !== 'string')\n throw 'expected name argument for writeSaveData';\n\n const file = join(this.savePath, saveName + '.brs');\n if (!file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n writeFileSync(file, new Uint8Array(brs.write(saveData)));\n }\n\n /**\n * Read a `.brz` prefab file (EA3) into a legacy save object. Brick geometry,\n * ownership, assets, materials, and components are reconstructed; wires are\n * not (the save-level `wires` array is left empty). Component names/data are\n * EA3-native (e.g. `Component_Internal_Seat`), not the legacy `BCD_*` names.\n */\n private readPrefabData(\n file: string,\n { nobricks = false } = {},\n ): ReadSaveObject {\n // gridId 1 is the world's main brick grid (MAIN_GRID); entity sub-grids\n // (>=2) are not captured, matching reader.bricks()'s default.\n const MAIN_GRID = 1;\n const reader = WorldReader.from(new Uint8Array(readFileSync(file)));\n const bricks = nobricks\n ? []\n : [...reader.bricks(MAIN_GRID)].map(b => ({\n ...b,\n physical_index: 0,\n components: {} as Record<string, unknown>,\n }));\n\n // Attach components to their bricks. Components are stored per chunk with a\n // chunk-local brick index; reader.bricks() yields bricks in the same chunk\n // order as brickChunkIndex(), so a running offset maps the chunk-local\n // index onto the flat bricks array.\n if (!nobricks) {\n let brickOffset = 0;\n for (const chunk of reader.brickChunkIndex(MAIN_GRID)) {\n if (chunk.numComponents > 0) {\n const { components } = reader.componentChunk(MAIN_GRID, chunk.index);\n for (const c of components) {\n const brick = bricks[brickOffset + c.brickIndex];\n if (brick) brick.components[c.typeName] = c.data ?? {};\n }\n }\n brickOffset += chunk.numBricks;\n }\n }\n\n return {\n version: 10,\n map: this.currentMap ?? 'Unknown',\n author: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n host: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n description: '',\n brick_count: bricks.length,\n mods: [],\n brick_assets: reader.brickAssets(),\n colors: [],\n materials: reader.materials(),\n physical_materials: [],\n brick_owners: reader.brickOwners(),\n game_version: this.version,\n save_time: new Uint8Array(),\n bricks,\n components: {},\n } as ReadSaveObject;\n }\n\n readSaveData(saveName: string, nobricks = false): ReadSaveObject {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readSaveData';\n\n // EA3: the legacy .brs format is gone; saved builds are `.brz` prefabs.\n // Read the named prefab and reconstruct a legacy save object, mirroring\n // getSaveData's EA3 path.\n if (brsRemoved(this.version)) {\n const file = this.getPrefabPath(saveName);\n if (!file || !file.startsWith(this.prefabPath))\n throw 'prefab file not in Saved/Prefabs directory';\n return this.readPrefabData(file, { nobricks });\n }\n\n const file = this.getSavePath(saveName);\n if (!file || !file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n if (file)\n return brs.read(readFileSync(file), {\n preview: false,\n bricks: !nobricks,\n });\n }\n\n async loadSaveData(\n saveData: WriteSaveObject,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n // EA3: the legacy Bricks.Load command was removed. Convert the save to a\n // .brz prefab and load it into the world via br.Prefab.Load. The palette\n // correction flags have no prefab equivalent and are ignored.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.loadPrefab(ref, { offX, offY, offZ });\n // the server reads the prefab synchronously and auto-closes the bundle a\n // couple seconds later; clean up the temp file lazily (cf. loadEnvironment)\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.Load} \"${saveFile}\" ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0}`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async loadSaveDataOnPlayer(\n saveData: WriteSaveObject,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // EA3: give the save to the player as a prefab (their inventory) via\n // br.Prefab.GiveToPlayer. Offsets have no equivalent and are ignored,\n // matching loadPrefabOnPlayer.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.givePrefabToPlayer(ref, player);\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n // The Bricks.LoadTemplate command was removed at EA2, before the prefab\n // commands existed; there is no working path on that intermediate version.\n if (\n warnRemoved(\n this.version,\n 'loadSaveDataOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.LoadTemplate} \"${saveFile}\" ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async getSaveData(region?: {\n center: [number, number, number];\n extent: [number, number, number];\n }) {\n // EA3: the legacy Bricks.Save command was removed. Save the world (or the\n // requested region) as a .brz prefab, then read it back into a legacy save\n // object. Brick geometry, ownership, assets, materials, colors, and\n // components are reconstructed; wires are not (the save-level `wires`\n // array is left empty). Component names/data are EA3-native (e.g.\n // `Component_Internal_Seat`), not the legacy `BCD_*` names.\n if (brsRemoved(this.version)) {\n const ref =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n const file = await this.savePrefabAsync(ref, { region });\n if (!file) return undefined;\n\n try {\n return this.readPrefabData(file);\n } finally {\n if (existsSync(file)) unlinkSync(file);\n }\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n\n await this.saveBricksAsync(saveFile, region);\n\n // read the save file\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n // read and parse the save file\n const saveData = brs.read(readFileSync(savePath));\n\n // delete the save file after we're done reading it\n unlinkSync(savePath);\n\n // return the parsed save\n return saveData;\n }\n\n return undefined;\n }\n\n /**\n * Load a prefab into the world (EA3). `path` is a bundle\n * path ref such as `Prefabs/Uploads/<hash>.brz`.\n * br.Prefab.Load <Path> [Offset X Y Z] [At Original Position] [Orientation]\n * [Root Entity Persistent Index] [Mirror Axes] [Override User Id]\n */\n loadPrefab(\n path: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n atOriginalPosition = false,\n orientation = 0,\n rootEntityPersistentIndex = -1,\n mirrorAxes = 0,\n overrideUserId = '',\n }: {\n offX?: number;\n offY?: number;\n offZ?: number;\n atOriginalPosition?: boolean;\n orientation?: number;\n rootEntityPersistentIndex?: number;\n /** bitmask: X=1 Y=2 Z=4 (e.g. 3 mirrors X and Y) */\n mirrorAxes?: number;\n overrideUserId?: string;\n } = {},\n ) {\n if (!path) return;\n // The root entity persistent index is looked up as a literal brick-grid\n // entity, so passing the -1 sentinel errors (\"No brick grid entity with\n // persistent index 4294967295\"). Omit it (and the positional args after\n // it) to load into the world grid; only include it when a real index is\n // given.\n const rootPart =\n rootEntityPersistentIndex >= 0\n ? ` ${rootEntityPersistentIndex} ${mirrorAxes}${\n overrideUserId ? ` \"${overrideUserId}\"` : ''\n }`\n : '';\n this.writeln(\n `${this.Console.Prefab.Load} \"${path}\" ${offX} ${offY} ${offZ} ${\n atOriginalPosition ? 1 : 0\n } ${orientation}${rootPart}`,\n );\n }\n\n /**\n * Load a prefab onto a player (EA3). Replaces the\n * removed {@link loadBricksOnPlayer}; backed by `br.Prefab.GiveToPlayer`.\n * @param path prefab bundle path ref\n * @param player player name/id or player object\n * @param options give options (preserve ownership)\n */\n loadPrefabOnPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n this.givePrefabToPlayer(path, player, { preserveOwnership });\n }\n\n /**\n * Save the world (or a region of it) as a prefab (EA3).\n * `path` is the destination bundle path ref (e.g. `Prefabs/MyPrefab.brz`).\n * br.Prefab.SaveRegion <Path> <Center X Y Z> <Extent X Y Z> [Include Entities]\n * [Root Entity Persistent Index] [Filter User Id]\n * @param path destination prefab bundle path ref\n * @param options save options; omit `region` to capture the whole world\n */\n savePrefab(\n path: string,\n {\n region,\n entities = true,\n rootEntityPersistentIndex = -1,\n userId = '',\n }: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n } = {},\n ) {\n if (!path) return;\n // the command always takes a region; with none given, capture the whole\n // world from the origin with a maximal extent\n const center = (region?.center ?? [0, 0, 0]).join(' ');\n const extent = (\n region?.extent ?? [\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n ]\n ).join(' ');\n this.writeln(\n `${this.Console.Prefab.SaveRegion} \"${path}\" ${center} ${extent} ${\n entities ? 1 : 0\n } ${rootEntityPersistentIndex}${userId ? ` \"${userId}\"` : ''}`,\n );\n }\n\n /**\n * Save a prefab and resolve once the prefab file has been written to disk.\n * @param path destination prefab bundle path ref\n * @param options same options as {@link savePrefab}\n * @returns the absolute path to the written prefab, or null on timeout\n */\n async savePrefabAsync(\n path: string,\n options?: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n },\n ): Promise<string | null> {\n if (!path) return null;\n this.savePrefab(path, options);\n\n // TODO: confirm the write via the prefab-saved log line once its exact\n // format is nailed down; for now poll for the written file on disk.\n const name = path.replace(/^Prefabs[\\\\/]/i, '').replace(/\\.brz$/i, '');\n const file = join(this.prefabPath, name + '.brz');\n const deadline = Date.now() + 30000;\n while (Date.now() < deadline) {\n if (existsSync(file)) return file;\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n return null;\n }\n\n /**\n * Give a prefab to a player's inventory (EA3).\n * br.Prefab.GiveToPlayer <Path> <Player Name or User Id> [Preserve Ownership]\n */\n givePrefabToPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n if (!path) return;\n // the command accepts a player name or user id; prefer the resolved id\n const target = typeof player === 'object' ? player.id : player;\n if (!target) return;\n this.writeln(\n `${this.Console.Prefab.GiveToPlayer} \"${path}\" \"${target}\" ${\n preserveOwnership ? 1 : 0\n }`,\n );\n }\n\n // TODO: switch this to use worlds...\n async changeMap(map: string) {\n if (!map) return;\n\n // ServerTravel requires /Game/Maps/Plate/Plate instead of Plate\n const brName = mapUtils.n2brn(map);\n\n // wait for the server to change maps\n const match = await this.addWatcher(\n /^.*(LogLoad: Took .+ seconds to LoadMap\\((?<map>.+)\\))|(ERROR: The map .+)$/,\n {\n timeoutDelay: 30000,\n exec: () => this.writeln(`ServerTravel ${brName}`),\n },\n );\n const success = !!(\n match &&\n match[0] &&\n match[0].groups &&\n match[0].groups.map\n );\n return success;\n }\n\n async getPlugin(name: string): Promise<PluginInterop> {\n const plugin = this.pluginLoader.plugins.find(p => p.getName() === name);\n\n if (plugin) {\n return {\n name,\n documentation: plugin.getDocumentation(),\n loaded: plugin.isLoaded(),\n emitPlugin: (event: string, ...args: any[]) => {\n return plugin.emitPlugin(event, 'unsafe', args);\n },\n };\n } else {\n return null;\n }\n }\n}\n"],"names":["version","PREFAB_VERSION","Logger","file","join","mkdir","writeFileSync","writeBrzLegacy","OmeggaWrapper","VERSION","commandInjector","CONFIG_SAVED_DIR","DATA_PATH","Webserver","PluginLoader","MATCHERS","pattern","resolveConsoleCommands","existsSync","readFileSync","index","unlinkSync","CONFIG_AUTH_DIR","CONFIG_HOME","copyFiles","BRICKADIA_AUTH_FILES","readWatchedJSON","basename","path","uuid","EA2_VERSION","readBrdbRevisions","match","WorldReader","mapUtils","plugin"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../../src/omegga/server.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { OmeggaLike, OmeggaPlayer, PluginInterop } from '@/plugin';\nimport {\n BRICKADIA_AUTH_FILES,\n CONFIG_AUTH_DIR,\n CONFIG_HOME,\n CONFIG_SAVED_DIR,\n DATA_PATH,\n} from '@/softconfig';\nimport { VERSION } from '@/version';\nimport { EnvironmentPreset } from '@brickadia/presets';\nimport {\n BRBanList,\n BRPlayerNameCache,\n BRRoleAssignments,\n BRRoleSetup,\n} from '@brickadia/types';\nimport { IConfig } from '@config/types';\nimport { map as mapUtils, pattern, uuid } from '@util';\nimport { readBrdbRevisions } from '@util/brdb';\nimport { copyFiles, mkdir, readWatchedJSON } from '@util/file';\nimport Webserver from '@webserver/backend';\nimport brs, {\n WorldReader,\n writeBrzLegacy,\n type ReadSaveObject,\n type WriteSaveObject,\n} from 'brs-js';\nimport 'colors';\nimport glob from 'glob';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { basename, join } from 'path';\nimport { AutoRestartConfig } from '..';\nimport commandInjector from './commandInjector';\nimport {\n ConsoleCommands,\n EA2_VERSION,\n PREFAB_VERSION,\n resolveConsoleCommands,\n} from './commands';\nimport MATCHERS from './matchers';\nimport { readBinaryVersion } from './matchers/version';\nimport Player from './player';\nimport { PluginLoader } from './plugin';\nimport {\n IGamemode,\n ILogMinigame,\n IMinigameList,\n IOmeggaOptions,\n IPlayerPositions,\n IServerStatus,\n} from './types';\nimport OmeggaWrapper from './wrapper';\n\nconst MISSING_CMD =\n '\"Command not found. Type <color=\\\\\"ffff00\\\\\">/help</> for a list of commands or <color=\\\\\"ffff00\\\\\">/plugins</> for plugin information.\"';\n\n// Prefab.SaveRegion requires a region; when saving the whole world we pass a\n// maximal extent centered on the origin to capture everything.\nconst WHOLE_WORLD_EXTENT = 1_000_000_000;\n\n// These helpers are module-level (not class methods) on purpose: safe plugins\n// call these methods through a ProxyOmegga whose prototype steals Omegga's\n// implementations (see injectOmeggaPrototypes). A `#private` method would fail\n// the brand check when `this` is a ProxyOmegga (\"Receiver must be an instance\n// of class Omegga\"), so anything a stolen method depends on must not be\n// `#private`. Module functions keep working regardless of the receiver.\n\n/**\n * Whether the running game version has removed the legacy .brs bricks console\n * commands (Bricks.Save/Load/ClearAll/ClearRegion, World.LoadAdditive) in\n * favor of the prefab (`br.Prefab.*`) and world (`br.World.Clear*`) commands.\n * Removed at Brickadia CL{@link PREFAB_VERSION}.\n */\nfunction brsRemoved(version: number): boolean {\n return version > 0 && version >= PREFAB_VERSION;\n}\n\n/**\n * Warn that a method backed by a removed console command is a no-op on the\n * running game version, and return whether the caller should bail.\n * @param version the running game version\n * @param method the omegga method being called (for the message)\n * @param since CL version the underlying command was removed at\n * @param release human release name for that version (e.g. `EA2`, `EA3`)\n * @param replacement suggested replacement method/API\n */\nfunction warnRemoved(\n version: number,\n method: string,\n since: number,\n release: string,\n replacement: string,\n): boolean {\n if (!(version > 0 && version >= since)) return false;\n Logger.warnp(\n `omegga.${method}() uses a console command removed in Brickadia ` +\n `${release}. This call has no effect - use ${replacement} instead.`,\n );\n return true;\n}\n\n/**\n * Write a save to a temporary `.brz` prefab in the prefab directory (EA3).\n * @returns the bare path ref for `br.Prefab.*` commands (no `Prefabs/` prefix\n * or `.brz` extension) and the absolute file path for cleanup.\n */\nfunction writeTempPrefab(\n omegga: {\n prefabPath: string;\n _tempSavePrefix: string;\n _tempCounter: { save: number };\n },\n saveData: WriteSaveObject,\n): { ref: string; file: string } {\n const ref =\n omegga._tempSavePrefix + Date.now() + '_' + omegga._tempCounter.save++;\n const file = join(omegga.prefabPath, ref + '.brz');\n if (!file.startsWith(omegga.prefabPath))\n throw 'prefab file not in Saved/Prefabs directory';\n mkdir(omegga.prefabPath);\n writeFileSync(file, new Uint8Array(writeBrzLegacy(saveData)));\n return { ref, file };\n}\n\n// TODO: safe broadcast parsing\n\nexport default class Omegga extends OmeggaWrapper implements OmeggaLike {\n /** The save counter prevents omegga from saving over the same file */\n _tempCounter = { save: 0, environment: 0 };\n /** The save prefix is prepended to all temporary saves */\n _tempSavePrefix = 'omegga_temp_';\n\n // pluginloader is not private so plugins can potentially add more formats\n pluginLoader: PluginLoader = undefined;\n webserver: Webserver;\n\n verbose: boolean;\n savePath: string;\n worldPath: string;\n prefabPath: string;\n presetPath: string;\n configPath: string;\n options: IOmeggaOptions;\n\n version: number;\n\n /** memoized version-resolved console commands ({@link Console}) */\n #console: { version: number; commands: ConsoleCommands };\n\n /**\n * version-resolved Brickadia console command names, nested by namespace.\n * e.g. `omegga.Console.Bricks.Clear` -> \"Bricks.Clear\" or \"br.Bricks.Clear\"\n * depending on the running game version.\n */\n get Console(): ConsoleCommands {\n if (this.#console?.version !== this.version)\n this.#console = {\n version: this.version,\n commands: resolveConsoleCommands(this.version),\n };\n return this.#console.commands;\n }\n\n host?: { id: string; name: string };\n players: OmeggaPlayer[];\n\n started = false;\n starting = false;\n stopping = false;\n crashDetected = false;\n currentMap: string;\n\n getServerStatus: () => Promise<IServerStatus>;\n listMinigames: () => Promise<IMinigameList>;\n getAllPlayerPositions: () => Promise<IPlayerPositions>;\n getMinigames: () => Promise<ILogMinigame[]>;\n getGamemode: () => Promise<IGamemode | null>;\n\n /**\n * Omegga instance\n */\n constructor(serverPath: string, cfg: IConfig, options: IOmeggaOptions = {}) {\n super(serverPath, cfg);\n this.verbose = Logger.VERBOSE;\n\n Logger.verbose('Running omegga', `v${VERSION}`.green);\n Logger.verbose('Versions', process.versions);\n Logger.verbose('Config', {\n ...cfg,\n credentials: cfg.credentials\n ? Object.fromEntries(\n Object.entries(cfg.credentials).map(([k, v]) => [k, v ? '***' : v]),\n )\n : cfg.credentials,\n server: {\n ...cfg.server,\n ...(cfg.server.password && { password: '***' }),\n ...(cfg.server.steambetaPassword && { steambetaPassword: '***' }),\n ...(cfg.server.launchArgs && {\n launchArgs: cfg.server.launchArgs\n .replace(/-Cookie=\".*?\"/g, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/g, '-Cookie=<hidden>'),\n }),\n },\n });\n\n // inject commands\n Logger.verbose('Setting up command injector');\n commandInjector(this, this.logWrangler);\n\n // launch options (disabling webserver)\n this.options = options;\n const savedDir = cfg.server.savedDir ?? CONFIG_SAVED_DIR;\n\n // path to save files\n this.savePath = join(this.path, DATA_PATH, savedDir, 'Builds');\n this.worldPath = join(this.path, DATA_PATH, savedDir, 'Worlds');\n this.prefabPath = join(this.path, DATA_PATH, savedDir, 'Prefabs');\n\n this.presetPath = join(this.path, DATA_PATH, savedDir, 'Presets');\n\n // path to config files\n this.configPath = join(this.path, DATA_PATH, savedDir, 'Server');\n\n // create dir folders\n Logger.verbose('Creating directories');\n mkdir(this.savePath);\n mkdir(this.configPath);\n\n // ignore auth file copy\n if (!options.noauth) {\n Logger.verbose('Copying auth files');\n this.copyAuthFiles();\n }\n\n // create the webserver if it's enabled\n // the web interface provides access to server information while the server is running\n // and lets you view chat logs, disable plugins, etc\n if (!options.noweb) {\n Logger.verbose('Creating webserver');\n this.webserver = new Webserver(cfg.omegga, this);\n }\n\n if (!options.noplugin) {\n Logger.verbose('Creating plugin loader');\n this.pluginLoader = new PluginLoader(this.path, this);\n }\n\n /** @type {Array<Player>}list of online players */\n this.players = [];\n\n /** host player info `{id: uuid, name: player name}` */\n this.host = undefined;\n\n /** @type {String} current game version - may later be turned into CL#### versions */\n this.version = -1;\n\n /** @type {Boolean} whether server has started */\n this.started = false;\n /** @type {Boolean} whether server is starting up */\n this.starting = false;\n\n /** @type {String} current map */\n this.currentMap = '';\n\n // add all the matchers to the server\n Logger.verbose('Adding matchers');\n for (const matcher of MATCHERS) {\n const { pattern, callback } = matcher(this);\n this.addMatcher(pattern, callback);\n }\n\n process.on('uncaughtException', async err => {\n Logger.verbose('Uncaught exception', err);\n this.emit('error', err);\n\n // publish stop to database\n this.webserver?.database?.addChatLog('server', {}, 'Server error');\n\n try {\n await this.stop();\n } catch (e) {\n Logger.error(e);\n }\n process.exit();\n });\n\n // when brickadia starts, mark the server as started\n this.on('start', ({ map }) => {\n this.started = true;\n this.starting = false;\n this.currentMap = map;\n this.writeln(`${this.Console.Chat.MessageForUnknownCommands} 0`);\n\n this.restoreServer();\n });\n\n // detect engine crash from stderr or stdout\n const crashHandler = (line: string) => {\n if (\n !this.crashDetected &&\n (/Engine crash handling finished; re-raising signal \\d+ for the default handler\\. Good bye\\./.test(\n line,\n ) ||\n /LogCore: === Critical error: ===/.test(line))\n ) {\n Logger.error('Engine crash detected!');\n this.crashDetected = true;\n }\n };\n this.on('err', crashHandler);\n this.on('line', crashHandler);\n\n // when brickadia exits, stop omegga\n this.on('exit', () => {\n this.stop();\n });\n\n // when the process closes, emit the exit signal and stop\n this.on('closed', () => {\n // capture crash state before 'exit' handler triggers stop()\n const wasCrash = this.crashDetected;\n this.crashDetected = false;\n if (this.started) this.emit('exit');\n const doRestart = async () => {\n if (!wasCrash) return;\n try {\n const config = await this.webserver?.database?.getAutoRestartConfig();\n if (config?.crashRestartEnabled) {\n Logger.logp('Restarting server after crash...');\n this.webserver?.database?.addChatLog(\n 'server',\n {},\n 'Server crashed, restarting...',\n );\n await this.start();\n }\n } catch (err) {\n Logger.error('Error restarting after crash', err);\n }\n };\n if (!this.stopping) {\n this.stop().then(doRestart);\n } else {\n // stop() already in progress from 'exit' handler - wait for it to finish\n this.once('server:stopped', () => doRestart());\n }\n });\n\n // detect when the game reports a command does not exist\n this.on('unknownCommand', (name: string, cmd: string) => {\n // if it's not registered to a plugin, send the missing command message\n if (!this.pluginLoader || !this.pluginLoader.isCommand(cmd)) {\n this.whisper(name, MISSING_CMD);\n }\n });\n }\n\n /** attempt to save server state */\n async saveServer(config: AutoRestartConfig) {\n if (config.players && this.players.length > 0) {\n Logger.logp('Getting player positions...');\n const players = await this.getAllPlayerPositions();\n Logger.logp(`Saving ${players.length} player positions...`);\n const data = players\n .filter(p => !p.isDead && p.pos)\n .map(p => ({ position: p.pos, id: p.player.id }));\n if (players.length > 0)\n writeFileSync(\n join(this.path, DATA_PATH, 'omegga_temp_players.json'),\n JSON.stringify(data),\n );\n }\n\n if (config.saveWorld) {\n Logger.logp('Saving world...');\n await this.saveWorld();\n }\n }\n\n async restartServer() {\n if (this.starting || this.stopping) return;\n if (!this.started) return await this.start();\n\n const nextWorld = this.getNextWorld();\n if (nextWorld) {\n Logger.logp('Loading world', nextWorld.file.yellow);\n Logger.verbose('Next world configured from', nextWorld.source.yellow);\n this.loadWorld(nextWorld.file);\n } else {\n this.changeMap(this.currentMap);\n }\n\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('Restart result:', res);\n }\n\n /** attempt to restore the server's state */\n async restoreServer() {\n const tempPlayersFile = join(\n this.path,\n DATA_PATH,\n 'omegga_temp_players.json',\n );\n if (!existsSync(tempPlayersFile)) return;\n\n try {\n Logger.logp('Loading previous player positions...');\n\n // player positions are an array to address multi-clienting\n const players: { position: number[]; id: string }[] = JSON.parse(\n readFileSync(tempPlayersFile).toString(),\n );\n\n // restore player position on join\n const callback = (player: OmeggaPlayer) => {\n const index = players.findIndex(p => p.id === player.id);\n if (index > -1) {\n const { position } = players[index];\n this.writeln(\n `${this.Console.Chat.Command} /TP \"${player.name}\" ${position.join(' ')} 0`,\n );\n\n // remove the entry\n players[index] = players[players.length - 1];\n players.pop();\n }\n };\n this.on('join', callback);\n\n let timeout = setTimeout(() => {\n try {\n this.off('join', callback);\n if (existsSync(tempPlayersFile)) unlinkSync(tempPlayersFile);\n } catch (err) {\n Logger.error('Error removing omegga_temp_players.json', err);\n }\n }, 10000);\n this.once('changemap', () => {\n clearTimeout(timeout);\n this.off('join', callback);\n });\n } catch (err) {\n Logger.error('Error restoring previous server state', err);\n }\n }\n\n /**\n * start webserver, load plugins, start the brickadia server\n * this should not be called by a plugin\n */\n //\n async start(): Promise<any> {\n this.starting = true;\n\n // Resolve the game version straight from the server binary before plugins\n // load, so `omegga.version` is valid at plugin `init` time (e.g. for\n // readSaveData) instead of staying -1 until the game boots and writes its\n // log. The log parser (matchers/version) still runs and corrects this if\n // the binary read fails or the launcher path can't be resolved.\n if (this.version < 0) {\n const binVersion = readBinaryVersion(this.getGameBinaryPath());\n if (binVersion != null) {\n this.version = binVersion;\n Logger.verbose('Brickadia Version (from binary)', binVersion);\n }\n }\n\n if (this.webserver) await this.webserver.start();\n if (this.pluginLoader) {\n // scan for plugins\n Logger.verbose('Scanning for plugins');\n await this.pluginLoader.scan();\n\n // load the plugins\n Logger.verbose('Loading plugins');\n await this.pluginLoader.reload();\n }\n\n Logger.verbose('Starting Brickadia');\n super.start();\n this.emit('server:starting');\n }\n\n /**\n * unload plugins and stop the server\n * this should not be called by a plugin\n */\n async stop() {\n if (!this.started && !this.starting) {\n Logger.verbose(\"Stop called while server wasn't started or was starting\");\n return;\n }\n\n if (this.stopping) {\n Logger.verbose('Stop called while server was starting');\n return;\n }\n\n this.stopping = true;\n this.emit('server:stopping');\n if (this.pluginLoader) {\n Logger.verbose('Unloading plugins');\n await this.pluginLoader.unload();\n }\n Logger.verbose('Stopping server');\n super.stop();\n\n const res = await Promise.race([\n new Promise(resolve => this.once('exit', () => resolve('exit'))),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n\n Logger.verbose('Stop result:', res);\n if (this.stopping) this.emit('server:stopped');\n this.stopping = false;\n this.started = false;\n this.starting = false;\n this.players = [];\n }\n\n /**\n * Copies auth files from home config dir\n * this should never be called by a plugin\n */\n copyAuthFiles() {\n const authDir = this.config.server.authDir ?? CONFIG_AUTH_DIR;\n const savedDir = this.config.server.savedDir ?? CONFIG_SAVED_DIR;\n const authPath = join(this.path, DATA_PATH, savedDir, authDir);\n const homeAuthPath = join(\n CONFIG_HOME,\n (savedDir !== CONFIG_SAVED_DIR ? savedDir : '') + authDir,\n );\n\n copyFiles(homeAuthPath, authPath, BRICKADIA_AUTH_FILES);\n }\n\n // TODO: split messages that longer than 512 characters\n // TODO: delete characters that are known to crash the game\n broadcast(...messages: string[]) {\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m => this.writeln(`${this.Console.Chat.Broadcast} ${m}`));\n }\n\n whisper(target: string | OmeggaPlayer, ...messages: string[]) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // player may have left before the message could be sent\n if (!target) return;\n\n // whisper the messages to that player\n messages\n .flatMap(m => m.toString().split('\\n'))\n .filter(m => m.length < 512)\n .forEach(m =>\n this.writeln(\n `${this.Console.Chat.Whisper} \"${(target as { name: string }).name}\" ${m}`,\n ),\n );\n }\n\n middlePrint(target: string | OmeggaPlayer, message: string) {\n // find the target player\n if (typeof target !== 'object') target = this.getPlayer(target);\n\n // player may have left before the message could be sent\n if (!target) return;\n\n // whisper the messages to that player\n if (message.length > 512) return;\n this.writeln(\n `${this.Console.Chat.StatusMessage} \"${(target as { name: string }).name}\" ${message}`,\n );\n }\n\n getPlayers(): {\n id: string;\n name: string;\n displayName: string;\n controller: string;\n state: string;\n }[] {\n return this.players.map(p => ({ ...p }));\n }\n\n getRoleSetup(): BRRoleSetup {\n // Read RoleSetup2, fallback to old RoleSetup if it doesn't exist\n return (readWatchedJSON(join(this.configPath, 'RoleSetup2.json')) ??\n readWatchedJSON(join(this.configPath, 'RoleSetup.json'))) as BRRoleSetup;\n }\n\n getRoleAssignments(): BRRoleAssignments {\n return readWatchedJSON(\n join(this.configPath, 'RoleAssignments.json'),\n ) as BRRoleAssignments;\n }\n\n getBanList(): BRBanList {\n return readWatchedJSON(join(this.configPath, 'BanList.json')) as BRBanList;\n }\n\n getNameCache(): BRPlayerNameCache {\n return readWatchedJSON(\n join(this.configPath, 'PlayerNameCache.json'),\n ) as BRPlayerNameCache;\n }\n\n getPlayer(target: string): OmeggaPlayer {\n return this.players.find(\n p =>\n p.name === target ||\n p.id === target ||\n p.controller === target ||\n p.state === target,\n );\n }\n\n findPlayerByName(name: string): OmeggaPlayer {\n name = name.toLowerCase();\n const exploded = pattern.explode(name);\n return (\n this.players.find(p => p.name === name || p.displayName === name) || // find by exact match\n this.players.find(\n p => p.name.indexOf(name) > -1 || p.displayName.indexOf(name) > -1,\n ) || // find by rough match\n this.players.find(\n p => p.name.match(exploded) || p.displayName.match(exploded),\n ) // find by exploded regex match (ck finds cake, tbp finds TheBlackParrot)\n );\n }\n\n getHostId(): string {\n return this.host?.id ?? '';\n }\n\n saveMinigame(index: number, name: string) {\n this.writeln(\n `${this.Console.Server.Minigames.SavePreset} ${index} \"${name}\"`,\n );\n }\n\n deleteMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Delete} ${index}`);\n }\n\n resetMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.Reset} ${index}`);\n }\n\n nextRoundMinigame(index: number) {\n this.writeln(`${this.Console.Server.Minigames.NextRound} ${index}`);\n }\n\n loadMinigame(presetName: string, owner = '') {\n this.writeln(\n `${this.Console.Server.Minigames.LoadPreset} \"${presetName}\" ${owner ? `\"${owner}\"` : ''}`,\n );\n }\n\n getMinigamePresets(): string[] {\n const presetPath = join(this.presetPath, 'Minigame');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n resetEnvironment() {\n this.writeln(`${this.Console.Server.Environment.Reset}`);\n }\n\n async saveEnvironment(presetName: string): Promise<void> {\n await this.addWatcher(/Environment preset saved.$/, {\n // request the pawn for this player's controller (should only be one)\n exec: () =>\n this.writeln(\n `${this.Console.Server.Environment.SavePreset} \"${presetName}\"`,\n ),\n timeoutDelay: 100,\n });\n }\n\n async getEnvironmentData(): Promise<EnvironmentPreset> {\n const saveName =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n await this.saveEnvironment(saveName);\n const data = this.readEnvironmentData(saveName);\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n if (existsSync(file)) unlinkSync(file);\n\n return data;\n }\n\n readEnvironmentData(saveName: string): EnvironmentPreset {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readEnvironmentData';\n\n const file = join(this.presetPath, 'Environment', saveName + '.bp');\n try {\n if (existsSync(file)) return JSON.parse(readFileSync(file).toString());\n } catch (err) {\n Logger.verbose('Error parsing save data in readEnvironmentData', err);\n }\n return null;\n }\n\n loadEnvironment(presetName: string) {\n this.writeln(`${this.Console.Server.Environment.LoadPreset} ${presetName}`);\n }\n\n loadEnvironmentData(\n preset: EnvironmentPreset | EnvironmentPreset['data']['groups'],\n ) {\n if ('data' in preset) preset = preset.data.groups;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.environment++;\n\n const path = join(this.presetPath, 'Environment', saveFile + '.bp');\n\n writeFileSync(\n path,\n JSON.stringify({\n formatVersion: '1',\n presetVersion: '1',\n type: 'Environment',\n data: {\n groups: {\n ...preset,\n },\n },\n }),\n );\n\n this.loadEnvironment(saveFile);\n\n // this is lazy, but environments should load much faster than builds\n // do, so it's not really worth keeping track of logs for this\n setTimeout(() => unlinkSync(path), 5000);\n }\n\n getEnvironmentPresets(): string[] {\n const presetPath = join(this.presetPath, 'Environment');\n return existsSync(presetPath)\n ? glob\n .sync(presetPath + '/**/*.bp')\n .map(f => basename(f).replace(/\\.bp$/, ''))\n : [];\n }\n\n clearBricks(target: string | { id: string }, quiet = false) {\n // target is a player object, just use that id\n if (typeof target === 'object' && target.id) target = target.id;\n // if the target isn't a uuid already, find the player by name or controller and use that uuid\n else if (typeof target === 'string' && !uuid.match(target)) {\n // only set the target if the player exists\n const player = this.getPlayer(target);\n target = player && player.id;\n }\n\n if (!target) return;\n\n this.writeln(`${this.Console.Bricks.Clear} ${target} ${quiet ? 1 : ''}`);\n }\n\n clearRegion(\n region: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n options?: {\n target?: string | OmeggaPlayer;\n /** clear bricks in the region (default true) */\n bricks?: boolean;\n /** also clear entities in the region (default false, EA3 only) */\n entities?: boolean;\n },\n ) {\n // resolve the optional owner filter (player object, uuid, or name) to a uuid\n let target = '';\n const rawTarget = options?.target;\n if (rawTarget) {\n if (typeof rawTarget === 'object') target = rawTarget.id;\n else if (uuid.match(rawTarget)) target = rawTarget;\n else target = this.getPlayer(rawTarget)?.id ?? '';\n }\n\n const center = region.center.join(' ');\n const extent = region.extent.join(' ');\n\n if (brsRemoved(this.version)) {\n // br.World.ClearRegion <Center> <Extent> [ClearBricks] [ClearEntities] [FilterUserId]\n const bricks = (options?.bricks ?? true) ? 1 : 0;\n const entities = (options?.entities ?? false) ? 1 : 0;\n this.writeln(\n `${this.Console.World.ClearRegion} ${center} ${extent} ${bricks} ${entities}${\n target ? ' ' + target : ''\n }`,\n );\n return;\n }\n\n // legacy .brs region clear (bricks only)\n this.writeln(\n `${this.Console.Bricks.ClearRegion} ${center} ${extent}${\n target ? ' ' + target : ''\n }`,\n );\n }\n\n clearAllBricks(\n options:\n | boolean\n | { quiet?: boolean; bricks?: boolean; entities?: boolean } = {},\n ) {\n // backwards compat: a bare boolean is the legacy `quiet` argument\n const {\n quiet = false,\n bricks = true,\n entities = false,\n } = typeof options === 'boolean' ? { quiet: options } : options;\n\n if (brsRemoved(this.version)) {\n // br.World.ClearAll [ClearBricks] [ClearEntities] [Silent]\n this.writeln(\n `${this.Console.World.ClearAll} ${bricks ? 1 : 0} ${\n entities ? 1 : 0\n } ${quiet ? 1 : 0}`,\n );\n return;\n }\n // legacy Bricks.ClearAll only ever cleared bricks\n this.writeln(`${this.Console.Bricks.ClearAll} ${quiet ? 1 : ''}`);\n }\n\n saveBricks(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ) {\n if (\n warnRemoved(\n this.version,\n 'saveBricks',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefab',\n )\n )\n return;\n if (!saveName) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n if (region?.center && region?.extent)\n this.writeln(\n `${this.Console.Bricks.SaveRegion} ${saveName} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`,\n );\n else this.writeln(`${this.Console.Bricks.Save} ${saveName}`);\n }\n\n async saveBricksAsync(\n saveName: string,\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n },\n ): Promise<void> {\n if (\n warnRemoved(\n this.version,\n 'saveBricksAsync',\n PREFAB_VERSION,\n 'EA3',\n 'savePrefabAsync',\n )\n )\n return;\n if (!saveName) return;\n\n let saveNameClean = saveName;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveNameClean = `\"${saveName}\"`;\n\n const command =\n region?.center && region?.extent\n ? `${this.Console.Bricks.SaveRegion} ${saveNameClean} ${region.center.join(\n ' ',\n )} ${region.extent.join(' ')}`\n : `${this.Console.Bricks.Save} ${saveNameClean}`;\n\n // wait for the server to save the file\n await this.watchLogChunk(command, /^(LogBrickSerializer|LogTemp): (.+)$/, {\n first: match => match[0].endsWith(saveName + '.brs...'),\n last: match =>\n Boolean(\n match[2].match(\n /Saved .+ bricks and .+ components from .+ owners|Error: No bricks in grid!|Error: No bricks selected to save!/,\n ),\n ),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n });\n }\n\n loadBricks(\n saveName: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricks',\n PREFAB_VERSION,\n 'EA3',\n 'loadPrefab',\n )\n )\n return;\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.Load} ${saveName} ${offX} ${offY} ${offZ} ${\n quiet ? 1 : 0\n } ${correctPalette ? 1 : 0} ${correctCustom ? 1 : 0}`,\n );\n }\n\n loadBricksOnPlayer(\n saveName: string,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n if (\n warnRemoved(\n this.version,\n 'loadBricksOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // add quotes around the filename if it doesn't have them (backwards compat w/ plugins)\n if (!(saveName.startsWith('\"') && saveName.endsWith('\"')))\n saveName = `\"${saveName}\"`;\n\n this.writeln(\n `${this.Console.Bricks.LoadTemplate} ${saveName} ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n );\n }\n\n getSaves(): string[] {\n return existsSync(this.savePath)\n ? glob.sync(this.savePath + '/**/*.brs')\n : [];\n }\n\n getWorlds(): string[] {\n return existsSync(this.worldPath)\n ? glob.sync(this.worldPath + '/**/*.brdb')\n : [];\n }\n\n getPrefabs(): string[] {\n return existsSync(this.prefabPath)\n ? glob.sync(this.prefabPath + '/**/*.brz')\n : [];\n }\n\n getSavePath(saveName: string) {\n const file = join(\n this.savePath,\n saveName.endsWith('.brs') ? saveName : saveName + '.brs',\n );\n return existsSync(file) ? file : undefined;\n }\n\n getPrefabPath(prefabName: string) {\n const file = join(\n this.prefabPath,\n prefabName.endsWith('.brz') ? prefabName : prefabName + '.brz',\n );\n return existsSync(file) ? file : undefined;\n }\n\n getWorldPath(worldName: string) {\n const file = join(\n this.worldPath,\n worldName.endsWith('.brdb') ? worldName : worldName + '.brdb',\n );\n return existsSync(file) ? file : undefined;\n }\n\n async getWorldRevisions(worldName: string) {\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n const path = this.getWorldPath(worldName);\n if (!worldName || !path) {\n throw new Error(`World \"${worldName}\" does not exist`);\n }\n\n // Revisions are read directly from the .brdb bundle (SQLite), so this no\n // longer requires the server to be running or a console round-trip. The\n // brdb revision indices/notes match the game's World.ListRevisions output.\n return readBrdbRevisions(path) ?? [];\n }\n\n async loadWorld(worldName: string): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n this.writeln(`${this.Console.World.Load} \"${worldName}\"`);\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async loadWorldRevision(\n worldName: string,\n revision: number,\n ): Promise<boolean> {\n worldName = worldName.replace(/\\.brdb$/i, '');\n if (!worldName || !this.getWorldPath(worldName)) return false;\n if (typeof revision !== 'number' || revision < 1) {\n throw new Error(`Invalid revision number: ${revision}`);\n }\n this.writeln(\n `${this.Console.World.LoadRevision} \"${worldName}\" ${revision}`,\n );\n const res = await Promise.race([\n // wait for the map to change\n new Promise(resolve =>\n this.once('mapchange', () => resolve('mapchange')),\n ),\n // Timeout after 10 seconds\n new Promise(resolve => setTimeout(() => resolve('timeout'), 10000)),\n ]);\n Logger.verbose('LoadWorld', worldName, 'result:', res);\n return res === 'mapchange';\n }\n\n async saveWorldAs(worldName: string) {\n if (!worldName) return false;\n if (this.stopping || this.starting || !this.started) return false;\n\n if (this.getWorldPath(worldName)) {\n return false;\n }\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(\n /^Error: (World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.SaveAs} \"${worldName}\"`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async saveWorld(): Promise<boolean> {\n // Don't allow saving while the server is starting or stopping\n if (this.stopping || this.starting || !this.started) return false;\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err =\n !match.groups.data.startsWith(\n 'Error: Failed to capture minigame settings',\n ) &&\n match.groups.data.match(/^Error: (World has not been saved\\.)?/);\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(`${this.Console.World.Save} 0`);\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n async createEmptyWorld(\n worldName: string,\n map: 'Plate' | 'Space' | 'Studio' | 'Peaks' = 'Plate',\n ): Promise<boolean> {\n if (!worldName) return;\n worldName = worldName.replace(/\\.brdb$/i, '');\n\n try {\n const match = await this.addWatcher<{ res: boolean }>(\n (_line, match) => {\n if (match?.groups?.generator !== 'LogBRWorldManager') return;\n\n const ok = match.groups.data.match(/^World files saved after /);\n const err = match.groups.data.match(\n /^Error: (Invalid preset|World already exists|Failed to create new world)?/,\n );\n return ok ? { res: true } : err ? { res: false } : undefined;\n },\n {\n exec: () => {\n this.writeln(\n `${this.Console.World.CreateEmpty} \"${worldName}\" ${map}`,\n );\n },\n timeoutDelay: 2000,\n },\n );\n return match?.[0]?.['res'] ?? false;\n } catch (err) {\n return false;\n }\n }\n\n writeSaveData(saveName: string, saveData: WriteSaveObject) {\n if (typeof saveName !== 'string')\n throw 'expected name argument for writeSaveData';\n\n const file = join(this.savePath, saveName + '.brs');\n if (!file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n writeFileSync(file, new Uint8Array(brs.write(saveData)));\n }\n\n /**\n * Read a `.brz` prefab file (EA3) into a legacy save object. Brick geometry,\n * ownership, assets, materials, and components are reconstructed; wires are\n * not (the save-level `wires` array is left empty). Component names/data are\n * EA3-native (e.g. `Component_Internal_Seat`), not the legacy `BCD_*` names.\n */\n private readPrefabData(\n file: string,\n { nobricks = false } = {},\n ): ReadSaveObject {\n // gridId 1 is the world's main brick grid (MAIN_GRID); entity sub-grids\n // (>=2) are not captured, matching reader.bricks()'s default.\n const MAIN_GRID = 1;\n const reader = WorldReader.from(new Uint8Array(readFileSync(file)));\n const bricks = nobricks\n ? []\n : [...reader.bricks(MAIN_GRID)].map(b => ({\n ...b,\n physical_index: 0,\n components: {} as Record<string, unknown>,\n }));\n\n // Attach components to their bricks. Components are stored per chunk with a\n // chunk-local brick index; reader.bricks() yields bricks in the same chunk\n // order as brickChunkIndex(), so a running offset maps the chunk-local\n // index onto the flat bricks array.\n if (!nobricks) {\n let brickOffset = 0;\n for (const chunk of reader.brickChunkIndex(MAIN_GRID)) {\n if (chunk.numComponents > 0) {\n const { components } = reader.componentChunk(MAIN_GRID, chunk.index);\n for (const c of components) {\n const brick = bricks[brickOffset + c.brickIndex];\n if (brick) brick.components[c.typeName] = c.data ?? {};\n }\n }\n brickOffset += chunk.numBricks;\n }\n }\n\n return {\n version: 10,\n map: this.currentMap ?? 'Unknown',\n author: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n host: { id: this.host?.id ?? '', name: this.host?.name ?? '' },\n description: '',\n brick_count: bricks.length,\n mods: [],\n brick_assets: reader.brickAssets(),\n colors: [],\n materials: reader.materials(),\n physical_materials: [],\n brick_owners: reader.brickOwners(),\n game_version: this.version,\n save_time: new Uint8Array(),\n bricks,\n components: {},\n } as ReadSaveObject;\n }\n\n readSaveData(saveName: string, nobricks = false): ReadSaveObject {\n if (typeof saveName !== 'string')\n throw 'expected name argument for readSaveData';\n\n // EA3: the legacy .brs format is gone; saved builds are `.brz` prefabs.\n // Read the named prefab and reconstruct a legacy save object, mirroring\n // getSaveData's EA3 path.\n if (brsRemoved(this.version)) {\n const file = this.getPrefabPath(saveName);\n if (!file || !file.startsWith(this.prefabPath))\n throw 'prefab file not in Saved/Prefabs directory';\n return this.readPrefabData(file, { nobricks });\n }\n\n const file = this.getSavePath(saveName);\n if (!file || !file.startsWith(this.savePath))\n throw 'save file not in Saved/Builds directory';\n if (file)\n return brs.read(readFileSync(file), {\n preview: false,\n bricks: !nobricks,\n });\n }\n\n async loadSaveData(\n saveData: WriteSaveObject,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n quiet = false,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n // EA3: the legacy Bricks.Load command was removed. Convert the save to a\n // .brz prefab and load it into the world via br.Prefab.Load. The palette\n // correction flags have no prefab equivalent and are ignored.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.loadPrefab(ref, { offX, offY, offZ });\n // the server reads the prefab synchronously and auto-closes the bundle a\n // couple seconds later; clean up the temp file lazily (cf. loadEnvironment)\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.Load} \"${saveFile}\" ${offX} ${offY} ${offZ} ${quiet ? 1 : 0} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0}`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async loadSaveDataOnPlayer(\n saveData: WriteSaveObject,\n player: string | OmeggaPlayer,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n correctPalette = false,\n correctCustom = false,\n } = {},\n ) {\n player = typeof player === 'string' ? this.getPlayer(player) : player;\n if (!player) return;\n\n // EA3: give the save to the player as a prefab (their inventory) via\n // br.Prefab.GiveToPlayer. Offsets have no equivalent and are ignored,\n // matching loadPrefabOnPlayer.\n if (brsRemoved(this.version)) {\n const { ref, file } = writeTempPrefab(this, saveData);\n this.givePrefabToPlayer(ref, player);\n setTimeout(() => {\n if (existsSync(file)) unlinkSync(file);\n }, 5000);\n return;\n }\n\n // The Bricks.LoadTemplate command was removed at EA2, before the prefab\n // commands existed; there is no working path on that intermediate version.\n if (\n warnRemoved(\n this.version,\n 'loadSaveDataOnPlayer',\n EA2_VERSION,\n 'EA2',\n 'loadPrefabOnPlayer',\n )\n )\n return;\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n // write savedata to file\n this.writeSaveData(saveFile, saveData);\n\n // wait for the server to finish reading the save\n await this.watchLogChunk(\n `${this.Console.Bricks.LoadTemplate} \"${saveFile}\" ${offX} ${offY} ${offZ} ${\n correctPalette ? 1 : 0\n } ${correctCustom ? 1 : 0} \"${player.name}\"`,\n /^LogBrickSerializer: (.+)$/,\n {\n first: match => match[0].endsWith(saveFile + '.brs...'),\n last: match => Boolean(match[1].match(/Read .+ bricks/)),\n afterMatchDelay: 0,\n timeoutDelay: 30000,\n },\n );\n\n // delete the save file after we're done\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n unlinkSync(savePath);\n }\n }\n\n async getSaveData(region?: {\n center: [number, number, number];\n extent: [number, number, number];\n }) {\n // EA3: the legacy Bricks.Save command was removed. Save the world (or the\n // requested region) as a .brz prefab, then read it back into a legacy save\n // object. Brick geometry, ownership, assets, materials, colors, and\n // components are reconstructed; wires are not (the save-level `wires`\n // array is left empty). Component names/data are EA3-native (e.g.\n // `Component_Internal_Seat`), not the legacy `BCD_*` names.\n if (brsRemoved(this.version)) {\n const ref =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n const file = await this.savePrefabAsync(ref, { region });\n if (!file) return undefined;\n\n try {\n return this.readPrefabData(file);\n } finally {\n if (existsSync(file)) unlinkSync(file);\n }\n }\n\n const saveFile =\n this._tempSavePrefix + Date.now() + '_' + this._tempCounter.save++;\n\n await this.saveBricksAsync(saveFile, region);\n\n // read the save file\n const savePath = this.getSavePath(saveFile);\n if (savePath) {\n // read and parse the save file\n const saveData = brs.read(readFileSync(savePath));\n\n // delete the save file after we're done reading it\n unlinkSync(savePath);\n\n // return the parsed save\n return saveData;\n }\n\n return undefined;\n }\n\n /**\n * Load a prefab into the world (EA3). `path` is a bundle\n * path ref such as `Prefabs/Uploads/<hash>.brz`.\n * br.Prefab.Load <Path> [Offset X Y Z] [At Original Position] [Orientation]\n * [Root Entity Persistent Index] [Mirror Axes] [Override User Id]\n */\n loadPrefab(\n path: string,\n {\n offX = 0,\n offY = 0,\n offZ = 0,\n atOriginalPosition = false,\n orientation = 0,\n rootEntityPersistentIndex = -1,\n mirrorAxes = 0,\n overrideUserId = '',\n }: {\n offX?: number;\n offY?: number;\n offZ?: number;\n atOriginalPosition?: boolean;\n orientation?: number;\n rootEntityPersistentIndex?: number;\n /** bitmask: X=1 Y=2 Z=4 (e.g. 3 mirrors X and Y) */\n mirrorAxes?: number;\n overrideUserId?: string;\n } = {},\n ) {\n if (!path) return;\n // The root entity persistent index is looked up as a literal brick-grid\n // entity, so passing the -1 sentinel errors (\"No brick grid entity with\n // persistent index 4294967295\"). Omit it (and the positional args after\n // it) to load into the world grid; only include it when a real index is\n // given.\n const rootPart =\n rootEntityPersistentIndex >= 0\n ? ` ${rootEntityPersistentIndex} ${mirrorAxes}${\n overrideUserId ? ` \"${overrideUserId}\"` : ''\n }`\n : '';\n this.writeln(\n `${this.Console.Prefab.Load} \"${path}\" ${offX} ${offY} ${offZ} ${\n atOriginalPosition ? 1 : 0\n } ${orientation}${rootPart}`,\n );\n }\n\n /**\n * Load a prefab onto a player (EA3). Replaces the\n * removed {@link loadBricksOnPlayer}; backed by `br.Prefab.GiveToPlayer`.\n * @param path prefab bundle path ref\n * @param player player name/id or player object\n * @param options give options (preserve ownership)\n */\n loadPrefabOnPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n this.givePrefabToPlayer(path, player, { preserveOwnership });\n }\n\n /**\n * Save the world (or a region of it) as a prefab (EA3).\n * `path` is the destination bundle path ref (e.g. `Prefabs/MyPrefab.brz`).\n * br.Prefab.SaveRegion <Path> <Center X Y Z> <Extent X Y Z> [Include Entities]\n * [Root Entity Persistent Index] [Filter User Id]\n * @param path destination prefab bundle path ref\n * @param options save options; omit `region` to capture the whole world\n */\n savePrefab(\n path: string,\n {\n region,\n entities = true,\n rootEntityPersistentIndex = -1,\n userId = '',\n }: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n } = {},\n ) {\n if (!path) return;\n // the command always takes a region; with none given, capture the whole\n // world from the origin with a maximal extent\n const center = (region?.center ?? [0, 0, 0]).join(' ');\n const extent = (\n region?.extent ?? [\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n WHOLE_WORLD_EXTENT,\n ]\n ).join(' ');\n this.writeln(\n `${this.Console.Prefab.SaveRegion} \"${path}\" ${center} ${extent} ${\n entities ? 1 : 0\n } ${rootEntityPersistentIndex}${userId ? ` \"${userId}\"` : ''}`,\n );\n }\n\n /**\n * Save a prefab and resolve once the prefab file has been written to disk.\n * @param path destination prefab bundle path ref\n * @param options same options as {@link savePrefab}\n * @returns the absolute path to the written prefab, or null on timeout\n */\n async savePrefabAsync(\n path: string,\n options?: {\n region?: {\n center: [number, number, number];\n extent: [number, number, number];\n };\n entities?: boolean;\n rootEntityPersistentIndex?: number;\n userId?: string;\n },\n ): Promise<string | null> {\n if (!path) return null;\n this.savePrefab(path, options);\n\n // TODO: confirm the write via the prefab-saved log line once its exact\n // format is nailed down; for now poll for the written file on disk.\n const name = path.replace(/^Prefabs[\\\\/]/i, '').replace(/\\.brz$/i, '');\n const file = join(this.prefabPath, name + '.brz');\n const deadline = Date.now() + 30000;\n while (Date.now() < deadline) {\n if (existsSync(file)) return file;\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n return null;\n }\n\n /**\n * Give a prefab to a player's inventory (EA3).\n * br.Prefab.GiveToPlayer <Path> <Player Name or User Id> [Preserve Ownership]\n */\n givePrefabToPlayer(\n path: string,\n player: string | OmeggaPlayer,\n { preserveOwnership = false }: { preserveOwnership?: boolean } = {},\n ) {\n if (!path) return;\n // the command accepts a player name or user id; prefer the resolved id\n const target = typeof player === 'object' ? player.id : player;\n if (!target) return;\n this.writeln(\n `${this.Console.Prefab.GiveToPlayer} \"${path}\" \"${target}\" ${\n preserveOwnership ? 1 : 0\n }`,\n );\n }\n\n // TODO: switch this to use worlds...\n async changeMap(map: string) {\n if (!map) return;\n\n // ServerTravel requires /Game/Maps/Plate/Plate instead of Plate\n const brName = mapUtils.n2brn(map);\n\n // wait for the server to change maps\n const match = await this.addWatcher(\n /^.*(LogLoad: Took .+ seconds to LoadMap\\((?<map>.+)\\))|(ERROR: The map .+)$/,\n {\n timeoutDelay: 30000,\n exec: () => this.writeln(`ServerTravel ${brName}`),\n },\n );\n const success = !!(\n match &&\n match[0] &&\n match[0].groups &&\n match[0].groups.map\n );\n return success;\n }\n\n async getPlugin(name: string): Promise<PluginInterop> {\n const plugin = this.pluginLoader.plugins.find(p => p.getName() === name);\n\n if (plugin) {\n return {\n name,\n documentation: plugin.getDocumentation(),\n loaded: plugin.isLoaded(),\n emitPlugin: (event: string, ...args: any[]) => {\n return plugin.emitPlugin(event, 'unsafe', args);\n },\n };\n } else {\n return null;\n }\n }\n}\n"],"names":["version","PREFAB_VERSION","Logger","file","join","mkdir","writeFileSync","writeBrzLegacy","OmeggaWrapper","VERSION","commandInjector","CONFIG_SAVED_DIR","DATA_PATH","Webserver","PluginLoader","MATCHERS","pattern","resolveConsoleCommands","existsSync","readFileSync","index","unlinkSync","readBinaryVersion","CONFIG_AUTH_DIR","CONFIG_HOME","copyFiles","BRICKADIA_AUTH_FILES","readWatchedJSON","basename","path","uuid","EA2_VERSION","readBrdbRevisions","match","WorldReader","mapUtils","plugin"],"mappings":"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"}
|
package/dist/omegga/wrapper.js
CHANGED
|
@@ -67,6 +67,10 @@ class OmeggaWrapper extends EventEmitter {
|
|
|
67
67
|
worldExists(file) {
|
|
68
68
|
return this.#server.worldExists(file);
|
|
69
69
|
}
|
|
70
|
+
/** Resolve the game server binary path (steam/override installs; null for launcher) */
|
|
71
|
+
getGameBinaryPath() {
|
|
72
|
+
return this.#server.getGameBinaryPath();
|
|
73
|
+
}
|
|
70
74
|
}
|
|
71
75
|
exports.default = OmeggaWrapper;
|
|
72
76
|
//# sourceMappingURL=wrapper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper.js","sources":["../../src/omegga/wrapper.ts"],"sourcesContent":["/*\n The wrapper combines the things looking at or waiting for logs with the actual server logs\n*/\n\nimport Logger from '@/logger';\nimport soft from '@/softconfig';\nimport BrickadiaServer from '@brickadia/server';\nimport { IConfig } from '@config/types';\nimport EventEmitter from 'events';\nimport path from 'path';\nimport { migrateConsoleCommand } from './commands';\nimport LogWrangler from './logWrangler';\nimport type Omegga from './server';\n\nclass OmeggaWrapper extends EventEmitter {\n #server: BrickadiaServer;\n dataPath: string;\n path: string;\n\n logWrangler: LogWrangler;\n addMatcher: LogWrangler['addMatcher'];\n addWatcher: LogWrangler['addWatcher'];\n watchLogArray: LogWrangler['watchLogArray'];\n watchLogChunk: LogWrangler['watchLogChunk'];\n\n config: IConfig;\n\n constructor(serverPath: string, cfg: IConfig) {\n super();\n this.setMaxListeners(Infinity);\n\n this.config = cfg;\n this.path =\n path.isAbsolute(serverPath) || serverPath.startsWith('/')\n ? serverPath\n : path.join(process.cwd(), serverPath);\n this.dataPath = path.join(this.path, soft.DATA_PATH);\n this.#server = new BrickadiaServer(this.dataPath, cfg);\n\n // log wrangler wrangles logs... it reads brickadia logs and clumps them together\n // this is cursed but the OmeggaWrapper will never be used without omegga...\n this.logWrangler = new LogWrangler(this as unknown as Omegga);\n this.#server.on('line', this.logWrangler.callback);\n this.#server.on('line', (line: string) => this.emit('line', line));\n this.#server.on('err', (line: string) => this.emit('err', line));\n this.#server.on('closed', () => this.emit('closed'));\n\n this.addMatcher = this.logWrangler.addMatcher;\n this.addWatcher = this.logWrangler.addWatcher;\n this.watchLogArray = this.logWrangler.watchLogArray;\n this.watchLogChunk = this.logWrangler.watchLogChunk;\n }\n\n // passthrough to server\n write(str: string) {\n this.#server.write(str);\n }\n writeln(str: string) {\n // auto-migrate stale console command names to the running game version's\n // name (e.g. `Bricks.Clear` -> `br.Bricks.Clear`) so older plugins keep\n // working. OmeggaWrapper is never used without Omegga, so `version` exists.\n this.#server.writeln(\n migrateConsoleCommand(str, (this as unknown as Omegga).version),\n );\n }\n start() {\n return this.#server.start();\n }\n stop() {\n return this.#server.stop();\n }\n\n // event emitter to catch everything\n emit(type: string, ...args: any) {\n if (type !== 'line') Logger.verbose('Emitting event', type);\n try {\n (super.emit as EventEmitter['emit'])('*', type, ...args);\n } catch (e) {\n Logger.errorp('Error in emitted event', type, e);\n // error emitting\n }\n return (super.emit as EventEmitter['emit'])(type, ...args);\n }\n\n /** Get which world will be loaded on startup (form file, config, or env) */\n getNextWorld() {\n return this.#server.getNextWorld();\n }\n\n /** Get the configured default world */\n getActiveWorld() {\n return this.#server.getActiveWorld();\n }\n\n /** Configure the default world */\n setActiveWorld(world: string | null): boolean {\n return this.#server.setActiveWorld(world);\n }\n\n /** Check if a world exists */\n worldExists(file: string) {\n return this.#server.worldExists(file);\n }\n}\n\nexport default OmeggaWrapper;\n"],"names":["soft","BrickadiaServer","LogWrangler","migrateConsoleCommand","Logger"],"mappings":";;;;;;;;;AAcA,MAAM,sBAAsB,aAAa;AAAA,EACvC;AAAA,EAYA,YAAY,YAAoB,KAAc;AAC5C,UAAA;AACA,SAAK,gBAAgB,QAAQ;AAE7B,SAAK,SAAS;AACd,SAAK,OACH,KAAK,WAAW,UAAU,KAAK,WAAW,WAAW,GAAG,IACpD,aACA,KAAK,KAAK,QAAQ,IAAA,GAAO,UAAU;AACzC,SAAK,WAAW,KAAK,KAAK,KAAK,MAAMA,WAAAA,QAAK,SAAS;AACnD,SAAK,UAAU,IAAIC,OAAAA,QAAgB,KAAK,UAAU,GAAG;AAIrD,SAAK,cAAc,IAAIC,YAAAA,QAAY,IAAyB;AAC5D,SAAK,QAAQ,GAAG,QAAQ,KAAK,YAAY,QAAQ;AACjD,SAAK,QAAQ,GAAG,QAAQ,CAAC,SAAiB,KAAK,KAAK,QAAQ,IAAI,CAAC;AACjE,SAAK,QAAQ,GAAG,OAAO,CAAC,SAAiB,KAAK,KAAK,OAAO,IAAI,CAAC;AAC/D,SAAK,QAAQ,GAAG,UAAU,MAAM,KAAK,KAAK,QAAQ,CAAC;AAEnD,SAAK,aAAa,KAAK,YAAY;AACnC,SAAK,aAAa,KAAK,YAAY;AACnC,SAAK,gBAAgB,KAAK,YAAY;AACtC,SAAK,gBAAgB,KAAK,YAAY;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,KAAa;AACjB,SAAK,QAAQ,MAAM,GAAG;AAAA,EACxB;AAAA,EACA,QAAQ,KAAa;AAInB,SAAK,QAAQ;AAAA,MACXC,+BAAsB,KAAM,KAA2B,OAAO;AAAA,IAAA;AAAA,EAElE;AAAA,EACA,QAAQ;AACN,WAAO,KAAK,QAAQ,MAAA;AAAA,EACtB;AAAA,EACA,OAAO;AACL,WAAO,KAAK,QAAQ,KAAA;AAAA,EACtB;AAAA;AAAA,EAGA,KAAK,SAAiB,MAAW;AAC/B,QAAI,SAAS,OAAQC,QAAAA,QAAO,QAAQ,kBAAkB,IAAI;AAC1D,QAAI;AACD,YAAM,KAA8B,KAAK,MAAM,GAAG,IAAI;AAAA,IACzD,SAAS,GAAG;AACVA,aAAAA,QAAO,OAAO,0BAA0B,MAAM,CAAC;AAAA,IAEjD;AACA,WAAQ,MAAM,KAA8B,MAAM,GAAG,IAAI;AAAA,EAC3D;AAAA;AAAA,EAGA,eAAe;AACb,WAAO,KAAK,QAAQ,aAAA;AAAA,EACtB;AAAA;AAAA,EAGA,iBAAiB;AACf,WAAO,KAAK,QAAQ,eAAA;AAAA,EACtB;AAAA;AAAA,EAGA,eAAe,OAA+B;AAC5C,WAAO,KAAK,QAAQ,eAAe,KAAK;AAAA,EAC1C;AAAA;AAAA,EAGA,YAAY,MAAc;AACxB,WAAO,KAAK,QAAQ,YAAY,IAAI;AAAA,EACtC;AACF;;"}
|
|
1
|
+
{"version":3,"file":"wrapper.js","sources":["../../src/omegga/wrapper.ts"],"sourcesContent":["/*\n The wrapper combines the things looking at or waiting for logs with the actual server logs\n*/\n\nimport Logger from '@/logger';\nimport soft from '@/softconfig';\nimport BrickadiaServer from '@brickadia/server';\nimport { IConfig } from '@config/types';\nimport EventEmitter from 'events';\nimport path from 'path';\nimport { migrateConsoleCommand } from './commands';\nimport LogWrangler from './logWrangler';\nimport type Omegga from './server';\n\nclass OmeggaWrapper extends EventEmitter {\n #server: BrickadiaServer;\n dataPath: string;\n path: string;\n\n logWrangler: LogWrangler;\n addMatcher: LogWrangler['addMatcher'];\n addWatcher: LogWrangler['addWatcher'];\n watchLogArray: LogWrangler['watchLogArray'];\n watchLogChunk: LogWrangler['watchLogChunk'];\n\n config: IConfig;\n\n constructor(serverPath: string, cfg: IConfig) {\n super();\n this.setMaxListeners(Infinity);\n\n this.config = cfg;\n this.path =\n path.isAbsolute(serverPath) || serverPath.startsWith('/')\n ? serverPath\n : path.join(process.cwd(), serverPath);\n this.dataPath = path.join(this.path, soft.DATA_PATH);\n this.#server = new BrickadiaServer(this.dataPath, cfg);\n\n // log wrangler wrangles logs... it reads brickadia logs and clumps them together\n // this is cursed but the OmeggaWrapper will never be used without omegga...\n this.logWrangler = new LogWrangler(this as unknown as Omegga);\n this.#server.on('line', this.logWrangler.callback);\n this.#server.on('line', (line: string) => this.emit('line', line));\n this.#server.on('err', (line: string) => this.emit('err', line));\n this.#server.on('closed', () => this.emit('closed'));\n\n this.addMatcher = this.logWrangler.addMatcher;\n this.addWatcher = this.logWrangler.addWatcher;\n this.watchLogArray = this.logWrangler.watchLogArray;\n this.watchLogChunk = this.logWrangler.watchLogChunk;\n }\n\n // passthrough to server\n write(str: string) {\n this.#server.write(str);\n }\n writeln(str: string) {\n // auto-migrate stale console command names to the running game version's\n // name (e.g. `Bricks.Clear` -> `br.Bricks.Clear`) so older plugins keep\n // working. OmeggaWrapper is never used without Omegga, so `version` exists.\n this.#server.writeln(\n migrateConsoleCommand(str, (this as unknown as Omegga).version),\n );\n }\n start() {\n return this.#server.start();\n }\n stop() {\n return this.#server.stop();\n }\n\n // event emitter to catch everything\n emit(type: string, ...args: any) {\n if (type !== 'line') Logger.verbose('Emitting event', type);\n try {\n (super.emit as EventEmitter['emit'])('*', type, ...args);\n } catch (e) {\n Logger.errorp('Error in emitted event', type, e);\n // error emitting\n }\n return (super.emit as EventEmitter['emit'])(type, ...args);\n }\n\n /** Get which world will be loaded on startup (form file, config, or env) */\n getNextWorld() {\n return this.#server.getNextWorld();\n }\n\n /** Get the configured default world */\n getActiveWorld() {\n return this.#server.getActiveWorld();\n }\n\n /** Configure the default world */\n setActiveWorld(world: string | null): boolean {\n return this.#server.setActiveWorld(world);\n }\n\n /** Check if a world exists */\n worldExists(file: string) {\n return this.#server.worldExists(file);\n }\n\n /** Resolve the game server binary path (steam/override installs; null for launcher) */\n getGameBinaryPath(): string | null {\n return this.#server.getGameBinaryPath();\n }\n}\n\nexport default OmeggaWrapper;\n"],"names":["soft","BrickadiaServer","LogWrangler","migrateConsoleCommand","Logger"],"mappings":";;;;;;;;;AAcA,MAAM,sBAAsB,aAAa;AAAA,EACvC;AAAA,EAYA,YAAY,YAAoB,KAAc;AAC5C,UAAA;AACA,SAAK,gBAAgB,QAAQ;AAE7B,SAAK,SAAS;AACd,SAAK,OACH,KAAK,WAAW,UAAU,KAAK,WAAW,WAAW,GAAG,IACpD,aACA,KAAK,KAAK,QAAQ,IAAA,GAAO,UAAU;AACzC,SAAK,WAAW,KAAK,KAAK,KAAK,MAAMA,WAAAA,QAAK,SAAS;AACnD,SAAK,UAAU,IAAIC,OAAAA,QAAgB,KAAK,UAAU,GAAG;AAIrD,SAAK,cAAc,IAAIC,YAAAA,QAAY,IAAyB;AAC5D,SAAK,QAAQ,GAAG,QAAQ,KAAK,YAAY,QAAQ;AACjD,SAAK,QAAQ,GAAG,QAAQ,CAAC,SAAiB,KAAK,KAAK,QAAQ,IAAI,CAAC;AACjE,SAAK,QAAQ,GAAG,OAAO,CAAC,SAAiB,KAAK,KAAK,OAAO,IAAI,CAAC;AAC/D,SAAK,QAAQ,GAAG,UAAU,MAAM,KAAK,KAAK,QAAQ,CAAC;AAEnD,SAAK,aAAa,KAAK,YAAY;AACnC,SAAK,aAAa,KAAK,YAAY;AACnC,SAAK,gBAAgB,KAAK,YAAY;AACtC,SAAK,gBAAgB,KAAK,YAAY;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,KAAa;AACjB,SAAK,QAAQ,MAAM,GAAG;AAAA,EACxB;AAAA,EACA,QAAQ,KAAa;AAInB,SAAK,QAAQ;AAAA,MACXC,+BAAsB,KAAM,KAA2B,OAAO;AAAA,IAAA;AAAA,EAElE;AAAA,EACA,QAAQ;AACN,WAAO,KAAK,QAAQ,MAAA;AAAA,EACtB;AAAA,EACA,OAAO;AACL,WAAO,KAAK,QAAQ,KAAA;AAAA,EACtB;AAAA;AAAA,EAGA,KAAK,SAAiB,MAAW;AAC/B,QAAI,SAAS,OAAQC,QAAAA,QAAO,QAAQ,kBAAkB,IAAI;AAC1D,QAAI;AACD,YAAM,KAA8B,KAAK,MAAM,GAAG,IAAI;AAAA,IACzD,SAAS,GAAG;AACVA,aAAAA,QAAO,OAAO,0BAA0B,MAAM,CAAC;AAAA,IAEjD;AACA,WAAQ,MAAM,KAA8B,MAAM,GAAG,IAAI;AAAA,EAC3D;AAAA;AAAA,EAGA,eAAe;AACb,WAAO,KAAK,QAAQ,aAAA;AAAA,EACtB;AAAA;AAAA,EAGA,iBAAiB;AACf,WAAO,KAAK,QAAQ,eAAA;AAAA,EACtB;AAAA;AAAA,EAGA,eAAe,OAA+B;AAC5C,WAAO,KAAK,QAAQ,eAAe,KAAK;AAAA,EAC1C;AAAA;AAAA,EAGA,YAAY,MAAc;AACxB,WAAO,KAAK,QAAQ,YAAY,IAAI;AAAA,EACtC;AAAA;AAAA,EAGA,oBAAmC;AACjC,WAAO,KAAK,QAAQ,kBAAA;AAAA,EACtB;AACF;;"}
|