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
+15 -23
View File
@@ -6,16 +6,14 @@
import Input from './primitives/Input.svelte';
import Select from './primitives/Select.svelte';
interface T5MapEntry {
friendly: string;
slug: string;
canonical: string;
}
import type { ZombieMapEntry, ZombieGame } from '$lib/map-aliases.js';
let { open = $bindable(false) }: { open?: boolean } = $props();
let dialog: HTMLDialogElement | undefined = $state();
let maps = $state<T5MapEntry[]>([]);
let game = $state<ZombieGame>('t5');
let catalogs = $state<Record<ZombieGame, ZombieMapEntry[]>>({ t5: [], t6: [] });
let maps = $derived(catalogs[game]);
let mapsError = $state<string | null>(null);
let mapsLoaded = $state(false);
@@ -31,8 +29,9 @@
onMount(async () => {
try {
const res = await fetch('/api/maps');
const body = (await res.json()) as { t5Zombie: T5MapEntry[] };
maps = body.t5Zombie;
if (!res.ok) throw new Error('Could not load map catalog');
const body = (await res.json()) as { t5Zombie: ZombieMapEntry[]; t6Zombie: ZombieMapEntry[] };
catalogs = { t5: body.t5Zombie, t6: body.t6Zombie };
if (maps.length > 0) mapSlug = maps[0].slug;
} catch (err) {
mapsError = (err as Error).message;
@@ -78,12 +77,15 @@
}
let lobby = $derived(Array.from({ length: 20 }, (_, i) => i + 1)
.find(n => !deployedIds.includes(`t5_${gameMode}_${mapSlug}_${n}`)) ?? null);
.find(n => !deployedIds.includes(`${game}_${gameMode}_${mapSlug}_${n}`)) ?? null);
let selectedMap = $derived(maps.find((m) => m.slug === mapSlug));
let previewName = $derived(selectedMap && keysLoaded && lobby !== null ? `t5_${gameMode}_${selectedMap.slug}_${lobby}` : '');
let previewName = $derived(selectedMap && keysLoaded && lobby !== null ? `${game}_${gameMode}_${selectedMap.slug}_${lobby}` : '');
let hasSavedKey = $derived(savedIds.includes(previewName));
$effect(() => { mapSlug; lobby; plutoKey = ''; });
$effect(() => { game; mapSlug; lobby; plutoKey = ''; });
$effect(() => {
if (maps.length && !maps.some((entry) => entry.slug === mapSlug)) mapSlug = maps[0].slug;
});
let modeOptions = [{ value: 'zombie', label: 'Zombies' }];
let mapOptions = $derived(maps.map((m) => ({ value: m.slug, label: m.friendly })));
@@ -99,7 +101,7 @@
const res = await fetch('/api/servers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ gameMode, mapSlug: selectedMap.slug, expectedName: previewName, plutoKey: plutoKey.trim() || undefined })
body: JSON.stringify({ game, gameMode, mapSlug: selectedMap.slug, expectedName: previewName, plutoKey: plutoKey.trim() || undefined })
});
const body = (await res.json()) as { ok: boolean; name?: string; port?: number; deploymentId?: string; error?: string; kind?: string };
if (!res.ok || !body.ok || !body.deploymentId) {
@@ -131,7 +133,7 @@
{:else}
<div class="fields">
<Field label="Game">
<div class="readonly">Black Ops 1 (t5)</div>
<Select bind:value={game} options={[{ value: 't5', label: 'Black Ops 1 (T5)' }, { value: 't6', label: 'Black Ops II (T6)' }]} disabled={deploying} />
</Field>
<Field label="Mode">
@@ -243,16 +245,6 @@
grid-column: 1 / -1;
}
.readonly {
padding: 9px 12px;
color: var(--text-secondary);
font-family: var(--font-body);
font-size: var(--text-sm);
background: var(--surface-sunken);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
}
.preview {
display: flex;
flex-wrap: wrap;
+4 -4
View File
@@ -1,15 +1,15 @@
import { parseServerName, T5_ZOMBIE_MAPS } from '../map-aliases.js';
import { parseServerName, T5_ZOMBIE_MAPS, T6_ZOMBIE_MAPS, zombieMaps } from '../map-aliases.js';
export function mapTitle(map: string | null | undefined): string {
return T5_ZOMBIE_MAPS.find((entry) => entry.canonical === map?.toLowerCase())?.friendly ?? map ?? '—';
return [...T5_ZOMBIE_MAPS, ...T6_ZOMBIE_MAPS].find((entry) => entry.canonical === map?.toLowerCase())?.friendly ?? map ?? '—';
}
export function serverTitle(name: string): string {
const parsed = parseServerName(name);
if (!parsed || parsed.game.toLowerCase() !== 't5' ||
if (!parsed || !['t5', 't6'].includes(parsed.game.toLowerCase()) ||
(parsed.gameMode !== null && parsed.gameMode !== 'zombie') || !parsed.mapCanonical) {
return name;
}
const title = mapTitle(parsed.mapCanonical);
const title = zombieMaps(parsed.game).find((entry) => entry.slug === parsed.mapSlug)?.friendly ?? mapTitle(parsed.mapCanonical);
return parsed.lobby === null ? title : `${title} · Lobby ${parsed.lobby}`;
}