63 lines
3.7 KiB
TypeScript
63 lines
3.7 KiB
TypeScript
import { composeServerCfg, parseCfg } from './config-compose.js';
|
|
import { selectedZombieMap, mapRotation } from './map-aliases.js';
|
|
import { configWarnings, type ConfigAssignment, type ConfigInspection, type ConfigSource } from '../../config-workbench.js';
|
|
|
|
export interface ConfigLayer { source: ConfigSource; text: string; }
|
|
|
|
/** Trace file composition, including assignments left in place by the composer.
|
|
* This is an intended-file preview, not a query of live runtime dvars. */
|
|
export function inspectConfig(baseline: ConfigLayer, overlays: ConfigLayer[], game: string | null, map: string | null, mapSlug?: string | null): ConfigInspection {
|
|
const sorted = [...overlays].sort((a, b) => a.source.id! - b.source.id!);
|
|
const parsed = parseCfg(baseline.text);
|
|
type TracedLine = { line: string; history: ConfigAssignment[] };
|
|
function assignment(line: string, source: ConfigSource, index: number): ConfigAssignment {
|
|
const match = /^\s*(?:(?:set|seta)\s+)(\S+)\s+(.*)$/i.exec(line)
|
|
?? /^\s*(sv_maprotation)\s+(.*)$/i.exec(line);
|
|
return { value: match?.[2] ?? '', source, line: index + 1 };
|
|
}
|
|
const lines: TracedLine[] = parsed.order.map((entry, index) => ({
|
|
line: entry.line, history: [assignment(entry.line, baseline.source, index)]
|
|
}));
|
|
const replacements = new Map<string, TracedLine>();
|
|
const warnings = configWarnings(baseline.text, false).map(w => `${baseline.source.name}: ${w}`);
|
|
for (const overlay of sorted) {
|
|
warnings.push(...configWarnings(overlay.text, true).map(w => `${overlay.source.name}: ${w}`));
|
|
overlay.text.split(/\r?\n/).forEach((line, index) => {
|
|
const match = /^\s*set\s+(\S+)\s+/.exec(line);
|
|
if (!match) return;
|
|
const previous = replacements.get(match[1]);
|
|
replacements.set(match[1], { line, history: [...(previous?.history ?? []), assignment(line, overlay.source, index)] });
|
|
});
|
|
}
|
|
for (const [key, replacement] of replacements) {
|
|
const index = parsed.entries.get(key);
|
|
if (index === undefined) lines.push(replacement);
|
|
else lines[index] = { line: replacement.line, history: [...lines[index].history, ...replacement.history] };
|
|
}
|
|
const sources = [baseline.source, ...sorted.map(o => o.source)];
|
|
const selected = selectedZombieMap(game, map, mapSlug);
|
|
if (selected) {
|
|
const source: ConfigSource = { id: null, name: 'Server map selection', version: null, kind: 'map' };
|
|
sources.push(source);
|
|
const rotation = /^\s*(?:(?:set|seta)\s+)?sv_maprotation\s+/i;
|
|
const history = lines.filter(entry => rotation.test(entry.line)).flatMap(entry => entry.history);
|
|
for (let i = lines.length - 1; i >= 0; i--) if (rotation.test(lines[i].line)) lines.splice(i, 1);
|
|
const line = `set sv_maprotation "${mapRotation(selected)}"`;
|
|
lines.push({ line, history: [...history, assignment(line, source, 0)] });
|
|
}
|
|
const byKey = new Map<string, { key: string; history: ConfigAssignment[] }>();
|
|
for (const entry of lines) {
|
|
const match = /^\s*(?:set|seta)\s+(\S+)\s+/i.exec(entry.line) ?? /^\s*(sv_maprotation)\s+/i.exec(entry.line);
|
|
if (!match) continue;
|
|
const normalized = match[1].toLowerCase();
|
|
const existing = byKey.get(normalized);
|
|
byKey.set(normalized, { key: match[1], history: [...(existing?.history ?? []), ...entry.history] });
|
|
}
|
|
return {
|
|
text: composeServerCfg(baseline.text, sorted.map(o => ({ id: o.source.id!, text: o.text })), game, map, mapSlug),
|
|
sources, warnings,
|
|
settings: [...byKey.values()].map(({ key, history }) => ({ key, history, value: history.at(-1)!.value, source: history.at(-1)!.source }))
|
|
.sort((a, b) => a.key.localeCompare(b.key))
|
|
};
|
|
}
|