"use client"; import { useState, useEffect, useCallback, Suspense, useMemo } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { Canvas, GLProps } from "@react-three/fiber"; import { NoToneMapping, SRGBColorSpace, PCFShadowMap } from "three"; import { Mission } from "@/src/components/Mission"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ObserverControls } from "@/src/components/ObserverControls"; import { InspectorControls } from "@/src/components/InspectorControls"; import { SettingsProvider } from "@/src/components/SettingsProvider"; import { ObserverCamera } from "@/src/components/ObserverCamera"; import { AudioProvider } from "@/src/components/AudioContext"; import { DebugElements } from "@/src/components/DebugElements"; import { CamerasProvider } from "@/src/components/CamerasProvider"; import { getMissionList, getMissionInfo } from "@/src/manifest"; // 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(); // Renderer settings to match Tribes 2's simple rendering pipeline. // Tribes 2 (Torque engine, 2001) worked entirely in gamma/sRGB space with no HDR // or tone mapping. We disable tone mapping and ensure proper sRGB output. const glSettings: GLProps = { toneMapping: NoToneMapping, outputColorSpace: SRGBColorSpace, }; function MapInspector() { const searchParams = useSearchParams(); const router = useRouter(); const [initialMissionName, initialMissionType] = useMemo( () => (searchParams.get("mission") || "RiverDance:CTF").split("~"), [], ); const [missionName, setMissionName] = useState(initialMissionName); const availableMissionTypes = getMissionInfo(missionName).missionTypes; const [missionType, setMissionType] = useState(() => initialMissionType && availableMissionTypes.includes(initialMissionType) ? initialMissionType : availableMissionTypes[0], ); const isOnlyMissionType = availableMissionTypes.length === 1; const [loadingProgress, setLoadingProgress] = useState(0); const [showLoadingIndicator, setShowLoadingIndicator] = useState(true); const isLoading = loadingProgress < 1; // Keep the loading indicator visible briefly after reaching 100% useEffect(() => { if (isLoading) { setShowLoadingIndicator(true); } else { const timer = setTimeout(() => setShowLoadingIndicator(false), 500); return () => clearTimeout(timer); } }, [isLoading]); useEffect(() => { // For automation, like the t2-maps app! window.setMissionName = setMissionName; window.getMissionList = getMissionList; window.getMissionInfo = getMissionInfo; return () => { delete window.setMissionName; delete window.getMissionList; delete window.getMissionInfo; }; }, []); // Update query params when state changes useEffect(() => { const params = new URLSearchParams(); const value = isOnlyMissionType ? missionName : `${missionName}~${missionType}`; params.set("mission", value); router.replace(`?${params.toString()}`, { scroll: false }); }, [missionName, missionType, isOnlyMissionType, router]); const handleLoadingChange = useCallback( (_loading: boolean, progress: number = 0) => { setLoadingProgress(progress); }, [], ); return (
{showLoadingIndicator && (
{Math.round(loadingProgress * 100)}%
)}
{ setMissionName(missionName); setMissionType(missionType); }} />
); } export default function HomePage() { return ( ); }