omegga 1.5.2 → 1.6.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/bin/omegga +1 -1
  3. package/dist/cli/terminal.js +46 -0
  4. package/dist/cli/terminal.js.map +1 -1
  5. package/dist/omegga/commandInjector.js +13 -6
  6. package/dist/omegga/commandInjector.js.map +1 -1
  7. package/dist/omegga/player.js +2 -2
  8. package/dist/omegga/player.js.map +1 -1
  9. package/dist/plugin.js.map +1 -1
  10. package/dist/webserver/backend/database.js +219 -7
  11. package/dist/webserver/backend/database.js.map +1 -1
  12. package/dist/webserver/backend/events.js +1 -1
  13. package/dist/webserver/backend/events.js.map +1 -1
  14. package/dist/webserver/backend/index.js +120 -2
  15. package/dist/webserver/backend/index.js.map +1 -1
  16. package/dist/webserver/backend/permissions.js +74 -0
  17. package/dist/webserver/backend/permissions.js.map +1 -0
  18. package/dist/webserver/backend/router/chat.js +9 -7
  19. package/dist/webserver/backend/router/chat.js.map +1 -1
  20. package/dist/webserver/backend/router/index.js +3 -1
  21. package/dist/webserver/backend/router/index.js.map +1 -1
  22. package/dist/webserver/backend/router/mfa.js +162 -0
  23. package/dist/webserver/backend/router/mfa.js.map +1 -0
  24. package/dist/webserver/backend/router/player.js +7 -6
  25. package/dist/webserver/backend/router/player.js.map +1 -1
  26. package/dist/webserver/backend/router/plugin.js +14 -10
  27. package/dist/webserver/backend/router/plugin.js.map +1 -1
  28. package/dist/webserver/backend/router/role.js +2 -1
  29. package/dist/webserver/backend/router/role.js.map +1 -1
  30. package/dist/webserver/backend/router/server.js +85 -71
  31. package/dist/webserver/backend/router/server.js.map +1 -1
  32. package/dist/webserver/backend/router/session.js +11 -2
  33. package/dist/webserver/backend/router/session.js.map +1 -1
  34. package/dist/webserver/backend/router/user.js +175 -13
  35. package/dist/webserver/backend/router/user.js.map +1 -1
  36. package/dist/webserver/backend/router/world.js +10 -9
  37. package/dist/webserver/backend/router/world.js.map +1 -1
  38. package/dist/webserver/backend/scopes.js +303 -0
  39. package/dist/webserver/backend/scopes.js.map +1 -0
  40. package/dist/webserver/backend/totp.js +32 -0
  41. package/dist/webserver/backend/totp.js.map +1 -0
  42. package/dist/webserver/backend/trpc.js +47 -4
  43. package/dist/webserver/backend/trpc.js.map +1 -1
  44. package/doc/permissions.md +193 -0
  45. package/package.json +7 -2
  46. package/public/app.html +932 -526
  47. package/public/assets/IconShieldCheck-Bjlm8mX2.js +2 -0
  48. package/public/assets/IconShieldCheck-Bjlm8mX2.js.map +1 -0
  49. package/public/assets/IconWorld-XmQNg6JI.js +2 -0
  50. package/public/assets/IconWorld-XmQNg6JI.js.map +1 -0
  51. package/public/assets/IconWorldPlus-CxGGv2eD.js +2 -0
  52. package/public/assets/IconWorldPlus-CxGGv2eD.js.map +1 -0
  53. package/public/assets/app-DMr_-wik.js +7 -0
  54. package/public/assets/app-DMr_-wik.js.map +1 -0
  55. package/public/assets/auth-CEYiizOt.js +2 -0
  56. package/public/assets/auth-CEYiizOt.js.map +1 -0
  57. package/public/assets/index-BDkarAkX.js +3 -0
  58. package/public/assets/index-BDkarAkX.js.map +1 -0
  59. package/public/assets/{index-2cgXLhrt.js → index-DEL-PD_8.js} +2 -2
  60. package/public/assets/{index-2cgXLhrt.js.map → index-DEL-PD_8.js.map} +1 -1
  61. package/public/assets/index-D_STNela.js +2 -0
  62. package/public/assets/index-D_STNela.js.map +1 -0
  63. package/public/assets/index-Dkh3HiBz.js +4 -0
  64. package/public/assets/index-Dkh3HiBz.js.map +1 -0
  65. package/public/assets/shared/@components-CgsSYMe6.js +2 -0
  66. package/public/assets/shared/@components-CgsSYMe6.js.map +1 -0
  67. package/public/assets/shared/@hooks-DT93N7wd.js +2 -0
  68. package/public/assets/shared/{@hooks-Ch_hJYh0.js.map → @hooks-DT93N7wd.js.map} +1 -1
  69. package/public/assets/shared/@tabler_icons-react-DrVFX3uL.js +3 -0
  70. package/public/assets/shared/{@tabler_icons-react-iQk11eD_.js.map → @tabler_icons-react-DrVFX3uL.js.map} +1 -1
  71. package/public/assets/shared/{wouter-CUC1caxu.js → wouter-CNISJ5i6.js} +2 -2
  72. package/public/assets/shared/wouter-CNISJ5i6.js.map +1 -0
  73. package/public/assets/useHasScope-BOdFktko.js +2 -0
  74. package/public/assets/useHasScope-BOdFktko.js.map +1 -0
  75. package/public/auth.html +97 -33
  76. package/templates/safe-ts/omegga.d.ts +1 -1
  77. package/public/assets/IconArrowRight-BuVxhqKX.js +0 -2
  78. package/public/assets/IconArrowRight-BuVxhqKX.js.map +0 -1
  79. package/public/assets/IconWorld-B6GK5qpx.js +0 -2
  80. package/public/assets/IconWorld-B6GK5qpx.js.map +0 -1
  81. package/public/assets/IconWorldPlus-Dkos72Ws.js +0 -2
  82. package/public/assets/IconWorldPlus-Dkos72Ws.js.map +0 -1
  83. package/public/assets/app-CLZ-5FIi.js +0 -6
  84. package/public/assets/app-CLZ-5FIi.js.map +0 -1
  85. package/public/assets/auth-6N37o421.js +0 -2
  86. package/public/assets/auth-6N37o421.js.map +0 -1
  87. package/public/assets/index-DLZ8-XzO.js +0 -4
  88. package/public/assets/index-DLZ8-XzO.js.map +0 -1
  89. package/public/assets/index-DY750sgZ.js +0 -2
  90. package/public/assets/index-DY750sgZ.js.map +0 -1
  91. package/public/assets/shared/@components-DPHARroA.js +0 -2
  92. package/public/assets/shared/@components-DPHARroA.js.map +0 -1
  93. package/public/assets/shared/@hooks-Ch_hJYh0.js +0 -2
  94. package/public/assets/shared/@tabler_icons-react-iQk11eD_.js +0 -3
  95. package/public/assets/shared/wouter-CUC1caxu.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -2,9 +2,26 @@
2
2
 
3
3
  ## Latest
4
4
 
5
- Add new changes here as they are implemented
5
+ - Future features go here
6
6
 
7
- ## 1.5.2 - 2026-05-06
7
+ ## 1.6.0 - 2026-05-24
8
+
9
+ - Add owner-only user disable and delete actions to the web UI
10
+ - Add granular permission system with scopes and domains for access control
11
+ - Add TOTP two-factor authentication, WebAuthn passkeys (passwordless), and recovery codes
12
+ - Add self-service account management (password change, MFA setup) for all users in the Account view
13
+ - Add `/user passwd` and `/user resetmfa` terminal commands
14
+ - Web UI: scope-aware enforcement -- buttons, views, queries, and subscriptions gated by user permissions
15
+ - Web UI: user inspector with permission editor, default permissions, user disable/delete
16
+ - Web UI: slightly improved responsive tablet layout for all views
17
+ - Deleted or banned users are immediately disconnected from active sessions and live subscriptions
18
+
19
+
20
+ ## 1.5.4 - 2026-05-18
21
+
22
+ - Fix vm2 error with Player.getRoles
23
+
24
+ ## 1.5.2-1.5.3 - 2026-05-06
8
25
 
9
26
  - Fix server status check regex error handling
10
27
 
package/bin/omegga CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env -S node --enable-source-maps --disable-warning=DEP0040
1
+ #!/usr/bin/env -S node --enable-source-maps --disable-warning=DEP0040 --trace-warnings
2
2
 
3
3
  require('../dist/main.js');
@@ -693,6 +693,52 @@ Players: ${status.players.length === 0 ? "none".grey : ""}
693
693
  this.omegga.createEmptyWorld(newWorldName, map);
694
694
  }
695
695
  }
696
+ },
697
+ {
698
+ aliases: ["user"],
699
+ desc: "manage web UI users",
700
+ descLines: () => [
701
+ "/user passwd <username> - Reset a user's password",
702
+ "/user resetmfa <username> - Clear TOTP and passkeys"
703
+ ],
704
+ async fn(subcommand, username) {
705
+ const db = this.omegga.webserver?.database;
706
+ if (!db) {
707
+ err("Database not available.");
708
+ return;
709
+ }
710
+ if (!subcommand || !username) {
711
+ err("Usage: /user <passwd|resetmfa> <username>");
712
+ return;
713
+ }
714
+ if (!await db.userExists(username)) {
715
+ err(`User "${username}" does not exist.`);
716
+ return;
717
+ }
718
+ switch (subcommand) {
719
+ case "passwd": {
720
+ const password = await new Promise(
721
+ (resolve) => this.rl.question("New password: ", resolve)
722
+ );
723
+ if (!password) {
724
+ err("Password cannot be empty.");
725
+ return;
726
+ }
727
+ await db.userPasswd(username, password);
728
+ log(`Password updated for "${username.yellow}".`);
729
+ break;
730
+ }
731
+ case "resetmfa": {
732
+ await db.resetUserMfa(username);
733
+ log(
734
+ `MFA cleared for "${username.yellow}" (TOTP, passkeys, recovery codes).`
735
+ );
736
+ break;
737
+ }
738
+ default:
739
+ err(`Unknown subcommand "${subcommand}". Use passwd or resetmfa.`);
740
+ }
741
+ }
696
742
  }
697
743
  ];
698
744
  class Terminal {
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.js","sources":["../../src/cli/terminal.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { OmeggaPlayer } from '@/plugin';\nimport { steamcmdCheckUpdate, steamcmdDownloadGame } from '@/updater';\nimport Omegga from '@omegga/server';\nimport { IOmeggaOptions } from '@omegga/types';\nimport { sanitize } from '@util/chat';\nimport { checkWsl } from '@util/wsl';\nimport { serverEvents } from '@webserver/backend/events';\nimport readline from 'readline';\nimport { install } from './plugin';\n\ndeclare global {\n interface String {\n brightRed: string;\n brightGreen: string;\n brightYellow: string;\n brightBlue: string;\n brightMagenta: string;\n brightCyan: string;\n brightWhite: string;\n }\n}\n\nlet log: (...args: any[]) => void,\n err: (...args: any[]) => void,\n warn: (...args: any[]) => void,\n info: (...args: any[]) => void;\n\ntype TerminalCommand = {\n aliases: string[];\n desc: string;\n descLines?: () => string[];\n fn(this: Terminal, ...args: string[]): void | Promise<void>;\n};\n\nfunction gameCommand(\n aliases: string[],\n desc: string,\n usage: string,\n command: string,\n quoteArgs?: boolean,\n): TerminalCommand {\n return {\n aliases,\n desc,\n fn(...args) {\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n if (!args.length) {\n err('usage:', ('/' + aliases[0] + ' ' + usage).yellow);\n return;\n }\n this.omegga.writeln(\n `${command} ${quoteArgs ? '\"' : ''}${args.join(' ')}${\n quoteArgs ? '\"' : ''\n }`,\n );\n this.rl.prompt(true);\n },\n };\n}\n\nconst COMMANDS: TerminalCommand[] = [\n // Brickadia commands\n gameCommand(\n ['kick'],\n 'kick a player',\n '<name|id> [reason]',\n 'Chat.Command /Kick',\n ),\n gameCommand(\n ['ban', 'banish'],\n 'ban a player',\n '<name|id> [time] [reason]',\n 'Ban',\n ),\n gameCommand(\n ['unban', 'pardon'],\n 'unban a player',\n '<name|id>',\n 'Chat.Command /Unban',\n ),\n gameCommand(\n ['grantrole', 'giverole'],\n 'grant a role to a player',\n '<role> <name|id>',\n 'Chat.Command /GrantRole',\n ),\n gameCommand(\n ['revokerole', 'takerole'],\n 'revoke a role from a player',\n '<role> <name|id>',\n 'Chat.Command /RevokeRole',\n ),\n gameCommand(\n ['clearbricks'],\n \"clear a player's bricks\",\n '<name|id>',\n 'Bricks.Clear',\n true,\n ),\n gameCommand(['clearallbricks'], 'clear all bricks', '', 'Bricks.ClearAll'),\n\n // Help command\n {\n aliases: ['help', 'cmds', 'commands'],\n desc: 'list supported commands and their descriptions',\n fn() {\n const maxLen = Math.max(...COMMANDS.map(c => c.aliases[0].length));\n log('Omegga Help Text:\\n');\n log(\n ' Console input not starting with / will be sent in chat from a \"SERVER\" user',\n );\n log(\n ' Console input starting with / will be treated as one of the following commands\\n',\n );\n log(\n '-- Available Omegga commands (type',\n '/command'.yellow.underline,\n 'to run)',\n );\n\n for (const command of COMMANDS.sort((a, b) =>\n a.aliases[0].localeCompare(b.aliases[0]),\n )) {\n const name = command.aliases[0];\n this.log(\n ' ',\n ('/' + name).yellow.underline,\n '-'.padStart(maxLen - name.length + 1),\n command.desc,\n );\n if (command.descLines)\n for (const descLine of command.descLines())\n this.log(' ', ' '.repeat(maxLen + 1), descLine);\n }\n },\n },\n\n // Server controls\n {\n aliases: ['stop', 'exit', 'quit', 'close'],\n desc: 'stop the server and close Omegga',\n async fn() {\n log('Stopping server...');\n await this.omegga.stop();\n process.exit();\n },\n },\n {\n aliases: ['restart'],\n desc: 'trigger a server restart (without announcement or saving)',\n async fn() {\n log('Restarting server...');\n await this.omegga.restartServer();\n },\n },\n {\n aliases: ['update'],\n desc: 'try to update the server and restart if started (without announcement or saving',\n async fn() {\n if (!this.omegga.config.__STEAM) {\n err(\n 'This command is only available when the server is installed via SteamCMD',\n );\n return;\n }\n if (this.omegga.stopping || this.omegga.starting) {\n err(\n 'The server is currently starting or stopping. Please wait a moment',\n );\n return;\n }\n const wasStarted = this.omegga.started;\n if (wasStarted) {\n log('Stopping server to update...');\n await this.omegga.stop();\n }\n log('Updating server...');\n try {\n await steamcmdDownloadGame({\n steambeta: this.omegga.config.server?.steambeta,\n steambetaPassword: this.omegga.config.server?.steambetaPassword,\n });\n log('Server updated successfully.');\n } catch (e) {\n err('Error updating server:', e);\n }\n if (wasStarted) {\n log('Starting server...');\n await this.omegga.start();\n }\n },\n },\n {\n aliases: ['updatecheck', 'uc', 'check'],\n desc: 'check for updates. /uc show for details, /uc on|off to toggle auto-update',\n async fn(subcommand?: string) {\n if (!this.omegga.config.__STEAM) {\n err(\n 'This command is only available when the server is installed via SteamCMD',\n );\n return;\n }\n\n const db = this.omegga.webserver?.database;\n\n if (subcommand === 'on' || subcommand === 'off') {\n if (!db) {\n err('Database not available.');\n return;\n }\n const config = await db.getAutoRestartConfig();\n config.autoUpdateEnabled = subcommand === 'on';\n await db.setAutoRestartConfig(config);\n log(\n subcommand === 'on'\n ? 'Auto-update enabled.'.green\n : 'Auto-update disabled.'.yellow,\n );\n return;\n }\n\n log('Checking for updates...');\n const result = steamcmdCheckUpdate(this.omegga.config.server?.steambeta);\n if (!result) {\n err('Failed to check for updates.');\n return;\n }\n const { local, remote, hasUpdate } = result;\n\n if (hasUpdate === true) log('Update available!'.green);\n else if (hasUpdate === false) log('Up to date.'.green);\n else log('Could not determine update status.'.yellow);\n\n if (subcommand === 'show') {\n const branch =\n local.betaKey && local.betaKey !== 'main' ? local.betaKey : 'public';\n const remoteBuild = remote?.branches?.[branch];\n const remotePart = remoteBuild\n ? `BuildID ${remoteBuild.buildid}` +\n (remoteBuild.timeupdated\n ? ` (${new Date(Number(remoteBuild.timeupdated) * 1000).toLocaleString()})`\n : '')\n : 'unknown (private branch?)';\n log(\n [\n ` Local: BuildID ${local.buildId} (${local.installState})`,\n ` Remote: ${remotePart}`,\n ` Branch: ${local.betaKey ?? 'public'}`,\n ].join('\\n'),\n );\n }\n\n if (db) {\n const config = await db.getAutoRestartConfig();\n log(\n config.autoUpdateEnabled\n ? `Auto-update: enabled (every ${config.autoUpdateIntervalMins}m)`\n : 'Auto-update: disabled',\n );\n }\n },\n },\n {\n aliases: ['kill'],\n desc: 'forcefully kill brickadia server process without closing omegga',\n async fn() {\n log('Stopping server...');\n await this.omegga.stop();\n },\n },\n {\n aliases: ['start'],\n desc: 'start the server if it is stopped',\n fn() {\n if (\n !this.omegga ||\n (this.omegga && (this.omegga.starting || this.omegga.started))\n ) {\n err('The server is already running');\n return;\n }\n\n log('Starting server...');\n this.omegga.start();\n },\n },\n {\n aliases: ['cmd'],\n desc: 'run a console command on the Brickadia server. requires /debug for log to show',\n fn(...args) {\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n this.omegga.writeln(args.join(' '));\n },\n },\n {\n aliases: ['debug'],\n desc: 'toggle visibility of brickadia console logs',\n fn() {\n this.options.debug = !this.options.debug;\n log(\n 'Brickadia logs now',\n this.options.debug ? 'visible'.green : 'hidden'.red,\n );\n },\n },\n {\n aliases: ['status'],\n desc: 'display server status information. brick count, online players, etc',\n async fn() {\n if (!this.omegga.started) {\n err('Omegga is not running');\n return;\n }\n const msToTime = (ms: number) => new Date(ms).toISOString().slice(11, 19);\n try {\n const status = await this.omegga.getServerStatus();\n\n log('Server Status');\n this.log(`\n${status.serverName.yellow}\nBricks: ${(status.bricks + '').yellow}\nComponents: ${(status.components + '').yellow}\nUptime: ${msToTime(status.time).yellow}\nPlayers: ${status.players.length === 0 ? 'none'.grey : ''}\n ${status.players\n .map(p => `[${msToTime(p.time).grey}] ${p.name.brightYellow}`)\n .join('\\n ')}\n`);\n } catch (e) {\n err('An error occurred while getting server status');\n }\n },\n },\n\n // plugin stuff\n {\n aliases: ['reload'],\n desc: 'shorthand for /plugins reload',\n async fn() {\n await COMMANDS.find(c => c.aliases.includes('plugins')).fn.bind(this)(\n 'reload',\n );\n },\n },\n {\n aliases: ['plugins', 'plugin', 'p'],\n desc: 'manage Omegga plugins',\n descLines: () => [\n '/plugins install <...plugin names>'.yellow + ' installs plugins',\n '/plugins load <...plugin names>'.yellow +\n ' loads plugins, and enables if they were disabled',\n '/plugins unload <...plugin names>'.yellow + ' unloads plugins',\n '/plugins reload [...plugin names]'.yellow +\n ' reloads all plugins, or those specified',\n '/plugins enable <...plugin names>'.yellow + ' enables plugins',\n '/plugins disable <...plugin names>'.yellow +\n ' disables plugins, and unloads if they were loaded',\n ],\n async fn(subcommand: string, ...args: string[]) {\n if (!this.omegga.pluginLoader) {\n err('Omegga is not using plugins');\n return;\n }\n\n if (!subcommand) {\n const plugins = this.omegga.pluginLoader.plugins;\n if (plugins.length === 0) {\n log('No plugins are installed');\n log(' Install some with', '/plugins install'.yellow.underline);\n } else {\n log('Installed plugins'.yellow.underline);\n for (const plugin of plugins) {\n const name = plugin.getName();\n const docs = plugin.getDocumentation();\n if (plugin.isLoaded()) log(name.cyan.underline + ' (loaded)');\n else if (plugin.isEnabled())\n log(name.green.underline + ' (enabled, not loaded)');\n else log(name.red.underline + ' (disabled)');\n\n log(' ' + docs.description);\n log((' Author: ' + docs.author).gray);\n }\n }\n } else if (subcommand === 'install') {\n // install plugins\n await install(args, { verbose: true });\n log();\n log(\n 'Use',\n '/plugins reload'.yellow,\n 'to reload all plugins and use the installed ones',\n );\n } else if (subcommand === 'load') {\n if (args.length === 0) {\n log('Please specify at least one plugin to load');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (plugin.isLoaded()) {\n warn(\n name.cyan,\n 'is already loaded, reload with',\n ('/plugins reload ' + name).yellow,\n );\n return;\n }\n\n if (!plugin.isEnabled()) {\n log('Enabling and loading', name.cyan.underline);\n plugin.setEnabled(true);\n await plugin.load();\n } else {\n log('Loading', name.cyan.underline);\n await plugin.load();\n }\n }\n } else if (subcommand === 'unload') {\n if (args.length === 0) {\n log('Please specify at least one plugin to unload');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (!plugin.isLoaded()) {\n warn(\n name.cyan,\n 'is already unloaded, load with',\n ('/plugins load ' + name).yellow,\n );\n return;\n }\n\n log('Unloading', name.cyan.underline);\n await plugin.unload();\n }\n } else if (subcommand === 'reload') {\n let plugins = (\n args.length === 0\n ? this.omegga.pluginLoader.plugins\n : this.omegga.pluginLoader.plugins.filter(p =>\n args\n .map(a => a.toLowerCase())\n .includes(p.getName().toLowerCase()),\n )\n ).filter(p => p.isEnabled());\n\n const loaded = plugins.filter(p => p.isLoaded());\n log('Unloading', loaded.length, 'plugins');\n for (const plugin of loaded) await plugin.unload();\n\n if (args.length === 0) {\n log('Scanning for new plugins');\n if (!(await this.omegga.pluginLoader.scan())) {\n err('Could not scan for plugins. All plugins are unloaded');\n return;\n }\n }\n\n plugins = (\n args.length === 0\n ? this.omegga.pluginLoader.plugins\n : this.omegga.pluginLoader.plugins.filter(p =>\n args\n .map(a => a.toLowerCase())\n .includes(p.getName().toLowerCase()),\n )\n ).filter(p => p.isEnabled());\n log('Loading', plugins.length, 'plugins');\n for (const plugin of plugins) await plugin.load();\n\n log('Reloaded', plugins.length, 'plugins');\n } else if (subcommand === 'enable') {\n if (args.length === 0) {\n log('Please specify at least one plugin to enable');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (plugin.isEnabled()) {\n warn(\n name.cyan,\n 'is already enabled, disable with',\n ('/plugins disable ' + name).yellow,\n );\n return;\n }\n\n log(\n 'Enabling',\n name.cyan.underline,\n '(load with',\n ('/plugins load ' + name).yellow + ')',\n );\n plugin.setEnabled(true);\n }\n } else if (subcommand === 'disable') {\n if (args.length === 0) {\n log('Please specify at least one plugin to disable');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (!plugin.isEnabled()) {\n warn(\n name.cyan,\n 'is already disabled, enable with',\n ('/plugins enable ' + name).yellow,\n );\n return;\n }\n\n if (plugin.isLoaded()) {\n log('Unloading and disabling', name.cyan.underline);\n await plugin.unload();\n plugin.setEnabled(false);\n } else {\n log('Disabling', name.cyan.underline);\n plugin.setEnabled(false);\n }\n }\n }\n },\n },\n {\n aliases: ['world', 'worlds', 'w'],\n desc: 'Manage worlds on the server',\n descLines: () => ['Type /worlds load a world'],\n async fn(subcommand: string, ...args: string[]) {\n switch (subcommand) {\n default:\n const commands = [\n {\n command: '/worlds use [world]',\n desc: 'set or remove a world as the default world for startup',\n short: '/w u [world]',\n },\n {\n command: '/worlds load <world> [rev]',\n desc: 'load a world',\n short: '/w load <world> [rev]',\n },\n {\n command: '/worlds saveas <world>',\n desc: 'save the current world as a new world',\n short: '/w sa <world>',\n },\n {\n command: '/worlds save',\n desc: 'save the current world',\n short: '/w s',\n },\n {\n command: '/worlds revisions <world>',\n desc: 'list revisions of a world',\n short: '/w r <world>',\n },\n {\n command: '/worlds new',\n desc: 'create a new world',\n short: '/w new <name> [Plate|Space|Studio|Peaks]',\n },\n ];\n log(\n 'Default world is',\n this.omegga.getNextWorld()?.file?.yellow || 'none'.grey,\n );\n log('Available world commands:');\n for (const { command, desc, short } of commands) {\n this.log(' ', command.yellow, '-', desc, `(${short.yellow})`);\n }\n return;\n\n case 'use':\n case 'u':\n const world = args[0]?.replace(/\\.brdb$/, '');\n if (world && !this.omegga.worldExists(world)) {\n err(`World \"${world}\" does not exist`);\n return;\n }\n if (this.omegga.setActiveWorld(world || null)) {\n if (world) {\n log(\n `Default world set to ${world.yellow}. It will be loaded next startup.`,\n );\n } else {\n log(\n 'Default world cleared. No world will be loaded on next startup.',\n );\n }\n } else {\n err(`Failed to set default world to ${world.yellow}.`);\n }\n return;\n\n case 'list':\n case 'ls':\n // list worlds\n const worlds = this.omegga.getWorlds();\n if (worlds.length === 0) {\n log(\n 'No worlds found. Create one with',\n '/worlds saveas <world>'.yellow,\n );\n } else {\n log('Available worlds:');\n const prefix = this.omegga.worldPath + '/';\n for (const world of worlds) {\n this.log(\n ' ',\n world.replace(prefix, '').replace(/\\.brdb$/, '').yellow,\n );\n }\n }\n return;\n case 'load':\n case 'l':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // load a world\n if (args.length === 0) {\n err('Please specify a world to load');\n return;\n }\n const worldName = args[0].replace(/\\.brdb$/, '');\n if (!this.omegga.getWorldPath(worldName)) {\n err(`World \"${worldName}\" does not exist`);\n return;\n }\n let revision: number | undefined;\n if (args.length > 1) {\n revision = parseInt(args[1], 10);\n if (isNaN(revision) || revision < 1) {\n err(`Invalid revision number: ${args[1]}`);\n return;\n }\n }\n let loadRes: boolean = false;\n if (revision) {\n log(`Loading world ${worldName.yellow} at revision ${revision}...`);\n loadRes = await this.omegga.loadWorldRevision(worldName, revision);\n } else {\n log(`Loading world ${worldName.yellow}...`);\n loadRes = await this.omegga.loadWorld(worldName);\n }\n if (loadRes) {\n log(`World ${worldName.yellow} loaded successfully.`);\n } else {\n err(`Failed to load world ${worldName.yellow}`);\n }\n\n return;\n case 'saveas':\n case 'sa':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // save the current world as a new world\n if (args.length === 0) {\n err('Please specify a world to save as');\n return;\n }\n const saveWorldName = args[0].replace(/\\.brdb$/, '');\n if (this.omegga.getWorldPath(saveWorldName)) {\n err(`World \"${saveWorldName}\" already exists`);\n return;\n }\n log(`Saving current world as ${saveWorldName.yellow}...`);\n const saveAsRes = await this.omegga.saveWorldAs(saveWorldName);\n if (saveAsRes) {\n log(`Current world saved as ${saveWorldName.yellow} successfully.`);\n } else {\n err(`Failed to save current world as ${saveWorldName.yellow}`);\n }\n return;\n\n case 'save':\n case 's':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // save the current world\n log('Saving current world...');\n const res = await this.omegga.saveWorld();\n if (res) {\n log('Current world saved successfully.');\n } else {\n err('Failed to save the current world');\n }\n return;\n\n case 'revisions':\n case 'r':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // list revisions of a world\n if (args.length === 0) {\n err('Please specify a world to list revisions for');\n return;\n }\n const revisionsWorldName = args[0].replace(/\\.brdb$/, '');\n if (!this.omegga.getWorldPath(revisionsWorldName)) {\n err(`World \"${revisionsWorldName}\" does not exist`);\n return;\n }\n log(`Listing revisions for world ${revisionsWorldName.yellow}...`);\n try {\n const revisions =\n await this.omegga.getWorldRevisions(revisionsWorldName);\n if (revisions.length === 0) {\n log('No revisions found for world', revisionsWorldName.yellow);\n } else {\n log('Revisions for world', revisionsWorldName.yellow + ':');\n for (const rev of revisions) {\n this.log(\n ' ',\n `Revision ${rev.index.toString().yellow} - ${\n rev.date\n .toISOString()\n .replace('T', ' ')\n .replace('.000Z', '').blue\n }`,\n `(${rev.note})`.grey,\n );\n }\n }\n } catch (e) {\n err(\n 'An error occurred while getting world revisions:',\n e.toString(),\n );\n }\n return;\n\n case 'new':\n case 'n':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // create a new world\n if (args.length === 0) {\n err('Please specify a name for the new world');\n return;\n }\n const newWorldName = args[0].replace(/\\.brdb$/, '');\n if (this.omegga.getWorldPath(newWorldName)) {\n err(`World \"${newWorldName}\" already exists`);\n return;\n }\n const map = (args[1] ? args[1] : 'Plate') as\n | 'Plate'\n | 'Space'\n | 'Studio'\n | 'Peaks';\n if (!['Plate', 'Space', 'Studio', 'Peaks'].includes(map)) {\n err(`Invalid preset \"${map}\". Valid map are: Plate, Space, Studio`);\n return;\n }\n log(`Creating new world ${newWorldName.yellow}...`);\n this.omegga.createEmptyWorld(newWorldName, map);\n }\n },\n },\n];\n\n// the terminal wraps omegga and displays console output and handles console input\nexport default class Terminal {\n options: IOmeggaOptions;\n omegga: Omegga;\n rl: readline.Interface;\n constructor(omegga: Omegga, options: IOmeggaOptions = {}) {\n this.options = options;\n this.omegga = omegga;\n\n // terminal interface\n this.rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true,\n });\n this.rl.setPrompt('> '.brightGreen);\n\n // shortand fns\n log = (...args) => this.log('>>'.green, ...args);\n err = (...args) => this.error('!>'.red, ...args);\n info = (...args) => this.log('?>'.blue, ...args);\n warn = (...args) => this.warn('W>'.yellow, ...args);\n\n // print log line if debug is enabled\n let launcherDone = false;\n let lastDoneMessage = '';\n omegga.on('line', l => {\n if (options.debug) this.log('::'.blue, l);\n else if (!launcherDone) {\n if (l.match(/Update Failed/)) {\n err(l);\n info(\n 'This might be resolved by deleting brickadia and reinstalling:',\n );\n info(' rm -rf ~/.local/share/brickadia-launcher'.grey);\n }\n if (l.startsWith('DOWNLOADING')) {\n // TODO: if someone is proactive enough in the future, you could turn these steps into a legitimate progress bar\n /* Here's some launcher log lines that might be helpful\n Grabbing manifest and checking game files...\n DOWNLOADING MANIFEST | Hold on...\n DOWNLOADING MANIFEST (100%) | Hold on...\n DOWNLOADING MANIFEST (100%) | 0 B/s - 13 KiB / 13 KiB (Unknown time remaining...)\n DOWNLOADING MANIFEST (100%) | Done!\n Checking launcher and downloading version info...\n Installing patches and booting...\n DOWNLOADING BRICKADIA (0%) | Done!\n DOWNLOADING BRICKADIA (100%) | 110 MiB/s - 584 MiB / 584 MiB (1 second remaining...)\n DOWNLOADING BRICKADIA (100%) | Nearly done...\n DOWNLOADING BRICKADIA (100%), INSTALLING BRICKADIA (86%) | Nearly done...\n */\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n process.stdout.write(l);\n // add a newline on \"Done!\" but prevent newlines on repeat done messages\n if (l.match(/Done!/) && l !== lastDoneMessage) {\n lastDoneMessage = l;\n process.stdout.write('\\n');\n }\n } else if (l.match(/Disabling core dumps./)) {\n launcherDone = true;\n }\n }\n\n // [2025.07.07-20.45.40:310][845]LogBRWorldManager: World successfully loaded.\n if (\n l.match(\n /^\\[[^\\]]+\\]\\[[^\\]]+\\]LogBRWorldManager: World successfully loaded\\.$/,\n )\n ) {\n info('World successfully loaded.');\n }\n });\n\n // print debug events regardless of debug status\n omegga.on('debug', l => this.log('?>'.magenta, l));\n\n // print chat events as players join/leave the server\n omegga.on('join', (p: OmeggaPlayer) =>\n this.log(\n `${p.displayName.underline} (${p.name.underline}) joined.`.brightBlue,\n ),\n );\n omegga.on('leave', (p: OmeggaPlayer) =>\n this.log(\n `${p.displayName.underline} (${p.name.underline}) left.`.brightBlue,\n ),\n );\n omegga.on('chat', (name, message) => {\n const player = omegga.getPlayer(name);\n this.log(\n `${(player.displayName ?? player.name ?? name).brightYellow.underline}: ${message}`,\n );\n });\n omegga.on('start', () => {\n const wsl = checkWsl();\n log(\n `Server has started${\n wsl === 1 ? ' in single thread mode due to WSL1' : ''\n }. Type ${'/help'.yellow} for more commands`,\n );\n if (!this.omegga.getNextWorld()) {\n info(\n 'No',\n 'default world'.yellow,\n 'is configured. Run',\n '/worlds use <world>'.yellow,\n 'to load one on startup.',\n );\n }\n\n // check if this is WSL2 and wsl2binds is installed\n if (\n wsl === 2 &&\n this.omegga.pluginLoader &&\n !this.omegga.pluginLoader.plugins.some(p => p.getName() === 'wsl2binds')\n ) {\n warn(\n '####'.yellow,\n `You are running ${'WSL2'.yellow} and you don't have the ${\n 'wsl2binds'.yellow\n } plugin.`,\n );\n warn(\n '####'.yellow,\n `The wsl2binds plugin works as a ${\n 'UDP proxy'.underline\n } between Windows and the WSL2 VM.`,\n );\n warn('####'.yellow, 'Install it by:');\n warn('####'.yellow, ' 1. Closing omegga with', '/stop'.green);\n warn(\n '####'.yellow,\n ' 2. Installing wsl2binds with',\n 'omegga install gh:Meshiest/wsl2binds'.green,\n );\n warn('####'.yellow, ' 3. Restarting', 'omegga'.green);\n }\n });\n omegga.on('mapchange', ({ map }) =>\n log('Map changed to', (map.charAt(0).toUpperCase() + map.slice(1)).green),\n );\n omegga.on('unauthorized', () => {\n err('Server failed authentication check');\n info('You can clear auth tokens with', 'omegga auth -gl'.green);\n info(\n 'This will require you to paste a new hosting token or sign-in again',\n );\n process.exit();\n });\n omegga.on('error', e => err('Server caught unhandled exception:\\n' + e));\n omegga.on('server:stopped', () =>\n log('Server has closed. Type', '/stop'.yellow, 'to close omegga'),\n );\n\n this.rl.on('line', this.handleLine.bind(this));\n // Gracefully shut down server on Ctrl+C and SIGTERM\n process.on('SIGINT', () => this.handleLine('/stop'));\n process.on('SIGTERM', () => this.handleLine('/stop'));\n }\n\n async handleLine(line: string) {\n if (line.startsWith('/')) {\n const [cmd, ...args] = line.slice(1).split(' ');\n const c = COMMANDS.find(c => c.aliases.includes(cmd));\n if (!c) {\n err(\n `unrecognized command /${cmd.underline}. Type /help for more info`\n .red,\n );\n } else {\n try {\n const res = c.fn.bind(this)(...args);\n if (res instanceof Promise) await res;\n } catch (e) {\n err('unhandled terminal error', e);\n }\n }\n } else if (line.trim().length > 0) {\n if (this.omegga.started) {\n // broadcast when the chat does not start with a command\n this.omegga.broadcast(\n `\"[<b><color=\\\\\"ff00ff\\\\\">SERVER</></>]: ${sanitize(line)}\"`,\n );\n process.stdout.clearLine(0);\n this.log(`[${'SERVER'.brightMagenta.underline}]: ${line}`);\n\n // if omegga is running a webserver - send this message in the chat log\n if (this.omegga.webserver) {\n const user = { name: 'SERVER', id: '', web: true, color: 'ff00ff' };\n // create database entry, send to web ui\n const chatLog = await this.omegga.webserver.database.addChatLog(\n 'msg',\n user,\n line,\n );\n serverEvents.emit('chat', chatLog);\n }\n } else {\n err(\n 'Server is not started yet. type'.red,\n '/help'.yellow,\n 'for more info'.red,\n );\n }\n }\n }\n\n // let readline render a log without interrupting user input\n log(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.log(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n\n // let readline render a debug log without interrupting user input\n debug(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.debug(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n\n // let readline render a warning log without interrupting user input\n warn(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.warn(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n\n // let readline render an error log without interrupting user input\n error(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.error(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n}\n"],"names":["steamcmdDownloadGame","steamcmdCheckUpdate","plugin","install","world","wsl","checkWsl","c","sanitize","serverEvents","Logger"],"mappings":"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"}
1
+ {"version":3,"file":"terminal.js","sources":["../../src/cli/terminal.ts"],"sourcesContent":["import Logger from '@/logger';\nimport { OmeggaPlayer } from '@/plugin';\nimport { steamcmdCheckUpdate, steamcmdDownloadGame } from '@/updater';\nimport Omegga from '@omegga/server';\nimport { IOmeggaOptions } from '@omegga/types';\nimport { sanitize } from '@util/chat';\nimport { checkWsl } from '@util/wsl';\nimport { serverEvents } from '@webserver/backend/events';\nimport readline from 'readline';\nimport { install } from './plugin';\n\ndeclare global {\n interface String {\n brightRed: string;\n brightGreen: string;\n brightYellow: string;\n brightBlue: string;\n brightMagenta: string;\n brightCyan: string;\n brightWhite: string;\n }\n}\n\nlet log: (...args: any[]) => void,\n err: (...args: any[]) => void,\n warn: (...args: any[]) => void,\n info: (...args: any[]) => void;\n\ntype TerminalCommand = {\n aliases: string[];\n desc: string;\n descLines?: () => string[];\n fn(this: Terminal, ...args: string[]): void | Promise<void>;\n};\n\nfunction gameCommand(\n aliases: string[],\n desc: string,\n usage: string,\n command: string,\n quoteArgs?: boolean,\n): TerminalCommand {\n return {\n aliases,\n desc,\n fn(...args) {\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n if (!args.length) {\n err('usage:', ('/' + aliases[0] + ' ' + usage).yellow);\n return;\n }\n this.omegga.writeln(\n `${command} ${quoteArgs ? '\"' : ''}${args.join(' ')}${\n quoteArgs ? '\"' : ''\n }`,\n );\n this.rl.prompt(true);\n },\n };\n}\n\nconst COMMANDS: TerminalCommand[] = [\n // Brickadia commands\n gameCommand(\n ['kick'],\n 'kick a player',\n '<name|id> [reason]',\n 'Chat.Command /Kick',\n ),\n gameCommand(\n ['ban', 'banish'],\n 'ban a player',\n '<name|id> [time] [reason]',\n 'Ban',\n ),\n gameCommand(\n ['unban', 'pardon'],\n 'unban a player',\n '<name|id>',\n 'Chat.Command /Unban',\n ),\n gameCommand(\n ['grantrole', 'giverole'],\n 'grant a role to a player',\n '<role> <name|id>',\n 'Chat.Command /GrantRole',\n ),\n gameCommand(\n ['revokerole', 'takerole'],\n 'revoke a role from a player',\n '<role> <name|id>',\n 'Chat.Command /RevokeRole',\n ),\n gameCommand(\n ['clearbricks'],\n \"clear a player's bricks\",\n '<name|id>',\n 'Bricks.Clear',\n true,\n ),\n gameCommand(['clearallbricks'], 'clear all bricks', '', 'Bricks.ClearAll'),\n\n // Help command\n {\n aliases: ['help', 'cmds', 'commands'],\n desc: 'list supported commands and their descriptions',\n fn() {\n const maxLen = Math.max(...COMMANDS.map(c => c.aliases[0].length));\n log('Omegga Help Text:\\n');\n log(\n ' Console input not starting with / will be sent in chat from a \"SERVER\" user',\n );\n log(\n ' Console input starting with / will be treated as one of the following commands\\n',\n );\n log(\n '-- Available Omegga commands (type',\n '/command'.yellow.underline,\n 'to run)',\n );\n\n for (const command of COMMANDS.sort((a, b) =>\n a.aliases[0].localeCompare(b.aliases[0]),\n )) {\n const name = command.aliases[0];\n this.log(\n ' ',\n ('/' + name).yellow.underline,\n '-'.padStart(maxLen - name.length + 1),\n command.desc,\n );\n if (command.descLines)\n for (const descLine of command.descLines())\n this.log(' ', ' '.repeat(maxLen + 1), descLine);\n }\n },\n },\n\n // Server controls\n {\n aliases: ['stop', 'exit', 'quit', 'close'],\n desc: 'stop the server and close Omegga',\n async fn() {\n log('Stopping server...');\n await this.omegga.stop();\n process.exit();\n },\n },\n {\n aliases: ['restart'],\n desc: 'trigger a server restart (without announcement or saving)',\n async fn() {\n log('Restarting server...');\n await this.omegga.restartServer();\n },\n },\n {\n aliases: ['update'],\n desc: 'try to update the server and restart if started (without announcement or saving',\n async fn() {\n if (!this.omegga.config.__STEAM) {\n err(\n 'This command is only available when the server is installed via SteamCMD',\n );\n return;\n }\n if (this.omegga.stopping || this.omegga.starting) {\n err(\n 'The server is currently starting or stopping. Please wait a moment',\n );\n return;\n }\n const wasStarted = this.omegga.started;\n if (wasStarted) {\n log('Stopping server to update...');\n await this.omegga.stop();\n }\n log('Updating server...');\n try {\n await steamcmdDownloadGame({\n steambeta: this.omegga.config.server?.steambeta,\n steambetaPassword: this.omegga.config.server?.steambetaPassword,\n });\n log('Server updated successfully.');\n } catch (e) {\n err('Error updating server:', e);\n }\n if (wasStarted) {\n log('Starting server...');\n await this.omegga.start();\n }\n },\n },\n {\n aliases: ['updatecheck', 'uc', 'check'],\n desc: 'check for updates. /uc show for details, /uc on|off to toggle auto-update',\n async fn(subcommand?: string) {\n if (!this.omegga.config.__STEAM) {\n err(\n 'This command is only available when the server is installed via SteamCMD',\n );\n return;\n }\n\n const db = this.omegga.webserver?.database;\n\n if (subcommand === 'on' || subcommand === 'off') {\n if (!db) {\n err('Database not available.');\n return;\n }\n const config = await db.getAutoRestartConfig();\n config.autoUpdateEnabled = subcommand === 'on';\n await db.setAutoRestartConfig(config);\n log(\n subcommand === 'on'\n ? 'Auto-update enabled.'.green\n : 'Auto-update disabled.'.yellow,\n );\n return;\n }\n\n log('Checking for updates...');\n const result = steamcmdCheckUpdate(this.omegga.config.server?.steambeta);\n if (!result) {\n err('Failed to check for updates.');\n return;\n }\n const { local, remote, hasUpdate } = result;\n\n if (hasUpdate === true) log('Update available!'.green);\n else if (hasUpdate === false) log('Up to date.'.green);\n else log('Could not determine update status.'.yellow);\n\n if (subcommand === 'show') {\n const branch =\n local.betaKey && local.betaKey !== 'main' ? local.betaKey : 'public';\n const remoteBuild = remote?.branches?.[branch];\n const remotePart = remoteBuild\n ? `BuildID ${remoteBuild.buildid}` +\n (remoteBuild.timeupdated\n ? ` (${new Date(Number(remoteBuild.timeupdated) * 1000).toLocaleString()})`\n : '')\n : 'unknown (private branch?)';\n log(\n [\n ` Local: BuildID ${local.buildId} (${local.installState})`,\n ` Remote: ${remotePart}`,\n ` Branch: ${local.betaKey ?? 'public'}`,\n ].join('\\n'),\n );\n }\n\n if (db) {\n const config = await db.getAutoRestartConfig();\n log(\n config.autoUpdateEnabled\n ? `Auto-update: enabled (every ${config.autoUpdateIntervalMins}m)`\n : 'Auto-update: disabled',\n );\n }\n },\n },\n {\n aliases: ['kill'],\n desc: 'forcefully kill brickadia server process without closing omegga',\n async fn() {\n log('Stopping server...');\n await this.omegga.stop();\n },\n },\n {\n aliases: ['start'],\n desc: 'start the server if it is stopped',\n fn() {\n if (\n !this.omegga ||\n (this.omegga && (this.omegga.starting || this.omegga.started))\n ) {\n err('The server is already running');\n return;\n }\n\n log('Starting server...');\n this.omegga.start();\n },\n },\n {\n aliases: ['cmd'],\n desc: 'run a console command on the Brickadia server. requires /debug for log to show',\n fn(...args) {\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n this.omegga.writeln(args.join(' '));\n },\n },\n {\n aliases: ['debug'],\n desc: 'toggle visibility of brickadia console logs',\n fn() {\n this.options.debug = !this.options.debug;\n log(\n 'Brickadia logs now',\n this.options.debug ? 'visible'.green : 'hidden'.red,\n );\n },\n },\n {\n aliases: ['status'],\n desc: 'display server status information. brick count, online players, etc',\n async fn() {\n if (!this.omegga.started) {\n err('Omegga is not running');\n return;\n }\n const msToTime = (ms: number) => new Date(ms).toISOString().slice(11, 19);\n try {\n const status = await this.omegga.getServerStatus();\n\n log('Server Status');\n this.log(`\n${status.serverName.yellow}\nBricks: ${(status.bricks + '').yellow}\nComponents: ${(status.components + '').yellow}\nUptime: ${msToTime(status.time).yellow}\nPlayers: ${status.players.length === 0 ? 'none'.grey : ''}\n ${status.players\n .map(p => `[${msToTime(p.time).grey}] ${p.name.brightYellow}`)\n .join('\\n ')}\n`);\n } catch (e) {\n err('An error occurred while getting server status');\n }\n },\n },\n\n // plugin stuff\n {\n aliases: ['reload'],\n desc: 'shorthand for /plugins reload',\n async fn() {\n await COMMANDS.find(c => c.aliases.includes('plugins')).fn.bind(this)(\n 'reload',\n );\n },\n },\n {\n aliases: ['plugins', 'plugin', 'p'],\n desc: 'manage Omegga plugins',\n descLines: () => [\n '/plugins install <...plugin names>'.yellow + ' installs plugins',\n '/plugins load <...plugin names>'.yellow +\n ' loads plugins, and enables if they were disabled',\n '/plugins unload <...plugin names>'.yellow + ' unloads plugins',\n '/plugins reload [...plugin names]'.yellow +\n ' reloads all plugins, or those specified',\n '/plugins enable <...plugin names>'.yellow + ' enables plugins',\n '/plugins disable <...plugin names>'.yellow +\n ' disables plugins, and unloads if they were loaded',\n ],\n async fn(subcommand: string, ...args: string[]) {\n if (!this.omegga.pluginLoader) {\n err('Omegga is not using plugins');\n return;\n }\n\n if (!subcommand) {\n const plugins = this.omegga.pluginLoader.plugins;\n if (plugins.length === 0) {\n log('No plugins are installed');\n log(' Install some with', '/plugins install'.yellow.underline);\n } else {\n log('Installed plugins'.yellow.underline);\n for (const plugin of plugins) {\n const name = plugin.getName();\n const docs = plugin.getDocumentation();\n if (plugin.isLoaded()) log(name.cyan.underline + ' (loaded)');\n else if (plugin.isEnabled())\n log(name.green.underline + ' (enabled, not loaded)');\n else log(name.red.underline + ' (disabled)');\n\n log(' ' + docs.description);\n log((' Author: ' + docs.author).gray);\n }\n }\n } else if (subcommand === 'install') {\n // install plugins\n await install(args, { verbose: true });\n log();\n log(\n 'Use',\n '/plugins reload'.yellow,\n 'to reload all plugins and use the installed ones',\n );\n } else if (subcommand === 'load') {\n if (args.length === 0) {\n log('Please specify at least one plugin to load');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (plugin.isLoaded()) {\n warn(\n name.cyan,\n 'is already loaded, reload with',\n ('/plugins reload ' + name).yellow,\n );\n return;\n }\n\n if (!plugin.isEnabled()) {\n log('Enabling and loading', name.cyan.underline);\n plugin.setEnabled(true);\n await plugin.load();\n } else {\n log('Loading', name.cyan.underline);\n await plugin.load();\n }\n }\n } else if (subcommand === 'unload') {\n if (args.length === 0) {\n log('Please specify at least one plugin to unload');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (!plugin.isLoaded()) {\n warn(\n name.cyan,\n 'is already unloaded, load with',\n ('/plugins load ' + name).yellow,\n );\n return;\n }\n\n log('Unloading', name.cyan.underline);\n await plugin.unload();\n }\n } else if (subcommand === 'reload') {\n let plugins = (\n args.length === 0\n ? this.omegga.pluginLoader.plugins\n : this.omegga.pluginLoader.plugins.filter(p =>\n args\n .map(a => a.toLowerCase())\n .includes(p.getName().toLowerCase()),\n )\n ).filter(p => p.isEnabled());\n\n const loaded = plugins.filter(p => p.isLoaded());\n log('Unloading', loaded.length, 'plugins');\n for (const plugin of loaded) await plugin.unload();\n\n if (args.length === 0) {\n log('Scanning for new plugins');\n if (!(await this.omegga.pluginLoader.scan())) {\n err('Could not scan for plugins. All plugins are unloaded');\n return;\n }\n }\n\n plugins = (\n args.length === 0\n ? this.omegga.pluginLoader.plugins\n : this.omegga.pluginLoader.plugins.filter(p =>\n args\n .map(a => a.toLowerCase())\n .includes(p.getName().toLowerCase()),\n )\n ).filter(p => p.isEnabled());\n log('Loading', plugins.length, 'plugins');\n for (const plugin of plugins) await plugin.load();\n\n log('Reloaded', plugins.length, 'plugins');\n } else if (subcommand === 'enable') {\n if (args.length === 0) {\n log('Please specify at least one plugin to enable');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (plugin.isEnabled()) {\n warn(\n name.cyan,\n 'is already enabled, disable with',\n ('/plugins disable ' + name).yellow,\n );\n return;\n }\n\n log(\n 'Enabling',\n name.cyan.underline,\n '(load with',\n ('/plugins load ' + name).yellow + ')',\n );\n plugin.setEnabled(true);\n }\n } else if (subcommand === 'disable') {\n if (args.length === 0) {\n log('Please specify at least one plugin to disable');\n return;\n }\n\n for (const pluginName of args) {\n const plugin = this.omegga.pluginLoader.plugins.find(\n p => p.getName().toLowerCase() === pluginName.toLowerCase(),\n );\n\n if (!plugin) {\n warn('No plugin by the name', pluginName.cyan);\n continue;\n }\n\n const name = plugin.getName();\n if (!plugin.isEnabled()) {\n warn(\n name.cyan,\n 'is already disabled, enable with',\n ('/plugins enable ' + name).yellow,\n );\n return;\n }\n\n if (plugin.isLoaded()) {\n log('Unloading and disabling', name.cyan.underline);\n await plugin.unload();\n plugin.setEnabled(false);\n } else {\n log('Disabling', name.cyan.underline);\n plugin.setEnabled(false);\n }\n }\n }\n },\n },\n {\n aliases: ['world', 'worlds', 'w'],\n desc: 'Manage worlds on the server',\n descLines: () => ['Type /worlds load a world'],\n async fn(subcommand: string, ...args: string[]) {\n switch (subcommand) {\n default:\n const commands = [\n {\n command: '/worlds use [world]',\n desc: 'set or remove a world as the default world for startup',\n short: '/w u [world]',\n },\n {\n command: '/worlds load <world> [rev]',\n desc: 'load a world',\n short: '/w load <world> [rev]',\n },\n {\n command: '/worlds saveas <world>',\n desc: 'save the current world as a new world',\n short: '/w sa <world>',\n },\n {\n command: '/worlds save',\n desc: 'save the current world',\n short: '/w s',\n },\n {\n command: '/worlds revisions <world>',\n desc: 'list revisions of a world',\n short: '/w r <world>',\n },\n {\n command: '/worlds new',\n desc: 'create a new world',\n short: '/w new <name> [Plate|Space|Studio|Peaks]',\n },\n ];\n log(\n 'Default world is',\n this.omegga.getNextWorld()?.file?.yellow || 'none'.grey,\n );\n log('Available world commands:');\n for (const { command, desc, short } of commands) {\n this.log(' ', command.yellow, '-', desc, `(${short.yellow})`);\n }\n return;\n\n case 'use':\n case 'u':\n const world = args[0]?.replace(/\\.brdb$/, '');\n if (world && !this.omegga.worldExists(world)) {\n err(`World \"${world}\" does not exist`);\n return;\n }\n if (this.omegga.setActiveWorld(world || null)) {\n if (world) {\n log(\n `Default world set to ${world.yellow}. It will be loaded next startup.`,\n );\n } else {\n log(\n 'Default world cleared. No world will be loaded on next startup.',\n );\n }\n } else {\n err(`Failed to set default world to ${world.yellow}.`);\n }\n return;\n\n case 'list':\n case 'ls':\n // list worlds\n const worlds = this.omegga.getWorlds();\n if (worlds.length === 0) {\n log(\n 'No worlds found. Create one with',\n '/worlds saveas <world>'.yellow,\n );\n } else {\n log('Available worlds:');\n const prefix = this.omegga.worldPath + '/';\n for (const world of worlds) {\n this.log(\n ' ',\n world.replace(prefix, '').replace(/\\.brdb$/, '').yellow,\n );\n }\n }\n return;\n case 'load':\n case 'l':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // load a world\n if (args.length === 0) {\n err('Please specify a world to load');\n return;\n }\n const worldName = args[0].replace(/\\.brdb$/, '');\n if (!this.omegga.getWorldPath(worldName)) {\n err(`World \"${worldName}\" does not exist`);\n return;\n }\n let revision: number | undefined;\n if (args.length > 1) {\n revision = parseInt(args[1], 10);\n if (isNaN(revision) || revision < 1) {\n err(`Invalid revision number: ${args[1]}`);\n return;\n }\n }\n let loadRes: boolean = false;\n if (revision) {\n log(`Loading world ${worldName.yellow} at revision ${revision}...`);\n loadRes = await this.omegga.loadWorldRevision(worldName, revision);\n } else {\n log(`Loading world ${worldName.yellow}...`);\n loadRes = await this.omegga.loadWorld(worldName);\n }\n if (loadRes) {\n log(`World ${worldName.yellow} loaded successfully.`);\n } else {\n err(`Failed to load world ${worldName.yellow}`);\n }\n\n return;\n case 'saveas':\n case 'sa':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // save the current world as a new world\n if (args.length === 0) {\n err('Please specify a world to save as');\n return;\n }\n const saveWorldName = args[0].replace(/\\.brdb$/, '');\n if (this.omegga.getWorldPath(saveWorldName)) {\n err(`World \"${saveWorldName}\" already exists`);\n return;\n }\n log(`Saving current world as ${saveWorldName.yellow}...`);\n const saveAsRes = await this.omegga.saveWorldAs(saveWorldName);\n if (saveAsRes) {\n log(`Current world saved as ${saveWorldName.yellow} successfully.`);\n } else {\n err(`Failed to save current world as ${saveWorldName.yellow}`);\n }\n return;\n\n case 'save':\n case 's':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // save the current world\n log('Saving current world...');\n const res = await this.omegga.saveWorld();\n if (res) {\n log('Current world saved successfully.');\n } else {\n err('Failed to save the current world');\n }\n return;\n\n case 'revisions':\n case 'r':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // list revisions of a world\n if (args.length === 0) {\n err('Please specify a world to list revisions for');\n return;\n }\n const revisionsWorldName = args[0].replace(/\\.brdb$/, '');\n if (!this.omegga.getWorldPath(revisionsWorldName)) {\n err(`World \"${revisionsWorldName}\" does not exist`);\n return;\n }\n log(`Listing revisions for world ${revisionsWorldName.yellow}...`);\n try {\n const revisions =\n await this.omegga.getWorldRevisions(revisionsWorldName);\n if (revisions.length === 0) {\n log('No revisions found for world', revisionsWorldName.yellow);\n } else {\n log('Revisions for world', revisionsWorldName.yellow + ':');\n for (const rev of revisions) {\n this.log(\n ' ',\n `Revision ${rev.index.toString().yellow} - ${\n rev.date\n .toISOString()\n .replace('T', ' ')\n .replace('.000Z', '').blue\n }`,\n `(${rev.note})`.grey,\n );\n }\n }\n } catch (e) {\n err(\n 'An error occurred while getting world revisions:',\n e.toString(),\n );\n }\n return;\n\n case 'new':\n case 'n':\n if (!this.omegga.started) {\n err('The server is not running');\n return;\n }\n\n // create a new world\n if (args.length === 0) {\n err('Please specify a name for the new world');\n return;\n }\n const newWorldName = args[0].replace(/\\.brdb$/, '');\n if (this.omegga.getWorldPath(newWorldName)) {\n err(`World \"${newWorldName}\" already exists`);\n return;\n }\n const map = (args[1] ? args[1] : 'Plate') as\n | 'Plate'\n | 'Space'\n | 'Studio'\n | 'Peaks';\n if (!['Plate', 'Space', 'Studio', 'Peaks'].includes(map)) {\n err(`Invalid preset \"${map}\". Valid map are: Plate, Space, Studio`);\n return;\n }\n log(`Creating new world ${newWorldName.yellow}...`);\n this.omegga.createEmptyWorld(newWorldName, map);\n }\n },\n },\n {\n aliases: ['user'],\n desc: 'manage web UI users',\n descLines: () => [\n \"/user passwd <username> - Reset a user's password\",\n '/user resetmfa <username> - Clear TOTP and passkeys',\n ],\n async fn(subcommand?: string, username?: string) {\n const db = this.omegga.webserver?.database;\n if (!db) {\n err('Database not available.');\n return;\n }\n if (!subcommand || !username) {\n err('Usage: /user <passwd|resetmfa> <username>');\n return;\n }\n if (!(await db.userExists(username))) {\n err(`User \"${username}\" does not exist.`);\n return;\n }\n switch (subcommand) {\n case 'passwd': {\n const password = await new Promise<string>(resolve =>\n this.rl.question('New password: ', resolve),\n );\n if (!password) {\n err('Password cannot be empty.');\n return;\n }\n await db.userPasswd(username, password);\n log(`Password updated for \"${username.yellow}\".`);\n break;\n }\n case 'resetmfa': {\n await db.resetUserMfa(username);\n log(\n `MFA cleared for \"${username.yellow}\" (TOTP, passkeys, recovery codes).`,\n );\n break;\n }\n default:\n err(`Unknown subcommand \"${subcommand}\". Use passwd or resetmfa.`);\n }\n },\n },\n];\n\n// the terminal wraps omegga and displays console output and handles console input\nexport default class Terminal {\n options: IOmeggaOptions;\n omegga: Omegga;\n rl: readline.Interface;\n constructor(omegga: Omegga, options: IOmeggaOptions = {}) {\n this.options = options;\n this.omegga = omegga;\n\n // terminal interface\n this.rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true,\n });\n this.rl.setPrompt('> '.brightGreen);\n\n // shortand fns\n log = (...args) => this.log('>>'.green, ...args);\n err = (...args) => this.error('!>'.red, ...args);\n info = (...args) => this.log('?>'.blue, ...args);\n warn = (...args) => this.warn('W>'.yellow, ...args);\n\n // print log line if debug is enabled\n let launcherDone = false;\n let lastDoneMessage = '';\n omegga.on('line', l => {\n if (options.debug) this.log('::'.blue, l);\n else if (!launcherDone) {\n if (l.match(/Update Failed/)) {\n err(l);\n info(\n 'This might be resolved by deleting brickadia and reinstalling:',\n );\n info(' rm -rf ~/.local/share/brickadia-launcher'.grey);\n }\n if (l.startsWith('DOWNLOADING')) {\n // TODO: if someone is proactive enough in the future, you could turn these steps into a legitimate progress bar\n /* Here's some launcher log lines that might be helpful\n Grabbing manifest and checking game files...\n DOWNLOADING MANIFEST | Hold on...\n DOWNLOADING MANIFEST (100%) | Hold on...\n DOWNLOADING MANIFEST (100%) | 0 B/s - 13 KiB / 13 KiB (Unknown time remaining...)\n DOWNLOADING MANIFEST (100%) | Done!\n Checking launcher and downloading version info...\n Installing patches and booting...\n DOWNLOADING BRICKADIA (0%) | Done!\n DOWNLOADING BRICKADIA (100%) | 110 MiB/s - 584 MiB / 584 MiB (1 second remaining...)\n DOWNLOADING BRICKADIA (100%) | Nearly done...\n DOWNLOADING BRICKADIA (100%), INSTALLING BRICKADIA (86%) | Nearly done...\n */\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n process.stdout.write(l);\n // add a newline on \"Done!\" but prevent newlines on repeat done messages\n if (l.match(/Done!/) && l !== lastDoneMessage) {\n lastDoneMessage = l;\n process.stdout.write('\\n');\n }\n } else if (l.match(/Disabling core dumps./)) {\n launcherDone = true;\n }\n }\n\n // [2025.07.07-20.45.40:310][845]LogBRWorldManager: World successfully loaded.\n if (\n l.match(\n /^\\[[^\\]]+\\]\\[[^\\]]+\\]LogBRWorldManager: World successfully loaded\\.$/,\n )\n ) {\n info('World successfully loaded.');\n }\n });\n\n // print debug events regardless of debug status\n omegga.on('debug', l => this.log('?>'.magenta, l));\n\n // print chat events as players join/leave the server\n omegga.on('join', (p: OmeggaPlayer) =>\n this.log(\n `${p.displayName.underline} (${p.name.underline}) joined.`.brightBlue,\n ),\n );\n omegga.on('leave', (p: OmeggaPlayer) =>\n this.log(\n `${p.displayName.underline} (${p.name.underline}) left.`.brightBlue,\n ),\n );\n omegga.on('chat', (name, message) => {\n const player = omegga.getPlayer(name);\n this.log(\n `${(player.displayName ?? player.name ?? name).brightYellow.underline}: ${message}`,\n );\n });\n omegga.on('start', () => {\n const wsl = checkWsl();\n log(\n `Server has started${\n wsl === 1 ? ' in single thread mode due to WSL1' : ''\n }. Type ${'/help'.yellow} for more commands`,\n );\n if (!this.omegga.getNextWorld()) {\n info(\n 'No',\n 'default world'.yellow,\n 'is configured. Run',\n '/worlds use <world>'.yellow,\n 'to load one on startup.',\n );\n }\n\n // check if this is WSL2 and wsl2binds is installed\n if (\n wsl === 2 &&\n this.omegga.pluginLoader &&\n !this.omegga.pluginLoader.plugins.some(p => p.getName() === 'wsl2binds')\n ) {\n warn(\n '####'.yellow,\n `You are running ${'WSL2'.yellow} and you don't have the ${\n 'wsl2binds'.yellow\n } plugin.`,\n );\n warn(\n '####'.yellow,\n `The wsl2binds plugin works as a ${\n 'UDP proxy'.underline\n } between Windows and the WSL2 VM.`,\n );\n warn('####'.yellow, 'Install it by:');\n warn('####'.yellow, ' 1. Closing omegga with', '/stop'.green);\n warn(\n '####'.yellow,\n ' 2. Installing wsl2binds with',\n 'omegga install gh:Meshiest/wsl2binds'.green,\n );\n warn('####'.yellow, ' 3. Restarting', 'omegga'.green);\n }\n });\n omegga.on('mapchange', ({ map }) =>\n log('Map changed to', (map.charAt(0).toUpperCase() + map.slice(1)).green),\n );\n omegga.on('unauthorized', () => {\n err('Server failed authentication check');\n info('You can clear auth tokens with', 'omegga auth -gl'.green);\n info(\n 'This will require you to paste a new hosting token or sign-in again',\n );\n process.exit();\n });\n omegga.on('error', e => err('Server caught unhandled exception:\\n' + e));\n omegga.on('server:stopped', () =>\n log('Server has closed. Type', '/stop'.yellow, 'to close omegga'),\n );\n\n this.rl.on('line', this.handleLine.bind(this));\n // Gracefully shut down server on Ctrl+C and SIGTERM\n process.on('SIGINT', () => this.handleLine('/stop'));\n process.on('SIGTERM', () => this.handleLine('/stop'));\n }\n\n async handleLine(line: string) {\n if (line.startsWith('/')) {\n const [cmd, ...args] = line.slice(1).split(' ');\n const c = COMMANDS.find(c => c.aliases.includes(cmd));\n if (!c) {\n err(\n `unrecognized command /${cmd.underline}. Type /help for more info`\n .red,\n );\n } else {\n try {\n const res = c.fn.bind(this)(...args);\n if (res instanceof Promise) await res;\n } catch (e) {\n err('unhandled terminal error', e);\n }\n }\n } else if (line.trim().length > 0) {\n if (this.omegga.started) {\n // broadcast when the chat does not start with a command\n this.omegga.broadcast(\n `\"[<b><color=\\\\\"ff00ff\\\\\">SERVER</></>]: ${sanitize(line)}\"`,\n );\n process.stdout.clearLine(0);\n this.log(`[${'SERVER'.brightMagenta.underline}]: ${line}`);\n\n // if omegga is running a webserver - send this message in the chat log\n if (this.omegga.webserver) {\n const user = { name: 'SERVER', id: '', web: true, color: 'ff00ff' };\n // create database entry, send to web ui\n const chatLog = await this.omegga.webserver.database.addChatLog(\n 'msg',\n user,\n line,\n );\n serverEvents.emit('chat', chatLog);\n }\n } else {\n err(\n 'Server is not started yet. type'.red,\n '/help'.yellow,\n 'for more info'.red,\n );\n }\n }\n }\n\n // let readline render a log without interrupting user input\n log(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.log(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n\n // let readline render a debug log without interrupting user input\n debug(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.debug(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n\n // let readline render a warning log without interrupting user input\n warn(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.warn(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n\n // let readline render an error log without interrupting user input\n error(...args: any[]) {\n process.stdout.clearLine(0);\n process.stdout.cursorTo(0);\n console.error(...Logger.timestamped(args));\n this.rl.prompt(true);\n }\n}\n"],"names":["steamcmdDownloadGame","steamcmdCheckUpdate","plugin","install","world","wsl","checkWsl","c","sanitize","serverEvents","Logger"],"mappings":"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"}
@@ -29,13 +29,20 @@ const COMMANDS = {
29
29
  );
30
30
  const [tableHeader, ...tableLines] = statusLines.filter((l) => l[1].startsWith("* ")).map((l) => l[1].slice(1));
31
31
  const columnRegExp = tableHeader ? buildTableHeaderRegex(tableHeader) : null;
32
+ if (statusLines.length < 5) return null;
33
+ const serverName = statusLines[0][1].match(/^Server Name: (.*)$/);
34
+ const description = statusLines[1][1].match(/^Description: (.*)$/);
35
+ const bricks = statusLines[2][1].match(/^Bricks: (\d+)$/);
36
+ const components = statusLines[3][1].match(/^Components: (\d+)$/);
37
+ const uptime = statusLines[4][1].match(/^Time: (.*)$/);
38
+ if (!serverName || !description || !bricks || !components || !uptime)
39
+ return null;
32
40
  const status = {
33
- // easily extract certain values from the server status
34
- serverName: statusLines[0][1].match(/^Server Name: (.*)$/)[1],
35
- description: statusLines[1][1].match(/^Description: (.*)$/)[1],
36
- bricks: Number(statusLines[2][1].match(/^Bricks: (\d+)$/)[1]),
37
- components: Number(statusLines[3][1].match(/^Components: (\d+)$/)[1]),
38
- time: index.time.parseDuration(statusLines[4][1].match(/^Time: (.*)$/)[1]),
41
+ serverName: serverName[1],
42
+ description: description[1],
43
+ bricks: Number(bricks[1]),
44
+ components: Number(components[1]),
45
+ time: index.time.parseDuration(uptime[1]),
39
46
  // extract players using the generated table regex
40
47
  players: columnRegExp ? tableLines.map((l) => {
41
48
  const {
@@ -1 +1 @@
1
- {"version":3,"file":"commandInjector.js","sources":["../../src/omegga/commandInjector.ts"],"sourcesContent":["import _ from 'lodash';\nimport { color, time } from '@util';\nimport type LogWrangler from './logWrangler';\nimport {\n ILogMinigame,\n IMinigameList,\n IPlayerPositions,\n IServerStatus,\n} from './types';\nimport { InjectedCommands, OmeggaPlayer, OmeggaLike } from '@/plugin';\nimport Logger from '@/logger';\n\ntype Cast<X, Y> = X extends Y ? X : Y;\ntype ArrayElement<A> = A extends readonly (infer T)[] ? T : never;\ntype Extract<T, U> = T extends U ? T : never;\ntype FromEntriesDataField<T> = T extends PawnDataField<infer Key, any>[]\n ? {\n [K in Cast<Key, string>]: ReturnType<\n Extract<ArrayElement<T>, PawnDataField<K, any>>['valueTransform']\n >;\n }\n : { [key in string]: any };\n\nexport type PawnDataField<F, T = string> = {\n /**\n * The command to execute and listen to.\n */\n command: string;\n\n /**\n * The game object name, like `BP_FigureV2_C`.\n */\n object: string;\n\n /**\n * What object should be matched, like `/(?<pawn>BP_FigureV2_C_\\d+)/`.\n */\n objectMatcher: RegExp;\n\n /**\n * The field on the game object, like `bIsDead`.\n */\n objectField: string;\n\n /**\n * The regex that should match the value of the game object field.\n * If `valueTransform` is not specified, `value` becomes the first capture group.\n */\n valueMatcher: RegExp;\n\n /**\n * An optional closure to transform what `valueMatcher` matched.\n */\n valueTransform?: (match: RegExpMatchArray) => T;\n\n /**\n * The field on the final object.\n */\n field: F;\n};\n\nconst buildTableHeaderRegex = (header: string) => {\n const columns = header.match(/[^|]+/g);\n return new RegExp(\n columns // get all strings between the |'s'\n .map((line, i) => [\n line.slice(1, -1),\n line.length - (i === columns.length - 1 ? 1 : 2),\n ]) // calculate the lengths (and remove the spaces)\n .map(\n ([name, len]: [string, number]) =>\n ` (?<${name.trim().toLowerCase()}>.{${len}})( |$)`,\n ) // create a regex pattern to match strings of that length (and trim off whitespace at the end)\n .join('\\\\|'),\n ); // join the regexes with the |\n};\n// A list of commands that can be injected to things with the log wrangler\n/**\n * List of injected commands\n */\nconst COMMANDS: InjectedCommands = {\n /**\n * Get a server status object containing bricks, time, players, player ping, player roles, etc\n */\n async getServerStatus(): Promise<IServerStatus> {\n const statusLines = await (\n this as OmeggaLike\n ).watchLogChunk<RegExpMatchArray>(\n 'Server.Status',\n /^LogConsoleCommands: (.+)$/,\n {\n first: match => match[1].startsWith('Server Name:'),\n timeoutDelay: 1000,\n },\n );\n\n // the table lines all start with '* '\n // find all those lines, and remove the asterisk\n const [tableHeader, ...tableLines] = statusLines\n .filter(l => l[1].startsWith('* '))\n .map(l => l[1].slice(1));\n\n // use the size of each column to build a regex that matches each row\n const columnRegExp = tableHeader\n ? buildTableHeaderRegex(tableHeader)\n : null;\n\n const status = {\n // easily extract certain values from the server status\n serverName: statusLines[0][1].match(/^Server Name: (.*)$/)[1],\n description: statusLines[1][1].match(/^Description: (.*)$/)[1],\n bricks: Number(statusLines[2][1].match(/^Bricks: (\\d+)$/)[1]),\n components: Number(statusLines[3][1].match(/^Components: (\\d+)$/)[1]),\n time: time.parseDuration(statusLines[4][1].match(/^Time: (.*)$/)[1]),\n // extract players using the generated table regex\n players: columnRegExp\n ? tableLines.map(l => {\n // match the player row with the generated regex\n const {\n groups: { name, ping, time: online, roles, address, id },\n } = l.match(columnRegExp);\n // trim and parse the matched data\n return {\n name: name.trim(),\n ping: time.parseDuration(ping.trim()),\n time: time.parseDuration(online.trim()),\n roles: roles\n .trim()\n .split(', ')\n .filter(r => r.length > 0), // roles are split by ', '\n address: address.replace('(Owner)', '').trim(),\n id: id.trim(),\n };\n })\n : [],\n };\n\n return status;\n },\n\n /**\n * Get a list of minigames\n * @return Minigame List\n */\n async listMinigames(): Promise<IMinigameList> {\n const minigameLines = await (\n this as OmeggaLike\n ).watchLogChunk<RegExpMatchArray>(\n 'Server.Minigames.List',\n /^LogConsoleCommands: (.+)$/,\n {\n first: match => match[1].startsWith('Minigame Count:'),\n timeoutDelay: 1000,\n },\n );\n\n // the table lines all start with '* '\n // find all those lines, and remove the asterisk\n const [tableHeader, ...tableLines] = minigameLines\n .filter(l => l[1].startsWith('* '))\n .map(l => l[1].slice(1));\n\n // use the size of each column to build a regex that matches each row\n const columnRegExp = buildTableHeaderRegex(tableHeader);\n\n return tableLines.map(l => {\n // match the player row with the generated regex\n const {\n groups: { id, name, ownername, ownerid, member },\n } = l.match(columnRegExp);\n // trim and parse the matched data\n return {\n index: Number(id),\n name: name.trim(),\n numMembers: Number(member),\n owner: { name: ownername.trim(), id: ownerid },\n };\n });\n },\n\n // async getAllPawnData<T extends PawnDataField<string, any>[]>(\n // fields: T\n // ): Promise<{\n // controller: string;\n // data: FromEntriesDataField<[PawnDataField<'pawn', string>, ...T]>;\n // }> {\n // const restFields: [PawnDataField<'pawn', string>, ...T] = [\n // {\n // command: 'GetAll BP_PlayerController_C Pawn',\n // object: 'BP_PlayerController_C',\n // objectMatcher: /(?<controller>BP_PlayerController_C_\\d+)/,\n // objectField: 'Pawn',\n // valueMatcher:\n // /(?:None|BP_FigureV2_C'.+?:PersistentLevel.(?<pawn>BP_FigureV2_C_\\d+)')?$/,\n // valueTransform: matches => matches.groups.pawn ?? null,\n // field: 'pawn',\n // } as PawnDataField<'pawn', string>,\n // ...fields,\n // ];\n\n // return {\n // controller: 'test',\n // data: Object.fromEntries(\n // restFields.map(f => [\n // f.field,\n // f.valueTransform({ groups: { pawn: 'foo' } } as any),\n // ])\n // ) as FromEntriesDataField<[PawnDataField<'pawn', string>, ...T]>,\n // };\n // },\n\n /**\n * get every player's position and alive states\n */\n async getAllPlayerPositions(): Promise<IPlayerPositions> {\n const pawnRegExp =\n /(?<index>\\d+)\\) BP_PlayerController_C .+?PersistentLevel\\.(?<controller>BP_PlayerController_C_\\d+)\\.Pawn = .*?(?:None|BP_FigureV2_C'.+?:PersistentLevel.(?<pawn>BP_FigureV2_C_\\d+)')?$/;\n const posRegExp =\n /(?<index>\\d+)\\) CapsuleComponent .+?PersistentLevel\\.(?<pawn>BP_FigureV2_C_\\d+)\\.CollisionCylinder\\.RelativeLocation = \\(X=(?<x>[\\d.-]+),Y=(?<y>[\\d.-]+),Z=(?<z>[\\d.-]+)\\)$/;\n const deadFigureRegExp =\n /(?<index>\\d+)\\) BP_FigureV2_C .+?PersistentLevel\\.(?<pawn>BP_FigureV2_C_\\d+)\\.bIsDead = (?<dead>(True|False))$/;\n\n // wait for the pawn and position watchers to return all the results\n const [pawns, deadFigures, positions] = await Promise.all([\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_PlayerController_C Pawn',\n pawnRegExp,\n {\n first: 'index',\n timeoutDelay: 250,\n },\n ),\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_FigureV2_C bIsDead',\n deadFigureRegExp,\n {\n first: 'index',\n timeoutDelay: 250,\n },\n ),\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll SceneComponent RelativeLocation Name=CollisionCylinder',\n posRegExp,\n { first: 'index', timeoutDelay: 250 },\n ),\n ]);\n\n return (\n pawns\n // iterate through the pawn+controllers\n .map(pawn => ({\n // find the player for the associated controller\n player: this.getPlayer(pawn.groups.controller),\n // find the position for the associated pawn\n pos: positions.find(pos => pawn.groups.pawn === pos.groups.pawn),\n isDead: deadFigures.find(\n dead => pawn.groups.pawn === dead.groups.pawn,\n ),\n pawn,\n }))\n // filter by only those who have both player. previously we filtered by position but this breaks for players without a pawn, instead it's preferable to pass null\n .filter(p => p.player)\n // turn the position into a [x, y, z] number array (last 3 items in the array)\n .map(p => ({\n player: p.player,\n pawn: p.pawn.groups.pawn || null,\n pos: p.pos ? p.pos.slice(3).map(Number) : null,\n isDead: p.isDead ? p.isDead.groups.dead === 'True' : true,\n }))\n );\n },\n\n /**\n * get all minigames and their players (and the player's teams)\n */\n async getMinigames(): Promise<ILogMinigame[]> {\n // patterns to match the console logs\n const ruleNameRegExp =\n /^(?<index>\\d+)\\) BP_Ruleset_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.RulesetName = (?<name>.*)$/;\n const ruleMembersRegExp =\n /^(?<index>\\d+)\\) BP_Ruleset_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.MemberStates =$/;\n const teamNameRegExp =\n /^(?<index>\\d+)\\) BP_Team(_\\w+)?_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.(?<team>BP_Team(_\\w+)?_C_\\d+)\\.TeamName = (?<name>.*)$/;\n const teamColorRegExp =\n /^(?<index>\\d+)\\) BP_Team(_\\w+)?_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.(?<team>BP_Team(_\\w+)?_C_\\d+)\\.TeamColor = \\(B=(?<b>\\d+),G=(?<g>\\d+),R=(?<r>\\d+),A=(?<a>\\d+)\\)$/;\n const teamMembersRegExp =\n /^(?<index>\\d+)\\) BP_Team(_\\w+)?_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.(?<team>BP_Team(_\\w+)?_C_\\d+)\\.MemberStates =$/;\n const playerStateRegExp =\n /^\\t(?<index>\\d+): .*?BP_PlayerState_C'(.+):PersistentLevel\\.(?<state>BP_PlayerState_C_\\d+)'$/;\n\n try {\n // parse console output to get the minigame info\n const [rulesets, ruleMembers, teamMembers, teamNames, teamColors] =\n await Promise.all([\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_Ruleset_C RulesetName',\n ruleNameRegExp,\n {\n first: 'index',\n },\n ),\n this.watchLogArray<\n { index: string; ruleset: string },\n { index: string; state: string }\n >(\n 'GetAll BP_Ruleset_C MemberStates',\n ruleMembersRegExp,\n playerStateRegExp,\n ),\n this.watchLogArray<\n { index: string; ruleset: string; team: string },\n { index: string; state: string }\n >(\n 'GetAll BP_Team_C MemberStates',\n teamMembersRegExp,\n playerStateRegExp,\n ),\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_Team_C TeamName',\n teamNameRegExp,\n {\n first: 'index',\n },\n ),\n // team color in a5 is based on (B=255,G=255,R=255,A=255)\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_Team_C TeamColor',\n teamColorRegExp,\n {\n first: 'index',\n },\n ),\n ]);\n\n // figure out what to do with the matched color results\n const handleColor = (\n match:\n | { color: string }\n | { r: string; g: string; b: string; a: string },\n ) => {\n // color index, return the colorset color\n if ('color' in match)\n return color.DEFAULT_COLORSET[Number(match)].slice();\n else return [match.r, match.g, match.b, match.a].map(Number);\n };\n\n const sortedRulesets = rulesets.sort((a, b) =>\n b.groups?.ruleset.localeCompare(a.groups?.ruleset),\n );\n const globalIndex = rulesets.findIndex(\n ruleset => ruleset.groups?.name === 'GLOBAL',\n );\n\n const indexMap = Object.fromEntries(\n sortedRulesets.map((ruleset, index) => {\n if (globalIndex > -1) {\n if (index > globalIndex) {\n index = index - 1;\n } else if (index === globalIndex) {\n index = -1;\n }\n }\n\n return [ruleset.groups?.ruleset, index];\n }),\n );\n\n // join the data into a big object\n return rulesets.map(r => ({\n name: r.groups.name,\n ruleset: r.groups.ruleset,\n index: indexMap[r.groups.ruleset],\n\n // get the players from the team members\n members: ruleMembers\n .find(m => m.item.ruleset === r.groups.ruleset)\n .members // get the members from this ruleset\n .map(m => this.getPlayer(m.state))\n .filter(Boolean), // get the players\n\n // get the teams for this ruleset\n teams: teamMembers\n .filter(m => m.item.ruleset === r.groups.ruleset) // only get teams from this ruleset\n .map(m => ({\n // team name\n name: _.get(\n teamNames.find(t => t.groups.team === m.item.team),\n 'groups.name',\n ) as string,\n team: m.item.team,\n\n // get the colors (different for a4 and a5)\n color: handleColor(\n _.pick(\n teamColors.find(t => t.groups.team === m.item.team)?.groups ??\n ({\n r: 0,\n g: 0,\n b: 0,\n a: 0,\n } as any),\n ['r', 'g', 'b', 'a'],\n ),\n ),\n\n // get the players from the team\n members: m.members\n .map(m => this.getPlayer(m.state))\n .filter(Boolean),\n })),\n }));\n } catch (e) {\n Logger.error('error getting minigames', e);\n return undefined;\n }\n },\n};\n\n// inject the commands into the object given a log wrangler\nexport default <T extends InjectedCommands>(\n obj: T,\n logWrangler: LogWrangler,\n) => {\n for (const cmd in COMMANDS) {\n // disgusting type casting because we're injecting functions\n (obj as unknown as Record<string, Function>)[cmd] =\n COMMANDS[cmd as keyof typeof COMMANDS].bind(logWrangler);\n }\n};\n"],"names":["time","color","index","m","Logger"],"mappings":";;;;;AA6DA,MAAM,wBAAwB,CAAC,WAAmB;AAChD,QAAM,UAAU,OAAO,MAAM,QAAQ;AACrC,SAAO,IAAI;AAAA,IACT,QACG,IAAI,CAAC,MAAM,MAAM;AAAA,MAChB,KAAK,MAAM,GAAG,EAAE;AAAA,MAChB,KAAK,UAAU,MAAM,QAAQ,SAAS,IAAI,IAAI;AAAA,IAAA,CAC/C,EACA;AAAA,MACC,CAAC,CAAC,MAAM,GAAG,MACT,OAAO,KAAK,KAAA,EAAO,YAAA,CAAa,MAAM,GAAG;AAAA,IAAA,EAE5C,KAAK,KAAK;AAAA,EAAA;AAEjB;AAKA,MAAM,WAA6B;AAAA;AAAA;AAAA;AAAA,EAIjC,MAAM,kBAA0C;AAC9C,UAAM,cAAc,MAClB,KACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,CAAA,UAAS,MAAM,CAAC,EAAE,WAAW,cAAc;AAAA,QAClD,cAAc;AAAA,MAAA;AAAA,IAChB;AAKF,UAAM,CAAC,aAAa,GAAG,UAAU,IAAI,YAClC,OAAO,CAAA,MAAK,EAAE,CAAC,EAAE,WAAW,IAAI,CAAC,EACjC,IAAI,CAAA,MAAK,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;AAGzB,UAAM,eAAe,cACjB,sBAAsB,WAAW,IACjC;AAEJ,UAAM,SAAS;AAAA;AAAA,MAEb,YAAY,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,qBAAqB,EAAE,CAAC;AAAA,MAC5D,aAAa,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,qBAAqB,EAAE,CAAC;AAAA,MAC7D,QAAQ,OAAO,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,iBAAiB,EAAE,CAAC,CAAC;AAAA,MAC5D,YAAY,OAAO,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,qBAAqB,EAAE,CAAC,CAAC;AAAA,MACpE,MAAMA,MAAAA,KAAK,cAAc,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,cAAc,EAAE,CAAC,CAAC;AAAA;AAAA,MAEnE,SAAS,eACL,WAAW,IAAI,CAAA,MAAK;AAElB,cAAM;AAAA,UACJ,QAAQ,EAAE,MAAM,MAAM,MAAM,QAAQ,OAAO,SAAS,GAAA;AAAA,QAAG,IACrD,EAAE,MAAM,YAAY;AAExB,eAAO;AAAA,UACL,MAAM,KAAK,KAAA;AAAA,UACX,MAAMA,MAAAA,KAAK,cAAc,KAAK,MAAM;AAAA,UACpC,MAAMA,MAAAA,KAAK,cAAc,OAAO,MAAM;AAAA,UACtC,OAAO,MACJ,KAAA,EACA,MAAM,IAAI,EACV,OAAO,CAAA,MAAK,EAAE,SAAS,CAAC;AAAA;AAAA,UAC3B,SAAS,QAAQ,QAAQ,WAAW,EAAE,EAAE,KAAA;AAAA,UACxC,IAAI,GAAG,KAAA;AAAA,QAAK;AAAA,MAEhB,CAAC,IACD,CAAA;AAAA,IAAC;AAGP,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAwC;AAC5C,UAAM,gBAAgB,MACpB,KACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,CAAA,UAAS,MAAM,CAAC,EAAE,WAAW,iBAAiB;AAAA,QACrD,cAAc;AAAA,MAAA;AAAA,IAChB;AAKF,UAAM,CAAC,aAAa,GAAG,UAAU,IAAI,cAClC,OAAO,CAAA,MAAK,EAAE,CAAC,EAAE,WAAW,IAAI,CAAC,EACjC,IAAI,CAAA,MAAK,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;AAGzB,UAAM,eAAe,sBAAsB,WAAW;AAEtD,WAAO,WAAW,IAAI,CAAA,MAAK;AAEzB,YAAM;AAAA,QACJ,QAAQ,EAAE,IAAI,MAAM,WAAW,SAAS,OAAA;AAAA,MAAO,IAC7C,EAAE,MAAM,YAAY;AAExB,aAAO;AAAA,QACL,OAAO,OAAO,EAAE;AAAA,QAChB,MAAM,KAAK,KAAA;AAAA,QACX,YAAY,OAAO,MAAM;AAAA,QACzB,OAAO,EAAE,MAAM,UAAU,KAAA,GAAQ,IAAI,QAAA;AAAA,MAAQ;AAAA,IAEjD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoCA,MAAM,wBAAmD;AACvD,UAAM,aACJ;AACF,UAAM,YACJ;AACF,UAAM,mBACJ;AAGF,UAAM,CAAC,OAAO,aAAa,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,MACxD,KAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,UACE,OAAO;AAAA,UACP,cAAc;AAAA,QAAA;AAAA,MAChB;AAAA,MAEF,KAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,UACE,OAAO;AAAA,UACP,cAAc;AAAA,QAAA;AAAA,MAChB;AAAA,MAEF,KAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA,EAAE,OAAO,SAAS,cAAc,IAAA;AAAA,MAAI;AAAA,IACtC,CACD;AAED,WACE,MAEG,IAAI,CAAA,UAAS;AAAA;AAAA,MAEZ,QAAQ,KAAK,UAAU,KAAK,OAAO,UAAU;AAAA;AAAA,MAE7C,KAAK,UAAU,KAAK,CAAA,QAAO,KAAK,OAAO,SAAS,IAAI,OAAO,IAAI;AAAA,MAC/D,QAAQ,YAAY;AAAA,QAClB,CAAA,SAAQ,KAAK,OAAO,SAAS,KAAK,OAAO;AAAA,MAAA;AAAA,MAE3C;AAAA,IAAA,EACA,EAED,OAAO,CAAA,MAAK,EAAE,MAAM,EAEpB,IAAI,CAAA,OAAM;AAAA,MACT,QAAQ,EAAE;AAAA,MACV,MAAM,EAAE,KAAK,OAAO,QAAQ;AAAA,MAC5B,KAAK,EAAE,MAAM,EAAE,IAAI,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI;AAAA,MAC1C,QAAQ,EAAE,SAAS,EAAE,OAAO,OAAO,SAAS,SAAS;AAAA,IAAA,EACrD;AAAA,EAER;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAwC;AAE5C,UAAM,iBACJ;AACF,UAAM,oBACJ;AACF,UAAM,iBACJ;AACF,UAAM,kBACJ;AACF,UAAM,oBACJ;AACF,UAAM,oBACJ;AAEF,QAAI;AAEF,YAAM,CAAC,UAAU,aAAa,aAAa,WAAW,UAAU,IAC9D,MAAM,QAAQ,IAAI;AAAA,QAChB,KAAK;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,YACE,OAAO;AAAA,UAAA;AAAA,QACT;AAAA,QAEF,KAAK;AAAA,UAIH;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAEF,KAAK;AAAA,UAIH;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAEF,KAAK;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,YACE,OAAO;AAAA,UAAA;AAAA,QACT;AAAA;AAAA,QAGF,KAAK;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,YACE,OAAO;AAAA,UAAA;AAAA,QACT;AAAA,MACF,CACD;AAGH,YAAM,cAAc,CAClB,UAGG;AAEH,YAAI,WAAW;AACb,iBAAOC,MAAAA,MAAM,iBAAiB,OAAO,KAAK,CAAC,EAAE,MAAA;AAAA,YAC1C,QAAO,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM;AAAA,MAC7D;AAEA,YAAM,iBAAiB,SAAS;AAAA,QAAK,CAAC,GAAG,MACvC,EAAE,QAAQ,QAAQ,cAAc,EAAE,QAAQ,OAAO;AAAA,MAAA;AAEnD,YAAM,cAAc,SAAS;AAAA,QAC3B,CAAA,YAAW,QAAQ,QAAQ,SAAS;AAAA,MAAA;AAGtC,YAAM,WAAW,OAAO;AAAA,QACtB,eAAe,IAAI,CAAC,SAASC,WAAU;AACrC,cAAI,cAAc,IAAI;AACpB,gBAAIA,SAAQ,aAAa;AACvB,cAAAA,SAAQA,SAAQ;AAAA,YAClB,WAAWA,WAAU,aAAa;AAChC,cAAAA,SAAQ;AAAA,YACV;AAAA,UACF;AAEA,iBAAO,CAAC,QAAQ,QAAQ,SAASA,MAAK;AAAA,QACxC,CAAC;AAAA,MAAA;AAIH,aAAO,SAAS,IAAI,CAAA,OAAM;AAAA,QACxB,MAAM,EAAE,OAAO;AAAA,QACf,SAAS,EAAE,OAAO;AAAA,QAClB,OAAO,SAAS,EAAE,OAAO,OAAO;AAAA;AAAA,QAGhC,SAAS,YACN,KAAK,CAAA,MAAK,EAAE,KAAK,YAAY,EAAE,OAAO,OAAO,EAC7C,QACA,IAAI,OAAK,KAAK,UAAU,EAAE,KAAK,CAAC,EAChC,OAAO,OAAO;AAAA;AAAA;AAAA,QAGjB,OAAO,YACJ,OAAO,CAAA,MAAK,EAAE,KAAK,YAAY,EAAE,OAAO,OAAO,EAC/C,IAAI,CAAA,OAAM;AAAA;AAAA,UAET,MAAM,EAAE;AAAA,YACN,UAAU,KAAK,CAAA,MAAK,EAAE,OAAO,SAAS,EAAE,KAAK,IAAI;AAAA,YACjD;AAAA,UAAA;AAAA,UAEF,MAAM,EAAE,KAAK;AAAA;AAAA,UAGb,OAAO;AAAA,YACL,EAAE;AAAA,cACA,WAAW,KAAK,CAAA,MAAK,EAAE,OAAO,SAAS,EAAE,KAAK,IAAI,GAAG,UAClD;AAAA,gBACC,GAAG;AAAA,gBACH,GAAG;AAAA,gBACH,GAAG;AAAA,gBACH,GAAG;AAAA,cAAA;AAAA,cAEP,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,YAAA;AAAA,UACrB;AAAA;AAAA,UAIF,SAAS,EAAE,QACR,IAAI,CAAAC,OAAK,KAAK,UAAUA,GAAE,KAAK,CAAC,EAChC,OAAO,OAAO;AAAA,QAAA,EACjB;AAAA,MAAA,EACJ;AAAA,IACJ,SAAS,GAAG;AACVC,qBAAO,MAAM,2BAA2B,CAAC;AACzC,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGA,MAAA,kBAAe,CACb,KACA,gBACG;AACH,aAAW,OAAO,UAAU;AAEzB,QAA4C,GAAG,IAC9C,SAAS,GAA4B,EAAE,KAAK,WAAW;AAAA,EAC3D;AACF;;"}
1
+ {"version":3,"file":"commandInjector.js","sources":["../../src/omegga/commandInjector.ts"],"sourcesContent":["import _ from 'lodash';\nimport { color, time } from '@util';\nimport type LogWrangler from './logWrangler';\nimport {\n ILogMinigame,\n IMinigameList,\n IPlayerPositions,\n IServerStatus,\n} from './types';\nimport { InjectedCommands, OmeggaPlayer, OmeggaLike } from '@/plugin';\nimport Logger from '@/logger';\n\ntype Cast<X, Y> = X extends Y ? X : Y;\ntype ArrayElement<A> = A extends readonly (infer T)[] ? T : never;\ntype Extract<T, U> = T extends U ? T : never;\ntype FromEntriesDataField<T> = T extends PawnDataField<infer Key, any>[]\n ? {\n [K in Cast<Key, string>]: ReturnType<\n Extract<ArrayElement<T>, PawnDataField<K, any>>['valueTransform']\n >;\n }\n : { [key in string]: any };\n\nexport type PawnDataField<F, T = string> = {\n /**\n * The command to execute and listen to.\n */\n command: string;\n\n /**\n * The game object name, like `BP_FigureV2_C`.\n */\n object: string;\n\n /**\n * What object should be matched, like `/(?<pawn>BP_FigureV2_C_\\d+)/`.\n */\n objectMatcher: RegExp;\n\n /**\n * The field on the game object, like `bIsDead`.\n */\n objectField: string;\n\n /**\n * The regex that should match the value of the game object field.\n * If `valueTransform` is not specified, `value` becomes the first capture group.\n */\n valueMatcher: RegExp;\n\n /**\n * An optional closure to transform what `valueMatcher` matched.\n */\n valueTransform?: (match: RegExpMatchArray) => T;\n\n /**\n * The field on the final object.\n */\n field: F;\n};\n\nconst buildTableHeaderRegex = (header: string) => {\n const columns = header.match(/[^|]+/g);\n return new RegExp(\n columns // get all strings between the |'s'\n .map((line, i) => [\n line.slice(1, -1),\n line.length - (i === columns.length - 1 ? 1 : 2),\n ]) // calculate the lengths (and remove the spaces)\n .map(\n ([name, len]: [string, number]) =>\n ` (?<${name.trim().toLowerCase()}>.{${len}})( |$)`,\n ) // create a regex pattern to match strings of that length (and trim off whitespace at the end)\n .join('\\\\|'),\n ); // join the regexes with the |\n};\n// A list of commands that can be injected to things with the log wrangler\n/**\n * List of injected commands\n */\nconst COMMANDS: InjectedCommands = {\n /**\n * Get a server status object containing bricks, time, players, player ping, player roles, etc\n */\n async getServerStatus(): Promise<IServerStatus | null> {\n const statusLines = await (\n this as OmeggaLike\n ).watchLogChunk<RegExpMatchArray>(\n 'Server.Status',\n /^LogConsoleCommands: (.+)$/,\n {\n first: match => match[1].startsWith('Server Name:'),\n timeoutDelay: 1000,\n },\n );\n\n // the table lines all start with '* '\n // find all those lines, and remove the asterisk\n const [tableHeader, ...tableLines] = statusLines\n .filter(l => l[1].startsWith('* '))\n .map(l => l[1].slice(1));\n\n // use the size of each column to build a regex that matches each row\n const columnRegExp = tableHeader\n ? buildTableHeaderRegex(tableHeader)\n : null;\n\n if (statusLines.length < 5) return null;\n\n const serverName = statusLines[0][1].match(/^Server Name: (.*)$/);\n const description = statusLines[1][1].match(/^Description: (.*)$/);\n const bricks = statusLines[2][1].match(/^Bricks: (\\d+)$/);\n const components = statusLines[3][1].match(/^Components: (\\d+)$/);\n const uptime = statusLines[4][1].match(/^Time: (.*)$/);\n\n if (!serverName || !description || !bricks || !components || !uptime)\n return null;\n\n const status = {\n serverName: serverName[1],\n description: description[1],\n bricks: Number(bricks[1]),\n components: Number(components[1]),\n time: time.parseDuration(uptime[1]),\n // extract players using the generated table regex\n players: columnRegExp\n ? tableLines.map(l => {\n // match the player row with the generated regex\n const {\n groups: { name, ping, time: online, roles, address, id },\n } = l.match(columnRegExp);\n // trim and parse the matched data\n return {\n name: name.trim(),\n ping: time.parseDuration(ping.trim()),\n time: time.parseDuration(online.trim()),\n roles: roles\n .trim()\n .split(', ')\n .filter(r => r.length > 0), // roles are split by ', '\n address: address.replace('(Owner)', '').trim(),\n id: id.trim(),\n };\n })\n : [],\n };\n\n return status;\n },\n\n /**\n * Get a list of minigames\n * @return Minigame List\n */\n async listMinigames(): Promise<IMinigameList> {\n const minigameLines = await (\n this as OmeggaLike\n ).watchLogChunk<RegExpMatchArray>(\n 'Server.Minigames.List',\n /^LogConsoleCommands: (.+)$/,\n {\n first: match => match[1].startsWith('Minigame Count:'),\n timeoutDelay: 1000,\n },\n );\n\n // the table lines all start with '* '\n // find all those lines, and remove the asterisk\n const [tableHeader, ...tableLines] = minigameLines\n .filter(l => l[1].startsWith('* '))\n .map(l => l[1].slice(1));\n\n // use the size of each column to build a regex that matches each row\n const columnRegExp = buildTableHeaderRegex(tableHeader);\n\n return tableLines.map(l => {\n // match the player row with the generated regex\n const {\n groups: { id, name, ownername, ownerid, member },\n } = l.match(columnRegExp);\n // trim and parse the matched data\n return {\n index: Number(id),\n name: name.trim(),\n numMembers: Number(member),\n owner: { name: ownername.trim(), id: ownerid },\n };\n });\n },\n\n // async getAllPawnData<T extends PawnDataField<string, any>[]>(\n // fields: T\n // ): Promise<{\n // controller: string;\n // data: FromEntriesDataField<[PawnDataField<'pawn', string>, ...T]>;\n // }> {\n // const restFields: [PawnDataField<'pawn', string>, ...T] = [\n // {\n // command: 'GetAll BP_PlayerController_C Pawn',\n // object: 'BP_PlayerController_C',\n // objectMatcher: /(?<controller>BP_PlayerController_C_\\d+)/,\n // objectField: 'Pawn',\n // valueMatcher:\n // /(?:None|BP_FigureV2_C'.+?:PersistentLevel.(?<pawn>BP_FigureV2_C_\\d+)')?$/,\n // valueTransform: matches => matches.groups.pawn ?? null,\n // field: 'pawn',\n // } as PawnDataField<'pawn', string>,\n // ...fields,\n // ];\n\n // return {\n // controller: 'test',\n // data: Object.fromEntries(\n // restFields.map(f => [\n // f.field,\n // f.valueTransform({ groups: { pawn: 'foo' } } as any),\n // ])\n // ) as FromEntriesDataField<[PawnDataField<'pawn', string>, ...T]>,\n // };\n // },\n\n /**\n * get every player's position and alive states\n */\n async getAllPlayerPositions(): Promise<IPlayerPositions> {\n const pawnRegExp =\n /(?<index>\\d+)\\) BP_PlayerController_C .+?PersistentLevel\\.(?<controller>BP_PlayerController_C_\\d+)\\.Pawn = .*?(?:None|BP_FigureV2_C'.+?:PersistentLevel.(?<pawn>BP_FigureV2_C_\\d+)')?$/;\n const posRegExp =\n /(?<index>\\d+)\\) CapsuleComponent .+?PersistentLevel\\.(?<pawn>BP_FigureV2_C_\\d+)\\.CollisionCylinder\\.RelativeLocation = \\(X=(?<x>[\\d.-]+),Y=(?<y>[\\d.-]+),Z=(?<z>[\\d.-]+)\\)$/;\n const deadFigureRegExp =\n /(?<index>\\d+)\\) BP_FigureV2_C .+?PersistentLevel\\.(?<pawn>BP_FigureV2_C_\\d+)\\.bIsDead = (?<dead>(True|False))$/;\n\n // wait for the pawn and position watchers to return all the results\n const [pawns, deadFigures, positions] = await Promise.all([\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_PlayerController_C Pawn',\n pawnRegExp,\n {\n first: 'index',\n timeoutDelay: 250,\n },\n ),\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_FigureV2_C bIsDead',\n deadFigureRegExp,\n {\n first: 'index',\n timeoutDelay: 250,\n },\n ),\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll SceneComponent RelativeLocation Name=CollisionCylinder',\n posRegExp,\n { first: 'index', timeoutDelay: 250 },\n ),\n ]);\n\n return (\n pawns\n // iterate through the pawn+controllers\n .map(pawn => ({\n // find the player for the associated controller\n player: this.getPlayer(pawn.groups.controller),\n // find the position for the associated pawn\n pos: positions.find(pos => pawn.groups.pawn === pos.groups.pawn),\n isDead: deadFigures.find(\n dead => pawn.groups.pawn === dead.groups.pawn,\n ),\n pawn,\n }))\n // filter by only those who have both player. previously we filtered by position but this breaks for players without a pawn, instead it's preferable to pass null\n .filter(p => p.player)\n // turn the position into a [x, y, z] number array (last 3 items in the array)\n .map(p => ({\n player: p.player,\n pawn: p.pawn.groups.pawn || null,\n pos: p.pos ? p.pos.slice(3).map(Number) : null,\n isDead: p.isDead ? p.isDead.groups.dead === 'True' : true,\n }))\n );\n },\n\n /**\n * get all minigames and their players (and the player's teams)\n */\n async getMinigames(): Promise<ILogMinigame[]> {\n // patterns to match the console logs\n const ruleNameRegExp =\n /^(?<index>\\d+)\\) BP_Ruleset_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.RulesetName = (?<name>.*)$/;\n const ruleMembersRegExp =\n /^(?<index>\\d+)\\) BP_Ruleset_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.MemberStates =$/;\n const teamNameRegExp =\n /^(?<index>\\d+)\\) BP_Team(_\\w+)?_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.(?<team>BP_Team(_\\w+)?_C_\\d+)\\.TeamName = (?<name>.*)$/;\n const teamColorRegExp =\n /^(?<index>\\d+)\\) BP_Team(_\\w+)?_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.(?<team>BP_Team(_\\w+)?_C_\\d+)\\.TeamColor = \\(B=(?<b>\\d+),G=(?<g>\\d+),R=(?<r>\\d+),A=(?<a>\\d+)\\)$/;\n const teamMembersRegExp =\n /^(?<index>\\d+)\\) BP_Team(_\\w+)?_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\\d+)\\.(?<team>BP_Team(_\\w+)?_C_\\d+)\\.MemberStates =$/;\n const playerStateRegExp =\n /^\\t(?<index>\\d+): .*?BP_PlayerState_C'(.+):PersistentLevel\\.(?<state>BP_PlayerState_C_\\d+)'$/;\n\n try {\n // parse console output to get the minigame info\n const [rulesets, ruleMembers, teamMembers, teamNames, teamColors] =\n await Promise.all([\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_Ruleset_C RulesetName',\n ruleNameRegExp,\n {\n first: 'index',\n },\n ),\n this.watchLogArray<\n { index: string; ruleset: string },\n { index: string; state: string }\n >(\n 'GetAll BP_Ruleset_C MemberStates',\n ruleMembersRegExp,\n playerStateRegExp,\n ),\n this.watchLogArray<\n { index: string; ruleset: string; team: string },\n { index: string; state: string }\n >(\n 'GetAll BP_Team_C MemberStates',\n teamMembersRegExp,\n playerStateRegExp,\n ),\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_Team_C TeamName',\n teamNameRegExp,\n {\n first: 'index',\n },\n ),\n // team color in a5 is based on (B=255,G=255,R=255,A=255)\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_Team_C TeamColor',\n teamColorRegExp,\n {\n first: 'index',\n },\n ),\n ]);\n\n // figure out what to do with the matched color results\n const handleColor = (\n match:\n | { color: string }\n | { r: string; g: string; b: string; a: string },\n ) => {\n // color index, return the colorset color\n if ('color' in match)\n return color.DEFAULT_COLORSET[Number(match)].slice();\n else return [match.r, match.g, match.b, match.a].map(Number);\n };\n\n const sortedRulesets = rulesets.sort((a, b) =>\n b.groups?.ruleset.localeCompare(a.groups?.ruleset),\n );\n const globalIndex = rulesets.findIndex(\n ruleset => ruleset.groups?.name === 'GLOBAL',\n );\n\n const indexMap = Object.fromEntries(\n sortedRulesets.map((ruleset, index) => {\n if (globalIndex > -1) {\n if (index > globalIndex) {\n index = index - 1;\n } else if (index === globalIndex) {\n index = -1;\n }\n }\n\n return [ruleset.groups?.ruleset, index];\n }),\n );\n\n // join the data into a big object\n return rulesets.map(r => ({\n name: r.groups.name,\n ruleset: r.groups.ruleset,\n index: indexMap[r.groups.ruleset],\n\n // get the players from the team members\n members: ruleMembers\n .find(m => m.item.ruleset === r.groups.ruleset)\n .members // get the members from this ruleset\n .map(m => this.getPlayer(m.state))\n .filter(Boolean), // get the players\n\n // get the teams for this ruleset\n teams: teamMembers\n .filter(m => m.item.ruleset === r.groups.ruleset) // only get teams from this ruleset\n .map(m => ({\n // team name\n name: _.get(\n teamNames.find(t => t.groups.team === m.item.team),\n 'groups.name',\n ) as string,\n team: m.item.team,\n\n // get the colors (different for a4 and a5)\n color: handleColor(\n _.pick(\n teamColors.find(t => t.groups.team === m.item.team)?.groups ??\n ({\n r: 0,\n g: 0,\n b: 0,\n a: 0,\n } as any),\n ['r', 'g', 'b', 'a'],\n ),\n ),\n\n // get the players from the team\n members: m.members\n .map(m => this.getPlayer(m.state))\n .filter(Boolean),\n })),\n }));\n } catch (e) {\n Logger.error('error getting minigames', e);\n return undefined;\n }\n },\n};\n\n// inject the commands into the object given a log wrangler\nexport default <T extends InjectedCommands>(\n obj: T,\n logWrangler: LogWrangler,\n) => {\n for (const cmd in COMMANDS) {\n // disgusting type casting because we're injecting functions\n (obj as unknown as Record<string, Function>)[cmd] =\n COMMANDS[cmd as keyof typeof COMMANDS].bind(logWrangler);\n }\n};\n"],"names":["time","color","index","m","Logger"],"mappings":"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"}
@@ -39,11 +39,11 @@ class Player {
39
39
  #omegga;
40
40
  static getRoles(omegga, id) {
41
41
  const data = omegga.getRoleAssignments().savedPlayerRoles[id];
42
- return Object.freeze(data && data.roles ? data.roles : []);
42
+ return data && data.roles ? [...data.roles] : [];
43
43
  }
44
44
  static getPermissions(omegga, id) {
45
45
  const { roles, defaultRole } = omegga.getRoleSetup();
46
- if (omegga.host.id === id) {
46
+ if (omegga.host?.id === id) {
47
47
  return Object.freeze(
48
48
  Object.fromEntries(
49
49
  [].concat(