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
+57 -9
View File
@@ -1,20 +1,25 @@
// T5 (Black Ops 1) zombies map catalog. Server names encode `slug` so we can
// T5/T6 zombies map catalogs. Server names encode `slug` so we can
// derive game, mode, and map from the folder name (t5_zombie_cosmodrome_1 →
// game 't5', mode 'zombie', slug 'cosmodrome', canonical 'zombie_cosmodrome').
//
// Fields:
// friendly — human name shown in the deploy UI dropdown
// slug — segment used in server name (t5_zombie_<slug>_<n>)
// canonical — actual mapname Pluto/RCON emits (matches T5 script scoping)
// canonical — actual mapname Pluto/RCON emits (used for script scoping)
//
// Add rows when new maps land; keep slugs short so paths stay readable.
export interface T5MapEntry {
export interface ZombieMapEntry {
friendly: string;
slug: string;
canonical: string;
/** T6 selects a ruleset/location before loading the engine map. */
gameSettings?: string;
}
export type T5MapEntry = ZombieMapEntry;
export type ZombieGame = 't5' | 't6';
export const T5_ZOMBIE_MAPS: readonly T5MapEntry[] = Object.freeze([
{ friendly: 'Kino Der Toten', slug: 'theater', canonical: 'zombie_theater' },
{ friendly: 'Ascension', slug: 'cosmodrome', canonical: 'zombie_cosmodrome' },
@@ -29,14 +34,52 @@ export const T5_ZOMBIE_MAPS: readonly T5MapEntry[] = Object.freeze([
{ friendly: 'Der Riese', slug: 'factory', canonical: 'zombie_cod5_factory' }
]);
// Rulesets from xerxes-at/T6ServerConfigs, also seeded by Plutainer.
// Several selections share an engine map; slug preserves the selected variant.
export const T6_ZOMBIE_MAPS: readonly ZombieMapEntry[] = Object.freeze([
{ friendly: 'TranZit', slug: 'transit', canonical: 'zm_transit', gameSettings: 'zm_classic_transit.cfg' },
{ friendly: 'Town Survival', slug: 'town', canonical: 'zm_transit', gameSettings: 'zm_standard_town.cfg' },
{ friendly: 'Farm Survival', slug: 'farm', canonical: 'zm_transit', gameSettings: 'zm_standard_farm.cfg' },
{ friendly: 'Bus Depot Survival', slug: 'busdepot', canonical: 'zm_transit', gameSettings: 'zm_standard_transit.cfg' },
{ friendly: 'Nuketown', slug: 'nuked', canonical: 'zm_nuked', gameSettings: 'zm_standard_nuked.cfg' },
{ friendly: 'Die Rise', slug: 'highrise', canonical: 'zm_highrise', gameSettings: 'zm_classic_rooftop.cfg' },
{ friendly: 'Mob of the Dead', slug: 'prison', canonical: 'zm_prison', gameSettings: 'zm_classic_prison.cfg' },
{ friendly: 'Buried', slug: 'buried', canonical: 'zm_buried', gameSettings: 'zm_classic_processing.cfg' },
{ friendly: 'Origins', slug: 'tomb', canonical: 'zm_tomb', gameSettings: 'zm_classic_tomb.cfg' },
{ friendly: 'Town Grief', slug: 'towngrief', canonical: 'zm_transit', gameSettings: 'zm_grief_town.cfg' },
{ friendly: 'Farm Grief', slug: 'farmgrief', canonical: 'zm_transit', gameSettings: 'zm_grief_farm.cfg' },
{ friendly: 'Bus Depot Grief', slug: 'busdepotgrief', canonical: 'zm_transit', gameSettings: 'zm_grief_transit.cfg' },
{ friendly: 'Cell Block Grief', slug: 'cellblock', canonical: 'zm_prison', gameSettings: 'zm_grief_cellblock.cfg' },
{ friendly: 'Buried Grief', slug: 'buriedgrief', canonical: 'zm_buried', gameSettings: 'zm_grief_street.cfg' },
{ friendly: 'Diner Turned', slug: 'diner', canonical: 'zm_transit_dr', gameSettings: 'zm_cleansed_diner.cfg' },
{ friendly: 'Buried Turned', slug: 'buriedturned', canonical: 'zm_buried', gameSettings: 'zm_cleansed_street.cfg' }
]);
export function zombieMaps(game: string | null): readonly ZombieMapEntry[] {
return game === 't5' ? T5_ZOMBIE_MAPS : game === 't6' ? T6_ZOMBIE_MAPS : [];
}
export function zombieMapBySlug(game: string, slug: string): ZombieMapEntry | undefined {
return zombieMaps(game).find((entry) => entry.slug === slug.toLowerCase());
}
export function selectedZombieMap(game: string | null, map: string | null, slug?: string | null): ZombieMapEntry | undefined {
const maps = zombieMaps(game);
// T5 maps are unambiguous, including legacy friendly slugs. T6 variants
// require the stored selection; never fall back from an unknown variant.
return maps.find((entry) => entry.canonical === map && (
game !== 't6' || !slug || entry.slug === slug
|| entry.friendly.toLowerCase().replace(/[^a-z0-9]/g, '') === slug
));
}
export function mapRotation(entry: ZombieMapEntry): string {
return `${entry.gameSettings ? `execgts ${entry.gameSettings} ` : ''}map ${entry.canonical}`;
}
const BY_SLUG: ReadonlyMap<string, T5MapEntry> = new Map(
T5_ZOMBIE_MAPS.map((m) => [m.slug, m])
);
// Older folder names used friendly names (shi_no_numa) instead of engine
// slugs (sumpf). Keep deriving their canonical map from the same catalog.
const BY_FRIENDLY_SLUG: ReadonlyMap<string, T5MapEntry> = new Map(
T5_ZOMBIE_MAPS.map((m) => [m.friendly.toLowerCase().replace(/[^a-z0-9]/g, ''), m])
);
export function t5ZombieMapBySlug(slug: string): T5MapEntry | undefined {
return BY_SLUG.get(slug.toLowerCase());
@@ -86,6 +129,11 @@ export function parseServerName(name: string): ParsedServerName | null {
if (slugParts.length === 0) return null;
const mapSlug = slugParts.join('').toLowerCase();
const mapCanonical = (BY_SLUG.get(mapSlug) ?? BY_FRIENDLY_SLUG.get(mapSlug))?.canonical ?? null;
const catalog = zombieMaps(game.toLowerCase());
const entry = gameMode === null || gameMode === 'zombie'
? catalog.find((m) => m.slug === mapSlug)
?? catalog.find((m) => m.friendly.toLowerCase().replace(/[^a-z0-9]/g, '') === mapSlug)
: undefined;
const mapCanonical = entry?.canonical ?? null;
return { game, gameMode, mapSlug, mapCanonical, lobby };
}