omegga 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/omegga/commandInjector.js +110 -18
- package/dist/omegga/commandInjector.js.map +1 -1
- package/dist/omegga/commands.js +666 -0
- package/dist/omegga/commands.js.map +1 -0
- package/dist/omegga/player.js +21 -10
- package/dist/omegga/player.js.map +1 -1
- package/dist/omegga/plugin/plugin_jsonrpc_stdio.js +38 -0
- package/dist/omegga/plugin/plugin_jsonrpc_stdio.js.map +1 -1
- package/dist/omegga/plugin/plugin_node_safe/proxyOmegga.js +41 -1
- package/dist/omegga/plugin/plugin_node_safe/proxyOmegga.js.map +1 -1
- package/dist/omegga/server.js +225 -38
- package/dist/omegga/server.js.map +1 -1
- package/dist/omegga/wrapper.js +4 -1
- package/dist/omegga/wrapper.js.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/updater/steam.js +42 -24
- package/dist/updater/steam.js.map +1 -1
- package/dist/webserver/backend/metrics.js +2 -1
- package/dist/webserver/backend/metrics.js.map +1 -1
- package/dist/webserver/backend/router/player.js +8 -4
- package/dist/webserver/backend/router/player.js.map +1 -1
- package/dist/webserver/backend/router/role.js +1 -1
- package/dist/webserver/backend/router/role.js.map +1 -1
- package/dist/worker.js +29 -2
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/public/app.html +12 -3
- package/public/assets/{app-BiBsxujC.js → app-CHjgCOH4.js} +2 -2
- package/public/assets/app-CHjgCOH4.js.map +1 -0
- package/public/auth.html +1 -1
- package/templates/safe-ts/omegga.d.ts +483 -7
- package/.claude/settings.local.json +0 -51
- package/public/assets/app-BiBsxujC.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
- Future features go here
|
|
6
6
|
|
|
7
|
+
## 1.8.1 - 2026-07-03
|
|
8
|
+
|
|
9
|
+
Prefab support for EA3, replacing the removed `.brs` bricks commands.
|
|
10
|
+
|
|
11
|
+
- Plugins: Add prefab methods (`loadPrefab`, `savePrefab`, `savePrefabAsync`, `givePrefabToPlayer`, `loadPrefabOnPlayer`, `getPrefabs`)
|
|
12
|
+
- Plugins: `clearRegion`/`clearAllBricks` route to the new world clear commands on EA3 and can optionally clear entities
|
|
13
|
+
- Plugins: Deprecate the `.brs` bricks methods removed by the game (`saveBricks`, `loadBricks`, `getSaveData`, `loadBricksOnPlayer`, etc.) - they warn and no-op
|
|
14
|
+
- Commands: Warn when a plugin writes a console command the running game version has removed
|
|
15
|
+
|
|
16
|
+
## 1.8.0 - 2026-06-27
|
|
17
|
+
|
|
18
|
+
Huge breaking changes on in console commands for EA3...
|
|
19
|
+
|
|
20
|
+
- Plugins: Add fetch/crypto/atob/etc builtins to safe plugins
|
|
21
|
+
- Plugins: Automatically use new console command names when old ones are attempted to write to console
|
|
22
|
+
- Add auto retry to steamcmd updater
|
|
23
|
+
- Start tracking console commands by name in `Omegga.Console....` like `Omegga.Console.Server.Players.GiveItem`
|
|
24
|
+
- Deprecate minigame functions for EA3, add a getGamemode function for EA3
|
|
25
|
+
|
|
7
26
|
## 1.7.0 - 2026-06-14
|
|
8
27
|
|
|
9
28
|
- Database: Replace NeDB with Drizzle ORM + better-sqlite3 for all database storage
|
|
@@ -14,13 +14,15 @@ const buildTableHeaderRegex = (header) => {
|
|
|
14
14
|
).join("\\|")
|
|
15
15
|
);
|
|
16
16
|
};
|
|
17
|
+
const GAMEMODE_MODEL_CL = 14e3;
|
|
17
18
|
const COMMANDS = {
|
|
18
19
|
/**
|
|
19
20
|
* Get a server status object containing bricks, time, players, player ping, player roles, etc
|
|
20
21
|
*/
|
|
21
22
|
async getServerStatus() {
|
|
23
|
+
const { omegga } = this;
|
|
22
24
|
const statusLines = await this.watchLogChunk(
|
|
23
|
-
|
|
25
|
+
omegga.Console.Server.Status,
|
|
24
26
|
/^LogConsoleCommands: (.+)$/,
|
|
25
27
|
{
|
|
26
28
|
first: (match) => match[1].startsWith("Server Name:"),
|
|
@@ -63,11 +65,26 @@ const COMMANDS = {
|
|
|
63
65
|
},
|
|
64
66
|
/**
|
|
65
67
|
* Get a list of minigames
|
|
68
|
+
* @deprecated minigames were replaced by a single gamemode (~CL14000); on
|
|
69
|
+
* modern servers this returns at most one entry with an empty `owner`.
|
|
70
|
+
* Prefer {@link getGamemode}.
|
|
66
71
|
* @return Minigame List
|
|
67
72
|
*/
|
|
68
73
|
async listMinigames() {
|
|
74
|
+
const w = this;
|
|
75
|
+
if (w.omegga.version >= GAMEMODE_MODEL_CL) {
|
|
76
|
+
const gamemode = await w.omegga.getGamemode();
|
|
77
|
+
return gamemode ? [
|
|
78
|
+
{
|
|
79
|
+
index: 0,
|
|
80
|
+
name: gamemode.name,
|
|
81
|
+
numMembers: gamemode.members.length,
|
|
82
|
+
owner: { name: "", id: "" }
|
|
83
|
+
}
|
|
84
|
+
] : [];
|
|
85
|
+
}
|
|
69
86
|
const minigameLines = await this.watchLogChunk(
|
|
70
|
-
|
|
87
|
+
w.omegga.Console.Server.Minigames.List,
|
|
71
88
|
/^LogConsoleCommands: (.+)$/,
|
|
72
89
|
{
|
|
73
90
|
first: (match) => match[1].startsWith("Minigame Count:"),
|
|
@@ -164,9 +181,27 @@ const COMMANDS = {
|
|
|
164
181
|
}));
|
|
165
182
|
},
|
|
166
183
|
/**
|
|
167
|
-
* get all minigames and their players (and the player's teams)
|
|
184
|
+
* get all minigames and their players (and the player's teams).
|
|
185
|
+
*
|
|
186
|
+
* On servers older than CL14000 this returns one entry per `BP_Ruleset_C`
|
|
187
|
+
* minigame. On modern servers minigames were replaced by a single gamemode,
|
|
188
|
+
* so this returns a single-element array (the gamemode and its teams) for
|
|
189
|
+
* backwards compatibility - prefer {@link getGamemode}.
|
|
168
190
|
*/
|
|
169
191
|
async getMinigames() {
|
|
192
|
+
const w = this;
|
|
193
|
+
if (w.omegga.version >= GAMEMODE_MODEL_CL) {
|
|
194
|
+
const gamemode = await w.omegga.getGamemode();
|
|
195
|
+
return gamemode ? [
|
|
196
|
+
{
|
|
197
|
+
name: gamemode.name,
|
|
198
|
+
ruleset: gamemode.gamestate,
|
|
199
|
+
index: 0,
|
|
200
|
+
members: gamemode.members,
|
|
201
|
+
teams: gamemode.teams
|
|
202
|
+
}
|
|
203
|
+
] : [];
|
|
204
|
+
}
|
|
170
205
|
const ruleNameRegExp = /^(?<index>\d+)\) BP_Ruleset_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\d+)\.RulesetName = (?<name>.*)$/;
|
|
171
206
|
const ruleMembersRegExp = /^(?<index>\d+)\) BP_Ruleset_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\d+)\.MemberStates =$/;
|
|
172
207
|
const teamNameRegExp = /^(?<index>\d+)\) BP_Team(_\w+)?_C (.+):PersistentLevel.(?<ruleset>BP_Ruleset_C_\d+)\.(?<team>BP_Team(_\w+)?_C_\d+)\.TeamName = (?<name>.*)$/;
|
|
@@ -178,9 +213,7 @@ const COMMANDS = {
|
|
|
178
213
|
this.watchLogChunk(
|
|
179
214
|
"GetAll BP_Ruleset_C RulesetName",
|
|
180
215
|
ruleNameRegExp,
|
|
181
|
-
{
|
|
182
|
-
first: "index"
|
|
183
|
-
}
|
|
216
|
+
{ first: "index" }
|
|
184
217
|
),
|
|
185
218
|
this.watchLogArray(
|
|
186
219
|
"GetAll BP_Ruleset_C MemberStates",
|
|
@@ -195,17 +228,13 @@ const COMMANDS = {
|
|
|
195
228
|
this.watchLogChunk(
|
|
196
229
|
"GetAll BP_Team_C TeamName",
|
|
197
230
|
teamNameRegExp,
|
|
198
|
-
{
|
|
199
|
-
first: "index"
|
|
200
|
-
}
|
|
231
|
+
{ first: "index" }
|
|
201
232
|
),
|
|
202
233
|
// team color in a5 is based on (B=255,G=255,R=255,A=255)
|
|
203
234
|
this.watchLogChunk(
|
|
204
235
|
"GetAll BP_Team_C TeamColor",
|
|
205
236
|
teamColorRegExp,
|
|
206
|
-
{
|
|
207
|
-
first: "index"
|
|
208
|
-
}
|
|
237
|
+
{ first: "index" }
|
|
209
238
|
)
|
|
210
239
|
]);
|
|
211
240
|
const handleColor = (match) => {
|
|
@@ -249,12 +278,7 @@ const COMMANDS = {
|
|
|
249
278
|
// get the colors (different for a4 and a5)
|
|
250
279
|
color: handleColor(
|
|
251
280
|
_.pick(
|
|
252
|
-
teamColors.find((t) => t.groups.team === m.item.team)?.groups ?? {
|
|
253
|
-
r: 0,
|
|
254
|
-
g: 0,
|
|
255
|
-
b: 0,
|
|
256
|
-
a: 0
|
|
257
|
-
},
|
|
281
|
+
teamColors.find((t) => t.groups.team === m.item.team)?.groups ?? { r: 0, g: 0, b: 0, a: 0 },
|
|
258
282
|
["r", "g", "b", "a"]
|
|
259
283
|
)
|
|
260
284
|
),
|
|
@@ -266,6 +290,74 @@ const COMMANDS = {
|
|
|
266
290
|
logger.default.error("error getting minigames", e);
|
|
267
291
|
return void 0;
|
|
268
292
|
}
|
|
293
|
+
},
|
|
294
|
+
/**
|
|
295
|
+
* get the single gamemode and its teams/players (modern servers, >=CL14000).
|
|
296
|
+
* Returns null on older servers (which instead have multiple minigames -
|
|
297
|
+
* use {@link getMinigames}).
|
|
298
|
+
*/
|
|
299
|
+
async getGamemode() {
|
|
300
|
+
const w = this;
|
|
301
|
+
if (w.omegga.version < GAMEMODE_MODEL_CL) return null;
|
|
302
|
+
const gameModeNameRegExp = /^(?<index>\d+)\) BP_GameStateBase_C (.+):PersistentLevel\.(?<gamestate>BP_GameStateBase_C_\d+)\.GameModeName = (?<name>.*)$/;
|
|
303
|
+
const teamNameRegExp = /^(?<index>\d+)\) BRGameModeTeam (.+):PersistentLevel\.(?<team>BRGameModeTeam_\d+)\.TeamName = (?<name>.*)$/;
|
|
304
|
+
const teamColorRegExp = /^(?<index>\d+)\) BRGameModeTeam (.+):PersistentLevel\.(?<team>BRGameModeTeam_\d+)\.TeamColor = \(B=(?<b>\d+),G=(?<g>\d+),R=(?<r>\d+),A=(?<a>\d+)\)$/;
|
|
305
|
+
const teamMembersRegExp = /^(?<index>\d+)\) BRGameModeTeam (.+):PersistentLevel\.(?<team>BRGameModeTeam_\d+)\.MemberStates =$/;
|
|
306
|
+
const playerStateRegExp = /^\t(?<index>\d+): .*?BP_PlayerState_C'(.+):PersistentLevel\.(?<state>BP_PlayerState_C_\d+)'$/;
|
|
307
|
+
try {
|
|
308
|
+
const [gamemodes, teamNames, teamColors, teamMembers] = await Promise.all(
|
|
309
|
+
[
|
|
310
|
+
this.watchLogChunk(
|
|
311
|
+
"GetAll BP_GameStateBase_C GameModeName",
|
|
312
|
+
gameModeNameRegExp,
|
|
313
|
+
{ first: "index", timeoutDelay: 250 }
|
|
314
|
+
),
|
|
315
|
+
this.watchLogChunk(
|
|
316
|
+
"GetAll BRGameModeTeam TeamName",
|
|
317
|
+
teamNameRegExp,
|
|
318
|
+
{ first: "index", timeoutDelay: 250 }
|
|
319
|
+
),
|
|
320
|
+
// team color is (B=255,G=255,R=255,A=255)
|
|
321
|
+
this.watchLogChunk(
|
|
322
|
+
"GetAll BRGameModeTeam TeamColor",
|
|
323
|
+
teamColorRegExp,
|
|
324
|
+
{ first: "index", timeoutDelay: 250 }
|
|
325
|
+
),
|
|
326
|
+
this.watchLogArray(
|
|
327
|
+
"GetAll BRGameModeTeam MemberStates",
|
|
328
|
+
teamMembersRegExp,
|
|
329
|
+
playerStateRegExp
|
|
330
|
+
)
|
|
331
|
+
]
|
|
332
|
+
);
|
|
333
|
+
const gamemode = gamemodes[0]?.groups;
|
|
334
|
+
if (!gamemode) return null;
|
|
335
|
+
const teams = teamNames.map((t) => {
|
|
336
|
+
const groups = t.groups;
|
|
337
|
+
const id = groups.team;
|
|
338
|
+
const colorGroups = teamColors.find((c) => c.groups?.team === id)?.groups;
|
|
339
|
+
return {
|
|
340
|
+
name: groups.name,
|
|
341
|
+
team: id,
|
|
342
|
+
// [r, g, b, a]
|
|
343
|
+
color: colorGroups ? [colorGroups.r, colorGroups.g, colorGroups.b, colorGroups.a].map(
|
|
344
|
+
Number
|
|
345
|
+
) : [0, 0, 0, 0],
|
|
346
|
+
// resolve the players on this team from their player states
|
|
347
|
+
members: (teamMembers.find((m) => m.item.team === id)?.members ?? []).map((m) => this.getPlayer(m.state)).filter((p) => Boolean(p))
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
return {
|
|
351
|
+
name: gamemode.name,
|
|
352
|
+
gamestate: gamemode.gamestate,
|
|
353
|
+
// the gamemode's members are every player across all teams
|
|
354
|
+
members: teams.flatMap((t) => t.members),
|
|
355
|
+
teams
|
|
356
|
+
};
|
|
357
|
+
} catch (e) {
|
|
358
|
+
logger.default.error("error getting gamemode", e);
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
269
361
|
}
|
|
270
362
|
};
|
|
271
363
|
const commandInjector = (obj, logWrangler) => {
|
|
@@ -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 | 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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"}
|
|
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 IGamemode,\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/**\n * CL at which the gamemode/team object model (`BP_GameStateBase_C` +\n * `BRGameModeTeam`) replaced the old `BP_Ruleset_C`/`BP_Team_C` minigame model\n * (same CL the `Server.Minigames.*` console commands were deprecated).\n */\nconst GAMEMODE_MODEL_CL = 14000;\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 { omegga } = this as unknown as LogWrangler;\n const statusLines = await (\n this as OmeggaLike\n ).watchLogChunk<RegExpMatchArray>(\n omegga.Console.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 * @deprecated minigames were replaced by a single gamemode (~CL14000); on\n * modern servers this returns at most one entry with an empty `owner`.\n * Prefer {@link getGamemode}.\n * @return Minigame List\n */\n async listMinigames(): Promise<IMinigameList> {\n const w = this as unknown as LogWrangler;\n\n // Modern servers (>=CL14000): `Server.Minigames.List` was removed when\n // gamemodes replaced minigames. Derive a single entry from the gamemode;\n // there is no per-minigame `owner` anymore.\n if (w.omegga.version >= GAMEMODE_MODEL_CL) {\n const gamemode = await w.omegga.getGamemode();\n return gamemode\n ? [\n {\n index: 0,\n name: gamemode.name,\n numMembers: gamemode.members.length,\n owner: { name: '', id: '' },\n },\n ]\n : [];\n }\n\n // Legacy: the `Server.Minigames.List` console command + table parsing.\n const minigameLines = await (\n this as OmeggaLike\n ).watchLogChunk<RegExpMatchArray>(\n w.omegga.Console.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 * On servers older than CL14000 this returns one entry per `BP_Ruleset_C`\n * minigame. On modern servers minigames were replaced by a single gamemode,\n * so this returns a single-element array (the gamemode and its teams) for\n * backwards compatibility - prefer {@link getGamemode}.\n */\n async getMinigames(): Promise<ILogMinigame[]> {\n const w = this as unknown as LogWrangler;\n\n // modern servers (>=CL14000) have a single gamemode in place of minigames\n if (w.omegga.version >= GAMEMODE_MODEL_CL) {\n const gamemode = await w.omegga.getGamemode();\n return gamemode\n ? [\n {\n name: gamemode.name,\n ruleset: gamemode.gamestate,\n index: 0,\n members: gamemode.members,\n teams: gamemode.teams,\n },\n ]\n : [];\n }\n\n // legacy (<CL14000): one entry per BP_Ruleset_C minigame\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 { first: 'index' },\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 { first: 'index' },\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 { first: 'index' },\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 ({ r: 0, g: 0, b: 0, a: 0 } 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 * get the single gamemode and its teams/players (modern servers, >=CL14000).\n * Returns null on older servers (which instead have multiple minigames -\n * use {@link getMinigames}).\n */\n async getGamemode(): Promise<IGamemode | null> {\n const w = this as unknown as LogWrangler;\n // the single-gamemode model only exists on modern servers\n if (w.omegga.version < GAMEMODE_MODEL_CL) return null;\n\n // patterns to match the console logs\n const gameModeNameRegExp =\n /^(?<index>\\d+)\\) BP_GameStateBase_C (.+):PersistentLevel\\.(?<gamestate>BP_GameStateBase_C_\\d+)\\.GameModeName = (?<name>.*)$/;\n const teamNameRegExp =\n /^(?<index>\\d+)\\) BRGameModeTeam (.+):PersistentLevel\\.(?<team>BRGameModeTeam_\\d+)\\.TeamName = (?<name>.*)$/;\n const teamColorRegExp =\n /^(?<index>\\d+)\\) BRGameModeTeam (.+):PersistentLevel\\.(?<team>BRGameModeTeam_\\d+)\\.TeamColor = \\(B=(?<b>\\d+),G=(?<g>\\d+),R=(?<r>\\d+),A=(?<a>\\d+)\\)$/;\n const teamMembersRegExp =\n /^(?<index>\\d+)\\) BRGameModeTeam (.+):PersistentLevel\\.(?<team>BRGameModeTeam_\\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 gamemode and team info\n const [gamemodes, teamNames, teamColors, teamMembers] = await Promise.all(\n [\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BP_GameStateBase_C GameModeName',\n gameModeNameRegExp,\n { first: 'index', timeoutDelay: 250 },\n ),\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BRGameModeTeam TeamName',\n teamNameRegExp,\n { first: 'index', timeoutDelay: 250 },\n ),\n // team color is (B=255,G=255,R=255,A=255)\n this.watchLogChunk<RegExpMatchArray>(\n 'GetAll BRGameModeTeam TeamColor',\n teamColorRegExp,\n { first: 'index', timeoutDelay: 250 },\n ),\n this.watchLogArray<\n { index: string; team: string },\n { index: string; state: string }\n >(\n 'GetAll BRGameModeTeam MemberStates',\n teamMembersRegExp,\n playerStateRegExp,\n ),\n ],\n );\n\n // there is a single gamemode (one BP_GameStateBase_C)\n const gamemode = gamemodes[0]?.groups;\n if (!gamemode) return null;\n\n // build the teams from the name/color/member lookups\n const teams = teamNames.map(t => {\n const groups = t.groups!;\n const id = groups.team;\n const colorGroups = teamColors.find(c => c.groups?.team === id)?.groups;\n return {\n name: groups.name,\n team: id,\n // [r, g, b, a]\n color: colorGroups\n ? [colorGroups.r, colorGroups.g, colorGroups.b, colorGroups.a].map(\n Number,\n )\n : [0, 0, 0, 0],\n // resolve the players on this team from their player states\n members: (teamMembers.find(m => m.item.team === id)?.members ?? [])\n .map(m => this.getPlayer(m.state))\n .filter((p): p is OmeggaPlayer => Boolean(p)),\n };\n });\n\n return {\n name: gamemode.name,\n gamestate: gamemode.gamestate,\n // the gamemode's members are every player across all teams\n members: teams.flatMap(t => t.members),\n teams,\n };\n } catch (e) {\n Logger.error('error getting gamemode', e);\n return null;\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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"}
|