omegga 1.5.1 → 1.5.2
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
CHANGED
|
@@ -28,7 +28,7 @@ const COMMANDS = {
|
|
|
28
28
|
}
|
|
29
29
|
);
|
|
30
30
|
const [tableHeader, ...tableLines] = statusLines.filter((l) => l[1].startsWith("* ")).map((l) => l[1].slice(1));
|
|
31
|
-
const columnRegExp = buildTableHeaderRegex(tableHeader);
|
|
31
|
+
const columnRegExp = tableHeader ? buildTableHeaderRegex(tableHeader) : null;
|
|
32
32
|
const status = {
|
|
33
33
|
// easily extract certain values from the server status
|
|
34
34
|
serverName: statusLines[0][1].match(/^Server Name: (.*)$/)[1],
|
|
@@ -37,7 +37,7 @@ const COMMANDS = {
|
|
|
37
37
|
components: Number(statusLines[3][1].match(/^Components: (\d+)$/)[1]),
|
|
38
38
|
time: index.time.parseDuration(statusLines[4][1].match(/^Time: (.*)$/)[1]),
|
|
39
39
|
// extract players using the generated table regex
|
|
40
|
-
players: tableLines.map((l) => {
|
|
40
|
+
players: columnRegExp ? tableLines.map((l) => {
|
|
41
41
|
const {
|
|
42
42
|
groups: { name, ping, time: online, roles, address, id }
|
|
43
43
|
} = l.match(columnRegExp);
|
|
@@ -50,7 +50,7 @@ const COMMANDS = {
|
|
|
50
50
|
address: address.replace("(Owner)", "").trim(),
|
|
51
51
|
id: id.trim()
|
|
52
52
|
};
|
|
53
|
-
})
|
|
53
|
+
}) : []
|
|
54
54
|
};
|
|
55
55
|
return status;
|
|
56
56
|
},
|
|
@@ -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 = buildTableHeaderRegex(tableHeader);\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: 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 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 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":"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"}
|