From 842ddb7df7fd625fb9e03241f5bd12b99a67ec72 Mon Sep 17 00:00:00 2001 From: Brian Beck Date: Fri, 13 Mar 2026 23:12:28 -0700 Subject: [PATCH] fix title and meta tags, remove more Next.js stuff --- app/global.d.ts | 11 - app/layout.tsx | 27 --- app/page.tsx | 24 -- app/shapes/page.module.css | 161 -------------- app/shapes/page.tsx | 441 ------------------------------------- app/style.css | 44 ---- {app => docs}/icon.png | Bin docs/index.html | 12 +- index.html | 12 +- next-env.d.ts | 6 - next.config.ts | 52 ----- public/icon.png | Bin 0 -> 1180 bytes vite.config.ts | 1 + 13 files changed, 17 insertions(+), 774 deletions(-) delete mode 100644 app/global.d.ts delete mode 100644 app/layout.tsx delete mode 100644 app/page.tsx delete mode 100644 app/shapes/page.module.css delete mode 100644 app/shapes/page.tsx delete mode 100644 app/style.css rename {app => docs}/icon.png (100%) delete mode 100644 next-env.d.ts delete mode 100644 next.config.ts create mode 100644 public/icon.png diff --git a/app/global.d.ts b/app/global.d.ts deleted file mode 100644 index 6061bbfa..00000000 --- a/app/global.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { getMissionList, getMissionInfo } from "@/src/manifest"; -import type { DemoRecording } from "@/src/demo/types"; - -declare global { - interface Window { - setMissionName?: (missionName: string) => void; - getMissionList?: typeof getMissionList; - getMissionInfo?: typeof getMissionInfo; - loadDemoRecording?: (recording: DemoRecording) => void; - } -} diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index c7f9f897..00000000 --- a/app/layout.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ReactNode } from "react"; -import { NuqsAdapter } from "nuqs/adapters/next/app"; -import "./style.css"; - -export const metadata = { - title: "MapGenius – Explore maps for Tribes 2", - description: "Tribes 2 forever.", -}; - -export const viewport = { - width: "device-width", - initialScale: 1, - maximumScale: 1, - userScalable: false, -}; - -export default function RootLayout({ children }: { children: ReactNode }) { - return ( - - - - {children} - - - - ); -} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index de5c8ddf..00000000 --- a/app/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -"use client"; -import { Suspense } from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { FeaturesProvider } from "@/src/components/FeaturesProvider"; -import { MapInspector } from "@/src/components/MapInspector"; -import { SettingsProvider } from "@/src/components/SettingsProvider"; - -// Three.js has its own loaders for textures and models, but we need to load other -// stuff too, e.g. missions, terrains, and more. This client is used for those. -const queryClient = new QueryClient(); - -export default function HomePage() { - return ( - - - - - - - - - - ); -} diff --git a/app/shapes/page.module.css b/app/shapes/page.module.css deleted file mode 100644 index e889f522..00000000 --- a/app/shapes/page.module.css +++ /dev/null @@ -1,161 +0,0 @@ -.CanvasContainer { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 0; -} - -.LoadingIndicator { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - display: flex; - flex-direction: column; - align-items: center; - gap: 16px; - pointer-events: none; - z-index: 1; - opacity: 0.8; -} - -.LoadingIndicator[data-complete="true"] { - animation: loadingComplete 0.3s ease-out forwards; -} - -.Spinner { - width: 48px; - height: 48px; - border: 4px solid rgba(255, 255, 255, 0.2); - border-top-color: white; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -@keyframes loadingComplete { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } -} - -.Sidebar { - position: fixed; - top: 0; - left: 0; - bottom: 0; - width: 260px; - background: rgba(0, 0, 0, 0.7); - backdrop-filter: blur(8px); - color: #fff; - font-size: 13px; - z-index: 2; - display: flex; - flex-direction: column; - overflow: hidden; -} - -.SidebarSection { - padding: 10px 12px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); -} - -.SidebarSection:last-child { - border-bottom: none; -} - -.SectionLabel { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.05em; - color: rgba(255, 255, 255, 0.4); - margin-bottom: 6px; -} - -.AnimationList { - flex: 1; - overflow-y: auto; - padding: 0 12px 12px; -} - -.AnimationItem { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - cursor: pointer; - user-select: none; -} - -.AnimationItem:hover { - background: rgba(255, 255, 255, 0.08); -} - -.AnimationItem[data-active="true"] { - background: rgba(255, 255, 255, 0.15); -} - -.PlayButton { - flex-shrink: 0; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - border: none; - border-radius: 4px; - background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.6); - cursor: pointer; - font-size: 11px; - padding: 0; -} - -.PlayButton:hover { - background: rgba(255, 255, 255, 0.2); - color: #fff; -} - -.AnimationItem[data-active="true"] .PlayButton { - background: rgba(100, 180, 255, 0.3); - color: #fff; -} - -.AnimationName { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.ClipName { - flex-shrink: 0; - font-size: 10px; - color: rgba(255, 255, 255, 0.3); - white-space: nowrap; -} - -.CyclicIcon { - flex-shrink: 0; - font-size: 13px; - color: rgba(255, 255, 255, 0.3); - title: "Cyclic (looping)"; -} - -.CheckboxField { - display: flex; - align-items: center; - gap: 6px; -} diff --git a/app/shapes/page.tsx b/app/shapes/page.tsx deleted file mode 100644 index 8c7a3dec..00000000 --- a/app/shapes/page.tsx +++ /dev/null @@ -1,441 +0,0 @@ -"use client"; - -import { - useState, - useEffect, - useEffectEvent, - useCallback, - Suspense, - useMemo, -} from "react"; -import { Canvas, GLProps } from "@react-three/fiber"; -import * as THREE from "three"; -import { NoToneMapping, SRGBColorSpace, PCFShadowMap } from "three"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { OrbitControls, Center, Bounds, useBounds } from "@react-three/drei"; -import { SettingsProvider, useDebug } from "@/src/components/SettingsProvider"; -import { ShapeRenderer, useStaticShape } from "@/src/components/GenericShape"; -import { ShapeInfoProvider } from "@/src/components/ShapeInfoProvider"; -import { DebugElements } from "@/src/components/DebugElements"; -import { TickProvider } from "@/src/components/TickProvider"; -import { ShapeSelect } from "@/src/components/ShapeSelect"; -import { engineStore, useEngineSelector } from "@/src/state/engineStore"; -import { - getResourceList, - getResourceMap, - getResourceKey, - getSourceAndPath, -} from "@/src/manifest"; -import { createParser, useQueryState } from "nuqs"; -import { createScriptLoader } from "@/src/torqueScript/scriptLoader.browser"; -import picomatch from "picomatch"; -import { - createScriptCache, - type FileSystemHandler, - runServer, - type TorqueObject, - type TorqueRuntime, -} from "@/src/torqueScript"; -import styles from "./page.module.css"; -import { ignoreScripts } from "@/src/torqueScript/ignoreScripts"; - -const queryClient = new QueryClient(); -const sceneBg = new THREE.Color(0.1, 0.1, 0.1); - -const glSettings: GLProps = { - toneMapping: NoToneMapping, - outputColorSpace: SRGBColorSpace, -}; - -const loadScript = createScriptLoader(); -const scriptCache = createScriptCache(); -const fileSystem: FileSystemHandler = { - findFiles: (pattern) => { - const isMatch = picomatch(pattern, { nocase: true }); - return getResourceList() - .filter((path) => isMatch(path)) - .map((resourceKey) => { - const [, actualPath] = getSourceAndPath(resourceKey); - return actualPath; - }); - }, - isFile: (resourcePath) => { - const resourceKeys = getResourceMap(); - const resourceKey = getResourceKey(resourcePath); - return resourceKeys[resourceKey] != null; - }, -}; - -const defaultShape = "deploy_inventory.dts"; - -const parseAsShape = createParser({ - parse: (query: string) => query, - serialize: (value: string) => value, - eq: (a, b) => a === b, -}).withDefault(defaultShape); - -/** - * Hook to run the TorqueScript runtime once (hardcoded to SC_Normal/CTF) - * so deploy animations and other script-driven behaviors work. - */ -function useShapeRuntime(): TorqueRuntime | null { - const [runtime, setRuntime] = useState(null); - - useEffect(() => { - const controller = new AbortController(); - let isDisposed = false; - - const { runtime, ready } = runServer({ - missionName: "SC_Normal", - missionType: "CTF", - runtimeOptions: { - loadScript, - fileSystem, - cache: scriptCache, - signal: controller.signal, - ignoreScripts, - }, - }); - - void ready - .then(() => { - if (isDisposed || controller.signal.aborted) return; - engineStore.getState().setRuntime(runtime); - setRuntime(runtime); - }) - .catch((err) => { - if (err instanceof Error && err.name === "AbortError") return; - console.error("Shape runtime failed:", err); - }); - - // Seed store immediately - engineStore.getState().setRuntime(runtime); - - const unsubscribe = runtime.subscribeRuntimeEvents((event) => { - if (event.type !== "batch.flushed") return; - engineStore.getState().applyRuntimeBatch(event.events, { - tick: event.tick, - }); - }); - - return () => { - isDisposed = true; - controller.abort(); - unsubscribe(); - engineStore.getState().clearRuntime(); - runtime.destroy(); - }; - }, []); - - return runtime; -} - -/** Create a minimal TorqueObject for the shape viewer. */ -function createFakeObject( - runtime: TorqueRuntime | null, - shapeName: string, -): TorqueObject { - // Try to find a matching datablock for this shape so deploy animations work. - let datablockName: string | undefined; - if (runtime) { - for (const obj of runtime.state.objectsById.values()) { - if ( - obj.shapeFile && - String(obj.shapeFile).toLowerCase() === shapeName.toLowerCase() - ) { - datablockName = obj._name; - break; - } - } - } - return { - _id: 99999, - _class: "StaticShapeData", - _className: "StaticShape", - ...(datablockName ? { datablock: datablockName } : {}), - } as TorqueObject; -} - -function FitOnLoad() { - const bounds = useBounds(); - useEffect(() => { - bounds.refresh().fit(); - }, [bounds]); - return null; -} - -interface AnimationInfo { - name: string; - alias: string | null; - cyclic: boolean | null; -} - -/** Reports available animations (with cyclic and alias info when available). */ -function AnimationReporter({ - shapeName, - onAnimations, -}: { - shapeName: string; - onAnimations: (anims: AnimationInfo[]) => void; -}) { - const gltf = useStaticShape(shapeName); - const shapeAliases = useEngineSelector((state) => - state.runtime.sequenceAliases.get(shapeName.toLowerCase()), - ); - const anims = useMemo(() => { - // Collect cyclic info from vis_sequence nodes on the scene - const visCyclic = new Map(); - gltf.scene.traverse((node: any) => { - const ud = node.userData; - if (ud?.vis_sequence && ud.vis_cyclic != null) { - visCyclic.set(ud.vis_sequence.toLowerCase(), !!ud.vis_cyclic); - } - }); - // Build reverse alias map: clip name -> alias - let reverseAliases: Map | undefined; - if (shapeAliases) { - reverseAliases = new Map(); - for (const [alias, clipName] of shapeAliases) { - reverseAliases.set(clipName, alias); - } - } - return gltf.animations.map((clip) => ({ - name: clip.name, - alias: reverseAliases?.get(clip.name.toLowerCase()) ?? null, - cyclic: visCyclic.get(clip.name.toLowerCase()) ?? null, - })); - }, [gltf, shapeAliases]); - const reportAnimations = useEffectEvent(onAnimations); - useEffect(() => { - reportAnimations(anims); - }, [anims]); - return null; -} - -/** Plays the selected animation via the TorqueScript runtime. */ -function AnimationPlayer({ - object, - runtime, - animation, -}: { - object: TorqueObject; - runtime: TorqueRuntime | null; - animation: string; -}) { - useEffect(() => { - if (!runtime || !animation) return; - // Use nsCall to dispatch directly on the ShapeBase namespace, bypassing - // class chain resolution (the fake object's _className won't resolve - // to ShapeBase through the namespace parent chain). - for (let slot = 0; slot < 4; slot++) { - runtime.$.nsCall("ShapeBase", "stopThread", object, slot); - } - runtime.$.nsCall("ShapeBase", "playThread", object, 0, animation); - return () => { - for (let slot = 0; slot < 4; slot++) { - runtime.$.nsCall("ShapeBase", "stopThread", object, slot); - } - }; - }, [runtime, object, animation]); - return null; -} - -function ShapeViewer({ - shapeName, - runtime, - onAnimations, - selectedAnimation, -}: { - shapeName: string; - runtime: TorqueRuntime | null; - onAnimations: (anims: AnimationInfo[]) => void; - selectedAnimation: string; -}) { - const object = useMemo( - () => createFakeObject(runtime, shapeName), - [runtime, shapeName], - ); - - return ( - -
- - - - -
-
- ); -} - -function SceneLighting() { - return ( - <> - - - - ); -} - -function ShapeInspector() { - const [currentShape, setCurrentShape] = useQueryState("shape", parseAsShape); - const runtime = useShapeRuntime(); - const [availableAnimations, setAvailableAnimations] = useState< - AnimationInfo[] - >([]); - const [selectedAnimation, setSelectedAnimation] = useState(""); - - const handleAnimations = useCallback((anims: AnimationInfo[]) => { - setAvailableAnimations(anims); - setSelectedAnimation(""); - }, []); - - const [showLoading, setShowLoading] = useState(true); - useEffect(() => { - if (runtime) { - const timer = setTimeout(() => setShowLoading(false), 300); - return () => clearTimeout(timer); - } - }, [runtime]); - - return ( - -
- -
- {showLoading && ( -
-
-
- )} - - - - - - - - - - - - -
- - -
-
- ); -} - -function ShapeControls({ - currentShape, - onChangeShape, - animations, - selectedAnimation, - onChangeAnimation, -}: { - currentShape: string; - onChangeShape: (shape: string) => void; - animations: AnimationInfo[]; - selectedAnimation: string; - onChangeAnimation: (name: string) => void; -}) { - const { debugMode, setDebugMode } = useDebug(); - - return ( -
-
- -
-
-
- setDebugMode(e.target.checked)} - /> - -
-
- {animations.length > 0 && ( - <> -
-
Animations
-
-
- {animations.map((anim) => ( -
- onChangeAnimation( - selectedAnimation === anim.name ? "" : anim.name, - ) - } - > - - - {anim.alias ?? anim.name} - - {anim.alias && ( - - {anim.name} - - )} - {anim.cyclic === true && ( - - {"\u221E"} - - )} -
- ))} -
- - )} -
- ); -} - -export default function ShapesPage() { - return ( - - - - ); -} diff --git a/app/style.css b/app/style.css deleted file mode 100644 index 4c9aaf83..00000000 --- a/app/style.css +++ /dev/null @@ -1,44 +0,0 @@ -html { - box-sizing: border-box; - margin: 0; - padding: 0; - background: black; - overflow: hidden; -} - -*, -*:before, -*:after { - box-sizing: inherit; -} - -body { - user-select: none; - -webkit-touch-callout: none; -} - -html { - font-family: - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Oxygen, - Ubuntu, - Cantarell, - "Open Sans", - "Helvetica Neue", - sans-serif; - font-size: 100%; -} - -body { - margin: 0; - padding: 0; - overflow: hidden; -} - -input[type="range"] { - max-width: 80px; -} diff --git a/app/icon.png b/docs/icon.png similarity index 100% rename from app/icon.png rename to docs/icon.png diff --git a/docs/index.html b/docs/index.html index 4ca04b7a..cb668d9e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,10 +1,14 @@ - - - - t2-mapper + + MapGenius – Explore maps for Tribes 2 + + + diff --git a/index.html b/index.html index 53096a18..0d6cc55d 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,14 @@ - - - - t2-mapper + + MapGenius – Explore maps for Tribes 2 + + +
diff --git a/next-env.d.ts b/next-env.d.ts deleted file mode 100644 index 9edff1c7..00000000 --- a/next-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// -/// -import "./.next/types/routes.d.ts"; - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts deleted file mode 100644 index dc957e10..00000000 --- a/next.config.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { NextConfig } from "next"; -import { PHASE_DEVELOPMENT_SERVER } from "next/constants"; - -const nextConfig = (phase, { defaultConfig }): NextConfig => { - return { - // Suppress static export config warnings in dev mode as they are not relevant. - output: phase === PHASE_DEVELOPMENT_SERVER ? undefined : "export", - distDir: "./docs", - basePath: "/t2-mapper", - assetPrefix: "/t2-mapper/", - trailingSlash: true, - reactCompiler: true, - experimental: { viewTransition: true }, - headers: - // TorqueScript files should be served as text. This won't affect what - // GitHub Pages does with the static export, but it'll at least improve - // the dev server. Otherwise, the responses can't be easily inspected in - // the Network tab. - phase === PHASE_DEVELOPMENT_SERVER - ? async () => { - return [ - { - source: "/:path*.cs", - headers: [ - { - key: "Content-Type", - value: "text/plain; charset=utf-8", - }, - ], - }, - ]; - } - : undefined, - // For the dev server, redirect / to the `basePath` for convenience, so you - // can just open localhost:3000. - redirects: - phase === PHASE_DEVELOPMENT_SERVER - ? async () => { - return [ - { - source: "/", - destination: "/t2-mapper/", - basePath: false, - permanent: false, - }, - ]; - } - : undefined, - }; -}; - -export default nextConfig; diff --git a/public/icon.png b/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3b2cc797e9f405cab661048923acdb6143807ad0 GIT binary patch literal 1180 zcmeAS@N?(olHy`uVBq!ia0vp^IY8XN!3-pC+SRZzFfe)r_=LEE1Xuk}nENAs?vKQI zzyAOKpS|#Z!n~hAl(g}G%A)`AbAKkx|Cze*NBle>`UzA6HnbJ;(&0Vz8MYS)d@0n>6n?P(j8*kV=qg3w|WbhiXY$3{nPEld=e8I8a&I z=Kl%ve*r~+B+x*RX<(E%?|0fVu&Y3JffRt%{06E4Sq<_95CMGz@&d@UbAO}(Jvje& z62xuue&lWg%Yys`H2D`$*(y*7fXo4!05SyZ0I&%^k{0~}x;AOS?|7hH3qWQj%>|j6 zvJ7Y)&{&X-z#symkH5UMfuWgK666=mz&7iD52NtW4ZMmsb=97HOVoHik5m5fC9&I| z{5ekiD=t}8Q|+!ypU;)>Csp80^GAD;&mWAse>zBnPb!rCv2@}C ze%rv7N9^l=CFNdczHuf*>waz50h4=febHO>pNfk%?YaEhMyqBk0|V1!PZ!6K3dS`T zLnj?E;9&?%tui_T1mE}HG_qF6fBLul=AUVZx^dbjAbFTehnN@U%W;{CDuKDXZ;LphWFhly|4)Pxt+^kfDY z&UXKOqvn~s&NP?b5~o|=9}KI=TyNUH_I5?&&$+qH?QhQsil%Qrdf3h8E&q%@^+0P` zL)mYSp7y*f66LH}IM=m|?d#Kiye(#l(^<)K(rh*qwCoN|_F;zPa2>zb= zzJ0o8cA)X!XU5WdyJkkZ9%kNiTk}Bokz0B7rccxg$lXD)=eAXRa z@K1if)BpSlFCW>>UKV{PJ@#1l@0>Yth4a5k{Jii_<`yt57>aBykxrR&Ao_{Oy*K{e z+LB9G^?#WyW%Jke&C+cC-6d}l+t1m)xw5zY?kd|g2j(Rv{JLR2qdp-aVViuCoxz6Z z%wUjx)2gjENBFSKTJ84U_NN=N_)jN3PPANe>u}MHiU>aK{|65sgMUKR%omqbS}{%* RdkV}L44$rjF6*2UngE&gaWnt` literal 0 HcmV?d00001 diff --git a/vite.config.ts b/vite.config.ts index 119ae29a..ce5e3234 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,7 @@ import babel from "@rolldown/plugin-babel"; // https://vite.dev/config/ export default defineConfig({ base: "/t2-mapper/", + server: { port: 3000 }, build: { outDir: "docs", emptyOutDir: false,