Separate chat messages with match dividers
This commit is contained in:
+42
-8
@@ -6,18 +6,52 @@ export interface ChatMessage {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type ChatEntry =
|
||||
| { type: 'message'; message: ChatMessage }
|
||||
| { type: 'boundary'; event: 'start' | 'end' | 'reset'; time: string | null };
|
||||
|
||||
function plainText(value: string): string {
|
||||
return value.replace(/\^[0-9]/g, '').replace(/[\x00-\x1f\x7f]/g, '').trim();
|
||||
}
|
||||
|
||||
/** Preserve log order: elapsed timestamps can reset between matches. */
|
||||
export function parseGameChat(lines: readonly string[]): ChatMessage[] {
|
||||
return lines.flatMap(line => {
|
||||
const match = /^\s*(\d+:[0-5]\d)\s+say;([^;]+);(\d+);([^;]*);(.*)$/.exec(line);
|
||||
if (!match) return [];
|
||||
const message = plainText(match[5]);
|
||||
if (!message) return [];
|
||||
return [{ time: match[1], guid: match[2], slot: Number(match[3]),
|
||||
player: plainText(match[4]) || 'Unknown player', message }];
|
||||
});
|
||||
return parseGameChatTimeline(lines).flatMap(entry => entry.type === 'message' ? [entry.message] : []);
|
||||
}
|
||||
|
||||
/** Keep match boundaries alongside messages, including when the log clock resets. */
|
||||
export function parseGameChatTimeline(lines: readonly string[]): ChatEntry[] {
|
||||
const entries: ChatEntry[] = [];
|
||||
let previousSeconds: number | null = null;
|
||||
let ended = false;
|
||||
|
||||
for (const line of lines) {
|
||||
const timestamp = /^\s*(\d+:[0-5]\d)\s+/.exec(line);
|
||||
const time = timestamp?.[1] ?? null;
|
||||
const seconds = time === null ? null : Number(time.split(':')[0]) * 60 + Number(time.split(':')[1]);
|
||||
const event = line.slice(timestamp?.[0].length ?? 0).trimStart();
|
||||
if (/^InitGame(?:\s|:|$)/.test(event)) {
|
||||
entries.push({ type: 'boundary', event: 'start', time });
|
||||
ended = false;
|
||||
} else if (/^(?:ExitLevel|ShutdownGame)(?:\s|:|$)/.test(event)) {
|
||||
// Engines may emit both end events for the same match.
|
||||
if (!ended) entries.push({ type: 'boundary', event: 'end', time });
|
||||
ended = true;
|
||||
} else if (seconds !== null && previousSeconds !== null && seconds < previousSeconds) {
|
||||
entries.push({ type: 'boundary', event: 'reset', time });
|
||||
ended = false;
|
||||
}
|
||||
// Untimed lifecycle events also clear the previous match's clock.
|
||||
if (seconds !== null || /^(?:InitGame|ExitLevel|ShutdownGame)(?:\s|:|$)/.test(event)) {
|
||||
previousSeconds = seconds;
|
||||
}
|
||||
|
||||
const match = /^\s*(\d+:[0-5]\d)\s+say;([^;]+);(\d+);([^;]*);(.*)$/.exec(line);
|
||||
if (!match) continue;
|
||||
const message = plainText(match[5]);
|
||||
if (!message) continue;
|
||||
entries.push({ type: 'message', message: { time: match[1], guid: match[2], slot: Number(match[3]),
|
||||
player: plainText(match[4]) || 'Unknown player', message } });
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user