Add T6 Zombies fleet support with Plutainer configuration

This commit is contained in:
2026-09-17 18:03:12 -04:00
parent 1350e6df7c
commit 9dcf4ac1d3
28 changed files with 492 additions and 146 deletions
+14 -13
View File
@@ -1,7 +1,7 @@
import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { dirname, join, posix, relative, sep } from 'node:path';
import { config, destOfKind, type ArtifactKind } from '../config.js';
import { configFileOf, destOfKind, type ArtifactKind } from '../config.js';
import { db } from '../db/index.js';
import { emit } from '../pipeline/bus.js';
import { sha256Buffer } from './files.js';
@@ -13,7 +13,7 @@ import {
} from './inventory.js';
import { getBaselineForScope, getVersion } from './scripts.js';
import { getPromotedServerConfig, getServerMapConfig, MAP_CONFIG_FILE } from './server-config.js';
import { T5_ZOMBIE_MAPS } from './map-aliases.js';
import { selectedZombieMap } from './map-aliases.js';
import { serverDirectories, getServer, listServers } from './servers.js';
import { promotedArtifacts } from './promoted-artifacts.js';
@@ -92,8 +92,8 @@ export async function scanServerDrift(server: string): Promise<DriftEntry[]> {
const srv = getServer(server);
// Without a promoted baseline, Plutainer owns the seeded config. Keep it
// until a baseline is promoted; startup arguments handle map selection.
const keepDefaultConfig = srv?.game === 't5'
&& T5_ZOMBIE_MAPS.some((entry) => entry.canonical === srv.map)
const keepDefaultConfig = srv?.game
&& selectedZombieMap(srv.game, srv.map, srv.map_slug)
&& !getBaselineForScope('game', srv.game)?.current_version_id;
// Scan for untracked files only after resolving managed targets.
@@ -104,9 +104,9 @@ export async function scanServerDrift(server: string): Promise<DriftEntry[]> {
for (const relPath of onDisk) {
if (tracked.has(relPath) || managed.has(`${kind}:${relPath}`)) continue;
if (keepDefaultConfig && kind === 'config' && relPath === config.configTargetFile) continue;
if (keepDefaultConfig && kind === 'config' && relPath === configFileOf(server)) continue;
if (kind === 'config' && relPath === MAP_CONFIG_FILE && srv?.created_by
&& srv.game_mode === 'zombie' && getServerMapConfig(srv.game, srv.map)) continue;
&& srv.game_mode === 'zombie' && getServerMapConfig(srv.game, srv.map, srv.map_slug)) continue;
const abs = join(dest, relPath);
try {
const st = await stat(abs);
@@ -138,7 +138,7 @@ async function scanManagedForServer(server: string): Promise<DriftEntry[]> {
// Provisioned servers load this generated file after their main cfg. It
// remains managed even when there is no promoted baseline yet.
const mapConfig = srv.created_by && srv.game_mode === 'zombie'
? getServerMapConfig(srv.game, srv.map) : null;
? getServerMapConfig(srv.game, srv.map, srv.map_slug) : null;
if (mapConfig) {
const abs = join(destOfKind(server, 'config'), MAP_CONFIG_FILE);
const content = await readFile(abs).catch(() => null);
@@ -168,10 +168,10 @@ async function scanManagedForServer(server: string): Promise<DriftEntry[]> {
if (srv.game) {
const baseline = getBaselineForScope('game', srv.game);
if (baseline?.current_version_id) {
const composed = composeExpectedCfg(srv.game, srv.map);
const composed = composeExpectedCfg(srv.game, srv.map, srv.map_slug);
if (composed) {
const dest = destOfKind(server, 'config');
const targetRel = config.configTargetFile;
const targetRel = configFileOf(server);
const abs = join(dest, targetRel);
const inv = getServerScript(server, targetRel);
const onDiskMatches = existsSync(abs) && inv?.sha256 === composed.sha256;
@@ -203,8 +203,8 @@ async function resolveManagedEntry(server: string, entry: DriftEntry): Promise<B
if (entry.kind === 'config') {
const srv = getServer(server);
if (!srv?.game) return null;
if (entry.relPath === MAP_CONFIG_FILE) return getServerMapConfig(srv.game, srv.map);
const composed = composeExpectedCfg(srv.game, srv.map);
if (entry.relPath === MAP_CONFIG_FILE) return getServerMapConfig(srv.game, srv.map, srv.map_slug);
const composed = composeExpectedCfg(srv.game, srv.map, srv.map_slug);
return composed?.content ?? null;
}
if (!entry.scriptVersionId) return null;
@@ -214,9 +214,10 @@ async function resolveManagedEntry(server: string, entry: DriftEntry): Promise<B
function composeExpectedCfg(
game: string | null,
map: string | null
map: string | null,
mapSlug?: string | null
): { content: Buffer; sha256: string } | null {
const content = getPromotedServerConfig(game, map);
const content = getPromotedServerConfig(game, map, mapSlug);
if (!content) return null;
return { content, sha256: sha256Buffer(content) };
}