bug fixes, add player name support

This commit is contained in:
Brian Beck 2026-03-09 23:19:14 -07:00
parent e4ae265184
commit d9b5e30831
75 changed files with 1139 additions and 544 deletions

View file

@ -24,7 +24,10 @@ import {
import { KeyboardControls } from "@react-three/drei";
import { InspectorControls } from "@/src/components/InspectorControls";
import { useTouchDevice } from "@/src/components/useTouchDevice";
import { SettingsProvider } from "@/src/components/SettingsProvider";
import {
SettingsProvider,
useSettings,
} from "@/src/components/SettingsProvider";
import { ObserverCamera } from "@/src/components/ObserverCamera";
import { AudioProvider } from "@/src/components/AudioContext";
import { DebugElements } from "@/src/components/DebugElements";
@ -38,10 +41,8 @@ import { EntityScene } from "@/src/components/EntityScene";
import { TickProvider } from "@/src/components/TickProvider";
import { SceneLighting } from "@/src/components/SceneLighting";
import { PlayerHUD } from "@/src/components/PlayerHUD";
import {
LiveConnectionProvider,
useLiveConnection,
} from "@/src/components/LiveConnection";
import { LiveConnectionProvider } from "@/src/components/LiveConnection";
import { useLiveSelector } from "@/src/state/liveConnectionStore";
import { ServerBrowser } from "@/src/components/ServerBrowser";
import {
FeaturesProvider,
@ -50,9 +51,9 @@ import {
// Lazy-load demo and live streaming modules — they pull in heavy dependencies
// (demo parser, streaming engine, particles) that aren't needed for mission-only mode.
const DemoPlayback = lazy(() =>
import("@/src/components/DemoPlayback").then((mod) => ({
default: mod.DemoPlayback,
const StreamPlayback = lazy(() =>
import("@/src/components/StreamPlayback").then((mod) => ({
default: mod.StreamPlayback,
})),
);
const DemoPlaybackControls = lazy(() =>
@ -152,7 +153,9 @@ function MapInspector() {
const isTouch = useTouchDevice();
const features = useFeatures();
const live = useLiveConnection();
const hasLiveAdapter = useLiveSelector((s) => s.adapter != null);
const liveReady = useLiveSelector((s) => s.liveReady);
const gameStatus = useLiveSelector((s) => s.gameStatus);
const { missionName, missionType } = currentMission;
const [mapInfoOpen, setMapInfoOpen] = useState(false);
const [serverBrowserOpen, setServerBrowserOpen] = useState(false);
@ -162,13 +165,13 @@ function MapInspector() {
// During live join, show progress based on connection status.
// Relay status order: connecting → challenging → authenticating → connected.
// Once liveReady (first ghost arrives), loading is complete.
const liveLoadingProgress = live.adapter != null
? live.liveReady
const liveLoadingProgress = hasLiveAdapter
? liveReady
? 1
: live.gameStatus === "connected" ? 0.8
: live.gameStatus === "authenticating" ? 0.6
: live.gameStatus === "challenging" ? 0.3
: live.gameStatus === "connecting" ? 0.2
: gameStatus === "connected" ? 0.8
: gameStatus === "authenticating" ? 0.6
: gameStatus === "challenging" ? 0.3
: gameStatus === "connecting" ? 0.2
: 0.1
: null;
@ -360,8 +363,8 @@ function MissionWhenIdle({
onLoadingChange: (isLoading: boolean, progress?: number) => void;
}) {
const recording = useRecording();
const { adapter: liveAdapter } = useLiveConnection();
const isStreaming = recording != null || liveAdapter != null;
const hasLiveAdapter = useLiveSelector((s) => s.adapter != null);
const isStreaming = recording != null || hasLiveAdapter;
if (isStreaming) return null;
@ -377,7 +380,7 @@ function MissionWhenIdle({
/**
* In-Canvas components that depend on streaming mode. Mounts the appropriate
* controller (DemoPlayback or LiveObserver) and disables observer controls
* controller (StreamPlayback or LiveObserver) and disables observer controls
* during streaming.
*/
function StreamingComponents({
@ -394,8 +397,7 @@ function StreamingComponents({
lookJoystickZoneRef: React.RefObject<HTMLDivElement | null>;
}) {
const recording = useRecording();
const live = useLiveConnection();
const isLive = live.adapter != null;
const isLive = useLiveSelector((s) => s.adapter != null);
const isStreaming = recording != null || isLive;
// Show ObserverControls for: non-streaming mode, OR live mode.
@ -409,7 +411,7 @@ function StreamingComponents({
<>
{recording && (
<Suspense fallback={null}>
<DemoPlayback />
<StreamPlayback />
</Suspense>
)}
{isLive && (
@ -441,16 +443,16 @@ function StreamingComponents({
/** HUD overlay — shown during streaming (demo or live). */
function StreamingHUD() {
const recording = useRecording();
const live = useLiveConnection();
if (!recording && !live.adapter) return null;
return <PlayerHUD />;
const hasLiveAdapter = useLiveSelector((s) => s.adapter != null);
if (!recording && !hasLiveAdapter) return null;
return <PlayerHUD isLive={hasLiveAdapter} />;
}
/** Playback controls overlay — only shown during demo playback. */
function StreamingOverlay() {
const recording = useRecording();
const live = useLiveConnection();
if (!recording || live.adapter != null) return null;
const hasLiveAdapter = useLiveSelector((s) => s.adapter != null);
if (!recording || hasLiveAdapter) return null;
return (
<Suspense fallback={null}>
<DemoPlaybackControls />
@ -466,16 +468,29 @@ function ServerBrowserDialog({
open: boolean;
onClose: () => void;
}) {
const live = useLiveConnection();
const servers = useLiveSelector((s) => s.servers);
const serversLoading = useLiveSelector((s) => s.serversLoading);
const browserToRelayPing = useLiveSelector((s) => s.browserToRelayPing);
const listServers = useLiveSelector((s) => s.listServers);
const joinServer = useLiveSelector((s) => s.joinServer);
const settings = useSettings();
const handleJoin = useCallback(
(address: string) => {
joinServer(address, settings?.warriorName);
},
[joinServer, settings?.warriorName],
);
return (
<ServerBrowser
open={open}
onClose={onClose}
servers={live.servers}
loading={live.serversLoading}
onRefresh={live.listServers}
onJoin={(address) => live.joinServer(address)}
wsPing={live.wsPing}
servers={servers}
loading={serversLoading}
onRefresh={listServers}
onJoin={handleJoin}
wsPing={browserToRelayPing}
warriorName={settings?.warriorName ?? ""}
onWarriorNameChange={(name) => settings?.setWarriorName(name)}
/>
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,11 @@
1:"$Sreact.fragment"
2:I[47257,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ClientPageRoot"]
3:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/2400be5b6a2e4806.js","/t2-mapper/_next/static/chunks/164bc8495505bc95.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/ee88398bb27ad4a1.js","/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","/t2-mapper/_next/static/chunks/b07469fc6c6cd3bb.js","/t2-mapper/_next/static/chunks/629d98e413c0344a.js","/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","/t2-mapper/_next/static/chunks/49bf5eb2ca42014f.js"],"default"]
3:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/ac206141b5c1882c.js","/t2-mapper/_next/static/chunks/164bc8495505bc95.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/c293826b70147a61.js","/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","/t2-mapper/_next/static/chunks/534ef931e88eb989.js","/t2-mapper/_next/static/chunks/5b60a5e8e48bc409.js","/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","/t2-mapper/_next/static/chunks/e4a5074909f0df67.js"],"default"]
6:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
7:"$Sreact.suspense"
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
:HL["/t2-mapper/_next/static/chunks/bd03a29a57c8ca45.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/bd03a29a57c8ca45.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/2400be5b6a2e4806.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/164bc8495505bc95.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/ee88398bb27ad4a1.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/b07469fc6c6cd3bb.js","async":true}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/629d98e413c0344a.js","async":true}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","async":true}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","async":true}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/49bf5eb2ca42014f.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
:HL["/t2-mapper/_next/static/chunks/9542fc38c512bd1a.css","style"]
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/9542fc38c512bd1a.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/ac206141b5c1882c.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/164bc8495505bc95.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/c293826b70147a61.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/534ef931e88eb989.js","async":true}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/5b60a5e8e48bc409.js","async":true}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","async":true}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","async":true}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/e4a5074909f0df67.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
4:{}
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
8:null

View file

@ -3,16 +3,16 @@
3:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
5:I[47257,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ClientPageRoot"]
6:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/2400be5b6a2e4806.js","/t2-mapper/_next/static/chunks/164bc8495505bc95.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/ee88398bb27ad4a1.js","/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","/t2-mapper/_next/static/chunks/b07469fc6c6cd3bb.js","/t2-mapper/_next/static/chunks/629d98e413c0344a.js","/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","/t2-mapper/_next/static/chunks/49bf5eb2ca42014f.js"],"default"]
6:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/ac206141b5c1882c.js","/t2-mapper/_next/static/chunks/164bc8495505bc95.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/c293826b70147a61.js","/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","/t2-mapper/_next/static/chunks/534ef931e88eb989.js","/t2-mapper/_next/static/chunks/5b60a5e8e48bc409.js","/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","/t2-mapper/_next/static/chunks/e4a5074909f0df67.js"],"default"]
9:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
a:"$Sreact.suspense"
c:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ViewportBoundary"]
e:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
10:I[68027,[],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
:HL["/t2-mapper/_next/static/chunks/bd03a29a57c8ca45.css","style"]
0:{"P":null,"b":"JablvlklHXp4NGWk4TTlC","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/bd03a29a57c8ca45.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/2400be5b6a2e4806.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/164bc8495505bc95.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/ee88398bb27ad4a1.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/b07469fc6c6cd3bb.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/629d98e413c0344a.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/49bf5eb2ca42014f.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
:HL["/t2-mapper/_next/static/chunks/9542fc38c512bd1a.css","style"]
0:{"P":null,"b":"MrEXjMpW9X_XkpwJ2-Z7S","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/9542fc38c512bd1a.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/ac206141b5c1882c.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/164bc8495505bc95.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/c293826b70147a61.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/534ef931e88eb989.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/5b60a5e8e48bc409.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/e4a5074909f0df67.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
7:{}
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]

View file

@ -3,4 +3,4 @@
3:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"IconMark"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}

View file

@ -3,4 +3,4 @@
3:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,4 +1,4 @@
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
:HL["/t2-mapper/_next/static/chunks/bd03a29a57c8ca45.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
:HL["/t2-mapper/_next/static/chunks/9542fc38c512bd1a.css","style"]
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
.GuiMarkup-module__CLgNnq__GuiMarkup{white-space:pre-wrap;font-size:14px;line-height:1.5}.GuiMarkup-module__CLgNnq__GuiMarkup a{color:inherit;text-decoration:underline}.GuiMarkup-module__CLgNnq__Bullet{margin-left:.5em;margin-right:.5em}
.DialogButton-module__LxvdOa__DialogButton{color:#99fff1;text-shadow:0 -1px 1px #0006;cursor:pointer;background:linear-gradient(#30a497cc,#1f9688cc 33%,#218377cc 67%,#046564cc);border:1px solid #387c74cc;border-top-color:#57b7b9cc;border-radius:4px;padding:4px 18px;font-family:inherit;font-size:14px;font-weight:500;box-shadow:inset 0 0 4px #02808e80}.DialogButton-module__LxvdOa__DialogButton:hover:not(:disabled){color:#b1fff5;border:1px solid #409188e6;border-top-color:#5ac6c2e6;box-shadow:inset 0 0 4px #02808e80,0 0 5px #3effbf80}.DialogButton-module__LxvdOa__DialogButton:active:not(:disabled){transform:translateY(1px)}.DialogButton-module__LxvdOa__DialogButton:disabled{opacity:.4;cursor:not-allowed}.DialogButton-module__LxvdOa__Secondary{box-shadow:none;color:#a2e2cfcc;text-shadow:none;background:0 0;border:1px solid #387c74cc;}.DialogButton-module__LxvdOa__Secondary:hover:not(:disabled){color:#a9ffe5cc;border:1px solid #3f9087e6}
.ServerBrowser-module__kqm3eq__Dialog{color:#b0d5c9;-webkit-user-select:text;user-select:text;-webkit-touch-callout:default;background:#142526cc;border:1px solid #41838b99;border-radius:4px;outline:none;grid-template-rows:auto 1fr auto;grid-template-columns:100%;width:860px;max-width:calc(100dvw - 40px);height:560px;max-height:calc(100dvh - 40px);font-size:14px;line-height:1.5;display:grid;position:relative;overflow:hidden;box-shadow:0 0 50px #0006,inset 0 0 60px #01070d99}.ServerBrowser-module__kqm3eq__Overlay{z-index:10;background:#000000b3;justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}.ServerBrowser-module__kqm3eq__Header{border-bottom:1px solid #00bedc40;align-items:center;gap:12px;padding:12px 16px 10px;display:flex}.ServerBrowser-module__kqm3eq__Title{color:#7dffff;text-shadow:0 1px 6px #0006;flex:1;margin:0;font-size:18px;font-weight:500}.ServerBrowser-module__kqm3eq__RefreshButton{padding:3px 14px;font-size:12px;}.ServerBrowser-module__kqm3eq__ServerCount{color:#c9dcd866;font-size:12px}.ServerBrowser-module__kqm3eq__TableWrapper{min-height:0;overflow-y:auto}.ServerBrowser-module__kqm3eq__Table{border-collapse:collapse;width:100%;font-size:13px}.ServerBrowser-module__kqm3eq__Table th{text-align:left;cursor:pointer;-webkit-user-select:none;user-select:none;letter-spacing:.04em;text-transform:uppercase;color:#7dffff99;background:#0a191af2;border-bottom:1px solid #00bedc33;padding:6px 12px;font-size:11px;font-weight:500;position:sticky;top:0}.ServerBrowser-module__kqm3eq__Table th:hover{color:#7dffff}.ServerBrowser-module__kqm3eq__Table th:nth-child(2),.ServerBrowser-module__kqm3eq__Table td:nth-child(2),.ServerBrowser-module__kqm3eq__Table th:nth-child(3),.ServerBrowser-module__kqm3eq__Table td:nth-child(3){text-align:right}.ServerBrowser-module__kqm3eq__Table td{white-space:nowrap;text-overflow:ellipsis;border-bottom:1px solid #ffffff0a;max-width:340px;padding:3px 12px;font-weight:500;overflow:hidden}.ServerBrowser-module__kqm3eq__Table tbody tr{cursor:pointer}.ServerBrowser-module__kqm3eq__Table tbody tr:hover{background:#41838b1f}.ServerBrowser-module__kqm3eq__Selected{color:#1e2828;background:#5dffe1e6!important}.ServerBrowser-module__kqm3eq__PasswordIcon{color:#ffc83c99;margin-right:4px;font-size:11px}.ServerBrowser-module__kqm3eq__Empty{text-align:center;color:#c9dcd84d;font-style:italic;padding:32px 12px!important}.ServerBrowser-module__kqm3eq__Footer{background:#021415b3;border-top:1px solid #00bedc40;flex-shrink:0;align-items:center;gap:16px;padding:10px 12px;display:flex}.ServerBrowser-module__kqm3eq__JoinButton{}.ServerBrowser-module__kqm3eq__CloseButton{}.ServerBrowser-module__kqm3eq__Hint{color:#c9dcd84d;margin-left:auto;font-size:12px}@media (max-width:719px){.ServerBrowser-module__kqm3eq__Dialog{border-radius:0;width:100%;max-width:100dvw;height:100%;max-height:100dvh}.ServerBrowser-module__kqm3eq__Hint{display:none}.ServerBrowser-module__kqm3eq__Table td{max-width:200px}}
.ServerBrowser-module__kqm3eq__Dialog{color:#b0d5c9;-webkit-user-select:text;user-select:text;-webkit-touch-callout:default;background:#142526cc;border:1px solid #41838b99;border-radius:4px;outline:none;grid-template-rows:auto 1fr auto;grid-template-columns:100%;width:860px;max-width:calc(100dvw - 40px);height:560px;max-height:calc(100dvh - 40px);font-size:14px;line-height:1.5;display:grid;position:relative;overflow:hidden;box-shadow:0 0 50px #0006,inset 0 0 60px #01070d99}.ServerBrowser-module__kqm3eq__Overlay{z-index:10;background:#000000b3;justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}.ServerBrowser-module__kqm3eq__Header{border-bottom:1px solid #00bedc40;align-items:center;gap:12px;padding:12px 16px 10px;display:flex}.ServerBrowser-module__kqm3eq__Title{color:#7dffff;text-shadow:0 1px 6px #0006;flex:1;margin:0;font-size:18px;font-weight:500}.ServerBrowser-module__kqm3eq__RefreshButton{padding:3px 14px;font-size:12px;}.ServerBrowser-module__kqm3eq__ServerCount{color:#c9dcd866;font-size:12px}.ServerBrowser-module__kqm3eq__TableWrapper{min-height:0;overflow-y:auto}.ServerBrowser-module__kqm3eq__Table{border-collapse:collapse;width:100%;font-size:13px}.ServerBrowser-module__kqm3eq__Table th{text-align:left;cursor:pointer;-webkit-user-select:none;user-select:none;letter-spacing:.04em;text-transform:uppercase;color:#7dffff99;background:#0a191af2;border-bottom:1px solid #00bedc33;padding:6px 12px;font-size:11px;font-weight:500;position:sticky;top:0}.ServerBrowser-module__kqm3eq__Table th:hover{color:#7dffff}.ServerBrowser-module__kqm3eq__Table th:nth-child(2),.ServerBrowser-module__kqm3eq__Table td:nth-child(2),.ServerBrowser-module__kqm3eq__Table th:nth-child(3),.ServerBrowser-module__kqm3eq__Table td:nth-child(3){text-align:right}.ServerBrowser-module__kqm3eq__Table td{white-space:nowrap;text-overflow:ellipsis;border-bottom:1px solid #ffffff0a;max-width:340px;padding:3px 12px;font-weight:500;overflow:hidden}.ServerBrowser-module__kqm3eq__Table tbody tr{cursor:pointer}.ServerBrowser-module__kqm3eq__Table tbody tr:hover{background:#41838b1f}.ServerBrowser-module__kqm3eq__Selected{color:#1e2828;background:#5dffe1e6!important}.ServerBrowser-module__kqm3eq__PasswordIcon{color:#ffc83c99;margin-right:4px;font-size:11px}.ServerBrowser-module__kqm3eq__Empty{text-align:center;color:#c9dcd84d;font-style:italic;padding:32px 12px!important}.ServerBrowser-module__kqm3eq__Footer{background:#021415b3;border-top:1px solid #00bedc40;flex-shrink:0;align-items:center;gap:16px;padding:10px 12px;display:flex}.ServerBrowser-module__kqm3eq__JoinButton{min-width:100px;}.ServerBrowser-module__kqm3eq__CloseButton{}.ServerBrowser-module__kqm3eq__WarriorField{align-items:center;gap:8px;display:flex}.ServerBrowser-module__kqm3eq__WarriorLabel{color:#7dffff99;font-size:12px;font-weight:500}.ServerBrowser-module__kqm3eq__WarriorInput{color:#b0d5c9;background:#00323ccc;border:1px solid #41838b80;border-radius:2px;outline:none;width:130px;padding:4px 6px;font-family:inherit;font-size:13px}.ServerBrowser-module__kqm3eq__WarriorInput:focus{border-color:#7dffff99}.ServerBrowser-module__kqm3eq__WarriorInput::placeholder{color:#c9dcd84d}.ServerBrowser-module__kqm3eq__Hint{color:#c9dcd84d;margin:0 auto;font-size:12px}@media (max-width:719px){.ServerBrowser-module__kqm3eq__Dialog{border-radius:0;width:100%;max-width:100dvw;height:100%;max-height:100dvh}.ServerBrowser-module__kqm3eq__Hint{display:none}.ServerBrowser-module__kqm3eq__Table td{max-width:200px}}
.page-module__E0kJGG__CanvasContainer{z-index:0;position:absolute;inset:0}.page-module__E0kJGG__LoadingIndicator{pointer-events:none;z-index:1;opacity:.8;flex-direction:column;align-items:center;gap:16px;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.page-module__E0kJGG__LoadingIndicator[data-complete=true]{animation:.3s ease-out forwards page-module__E0kJGG__loadingComplete}.page-module__E0kJGG__Spinner{border:4px solid #fff3;border-top-color:#fff;border-radius:50%;width:48px;height:48px;animation:1s linear infinite page-module__E0kJGG__spin}.page-module__E0kJGG__Progress{background:#fff3;border-radius:2px;width:200px;height:4px;overflow:hidden}.page-module__E0kJGG__ProgressBar{background:#fff;border-radius:2px;height:100%;transition:width .1s ease-out}.page-module__E0kJGG__ProgressText{color:#ffffffb3;font-variant-numeric:tabular-nums;font-size:14px}@keyframes page-module__E0kJGG__spin{to{transform:rotate(360deg)}}@keyframes page-module__E0kJGG__loadingComplete{0%{opacity:1}to{opacity:0}}
.MapInfoDialog-module__m0lXla__Dialog{color:#bccec3;-webkit-user-select:text;user-select:text;-webkit-touch-callout:default;background:#142526cc;border:1px solid #41838b99;border-radius:4px;outline:none;grid-template-rows:1fr auto;grid-template-columns:100%;width:800px;max-width:calc(100dvw - 40px);height:600px;max-height:calc(100dvh - 40px);font-size:14px;line-height:1.5;display:grid;position:relative;overflow:hidden;box-shadow:0 0 50px #0006,inset 0 0 60px #01070d99}.MapInfoDialog-module__m0lXla__Overlay{z-index:10;background:#000000b3;justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}.MapInfoDialog-module__m0lXla__Body{grid-template-rows:100%;grid-template-columns:1fr auto;min-height:0;display:grid;overflow:hidden}.MapInfoDialog-module__m0lXla__Left{padding:24px 28px;overflow-y:auto}.MapInfoDialog-module__m0lXla__PreviewImage{border-left:1px solid #00bedc40;height:100%;display:block}.MapInfoDialog-module__m0lXla__PreviewImageFloating{float:right;clear:right;width:auto;max-width:30%;max-height:260px;margin:0 0 16px 20px;display:block}.MapInfoDialog-module__m0lXla__Title{color:#7dffff;text-shadow:0 1px 6px #0006;margin:0;font-size:26px;font-weight:500}.MapInfoDialog-module__m0lXla__MapMeta{flex-wrap:wrap;gap:8px 16px;margin-bottom:4px;font-size:15px;font-weight:400;display:flex}.MapInfoDialog-module__m0lXla__MapPlanet{color:#dbcaa8b3}.MapInfoDialog-module__m0lXla__MapQuote{border-left:2px solid #00bedc59;margin:16px 0;padding:0 0 0 14px;font-style:italic}.MapInfoDialog-module__m0lXla__MapQuote p{white-space:pre-line;margin:0 0 4px}.MapInfoDialog-module__m0lXla__MapQuote cite{color:#ffffff73;font-size:12px;font-style:normal;display:block}.MapInfoDialog-module__m0lXla__MapBlurb{margin:0 0 16px;font-size:13px}.MapInfoDialog-module__m0lXla__Section{margin-top:20px}.MapInfoDialog-module__m0lXla__SectionTitle{color:#7dffff;letter-spacing:.04em;text-transform:uppercase;text-shadow:0 0 16px #00d2f040;margin:0 0 8px;font-size:16px;font-weight:500}.MapInfoDialog-module__m0lXla__MusicTrack{color:#cad0ac80;align-items:center;gap:6px;margin-top:16px;font-size:14px;font-style:italic;display:flex}.MapInfoDialog-module__m0lXla__MusicTrack[data-playing=true]{color:#f7fdd8b3}.MapInfoDialog-module__m0lXla__MusicButton{cursor:pointer;color:#557663;opacity:.5;background:0 0;border:0;border-radius:20px;flex-shrink:0;place-content:center;width:32px;height:32px;padding:0;font-size:20px;font-style:normal;line-height:1;display:grid}.MapInfoDialog-module__m0lXla__MusicTrack[data-playing=true] .MapInfoDialog-module__m0lXla__MusicButton{color:#6dffaa;opacity:1}.MapInfoDialog-module__m0lXla__MusicTrack[data-playing=true] .MapInfoDialog-module__m0lXla__MusicButton:hover{opacity:.7}.MapInfoDialog-module__m0lXla__Footer{background:#021415b3;border-top:1px solid #00bedc40;flex-shrink:0;align-items:center;gap:16px;padding:10px 12px;display:flex}.MapInfoDialog-module__m0lXla__CloseButton{}.MapInfoDialog-module__m0lXla__Hint{color:#c9dcd84d;margin-left:auto;font-size:12px}.MapInfoDialog-module__m0lXla__MusicTrackName{text-transform:capitalize}@media (max-width:719px){.MapInfoDialog-module__m0lXla__Body{display:block;overflow:auto}.MapInfoDialog-module__m0lXla__Hint{display:none}.MapInfoDialog-module__m0lXla__Left{width:100%;height:auto;margin:0;padding:16px 20px;overflow:auto}.MapInfoDialog-module__m0lXla__PreviewImage{width:auto;height:auto;margin:16px auto}.MapInfoDialog-module__m0lXla__CloseButton{width:220px;height:36px;margin:0 auto}}

View file

@ -9,5 +9,5 @@
.FloatingLabel-module__8y09Ka__Label{color:#fff;white-space:nowrap;text-align:center;background:#00000080;border-radius:1px;padding:1px 3px;font-size:11px}
.PlayerNameplate-module__zYDm0a__Root{pointer-events:none;white-space:nowrap;flex-direction:column;align-items:center;display:inline-flex}.PlayerNameplate-module__zYDm0a__Top{padding-bottom:20px;}.PlayerNameplate-module__zYDm0a__Bottom{padding-top:20px;}.PlayerNameplate-module__zYDm0a__IffArrow{width:12px;height:12px;image-rendering:pixelated;filter:drop-shadow(0 1px 2px #000000b3)}.PlayerNameplate-module__zYDm0a__Name{color:#fff;text-shadow:0 1px 3px #000000e6,0 0 1px #000000b3;font-size:11px}.PlayerNameplate-module__zYDm0a__HealthBar{background:#00000080;border:1px solid #fff3;width:60px;height:4px;margin:2px auto 0;overflow:hidden}.PlayerNameplate-module__zYDm0a__HealthFill{background:#2ecc40;height:100%}
.FlagMarker-module__INpLba__Root{pointer-events:none;white-space:nowrap;flex-direction:column;align-items:center;gap:1px;display:inline-flex}.FlagMarker-module__INpLba__Distance{color:#fff;text-shadow:0 1px 3px #000000e6,0 0 1px #000000b3;opacity:.5;font-size:10px}.FlagMarker-module__INpLba__Icon{width:16px;height:16px;image-rendering:pixelated;opacity:.5;filter:drop-shadow(0 1px 3px #000c);-webkit-mask-image:var(--flag-icon-url);mask-image:var(--flag-icon-url);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-image:var(--flag-icon-url);-webkit-mask-position:50%;-webkit-mask-size:contain;-webkit-mask-repeat:no-repeat}
.PlayerHUD-module__-E1Scq__PlayerHUD{z-index:1;pointer-events:none;position:absolute;inset:0}.PlayerHUD-module__-E1Scq__TopRight{align-items:flex-start;gap:6px;display:flex;position:absolute;top:56px;right:8px}.PlayerHUD-module__-E1Scq__Compass{flex-shrink:0;width:64px;height:64px;position:relative}.PlayerHUD-module__-E1Scq__CompassRing{image-rendering:auto;width:100%;height:100%;position:absolute;top:0;left:0}.PlayerHUD-module__-E1Scq__CompassNSEW{width:100%;height:100%;image-rendering:pixelated;position:absolute;top:0;left:0}.PlayerHUD-module__-E1Scq__Bars{flex-direction:column;gap:3px;padding-top:10px;display:flex}.PlayerHUD-module__-E1Scq__BarTrack{background:#00000080;border:1px solid #ffffff26;width:120px;height:10px;overflow:hidden}.PlayerHUD-module__-E1Scq__BarFillHealth{background:#2ecc40;height:100%;transition:width .15s ease-out}.PlayerHUD-module__-E1Scq__BarFillEnergy{background:#0af;height:100%;transition:width .15s ease-out}.PlayerHUD-module__-E1Scq__WeaponHUD{flex-direction:column;gap:2px;display:flex;position:absolute;top:50%;right:8px;transform:translateY(-50%)}.PlayerHUD-module__-E1Scq__WeaponSeparator{height:6px}.PlayerHUD-module__-E1Scq__ChatWindow{background:#00323ca6;max-width:420px;padding:4px 8px;font-size:12px;line-height:1.3;position:absolute;top:56px;left:0}.PlayerHUD-module__-E1Scq__ChatMessage{color:#2cacb5;padding:1px 0;transition:opacity .3s ease-out}.PlayerHUD-module__-E1Scq__ChatColor0{color:#2cacb5}.PlayerHUD-module__-E1Scq__ChatColor1{color:#04eb69}.PlayerHUD-module__-E1Scq__ChatColor2{color:#dbc880}.PlayerHUD-module__-E1Scq__ChatColor3{color:#4dfd5f}.PlayerHUD-module__-E1Scq__ChatColor4{color:#28e7f0}.PlayerHUD-module__-E1Scq__ChatColor5{color:#c8c832}.PlayerHUD-module__-E1Scq__ChatColor6{color:#c8c8c8}.PlayerHUD-module__-E1Scq__ChatColor7{color:#dcdc14}.PlayerHUD-module__-E1Scq__ChatColor8{color:#9696fa}.PlayerHUD-module__-E1Scq__ChatColor9{color:#3cdc96}.PlayerHUD-module__-E1Scq__TeamScores{font-family:monospace;font-size:12px;position:absolute;bottom:130px;left:0}.PlayerHUD-module__-E1Scq__TeamRow{background:#00323ca6;gap:6px;padding:2px 8px;display:flex}.PlayerHUD-module__-E1Scq__TeamRow+.PlayerHUD-module__-E1Scq__TeamRow{border-top:1px solid #80ffc826}.PlayerHUD-module__-E1Scq__TeamNameFriendly{color:#2ecc40;min-width:60px}.PlayerHUD-module__-E1Scq__TeamNameEnemy{color:#e44;min-width:60px}.PlayerHUD-module__-E1Scq__TeamScore{color:#fff;text-align:right;min-width:24px;font-weight:700}.PlayerHUD-module__-E1Scq__TeamCount{color:#9ba;text-align:right;min-width:24px}.PlayerHUD-module__-E1Scq__PackInventoryHUD{align-items:center;gap:4px;display:flex;position:absolute;bottom:100px;right:8px}.PlayerHUD-module__-E1Scq__PackInvItem{background:#00323ca6;border:1px solid #80ffc826;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:4px;display:flex}.PlayerHUD-module__-E1Scq__PackInvItemActive{border-color:#80ffc880;box-shadow:0 0 6px #80ffc84d}.PlayerHUD-module__-E1Scq__PackInvItemDim{opacity:.5}.PlayerHUD-module__-E1Scq__PackInvIcon{image-rendering:pixelated;display:block}.PlayerHUD-module__-E1Scq__PackInvCount{color:#bfe;text-align:center;min-width:12px;font-family:monospace;font-size:11px}.PlayerHUD-module__-E1Scq__PackInvInfinity{image-rendering:pixelated;opacity:.8;display:block}.PlayerHUD-module__-E1Scq__Reticle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.PlayerHUD-module__-E1Scq__ReticleImage{opacity:.85;width:64px;height:64px;image-rendering:pixelated}.PlayerHUD-module__-E1Scq__ReticleDot{background:#2ecc40b3;border-radius:50%;width:4px;height:4px;box-shadow:0 0 4px #2ecc4080}
.PlayerHUD-module__-E1Scq__PlayerHUD{z-index:1;pointer-events:none;position:absolute;inset:0}.PlayerHUD-module__-E1Scq__TopRight{align-items:flex-start;gap:6px;display:flex;position:absolute;top:56px;right:8px}.PlayerHUD-module__-E1Scq__Compass{flex-shrink:0;width:64px;height:64px;position:relative}.PlayerHUD-module__-E1Scq__CompassRing{image-rendering:auto;width:100%;height:100%;position:absolute;top:0;left:0}.PlayerHUD-module__-E1Scq__CompassNSEW{width:100%;height:100%;image-rendering:pixelated;position:absolute;top:0;left:0}.PlayerHUD-module__-E1Scq__Bars{flex-direction:column;gap:3px;padding-top:10px;display:flex}.PlayerHUD-module__-E1Scq__BarTrack{background:#00000080;border:1px solid #ffffff26;width:120px;height:10px;overflow:hidden}.PlayerHUD-module__-E1Scq__BarFillHealth{background:#2ecc40;height:100%;transition:width .15s ease-out}.PlayerHUD-module__-E1Scq__BarFillEnergy{background:#0af;height:100%;transition:width .15s ease-out}.PlayerHUD-module__-E1Scq__WeaponHUD{flex-direction:column;gap:2px;display:flex;position:absolute;top:50%;right:8px;transform:translateY(-50%)}.PlayerHUD-module__-E1Scq__WeaponSeparator{height:6px}.PlayerHUD-module__-E1Scq__ChatContainer{pointer-events:auto;border:1px solid #2cacb566;flex-direction:column;max-width:420px;display:flex;position:absolute;top:56px;left:0}.PlayerHUD-module__-E1Scq__ChatWindow{-webkit-user-select:text;user-select:text;scrollbar-width:thin;scrollbar-color:#2cacb566 transparent;background:#00323ca6;max-width:450px;max-height:12.5em;padding:6px 8px;font-size:12px;line-height:1.33333;overflow-y:auto}.PlayerHUD-module__-E1Scq__ChatMessage{color:#2cacb5;padding:1px 0}.PlayerHUD-module__-E1Scq__ChatInputForm{display:flex}.PlayerHUD-module__-E1Scq__ChatInput{color:#28e7f0;background:#00323ccc;border:0;border-top:1px solid #4eb3a733;border-radius:0;outline:none;width:100%;margin:0;padding:6px 8px;font-family:inherit;font-size:12px}.PlayerHUD-module__-E1Scq__ChatInput::placeholder{color:#2cacb580}.PlayerHUD-module__-E1Scq__ChatInput:focus{background:#00323ce6}.PlayerHUD-module__-E1Scq__ChatColor0{color:#2cacb5}.PlayerHUD-module__-E1Scq__ChatColor1{color:#04eb69}.PlayerHUD-module__-E1Scq__ChatColor2{color:#dbc880}.PlayerHUD-module__-E1Scq__ChatColor3{color:#4dfd5f}.PlayerHUD-module__-E1Scq__ChatColor4{color:#28e7f0}.PlayerHUD-module__-E1Scq__ChatColor5{color:#c8c832}.PlayerHUD-module__-E1Scq__ChatColor6{color:#c8c8c8}.PlayerHUD-module__-E1Scq__ChatColor7{color:#dcdc14}.PlayerHUD-module__-E1Scq__ChatColor8{color:#9696fa}.PlayerHUD-module__-E1Scq__ChatColor9{color:#3cdc96}.PlayerHUD-module__-E1Scq__TeamScores{font-family:monospace;font-size:12px;position:absolute;bottom:130px;left:0}.PlayerHUD-module__-E1Scq__TeamRow{background:#00323ca6;gap:6px;padding:2px 8px;display:flex}.PlayerHUD-module__-E1Scq__TeamRow+.PlayerHUD-module__-E1Scq__TeamRow{border-top:1px solid #80ffc826}.PlayerHUD-module__-E1Scq__TeamNameFriendly{color:#2ecc40;min-width:60px}.PlayerHUD-module__-E1Scq__TeamNameEnemy{color:#e44;min-width:60px}.PlayerHUD-module__-E1Scq__TeamScore{color:#fff;text-align:right;min-width:24px;font-weight:700}.PlayerHUD-module__-E1Scq__TeamCount{color:#9ba;text-align:right;min-width:24px}.PlayerHUD-module__-E1Scq__PackInventoryHUD{align-items:center;gap:4px;display:flex;position:absolute;bottom:100px;right:8px}.PlayerHUD-module__-E1Scq__PackInvItem{background:#00323ca6;border:1px solid #80ffc826;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:4px;display:flex}.PlayerHUD-module__-E1Scq__PackInvItemActive{border-color:#80ffc880;box-shadow:0 0 6px #80ffc84d}.PlayerHUD-module__-E1Scq__PackInvItemDim{opacity:.5}.PlayerHUD-module__-E1Scq__PackInvIcon{image-rendering:pixelated;display:block}.PlayerHUD-module__-E1Scq__PackInvCount{color:#bfe;text-align:center;min-width:12px;font-family:monospace;font-size:11px}.PlayerHUD-module__-E1Scq__PackInvInfinity{image-rendering:pixelated;opacity:.8;display:block}.PlayerHUD-module__-E1Scq__Reticle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.PlayerHUD-module__-E1Scq__ReticleImage{opacity:.85;width:64px;height:64px;image-rendering:pixelated}.PlayerHUD-module__-E1Scq__ReticleDot{background:#2ecc40b3;border-radius:50%;width:4px;height:4px;box-shadow:0 0 4px #2ecc4080}
.page-module__v6zvCa__CanvasContainer{z-index:0;position:absolute;inset:0}.page-module__v6zvCa__LoadingIndicator{pointer-events:none;z-index:1;opacity:.8;flex-direction:column;align-items:center;gap:16px;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.page-module__v6zvCa__LoadingIndicator[data-complete=true]{animation:.3s ease-out forwards page-module__v6zvCa__loadingComplete}.page-module__v6zvCa__Spinner{border:4px solid #fff3;border-top-color:#fff;border-radius:50%;width:48px;height:48px;animation:1s linear infinite page-module__v6zvCa__spin}@keyframes page-module__v6zvCa__spin{to{transform:rotate(360deg)}}@keyframes page-module__v6zvCa__loadingComplete{0%{opacity:1}to{opacity:0}}.page-module__v6zvCa__Sidebar{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#fff;z-index:2;background:#000000b3;flex-direction:column;width:260px;font-size:13px;display:flex;position:fixed;top:0;bottom:0;left:0;overflow:hidden}.page-module__v6zvCa__SidebarSection{border-bottom:1px solid #ffffff1a;padding:10px 12px}.page-module__v6zvCa__SidebarSection:last-child{border-bottom:none}.page-module__v6zvCa__SectionLabel{text-transform:uppercase;letter-spacing:.05em;color:#fff6;margin-bottom:6px;font-size:10px}.page-module__v6zvCa__AnimationList{flex:1;padding:0 12px 12px;overflow-y:auto}.page-module__v6zvCa__AnimationItem{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:4px;align-items:center;gap:6px;padding:4px 6px;display:flex}.page-module__v6zvCa__AnimationItem:hover{background:#ffffff14}.page-module__v6zvCa__AnimationItem[data-active=true]{background:#ffffff26}.page-module__v6zvCa__PlayButton{color:#fff9;cursor:pointer;background:#ffffff1a;border:none;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;width:22px;height:22px;padding:0;font-size:11px;display:flex}.page-module__v6zvCa__PlayButton:hover{color:#fff;background:#fff3}.page-module__v6zvCa__AnimationItem[data-active=true] .page-module__v6zvCa__PlayButton{color:#fff;background:#64b4ff4d}.page-module__v6zvCa__AnimationName{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.page-module__v6zvCa__ClipName{color:#ffffff4d;white-space:nowrap;flex-shrink:0;font-size:10px}.page-module__v6zvCa__CyclicIcon{color:#ffffff4d;title:"Cyclic (looping)";flex-shrink:0;font-size:13px}.page-module__v6zvCa__CheckboxField{align-items:center;gap:6px;display:flex}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -8,7 +8,7 @@
a:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
c:I[68027,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
0:{"P":null,"b":"JablvlklHXp4NGWk4TTlC","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
0:{"P":null,"b":"MrEXjMpW9X_XkpwJ2-Z7S","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]
d:I[27201,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"IconMark"]
7:null

View file

@ -3,4 +3,4 @@
3:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"IconMark"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}

View file

@ -3,4 +3,4 @@
3:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,5 +1,5 @@
1:"$Sreact.fragment"
2:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
3:"$Sreact.suspense"
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
4:null

View file

@ -1,4 +1,4 @@
1:"$Sreact.fragment"
2:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
3:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,2 +1,2 @@
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}

File diff suppressed because one or more lines are too long

View file

@ -8,7 +8,7 @@
a:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
c:I[68027,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
0:{"P":null,"b":"JablvlklHXp4NGWk4TTlC","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
0:{"P":null,"b":"MrEXjMpW9X_XkpwJ2-Z7S","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],null]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]
d:I[27201,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"IconMark"]
7:null

File diff suppressed because one or more lines are too long

View file

@ -3,16 +3,16 @@
3:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
5:I[47257,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ClientPageRoot"]
6:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/2400be5b6a2e4806.js","/t2-mapper/_next/static/chunks/164bc8495505bc95.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/ee88398bb27ad4a1.js","/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","/t2-mapper/_next/static/chunks/b07469fc6c6cd3bb.js","/t2-mapper/_next/static/chunks/629d98e413c0344a.js","/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","/t2-mapper/_next/static/chunks/49bf5eb2ca42014f.js"],"default"]
6:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/ac206141b5c1882c.js","/t2-mapper/_next/static/chunks/164bc8495505bc95.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/c293826b70147a61.js","/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","/t2-mapper/_next/static/chunks/534ef931e88eb989.js","/t2-mapper/_next/static/chunks/5b60a5e8e48bc409.js","/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","/t2-mapper/_next/static/chunks/e4a5074909f0df67.js"],"default"]
9:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
a:"$Sreact.suspense"
c:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ViewportBoundary"]
e:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
10:I[68027,[],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
:HL["/t2-mapper/_next/static/chunks/bd03a29a57c8ca45.css","style"]
0:{"P":null,"b":"JablvlklHXp4NGWk4TTlC","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/bd03a29a57c8ca45.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/2400be5b6a2e4806.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/164bc8495505bc95.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/ee88398bb27ad4a1.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/b07469fc6c6cd3bb.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/629d98e413c0344a.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/49bf5eb2ca42014f.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
:HL["/t2-mapper/_next/static/chunks/9542fc38c512bd1a.css","style"]
0:{"P":null,"b":"MrEXjMpW9X_XkpwJ2-Z7S","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/9542fc38c512bd1a.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/ac206141b5c1882c.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/164bc8495505bc95.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/c293826b70147a61.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/576b06837c0cb7a0.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/534ef931e88eb989.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/5b60a5e8e48bc409.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9eaea0ae086bad69.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/037fbc56cebf7caa.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/e4a5074909f0df67.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
7:{}
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]

View file

@ -3,15 +3,15 @@
3:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
5:I[47257,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ClientPageRoot"]
6:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","/t2-mapper/_next/static/chunks/12261e943ff623d2.js","/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js"],"default"]
6:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","/t2-mapper/_next/static/chunks/82a44da545bac352.js","/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js"],"default"]
9:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
a:"$Sreact.suspense"
c:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ViewportBoundary"]
e:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
10:I[68027,[],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
0:{"P":null,"b":"JablvlklHXp4NGWk4TTlC","c":["","shapes",""],"q":"","i":false,"f":[[["",{"children":["shapes",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/12261e943ff623d2.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
0:{"P":null,"b":"MrEXjMpW9X_XkpwJ2-Z7S","c":["","shapes",""],"q":"","i":false,"f":[[["",{"children":["shapes",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/82a44da545bac352.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
7:{}
8:"$0:f:0:1:1:children:1:children:0:props:children:0:props:serverProvidedParams:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]

View file

@ -3,4 +3,4 @@
3:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"IconMark"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"MapGenius  Explore maps for Tribes 2"}],["$","meta","1",{"name":"description","content":"Tribes 2 forever."}],["$","link","2",{"rel":"icon","href":"/t2-mapper/icon.png?icon.2911bba1.png","sizes":"108x128","type":"image/png"}],["$","$L5","3",{}]]}]}]}],null]}],"loading":null,"isPartial":false}

View file

@ -3,4 +3,4 @@
3:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","template":["$","$L4",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,3 +1,3 @@
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"shapes","paramType":null,"paramKey":"shapes","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"shapes","paramType":null,"paramKey":"shapes","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}

View file

@ -1,10 +1,10 @@
1:"$Sreact.fragment"
2:I[47257,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ClientPageRoot"]
3:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","/t2-mapper/_next/static/chunks/12261e943ff623d2.js","/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js"],"default"]
3:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","/t2-mapper/_next/static/chunks/82a44da545bac352.js","/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js"],"default"]
6:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
7:"$Sreact.suspense"
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/12261e943ff623d2.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","async":true}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/82a44da545bac352.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","async":true}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
4:{}
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
8:null

View file

@ -1,4 +1,4 @@
1:"$Sreact.fragment"
2:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
3:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
0:{"buildId":"JablvlklHXp4NGWk4TTlC","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"MrEXjMpW9X_XkpwJ2-Z7S","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}

File diff suppressed because one or more lines are too long

View file

@ -3,15 +3,15 @@
3:I[39756,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
4:I[37457,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"default"]
5:I[47257,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ClientPageRoot"]
6:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","/t2-mapper/_next/static/chunks/12261e943ff623d2.js","/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js"],"default"]
6:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","/t2-mapper/_next/static/chunks/82a44da545bac352.js","/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/1a2c6dc513278881.js"],"default"]
9:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
a:"$Sreact.suspense"
c:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"ViewportBoundary"]
e:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"MetadataBoundary"]
10:I[68027,[],"default"]
:HL["/t2-mapper/_next/static/chunks/e620039d1c837dab.css","style"]
:HL["/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","style"]
0:{"P":null,"b":"JablvlklHXp4NGWk4TTlC","c":["","shapes",""],"q":"","i":false,"f":[[["",{"children":["shapes",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/12ed5d454f7c6ac3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/12261e943ff623d2.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
:HL["/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","style"]
0:{"P":null,"b":"MrEXjMpW9X_XkpwJ2-Z7S","c":["","shapes",""],"q":"","i":false,"f":[[["",{"children":["shapes",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/e620039d1c837dab.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"defaultOptions":{"clearOnDefault":false},"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/a2e31798d9bc121e.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/88a5cf0ea7f16af4.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/14c6376ae0b23060.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/b00acbf8afd8b4b6.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/818dce712bc45cb4.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/2edeeda5ca6dc680.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/82a44da545bac352.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/e1a8caa90a5343cf.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/1a2c6dc513278881.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],null]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
7:{}
8:"$0:f:0:1:1:children:1:children:0:props:children:0:props:serverProvidedParams:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"}]]

View file

@ -116,6 +116,8 @@ export async function computeGameCRC(
let filesFound = 0;
let filesMissing = 0;
const startTime = performance.now();
console.log(
`[crc] starting computation: seed=0x${(seed >>> 0).toString(16)}, ` +
`${sorted.length} ShapeBaseData datablocks (of ${datablocks.length} total), ` +
@ -166,9 +168,11 @@ export async function computeGameCRC(
crc = (crc + totalSize) >>> 0;
const elapsed = performance.now() - startTime;
console.log(
`[crc] RESULT: ${filesFound} files CRC'd, ${filesMissing} missing, ` +
`crc=0x${crc.toString(16)}, totalSize=${totalSize}`,
`crc=0x${crc.toString(16)}, totalSize=${totalSize}, elapsed=${elapsed.toFixed(0)}ms`,
);
return { crc, totalSize };

View file

@ -79,11 +79,15 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
private smoothedPing = 0;
private lastPingEmit = 0;
constructor(address: string) {
/** Warrior name to send in the ConnectRequest. */
private warriorName: string;
constructor(address: string, options?: { warriorName?: string }) {
super();
const [host, portStr] = address.split(":");
this.host = host;
this.port = parseInt(portStr, 10);
this.warriorName = options?.warriorName || "";
// Wire up packet delivery notifications for event retransmission.
this.protocol.onNotify = (packetSeq, acked) => {
@ -316,7 +320,7 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
/** Build the connection argv (name, race/gender, skin, voice, voicePitch). */
private buildConnectArgv(): string[] {
const name = process.env.T2_ACCOUNT_NAME || "Observer";
const name = this.warriorName || process.env.T2_ACCOUNT_NAME || "Observer";
return [
name, // player name
"Male Human", // race/gender

View file

@ -79,6 +79,7 @@ wss.on("connection", (ws) => {
let gameConnection: GameConnection | null = null;
let lastJoinAddress: string | null = null;
let lastWarriorName: string | undefined;
let retryCount = 0;
let retryTimer: ReturnType<typeof setTimeout> | null = null;
@ -86,12 +87,12 @@ wss.on("connection", (ws) => {
const RETRY_DELAY_MS = 6000;
const RETRYABLE_REASONS = ["Server is cycling mission"];
async function connectToServer(ws: WebSocket, address: string): Promise<void> {
async function connectToServer(ws: WebSocket, address: string, warriorName?: string): Promise<void> {
if (gameConnection) {
gameConnection.disconnect();
}
gameConnection = new GameConnection(address);
gameConnection = new GameConnection(address, { warriorName });
// Set mapName from the cached server list if available.
const cachedServer = cachedServers.find(
@ -135,7 +136,7 @@ wss.on("connection", (ws) => {
retryTimer = setTimeout(() => {
retryTimer = null;
if (lastJoinAddress === address && ws.readyState === WebSocket.OPEN) {
connectToServer(ws, address);
connectToServer(ws, address, lastWarriorName);
}
}, RETRY_DELAY_MS);
return;
@ -242,7 +243,7 @@ wss.on("connection", (ws) => {
}
case "joinServer": {
relayLog.info({ address: message.address }, "Join server requested");
relayLog.info({ address: message.address, warriorName: message.warriorName }, "Join server requested");
if (gameConnection) {
relayLog.info("Disconnecting existing game connection");
gameConnection.disconnect();
@ -253,8 +254,9 @@ wss.on("connection", (ws) => {
}
retryCount = 0;
lastJoinAddress = message.address;
lastWarriorName = message.warriorName;
await connectToServer(ws, message.address);
await connectToServer(ws, message.address, message.warriorName);
break;
}

View file

@ -1,7 +1,7 @@
/** Messages from browser client to relay server. */
export type ClientMessage =
| { type: "listServers" }
| { type: "joinServer"; address: string }
| { type: "joinServer"; address: string; warriorName?: string }
| { type: "disconnect" }
| { type: "sendMove"; move: ClientMove }
| { type: "sendCommand"; command: string; args: string[] }

View file

@ -202,7 +202,7 @@ const sceneInfo = await page.evaluate(() => {
// Check AnimationMixer state.
let mixerInfo = "No mixer found";
scene.traverse((obj: any) => {
// The root group of DemoPlayback is the direct parent of entity groups.
// The root group of StreamPlayback is the direct parent of entity groups.
if (obj.children?.some((c: any) => c.name?.startsWith("player_"))) {
// This is the root group. Check for active animations.
const animations = (obj as any)._mixer;

View file

@ -1,9 +0,0 @@
import { useRecording } from "./RecordingProvider";
import { DemoPlaybackController } from "./DemoPlaybackController";
export function DemoPlayback() {
const recording = useRecording();
if (!recording) return null;
return <DemoPlaybackController recording={recording} />;
}

View file

@ -10,7 +10,7 @@ import { CopyCoordinatesButton } from "./CopyCoordinatesButton";
import { LoadDemoButton } from "./LoadDemoButton";
import { JoinServerButton } from "./JoinServerButton";
import { useRecording } from "./RecordingProvider";
import { useLiveConnectionOptional } from "./LiveConnection";
import { useLiveSelector } from "../state/liveConnectionStore";
import { FiInfo, FiSettings } from "react-icons/fi";
import { Camera } from "three";
import styles from "./InspectorControls.module.css";
@ -51,8 +51,7 @@ export function InspectorControls({
useControls();
const { debugMode, setDebugMode } = useDebug();
const demoRecording = useRecording();
const live = useLiveConnectionOptional();
const isLive = live?.adapter != null;
const isLive = useLiveSelector((s) => s.adapter != null);
const isStreaming = demoRecording != null || isLive;
// Hide FOV/speed controls during .rec playback (faithfully replaying),
// but show them in .mis browsing and live observer mode.

View file

@ -1,5 +1,5 @@
import { BsFillLightningChargeFill } from "react-icons/bs";
import { useLiveConnectionOptional } from "./LiveConnection";
import { useLiveSelector, selectPing } from "../state/liveConnectionStore";
import styles from "./JoinServerButton.module.css";
function formatPing(ms: number): string {
@ -11,24 +11,26 @@ export function JoinServerButton({
}: {
onOpenServerBrowser: () => void;
}) {
const live = useLiveConnectionOptional();
if (!live) return null;
const gameStatus = useLiveSelector((s) => s.gameStatus);
const serverName = useLiveSelector((s) => s.serverName);
const ping = useLiveSelector(selectPing);
const disconnectServer = useLiveSelector((s) => s.disconnectServer);
const isLive = live.gameStatus === "connected";
const isLive = gameStatus === "connected";
const isConnecting =
live.gameStatus === "connecting" ||
live.gameStatus === "challenging" ||
live.gameStatus === "authenticating";
gameStatus === "connecting" ||
gameStatus === "challenging" ||
gameStatus === "authenticating";
return (
<button
type="button"
className={styles.Root}
aria-label={isLive ? "Disconnect" : "Join server"}
title={isLive ? "Disconnect" : "Join server"}
aria-label={isLive ? `Disconnect from ${serverName ?? "server"}` : "Join server"}
title={isLive ? `Disconnect from ${serverName ?? "server"}` : "Join server"}
onClick={() => {
if (isLive) {
live.disconnectServer();
disconnectServer();
} else {
onOpenServerBrowser();
}
@ -43,9 +45,9 @@ export function JoinServerButton({
{isConnecting ? "Connecting..." : "Connect"}
</span>
)}
{isLive && (
{isLive && ping != null && (
<span className={styles.PingLabel}>
{live.ping != null ? formatPing(live.ping) : "Live"}
{formatPing(ping)}
</span>
)}
</button>

View file

@ -1,266 +1,15 @@
import {
createContext,
useContext,
useState,
useCallback,
useRef,
useEffect,
} from "react";
import { RelayClient } from "../stream/relayClient";
import { LiveStreamAdapter } from "../stream/liveStreaming";
import type {
ClientMove,
ServerInfo,
ConnectionStatus,
} from "../../relay/types";
interface LiveConnectionState {
relayConnected: boolean;
gameStatus: ConnectionStatus | null;
gameStatusMessage?: string;
/** Map name from the server being joined (from GameInfoResponse or status). */
mapName?: string;
/** Effective RTT to the game server (relay↔T2 + browser↔relay). */
ping: number | null;
/** Browser↔relay WebSocket RTT in ms. */
wsPing: number | null;
servers: ServerInfo[];
serversLoading: boolean;
adapter: LiveStreamAdapter | null;
/** True once the first ghost entity arrives (game is rendering). */
liveReady: boolean;
}
interface LiveConnectionActions {
connectRelay: (url?: string) => void;
disconnectRelay: () => void;
listServers: () => void;
joinServer: (address: string) => void;
disconnectServer: () => void;
sendMove: (move: ClientMove) => void;
sendCommand: (command: string, ...args: string[]) => void;
}
const LiveConnectionContext = createContext<
(LiveConnectionState & LiveConnectionActions) | null
>(null);
export function useLiveConnection() {
const ctx = useContext(LiveConnectionContext);
if (!ctx) {
throw new Error("useLiveConnection must be used within LiveConnectionProvider");
}
return ctx;
}
export function useLiveConnectionOptional() {
return useContext(LiveConnectionContext);
}
const DEFAULT_RELAY_URL =
process.env.NEXT_PUBLIC_RELAY_URL || "ws://localhost:8765";
import { useEffect } from "react";
import { disposeLiveConnection } from "../state/liveConnectionStore";
/** Cleanup-only provider — disposes the relay connection on unmount. */
export function LiveConnectionProvider({
children,
}: {
children: React.ReactNode;
}) {
const relayRef = useRef<RelayClient | null>(null);
const adapterRef = useRef<LiveStreamAdapter | null>(null);
// Queue of actions to run once the relay WebSocket opens.
const pendingRef = useRef<Array<() => void>>([]);
const listInFlightRef = useRef(false);
const [relayConnected, setRelayConnected] = useState(false);
const [gameStatus, setGameStatus] = useState<ConnectionStatus | null>(null);
const [gameStatusMessage, setGameStatusMessage] = useState<
string | undefined
>();
const [mapName, setMapName] = useState<string | undefined>();
const [servers, setServers] = useState<ServerInfo[]>([]);
const [serversLoading, setServersLoading] = useState(false);
const [adapter, setAdapter] = useState<LiveStreamAdapter | null>(null);
const [liveReady, setLiveReady] = useState(false);
const [relayPing, setRelayPing] = useState<number | null>(null);
const [wsPing, setWsPing] = useState<number | null>(null);
const connectRelay = useCallback((url: string = DEFAULT_RELAY_URL) => {
if (relayRef.current) {
relayRef.current.close();
relayRef.current = null;
}
const relay = new RelayClient(url, {
onOpen() {
setRelayConnected(true);
// Flush any queued actions (e.g. listServers called before open).
for (const fn of pendingRef.current) fn();
pendingRef.current = [];
},
onStatus(status, message, _connectSequence, statusMapName) {
console.log(
`[relay] game status: ${status}${message ? `${message}` : ""}${statusMapName ? ` map=${statusMapName}` : ""}`,
);
setGameStatus(status);
setGameStatusMessage(message);
if (statusMapName) {
setMapName(statusMapName);
}
},
onServerList(list) {
setServers(list);
setServersLoading(false);
listInFlightRef.current = false;
},
onGamePacket(data) {
if (!adapterRef.current) {
console.warn("[relay] received game packet but no adapter is active");
}
adapterRef.current?.feedPacket(data);
},
onPing(ms) {
setRelayPing(ms);
},
onWsPing(ms) {
setWsPing(ms);
},
onError(message) {
console.error("Relay error:", message);
setServersLoading(false);
listInFlightRef.current = false;
},
onClose() {
// Only update state if this is still the active relay.
if (relayRef.current === relay) {
relayRef.current = null;
setRelayConnected(false);
setGameStatus(null);
setMapName(undefined);
setRelayPing(null);
setWsPing(null);
setAdapter(null);
setLiveReady(false);
adapterRef.current = null;
pendingRef.current = [];
listInFlightRef.current = false;
}
},
});
relay.connect();
relayRef.current = relay;
}, []);
const disconnectRelay = useCallback(() => {
relayRef.current?.close();
relayRef.current = null;
adapterRef.current = null;
pendingRef.current = [];
setRelayConnected(false);
setGameStatus(null);
setMapName(undefined);
setAdapter(null);
setLiveReady(false);
}, []);
const listServers = useCallback(() => {
if (listInFlightRef.current) return;
listInFlightRef.current = true;
const doList = () => {
relayRef.current?.sendWsPing();
relayRef.current?.listServers();
};
setServersLoading(true);
if (relayRef.current?.connected) {
doList();
} else {
// Connect first, then list once the socket opens.
pendingRef.current.push(doList);
if (!relayRef.current) {
connectRelay();
}
}
}, [connectRelay]);
const joinServer = useCallback((address: string) => {
if (!relayRef.current) return;
// Set mapName from the cached server list immediately so the browser
// can start loading the mission before the relay even connects to the
// game server.
const cachedServer = servers.find((s) => s.address === address);
if (cachedServer?.mapName) {
setMapName(cachedServer.mapName);
}
const newAdapter = new LiveStreamAdapter(relayRef.current);
newAdapter.onReady = () => setLiveReady(true);
adapterRef.current = newAdapter;
setLiveReady(false);
setGameStatus(null);
setAdapter(newAdapter);
relayRef.current.joinServer(address);
}, [servers]);
const disconnectServer = useCallback(() => {
relayRef.current?.disconnectServer();
adapterRef.current?.reset();
adapterRef.current = null;
setAdapter(null);
setLiveReady(false);
setGameStatus(null);
setMapName(undefined);
setRelayPing(null);
}, []);
const sendMove = useCallback((move: ClientMove) => {
relayRef.current?.sendMove(move);
}, []);
const sendCommand = useCallback((command: string, ...args: string[]) => {
relayRef.current?.sendCommand(command, args);
}, []);
// Clean up on unmount.
useEffect(() => {
return () => {
relayRef.current?.close();
};
return () => disposeLiveConnection();
}, []);
// Effective RTT = relay↔T2 RTT + browser↔relay RTT.
const ping =
relayPing != null && wsPing != null
? relayPing + wsPing
: relayPing ?? null;
const value: LiveConnectionState & LiveConnectionActions = {
relayConnected,
gameStatus,
gameStatusMessage,
mapName,
ping,
wsPing,
servers,
serversLoading,
adapter,
liveReady,
connectRelay,
disconnectRelay,
listServers,
joinServer,
disconnectServer,
sendMove,
sendCommand,
};
return (
<LiveConnectionContext.Provider value={value}>
{children}
</LiveConnectionContext.Provider>
);
return children;
}

View file

@ -2,7 +2,7 @@ import { useRef, useEffect } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { Vector3 } from "three";
import { useKeyboardControls } from "@react-three/drei";
import { useLiveConnection } from "./LiveConnection";
import { useLiveSelector } from "../state/liveConnectionStore";
import { useEngineStoreApi } from "../state/engineStore";
import { streamPlaybackStore } from "../state/streamPlaybackStore";
import { Controls, MOUSE_SENSITIVITY, ARROW_LOOK_SPEED } from "./ObserverControls";
@ -44,7 +44,9 @@ interface PredictionState {
* Tribes 2 client works (predict locally, correct from server).
*/
export function LiveObserver() {
const { adapter, gameStatus, sendMove } = useLiveConnection();
const adapter = useLiveSelector((s) => s.adapter);
const gameStatus = useLiveSelector((s) => s.gameStatus);
const sendMove = useLiveSelector((s) => s.sendMove);
const store = useEngineStoreApi();
const { speedMultiplier } = useControls();
const activeAdapterRef = useRef<LiveStreamAdapter | null>(null);
@ -131,11 +133,13 @@ export function LiveObserver() {
};
}, [gl.domElement]);
// Left-click when pointer-locked: enter follow mode (from fly) or cycle
// to next player (in follow). Capture phase intercepts before ObserverControls.
// Left-click when pointer-locked in follow mode: cycle to next player.
// Only intercepts in follow mode — in fly mode, clicks pass through to
// ObserverControls for pointer lock acquisition.
useEffect(() => {
const handleClick = (e: MouseEvent) => {
if (!document.pointerLockElement || !activeAdapterRef.current) return;
if (activeAdapterRef.current.observerMode !== "follow") return;
e.stopImmediatePropagation();
activeAdapterRef.current.cycleObserveNext();
};
@ -209,8 +213,11 @@ export function LiveObserver() {
// Reset sub-tick accumulator for interpolation.
tickAccRef.current = 0;
// Scale movement axes by speed multiplier. Values > 1 still clamp to
// [-1, 1] server-side, but < 1 lets the user move slower.
// Always set trigger[1] (altTrigger) to enable the server's 2× speed mode
// (80 u/s max). We use altTrigger instead of trigger[0] (fire) because the
// Observer::onTrigger script interprets fire as "join team" / "cycle player"
// depending on camera mode, but altTrigger is unhandled in all observer modes.
// The C++ Camera::processTick checks `trigger[0] || trigger[1]` for fast mode.
const speed = Math.min(1, speedMultiplier);
sendMove({
x: mx * speed,
@ -219,13 +226,13 @@ export function LiveObserver() {
yaw,
pitch,
roll: 0,
trigger: [false, false, false, false, false, false],
trigger: [false, true, false, false, false, false],
freeLook: false,
});
});
// Override camera rotation with predicted values at frame rate.
// Priority 1 ensures this runs AFTER DemoPlaybackController (priority 0),
// Priority 1 ensures this runs AFTER StreamingController (priority 0),
// which handles position from server snapshots.
useFrame((state, delta) => {
if (!activeAdapterRef.current || gameStatus !== "connected") return;
@ -298,7 +305,11 @@ export function LiveObserver() {
const cx = Math.cos(interpPitch);
const sz = Math.sin(interpYaw);
const cz = Math.cos(interpYaw);
_orbitDir.set(-cx, -sz * sx, -cz * sx);
// Camera pulls back along negative forward direction (Torque column 1
// of Rz*Rx, converted to Three.js coords).
// Torque forward = (-sz*cx, cz*cx, sx) → Three.js = (cz*cx, sx, -sz*cx)
// Negate for pull-back: (-cz*cx, -sx, sz*cx)
_orbitDir.set(-cz * cx, -sx, sz * cx);
if (_orbitDir.lengthSq() > 1e-8) {
_orbitDir.normalize();
@ -309,10 +320,10 @@ export function LiveObserver() {
}
} else {
// Observer fly or first-person: override rotation only (position comes
// from DemoPlaybackController's server snapshot interpolation).
// from StreamingController's server snapshot interpolation).
state.camera.quaternion.set(qx, qy, qz, qw);
}
});
}, 1);
// Clean up on unmount.
useEffect(() => {

View file

@ -180,7 +180,7 @@ function CameraMovement() {
useFrame((state, delta) => {
// When streaming is active and not in free-fly mode, the stream
// (DemoPlaybackController) drives the camera — skip our movement.
// (StreamingController) drives the camera — skip our movement.
const spState = streamPlaybackStore.getState();
if (spState.playback && !spState.freeFlyCamera) return;

View file

@ -89,35 +89,94 @@
/* ── Chat Window (top-left) ── */
.ChatWindow {
.ChatContainer {
position: absolute;
top: 56px;
left: 0;
max-width: 420px;
display: flex;
flex-direction: column;
pointer-events: auto;
border: 1px solid rgba(44, 172, 181, 0.4);
}
.ChatWindow {
max-width: 450px;
max-height: 12.5em;
overflow-y: auto;
background: rgba(0, 50, 60, 0.65);
padding: 4px 8px;
padding: 6px 8px;
user-select: text;
font-size: 12px;
line-height: 1.3;
line-height: 1.333333;
/* Thin scrollbar that doesn't take much space. */
scrollbar-width: thin;
scrollbar-color: rgba(44, 172, 181, 0.4) transparent;
}
.ChatMessage {
padding: 1px 0;
transition: opacity 0.3s ease-out;
/* Default to \c0 (GuiChatHudProfile fontColor) for untagged messages. */
color: rgb(44, 172, 181);
}
.ChatInputForm {
display: flex;
}
.ChatInput {
width: 100%;
background: rgba(0, 50, 60, 0.8);
border: 0;
border-top: 1px solid rgba(78, 179, 167, 0.2);
border-radius: 0;
color: rgb(40, 231, 240);
font-family: inherit;
font-size: 12px;
margin: 0;
padding: 6px 8px;
outline: none;
}
.ChatInput::placeholder {
color: rgba(44, 172, 181, 0.5);
}
.ChatInput:focus {
background: rgba(0, 50, 60, 0.9);
}
/* T2 GuiChatHudProfile fontColors palette (\c0\c9). */
.ChatColor0 { color: rgb(44, 172, 181); }
.ChatColor1 { color: rgb(4, 235, 105); }
.ChatColor2 { color: rgb(219, 200, 128); }
.ChatColor3 { color: rgb(77, 253, 95); }
.ChatColor4 { color: rgb(40, 231, 240); }
.ChatColor5 { color: rgb(200, 200, 50); }
.ChatColor6 { color: rgb(200, 200, 200); }
.ChatColor7 { color: rgb(220, 220, 20); }
.ChatColor8 { color: rgb(150, 150, 250); }
.ChatColor9 { color: rgb(60, 220, 150); }
.ChatColor0 {
color: rgb(44, 172, 181);
}
.ChatColor1 {
color: rgb(4, 235, 105);
}
.ChatColor2 {
color: rgb(219, 200, 128);
}
.ChatColor3 {
color: rgb(77, 253, 95);
}
.ChatColor4 {
color: rgb(40, 231, 240);
}
.ChatColor5 {
color: rgb(200, 200, 50);
}
.ChatColor6 {
color: rgb(200, 200, 200);
}
.ChatColor7 {
color: rgb(220, 220, 20);
}
.ChatColor8 {
color: rgb(150, 150, 250);
}
.ChatColor9 {
color: rgb(60, 220, 150);
}
/* ── Team Scores (bottom-left) ── */

View file

@ -1,6 +1,8 @@
import { useRef, useEffect, useState, useCallback } from "react";
import { useRecording } from "./RecordingProvider";
import { useEngineSelector } from "../state";
import { textureToUrl } from "../loaders";
import { liveConnectionStore } from "../state/liveConnectionStore";
import type {
ChatSegment,
ChatMessage,
@ -266,50 +268,72 @@ function chatColorClass(msg: ChatMessage): string {
// byte color code, so the correct default for server messages is c0.
return CHAT_COLOR_CLASSES[0];
}
function ChatWindow() {
function ChatWindow({ isLive }: { isLive: boolean }) {
const messages = useEngineSelector(
(state) => state.playback.streamSnapshot?.chatMessages,
);
const timeSec = useEngineSelector(
(state) => state.playback.streamSnapshot?.timeSec,
const scrollRef = useRef<HTMLDivElement>(null);
const prevCountRef = useRef(0);
const [chatText, setChatText] = useState("");
// Auto-scroll to bottom when new messages arrive.
const msgCount = messages?.length ?? 0;
useEffect(() => {
if (msgCount > prevCountRef.current && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
prevCountRef.current = msgCount;
}, [msgCount]);
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
const text = chatText.trim();
if (!text) return;
liveConnectionStore.getState().sendCommand("messageSent", text);
setChatText("");
},
[chatText],
);
if (!messages || !messages.length || timeSec == null) return null;
const fadeStart = 6;
const fadeDuration = 1.5;
const cutoff = timeSec - (fadeStart + fadeDuration);
const visible = messages.filter(
(m: ChatMessage) => m.timeSec > cutoff && m.text.trim() !== "",
);
if (!visible.length) return null;
const hasMessages = !!messages?.length;
return (
<div className={styles.ChatWindow}>
{visible.map((msg: ChatMessage, i: number) => {
const age = timeSec - msg.timeSec;
const opacity =
age <= fadeStart
? 1
: Math.max(0, 1 - (age - fadeStart) / fadeDuration);
return (
<div
key={`${msg.timeSec}-${i}`}
className={styles.ChatMessage}
style={{ opacity }}
>
{msg.segments ? (
msg.segments.map((seg: ChatSegment, j: number) => (
<span key={j} className={segmentColorClass(seg.colorCode)}>
{seg.text}
<div className={styles.ChatContainer}>
{hasMessages && (
<div ref={scrollRef} className={styles.ChatWindow}>
{messages!.map((msg: ChatMessage, i: number) => (
<div key={msg.id} className={styles.ChatMessage}>
{msg.segments ? (
msg.segments.map((seg: ChatSegment, j: number) => (
<span key={j} className={segmentColorClass(seg.colorCode)}>
{seg.text}
</span>
))
) : (
<span className={chatColorClass(msg)}>
{msg.sender ? `${msg.sender}: ` : ""}
{msg.text}
</span>
))
) : (
<span className={chatColorClass(msg)}>
{msg.sender ? `${msg.sender}: ` : ""}
{msg.text}
</span>
)}
</div>
);
})}
)}
</div>
))}
</div>
)}
{isLive && (
<form className={styles.ChatInputForm} onSubmit={handleSubmit}>
<input
className={styles.ChatInput}
type="text"
placeholder="Say something…"
value={chatText}
onChange={(e) => setChatText(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
onKeyUp={(e) => e.stopPropagation()}
maxLength={255}
/>
</form>
)}
</div>
);
}
@ -434,28 +458,31 @@ function PackAndInventoryHUD() {
);
}
// ── Main HUD ──
export function PlayerHUD() {
export function PlayerHUD({ isLive = false }: { isLive?: boolean } = {}) {
const recording = useRecording();
const streamSnapshot = useEngineSelector(
(state) => state.playback.streamSnapshot,
);
if (!recording) return null;
if (!recording && !isLive) return null;
const status = streamSnapshot?.status;
if (!status) return null;
return (
<div className={styles.PlayerHUD}>
<ChatWindow />
<div className={styles.TopRight}>
<div className={styles.Bars}>
<HealthBar value={status.health} />
<EnergyBar value={status.energy} />
</div>
<Compass yaw={streamSnapshot?.camera?.yaw} />
</div>
<WeaponHUD />
<PackAndInventoryHUD />
<TeamScores />
<Reticle />
<ChatWindow isLive={isLive} />
{status && (
<>
<div className={styles.TopRight}>
<div className={styles.Bars}>
<HealthBar value={status.health} />
<EnergyBar value={status.energy} />
</div>
<Compass yaw={streamSnapshot?.camera?.yaw} />
</div>
<WeaponHUD />
<PackAndInventoryHUD />
<TeamScores />
<Reticle />
</>
)}
</div>
);
}

View file

@ -56,11 +56,11 @@ function getArmThread(weaponShape: string | undefined): string {
return "lookde";
}
/** Number of table actions in the engine's ActionAnimationList. */
const NUM_TABLE_ACTION_ANIMS = 7;
/** Number of table actions in the engine's ActionAnimationList (Tribes2.exe build 25034). */
const NUM_TABLE_ACTION_ANIMS = 8;
/** Table action names in engine order (indices 0-6). */
const TABLE_ACTION_NAMES = ["root", "run", "back", "side", "fall", "jump", "land"];
/** Table action names in engine order (indices 0-7). */
const TABLE_ACTION_NAMES = ["root", "run", "back", "side", "fall", "jet", "jump", "land"];
interface ActionAnimEntry {
@ -75,8 +75,8 @@ interface ActionAnimEntry {
* TSShapeConstructor's sequence entries (e.g. `"heavy_male_root.dsq root"`).
*
* The engine builds its action list as:
* 1. Table actions (0-6): found by searching for aliased names (root, run, etc.)
* 2. Non-table actions (7+): remaining sequences in TSShapeConstructor order.
* 1. Table actions (0-7): found by searching for aliased names (root, run, etc.)
* 2. Non-table actions (8+): remaining sequences in TSShapeConstructor order.
*/
function buildActionAnimMap(
sequences: string[],
@ -157,8 +157,8 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
: undefined;
});
// Clone scene preserving skeleton bindings, create mixer, find Mount0 bone.
const { clonedScene, mixer, mount0, iflInitializers } = useMemo(() => {
// Clone scene preserving skeleton bindings, create mixer, find mount bones.
const { clonedScene, mixer, mount0, mount1, iflInitializers } = useMemo(() => {
const scene = SkeletonUtils.clone(gltf.scene) as Group;
const iflInits = processShapeScene(scene);
@ -174,11 +174,13 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
const mix = new AnimationMixer(scene);
let m0: Object3D | null = null;
let m1: Object3D | null = null;
scene.traverse((n) => {
if (!m0 && n.name === "Mount0") m0 = n;
if (!m1 && n.name === "Mount1") m1 = n;
});
return { clonedScene: scene, mixer: mix, mount0: m0, iflInitializers: iflInits };
return { clonedScene: scene, mixer: mix, mount0: m0, mount1: m1, iflInitializers: iflInits };
}, [gltf]);
// Build case-insensitive clip lookup with alias support.
@ -314,6 +316,8 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
const [currentWeaponShape, setCurrentWeaponShape] = useState(
entity.weaponShape,
);
const packShapeRef = useRef(entity.packShape);
const [currentPackShape, setCurrentPackShape] = useState(entity.packShape);
// Per-frame animation selection and mixer update.
useFrame((_, delta) => {
@ -321,6 +325,10 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
weaponShapeRef.current = entity.weaponShape;
setCurrentWeaponShape(entity.weaponShape);
}
if (entity.packShape !== packShapeRef.current) {
packShapeRef.current = entity.packShape;
setCurrentPackShape(entity.packShape);
}
const playback = engineStore.getState().playback;
const isPlaying = playback.status === "playing";
const time = streamPlaybackStore.getState().time;
@ -430,6 +438,8 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
const anim = pickMoveAnimation(
kf?.velocity,
kf?.rotation ?? [0, 0, 0, 1],
entity.falling,
entity.jetting,
);
const prev = currentAnimRef.current;
@ -518,6 +528,16 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
</Suspense>
</ShapeErrorBoundary>
)}
{currentPackShape && mount1 && (
<ShapeErrorBoundary key={currentPackShape} fallback={null}>
<Suspense fallback={null}>
<MountedPackModel
packShape={currentPackShape}
mountBone={mount1}
/>
</Suspense>
</ShapeErrorBoundary>
)}
</>
);
}
@ -896,6 +916,61 @@ function applyWeaponAnim(
}
}
/**
* Attaches a pack shape to the player's Mount1 bone. Packs are static
* mounted images (no state machine or animation) just positioned via
* the pack shape's Mountpoint node inverse offset, same as weapons.
*/
function MountedPackModel({
packShape,
mountBone,
}: {
packShape: string;
mountBone: Object3D;
}) {
const packGltf = useStaticShape(packShape);
const { packClone, packIflInitializers } = useMemo(() => {
const clone = SkeletonUtils.clone(packGltf.scene) as Group;
const iflInits = processShapeScene(clone);
// Compute Mountpoint inverse offset so the pack aligns to Mount1.
const mp = getPosedNodeTransform(
packGltf.scene,
packGltf.animations,
"Mountpoint",
);
if (mp) {
const invQuat = mp.quaternion.clone().invert();
const invPos = mp.position.clone().negate().applyQuaternion(invQuat);
clone.position.copy(invPos);
clone.quaternion.copy(invQuat);
}
return { packClone: clone, packIflInitializers: iflInits };
}, [packGltf]);
useEffect(() => {
mountBone.add(packClone);
return () => {
mountBone.remove(packClone);
};
}, [packClone, mountBone]);
// Initialize IFL materials (animated texture sequences).
useEffect(() => {
const cleanups: (() => void)[] = [];
for (const { mesh, initialize } of packIflInitializers) {
initialize(mesh, () => streamPlaybackStore.getState().time)
.then((dispose) => cleanups.push(dispose))
.catch(() => {});
}
return () => cleanups.forEach((fn) => fn());
}, [packIflInitializers]);
return null;
}
/**
* Extracts the eye offset from a player model's Eye bone in the idle ("Root"
* animation) pose. The Eye node is a child of "Bip01 Head" in the skeleton

View file

@ -36,16 +36,7 @@ export function PlayerNameplate({ entity }: { entity: PlayerEntity }) {
const fillRef = useRef<HTMLDivElement>(null);
const iffImgRef = useRef<HTMLImageElement>(null);
const [isVisible, setIsVisible] = useState(true);
const displayName = useMemo(() => {
if (entity.playerName) return entity.playerName;
if (typeof entity.id === "string") {
const m = entity.id.match(/\d+/);
if (m) return `<Player #${m[0]}>`;
return entity.id;
}
return "<Player>";
}, [entity.id, entity.playerName]);
const nameRef = useRef<HTMLDivElement>(null);
// Derive IFF height from the shape's bounding box.
const iffHeight = useMemo(() => {
@ -106,6 +97,15 @@ export function PlayerNameplate({ entity }: { entity: PlayerEntity }) {
nameContainerRef.current.style.opacity = opacityStr;
}
// Update player name imperatively — entity.playerName is mutated in-place
// by streaming playback without triggering re-renders.
if (nameRef.current) {
const name = entity.playerName ?? entity.id;
if (nameRef.current.textContent !== name) {
nameRef.current.textContent = name;
}
}
// Update IFF arrow image imperatively — entity.iffColor is mutated in-place
// by streaming playback without triggering re-renders.
if (iffImgRef.current && entity.iffColor) {
@ -148,7 +148,9 @@ export function PlayerNameplate({ entity }: { entity: PlayerEntity }) {
</Html>
<Html position={[0, NAME_HEIGHT, 0]} center>
<div ref={nameContainerRef} className={styles.Bottom}>
<div className={styles.Name}>{displayName}</div>
<div ref={nameRef} className={styles.Name}>
{entity.playerName ?? entity.id}
</div>
{hasHealthData && (
<div className={styles.HealthBar}>
<div ref={fillRef} className={styles.HealthFill} />

View file

@ -147,16 +147,49 @@
.JoinButton {
composes: DialogButton from "./DialogButton.module.css";
min-width: 100px;
}
.CloseButton {
composes: Secondary from "./DialogButton.module.css";
}
.WarriorField {
display: flex;
align-items: center;
gap: 8px;
}
.WarriorLabel {
font-size: 12px;
color: rgba(125, 255, 255, 0.6);
font-weight: 500;
}
.WarriorInput {
width: 130px;
padding: 4px 6px;
background: rgba(0, 50, 60, 0.8);
border: 1px solid rgba(65, 131, 139, 0.5);
border-radius: 2px;
color: #b0d5c9;
font-family: inherit;
font-size: 13px;
outline: none;
}
.WarriorInput:focus {
border-color: rgba(125, 255, 255, 0.6);
}
.WarriorInput::placeholder {
color: rgba(201, 220, 216, 0.3);
}
.Hint {
font-size: 12px;
color: rgba(201, 220, 216, 0.3);
margin-left: auto;
margin: 0 auto;
}
@media (max-width: 719px) {

View file

@ -9,6 +9,8 @@ export function ServerBrowser({
onRefresh,
onJoin,
wsPing,
warriorName,
onWarriorNameChange,
}: {
open: boolean;
onClose: () => void;
@ -18,6 +20,8 @@ export function ServerBrowser({
onJoin: (address: string) => void;
/** Browser↔relay RTT to add to server pings for effective latency. */
wsPing?: number | null;
warriorName: string;
onWarriorNameChange: (name: string) => void;
}) {
const [selectedAddress, setSelectedAddress] = useState<string | null>(null);
const [sortKey, setSortKey] = useState<keyof ServerInfo>("ping");
@ -176,6 +180,24 @@ export function ServerBrowser({
</table>
</div>
<div className={styles.Footer}>
<div className={styles.WarriorField}>
<label className={styles.WarriorLabel} htmlFor="warriorName">
Warrior
</label>
<input
id="warriorName"
className={styles.WarriorInput}
type="text"
value={warriorName}
onChange={(e) => onWarriorNameChange(e.target.value)}
placeholder="Name thyself…"
maxLength={24}
/>
</div>
<span className={styles.Hint}>Double-click a server to join</span>
<button onClick={onClose} className={styles.CloseButton}>
Cancel
</button>
<button
onClick={handleJoin}
disabled={!selectedAddress}
@ -183,10 +205,6 @@ export function ServerBrowser({
>
Join
</button>
<button onClick={onClose} className={styles.CloseButton}>
Cancel
</button>
<span className={styles.Hint}>Double-click a server to join</span>
</div>
</div>
</div>

View file

@ -25,6 +25,8 @@ type SettingsContext = {
setAudioEnabled: StateSetter<boolean>;
animationEnabled: boolean;
setAnimationEnabled: StateSetter<boolean>;
warriorName: string;
setWarriorName: StateSetter<string>;
};
type DebugContext = {
@ -52,6 +54,7 @@ type PersistedSettings = {
animationEnabled?: boolean;
debugMode?: boolean;
touchMode?: TouchMode;
warriorName?: string;
};
export function useSettings() {
@ -83,6 +86,7 @@ export function SettingsProvider({
const [animationEnabled, setAnimationEnabled] = useState(true);
const [debugMode, setDebugMode] = useState(false);
const [touchMode, setTouchMode] = useState<TouchMode>("moveLookStick");
const [warriorName, setWarriorName] = useState("MapGenius");
const setFogEnabledWithoutOverride: StateSetter<boolean> = useCallback(
(value) => {
@ -104,6 +108,8 @@ export function SettingsProvider({
setAudioEnabled,
animationEnabled,
setAnimationEnabled,
warriorName,
setWarriorName,
}),
[
fogEnabled,
@ -113,6 +119,7 @@ export function SettingsProvider({
fov,
audioEnabled,
animationEnabled,
warriorName,
],
);
@ -158,6 +165,9 @@ export function SettingsProvider({
if (savedSettings.touchMode != null) {
setTouchMode(savedSettings.touchMode);
}
if (savedSettings.warriorName != null) {
setWarriorName(savedSettings.warriorName);
}
}, []);
// Persist settings to localStorage with debouncing to avoid excessive writes
@ -180,6 +190,7 @@ export function SettingsProvider({
animationEnabled,
debugMode,
touchMode,
warriorName,
};
try {
localStorage.setItem("settings", JSON.stringify(settingsToSave));
@ -202,6 +213,7 @@ export function SettingsProvider({
animationEnabled,
debugMode,
touchMode,
warriorName,
]);
return (

View file

@ -0,0 +1,9 @@
import { useRecording } from "./RecordingProvider";
import { StreamingController } from "./StreamingController";
export function StreamPlayback() {
const recording = useRecording();
if (!recording) return null;
return <StreamingController recording={recording} />;
}

View file

@ -48,6 +48,9 @@ function mutateRenderFields(
const e = renderEntity as unknown as Record<string, unknown>;
e.threads = stream.threads;
e.weaponShape = stream.weaponShape;
e.packShape = stream.packShape;
e.falling = stream.falling;
e.jetting = stream.jetting;
e.weaponImageState = stream.weaponImageState;
e.weaponImageStates = stream.weaponImageStates;
e.playerName = stream.playerName;
@ -96,7 +99,7 @@ const _orbitDir = new Vector3();
const _orbitTarget = new Vector3();
const _orbitCandidate = new Vector3();
export function DemoPlaybackController({ recording }: { recording: StreamRecording }) {
export function StreamingController({ recording }: { recording: StreamRecording }) {
const engineStore = useEngineStoreApi();
const playbackClockRef = useRef(0);
const prevTickSnapshotRef = useRef<StreamSnapshot | null>(null);
@ -235,7 +238,13 @@ export function DemoPlaybackController({ recording }: { recording: StreamRecordi
return;
}
stream.reset();
// Reset the stream cursor for demo playback (replay from the beginning).
// For live streams, skip reset — the adapter is already receiving packets
// and has accumulated protocol state (net strings, target info, sensor
// group colors) that the server won't re-send.
if (recording.source !== "live") {
stream.reset();
}
// Preload weapon effect shapes (explosions) so they're cached before
// the first projectile detonates -- otherwise the GLB fetch latency
// causes the short-lived explosion entity to expire before it renders.
@ -354,7 +363,7 @@ export function DemoPlaybackController({ recording }: { recording: StreamRecordi
// ObserverControls drives the camera instead.
const freeFly = streamPlaybackStore.getState().freeFlyCamera;
// In live mode, LiveObserver owns camera rotation (client-side prediction).
// DemoPlaybackController still handles position, FOV, and entity interpolation.
// StreamingController still handles position, FOV, and entity interpolation.
const isLive = recording.source === "live";
if (currentCamera && !freeFly) {
@ -388,7 +397,6 @@ export function DemoPlaybackController({ recording }: { recording: StreamRecordi
}
if (
!isLive &&
Number.isFinite(currentCamera.fov) &&
"isPerspectiveCamera" in state.camera &&
(state.camera as any).isPerspectiveCamera
@ -476,6 +484,8 @@ export function DemoPlaybackController({ recording }: { recording: StreamRecordi
}
const mode = currentCamera?.mode;
// In live mode, LiveObserver handles orbit positioning from predicted
// angles so the orbit responds at frame rate. Skip here to avoid fighting.
if (!freeFly && !isLive && mode === "third-person" && root && currentCamera?.orbitTargetId) {
const targetGroup = root.children.find(
(child) => child.name === currentCamera.orbitTargetId,
@ -490,7 +500,16 @@ export function DemoPlaybackController({ recording }: { recording: StreamRecordi
}
let hasDirection = false;
if (
if (currentCamera.orbitDirection) {
// Use explicit pullback direction (e.g. from full vehicle quaternion
// including roll) when available.
_orbitDir.set(
currentCamera.orbitDirection[0],
currentCamera.orbitDirection[1],
currentCamera.orbitDirection[2],
);
hasDirection = _orbitDir.lengthSq() > 1e-8;
} else if (
typeof currentCamera.yaw === "number" &&
typeof currentCamera.pitch === "number"
) {
@ -498,10 +517,10 @@ export function DemoPlaybackController({ recording }: { recording: StreamRecordi
const cx = Math.cos(currentCamera.pitch);
const sz = Math.sin(currentCamera.yaw);
const cz = Math.cos(currentCamera.yaw);
// Camera::validateEyePoint uses Camera::setPosition's column1 in
// Torque space as the orbit pull-back direction. Converted to Three,
// that target->camera vector is (-cx, -sz*sx, -cz*sx).
_orbitDir.set(-cx, -sz * sx, -cz * sx);
// Pull back behind the model. playerYawToQuaternion uses Ry(-yaw),
// so model forward in Three.js is (cz, 0, sz) at pitch=0.
// Behind = (-cz*cx, -sx, -sz*cx).
_orbitDir.set(-cz * cx, -sx, -sz * cx);
hasDirection = _orbitDir.lengthSq() > 1e-8;
}
if (!hasDirection) {

View file

@ -311,7 +311,7 @@ export const engineStore = createStore<EngineStoreState>()(
// Components use effectNow() instead of performance.now() so that effect
// timers (explosions, particles, shockwaves, animation threads) automatically
// pause when the demo is paused and speed up / slow down with the playback
// rate. The DemoPlaybackController component calls advanceEffectClock()
// rate. The StreamingController component calls advanceEffectClock()
// once per frame.
let _effectClockMs = 0;
@ -327,7 +327,7 @@ export function effectNow(): number {
/**
* Advance the effect clock. Called once per frame from
* DemoPlaybackController before other useFrame callbacks run.
* StreamingController before other useFrame callbacks run.
*/
export function advanceEffectClock(deltaSec: number, rate: number): void {
_effectClockMs += deltaSec * rate * 1000;

View file

@ -139,6 +139,9 @@ export interface PlayerEntity extends PositionedBase {
shapeName?: string;
dataBlock?: string;
weaponShape?: string;
packShape?: string;
falling?: boolean;
jetting?: boolean;
playerName?: string;
iffColor?: { r: number; g: number; b: number };
threads?: ThreadState[];

View file

@ -0,0 +1,249 @@
import { createStore } from "zustand/vanilla";
import { useStoreWithEqualityFn } from "zustand/traditional";
import { RelayClient } from "../stream/relayClient";
import { LiveStreamAdapter } from "../stream/liveStreaming";
import type {
ClientMove,
ServerInfo,
ConnectionStatus,
} from "../../relay/types";
export interface LiveConnectionState {
relayConnected: boolean;
gameStatus: ConnectionStatus | null;
gameStatusMessage?: string;
/** Map name from the server being joined (from GameInfoResponse or status). */
mapName?: string;
/** Display name of the joined server. */
serverName?: string;
/** Relay↔T2 server RTT in ms. */
relayToGameServerPing: number | null;
/** Browser↔relay WebSocket RTT in ms. */
browserToRelayPing: number | null;
servers: ServerInfo[];
serversLoading: boolean;
adapter: LiveStreamAdapter | null;
/** True once the first ghost entity arrives (game is rendering). */
liveReady: boolean;
}
export interface LiveConnectionStore extends LiveConnectionState {
// Non-reactive refs.
_relay: RelayClient | null;
_adapter: LiveStreamAdapter | null;
_pending: Array<() => void>;
_listInFlight: boolean;
connectRelay(url?: string): void;
disconnectRelay(): void;
listServers(): void;
joinServer(address: string, warriorName?: string): void;
disconnectServer(): void;
sendMove(move: ClientMove): void;
sendCommand(command: string, ...args: string[]): void;
}
const DEFAULT_RELAY_URL =
process.env.NEXT_PUBLIC_RELAY_URL || "ws://localhost:8765";
export const liveConnectionStore = createStore<LiveConnectionStore>(
(set, get) => ({
relayConnected: false,
gameStatus: null,
gameStatusMessage: undefined,
mapName: undefined,
serverName: undefined,
relayToGameServerPing: null,
browserToRelayPing: null,
servers: [],
serversLoading: false,
adapter: null,
liveReady: false,
_relay: null,
_adapter: null,
_pending: [],
_listInFlight: false,
connectRelay(url = DEFAULT_RELAY_URL) {
const s = get();
if (s._relay) {
s._relay.close();
}
const relay = new RelayClient(url, {
onOpen() {
set({ relayConnected: true });
const s = get();
for (const fn of s._pending) fn();
s._pending = [];
},
onStatus(status, message, _connectSequence, statusMapName) {
console.log(
`[relay] game status: ${status}${message ? `${message}` : ""}${statusMapName ? ` map=${statusMapName}` : ""}`,
);
set({
gameStatus: status,
gameStatusMessage: message,
...(statusMapName ? { mapName: statusMapName } : {}),
});
},
onServerList(list) {
get()._listInFlight = false;
set({ servers: list, serversLoading: false });
},
onGamePacket(data) {
const a = get()._adapter;
if (!a) {
console.warn(
"[relay] received game packet but no adapter is active",
);
}
a?.feedPacket(data);
},
onPing(ms) {
set({ relayToGameServerPing: ms });
},
onWsPing(ms) {
set({ browserToRelayPing: ms });
},
onError(message) {
console.error("Relay error:", message);
get()._listInFlight = false;
set({ serversLoading: false });
},
onClose() {
const s = get();
if (s._relay === relay) {
s._relay = null;
s._adapter = null;
s._pending = [];
s._listInFlight = false;
set({
relayConnected: false,
gameStatus: null,
gameStatusMessage: undefined,
mapName: undefined,
serverName: undefined,
relayToGameServerPing: null,
browserToRelayPing: null,
adapter: null,
liveReady: false,
});
}
},
});
relay.connect();
get()._relay = relay;
},
disconnectRelay() {
const s = get();
s._relay?.close();
s._relay = null;
s._adapter = null;
s._pending = [];
s._listInFlight = false;
set({
relayConnected: false,
gameStatus: null,
gameStatusMessage: undefined,
mapName: undefined,
serverName: undefined,
relayToGameServerPing: null,
browserToRelayPing: null,
adapter: null,
liveReady: false,
});
},
listServers() {
const s = get();
if (s._listInFlight) return;
s._listInFlight = true;
const doList = () => {
const s = get();
s._relay?.sendWsPing();
s._relay?.listServers();
};
set({ serversLoading: true });
if (s._relay?.connected) {
doList();
} else {
s._pending.push(doList);
if (!s._relay) {
get().connectRelay();
}
}
},
joinServer(address, warriorName) {
const s = get();
if (!s._relay) return;
const cachedServer = s.servers.find((sv) => sv.address === address);
const newAdapter = new LiveStreamAdapter(s._relay);
newAdapter.onReady = () => set({ liveReady: true });
s._adapter = newAdapter;
set({
mapName: cachedServer?.mapName ?? s.mapName,
serverName: cachedServer?.name,
liveReady: false,
gameStatus: null,
adapter: newAdapter,
});
s._relay.joinServer(address, warriorName);
},
disconnectServer() {
const s = get();
s._relay?.disconnectServer();
s._adapter?.reset();
s._adapter = null;
set({
adapter: null,
liveReady: false,
gameStatus: null,
mapName: undefined,
serverName: undefined,
relayToGameServerPing: null,
});
},
sendMove(move) {
get()._relay?.sendMove(move);
},
sendCommand(command, ...args) {
get()._relay?.sendCommand(command, args);
},
}),
);
/** Select state from the live connection store with optional equality fn. */
export function useLiveSelector<T>(
selector: (state: LiveConnectionStore) => T,
equality?: (a: T, b: T) => boolean,
): T {
return useStoreWithEqualityFn(liveConnectionStore, selector, equality);
}
/** Effective RTT to the game server (relay↔T2 + browser↔relay). */
export function selectPing(s: LiveConnectionStore): number | null {
return s.relayToGameServerPing != null && s.browserToRelayPing != null
? s.relayToGameServerPing + s.browserToRelayPing
: s.relayToGameServerPing ?? null;
}
/** Dispose the relay connection (for cleanup on unmount). */
export function disposeLiveConnection(): void {
const s = liveConnectionStore.getState();
s._relay?.close();
s._relay = null;
}

View file

@ -18,6 +18,8 @@ import {
yawPitchToQuaternion,
playerYawToQuaternion,
torqueQuatToThreeJS,
torqueQuatHeading,
torqueQuatPitch,
isValidPosition,
isVec3Like,
isQuatLike,
@ -94,6 +96,9 @@ export interface MutableEntity {
weaponImageState?: WeaponImageState;
weaponImageStates?: WeaponImageDataBlockState[];
weaponImageStatesDbId?: number;
packShape?: string;
falling?: boolean;
jetting?: boolean;
headPitch?: number;
headYaw?: number;
targetRenderFlags?: number;
@ -158,6 +163,7 @@ export abstract class StreamEngine implements StreamingPlayback {
// ── Chat & audio ──
protected chatMessages: ChatMessage[] = [];
protected chatMessageIdCounter = 0;
protected audioEvents: PendingAudioEvent[] = [];
// ── Net strings ──
@ -181,6 +187,17 @@ export abstract class StreamEngine implements StreamingPlayback {
protected controlPlayerGhostId?: string;
protected lastControlType: "camera" | "player" = "camera";
protected isPiloting = false;
protected lastPilotGhostIndex?: number;
protected lastVehicleHeading = 0;
protected lastVehiclePitch = 0;
protected lastVehicleOrbitDir?: [number, number, number];
/** Vehicle velocity in Torque space (estimated from linMomentum/mass). */
protected lastVehicleVelocity?: [number, number, number];
/** Time (sec) of last vehicle position update from controlObjectData. */
protected lastVehiclePosTime = 0;
/** Last known vehicle position in Torque space for extrapolation. */
protected lastVehiclePos?: [number, number, number];
protected firstPerson = true;
protected lastCameraMode?: number;
protected lastOrbitGhostIndex?: number;
protected lastOrbitDistance?: number;
@ -267,6 +284,7 @@ export abstract class StreamEngine implements StreamingPlayback {
this.tickCount = 0;
this.camera = null;
this.chatMessages = [];
this.chatMessageIdCounter = 0;
this.audioEvents = [];
this.netStrings.clear();
this.targetNames.clear();
@ -279,6 +297,14 @@ export abstract class StreamEngine implements StreamingPlayback {
this.controlPlayerGhostId = undefined;
this.lastControlType = "camera";
this.isPiloting = false;
this.lastPilotGhostIndex = undefined;
this.lastVehicleHeading = 0;
this.lastVehiclePitch = 0;
this.lastVehicleOrbitDir = undefined;
this.lastVehicleVelocity = undefined;
this.lastVehiclePosTime = 0;
this.lastVehiclePos = undefined;
this.firstPerson = true;
this.lastCameraMode = undefined;
this.lastOrbitGhostIndex = undefined;
this.lastOrbitDistance = undefined;
@ -359,6 +385,17 @@ export abstract class StreamEngine implements StreamingPlayback {
this.isPiloting = !!(
controlData.pilot || controlData.controlObjectGhost != null
);
if (this.isPiloting && typeof controlData.controlObjectGhost === "number") {
this.lastPilotGhostIndex = controlData.controlObjectGhost;
} else if (!this.isPiloting) {
this.lastPilotGhostIndex = undefined;
this.lastVehicleHeading = 0;
this.lastVehiclePitch = 0;
this.lastVehicleOrbitDir = undefined;
this.lastVehicleVelocity = undefined;
this.lastVehiclePosTime = 0;
this.lastVehiclePos = undefined;
}
} else {
this.isPiloting = false;
if (typeof controlData.cameraMode === "number") {
@ -772,6 +809,9 @@ export abstract class StreamEngine implements StreamingPlayback {
entity.sensorGroup = undefined;
entity.playerName = undefined;
entity.weaponShape = undefined;
entity.packShape = undefined;
entity.falling = undefined;
entity.jetting = undefined;
entity.weaponImageState = undefined;
entity.weaponImageStates = undefined;
entity.weaponImageStatesDbId = undefined;
@ -903,6 +943,18 @@ export abstract class StreamEngine implements StreamingPlayback {
entity.weaponImageStates = undefined;
}
// Pack image (slot 2 = $BackpackSlot, mountPoint 1 = Mount1)
const packImage = images.find((img) => img.index === 2);
if (packImage?.dataBlockId && packImage.dataBlockId > 0) {
const blockData = this.getDataBlockData(packImage.dataBlockId);
const shape = resolveShapeName("ShapeBaseImageData", blockData);
if (shape) {
entity.packShape = shape;
}
} else if (packImage && !packImage.dataBlockId) {
entity.packShape = undefined;
}
// Flag tracking
const flagImage = images.find((img) => img.index === 3);
if (flagImage) {
@ -1003,6 +1055,10 @@ export abstract class StreamEngine implements StreamingPlayback {
}
}
// Movement state flags (from Player MoveMask ghost data).
if (typeof data.moveFlag0 === "boolean") entity.falling = data.moveFlag0;
if (typeof data.moveFlag1 === "boolean") entity.jetting = data.moveFlag1;
// Item physics: when the server sends a position update with
// atRest=false and a velocity, start client-side physics simulation.
if (entity.type === "Item") {
@ -1439,7 +1495,45 @@ export abstract class StreamEngine implements StreamingPlayback {
this.removeExpiredExplosions();
if (control.position) {
const { yaw, pitch } = this.getCameraYawPitch(data);
let { yaw, pitch } = this.getCameraYawPitch(data);
// When piloting a vehicle (without freelook), mouse yaw goes to
// vehicle steering (mRot.z) and the player's head rotation (mHead)
// decays by 50% per tick — the camera is locked to the vehicle.
// Use the vehicle's heading directly instead of move-accumulated yaw.
// Verified against tribes2-engine Player::updateMove and Tribes2.exe.
if (this.isPiloting) {
if (data) {
const nested = data.controlObjectData as
| Record<string, unknown>
| undefined;
const ang = nested?.angPosition as
| { x: number; y: number; z: number; w: number }
| undefined;
if (ang && typeof ang.w === "number") {
this.lastVehicleHeading = torqueQuatHeading(ang);
this.lastVehiclePitch = torqueQuatPitch(ang);
// Compute pullback direction from full quaternion (preserves roll).
// ShapeBase::getCameraTransform pulls back along the eye's -Y axis.
// In Torque space, forward is +Y. Transform +Y by the quaternion,
// convert to Three.js, then negate for pullback.
const threeQ = torqueQuatToThreeJS(ang);
if (threeQ) {
// Rotate Three.js forward (+X, since model default is +X) by the
// converted quaternion: v' = q * v * q^-1.
// For unit vector (1,0,0), this simplifies to:
const [qx, qy, qz, qw] = threeQ;
const fx = 1 - 2 * (qy * qy + qz * qz);
const fy = 2 * (qx * qy + qz * qw);
const fz = 2 * (qx * qz - qy * qw);
// Pullback = -forward
this.lastVehicleOrbitDir = [-fx, -fy, -fz];
}
}
}
yaw = this.lastVehicleHeading;
pitch = this.lastVehiclePitch;
}
this.camera = {
time: timeSec,
@ -1476,32 +1570,126 @@ export abstract class StreamEngine implements StreamingPlayback {
this.camera.mode = "observer";
}
} else {
this.camera.mode = "first-person";
// Player control object.
if (control.ghostIndex >= 0) {
this.controlPlayerGhostId =
this.resolveEntityIdForGhostIndex(control.ghostIndex);
}
if (!this.firstPerson) {
// Third-person: orbit the vehicle (if piloting) or the player.
this.camera.mode = "third-person";
if (this.isPiloting && this.lastPilotGhostIndex != null) {
this.camera.orbitTargetId =
this.resolveEntityIdForGhostIndex(this.lastPilotGhostIndex);
this.camera.orbitDistance = 15;
if (this.lastVehicleOrbitDir) {
this.camera.orbitDirection = this.lastVehicleOrbitDir;
}
} else {
this.camera.orbitTargetId = this.controlPlayerGhostId;
// Player datablock cameraMaxDist is typically 3.
this.camera.orbitDistance = 3;
}
} else {
this.camera.mode = "first-person";
}
if (this.controlPlayerGhostId) {
this.camera.controlEntityId = this.controlPlayerGhostId;
}
}
// Sync control player position
if (
controlType === "player" &&
!this.isPiloting &&
this.controlPlayerGhostId &&
control.position
) {
const ghostEntity = this.entities.get(this.controlPlayerGhostId);
if (ghostEntity) {
ghostEntity.position = [
control.position.x,
control.position.y,
control.position.z,
];
ghostEntity.rotation = playerYawToQuaternion(yaw);
ghostEntity.headPitch = this.getControlPlayerHeadPitch(pitch);
// Sync control object positions from controlObjectData.
if (controlType === "player" && control.position) {
if (this.isPiloting && this.lastPilotGhostIndex != null) {
const vehicleId =
this.resolveEntityIdForGhostIndex(this.lastPilotGhostIndex);
const vehicleEntity = vehicleId
? this.entities.get(vehicleId)
: undefined;
if (vehicleEntity) {
const nested = data?.controlObjectData as
| Record<string, unknown>
| undefined;
if (nested) {
// Fresh position from controlObjectData (linPosition →
// compressionPoint → control.position).
vehicleEntity.position = [
control.position.x,
control.position.y,
control.position.z,
];
this.lastVehiclePos = vehicleEntity.position.slice() as [number, number, number];
this.lastVehiclePosTime = timeSec;
// Extract velocity from linMomentum for interpolation between
// the sparse position updates (~10 of ~62 packets contain data).
const mom = nested.linMomentum as
| { x: number; y: number; z: number }
| undefined;
if (mom && isValidPosition(mom)) {
// linMomentum = mass * velocity; look up mass from datablock.
const dbId = vehicleEntity.dataBlockId;
const dbData = dbId != null ? this.getDataBlockData(dbId) : undefined;
const mass = (dbData?.mass as number) ?? 200;
const invMass = mass > 0 ? 1 / mass : 1 / 200;
this.lastVehicleVelocity = [
mom.x * invMass,
mom.y * invMass,
mom.z * invMass,
];
vehicleEntity.velocity = this.lastVehicleVelocity;
}
// Sync vehicle rotation from nested angPosition quaternion.
const ang = nested.angPosition as
| { x: number; y: number; z: number; w: number }
| undefined;
if (ang && typeof ang.w === "number") {
const converted = torqueQuatToThreeJS(ang);
if (converted) vehicleEntity.rotation = converted;
}
} else if (
this.lastVehiclePos &&
this.lastVehicleVelocity &&
this.lastVehiclePosTime > 0
) {
// No nested data this packet — extrapolate from last known
// position + velocity to avoid stutter.
const dt = timeSec - this.lastVehiclePosTime;
if (dt > 0 && dt < 1) {
const [vx, vy, vz] = this.lastVehicleVelocity;
vehicleEntity.position = [
this.lastVehiclePos[0] + vx * dt,
this.lastVehiclePos[1] + vy * dt,
this.lastVehiclePos[2] + vz * dt,
];
}
}
}
} else if (this.controlPlayerGhostId) {
const ghostEntity = this.entities.get(this.controlPlayerGhostId);
if (ghostEntity) {
ghostEntity.position = [
control.position.x,
control.position.y,
control.position.z,
];
ghostEntity.rotation = playerYawToQuaternion(yaw);
ghostEntity.headPitch = this.getControlPlayerHeadPitch(pitch);
// Sync velocity from controlObjectData. Ghost updates skip the
// control player (MoveMask is not read), so velocity and state
// flags must come from here for movement animation selection.
const vel = data?.velocity as
| { x: number; y: number; z: number }
| undefined;
if (isVec3Like(vel)) {
ghostEntity.velocity = [vel.x, vel.y, vel.z];
// Approximate mFalling: engine sets it when no ground contact
// and vz < sFallingThreshold (-10). controlObjectData lacks
// the explicit flag, so use the velocity heuristic.
ghostEntity.falling = vel.z < -10;
}
}
}
}
} else if (this.camera) {
@ -1575,8 +1763,8 @@ export abstract class StreamEngine implements StreamingPlayback {
// ── Chat + HUD ──
protected pushChatMessage(msg: ChatMessage): void {
this.chatMessages.push(msg);
protected pushChatMessage(msg: Omit<ChatMessage, "id">): void {
this.chatMessages.push({ ...msg, id: ++this.chatMessageIdCounter });
if (this.chatMessages.length > 200) {
this.chatMessages.splice(0, this.chatMessages.length - 200);
}
@ -1771,6 +1959,9 @@ export abstract class StreamEngine implements StreamingPlayback {
shapeHint: entity.shapeHint,
dataBlock: entity.dataBlock,
weaponShape: entity.weaponShape,
packShape: entity.packShape,
falling: entity.falling,
jetting: entity.jetting,
playerName: entity.playerName,
targetRenderFlags: renderFlags,
iffColor:
@ -1857,9 +2048,7 @@ export abstract class StreamEngine implements StreamingPlayback {
chatMessages: ChatMessage[];
audioEvents: PendingAudioEvent[];
} {
const chatMessages = this.chatMessages.filter(
(m) => m.timeSec > timeSec - 15,
);
const chatMessages = this.chatMessages.slice();
const audioEvents = this.audioEvents.filter(
(e) => e.timeSec > timeSec - 0.5 && e.timeSec <= timeSec,
);

View file

@ -18,6 +18,9 @@ import {
detectControlObjectType,
parseColorSegments,
backpackBitmapToIndex,
torqueQuatHeading,
torqueQuatPitch,
torqueQuatToThreeJS,
} from "./streamHelpers";
import type { Vec3 } from "./streamHelpers";
import type {
@ -276,6 +279,7 @@ class StreamingPlayback extends StreamEngine {
parsedData?: Record<string, unknown>;
}>;
demoValues: string[];
firstPerson: boolean;
};
// Demo-specific: move delta tracking for V12-style camera rotation
private moveTicks = 0;
@ -326,6 +330,7 @@ class StreamingPlayback extends StreamEngine {
taggedStrings: initial.taggedStrings,
initialEvents: initial.initialEvents,
demoValues: initial.demoValues,
firstPerson: initial.firstPerson,
};
this.reset();
@ -368,7 +373,9 @@ class StreamingPlayback extends StreamEngine {
protected getCameraYawPitch(
_data: Record<string, unknown> | undefined,
): { yaw: number; pitch: number } {
const hasMoves = !this.isPiloting && this.lastControlType === "player";
// Move-derived angles are valid when the control object is a Player
// (including when piloting a vehicle — moves still drive the camera).
const hasMoves = this.lastControlType === "player";
const yaw = hasMoves ? this.absoluteYaw : this.lastAbsYaw;
const pitch = hasMoves ? this.absolutePitch : this.lastAbsPitch;
@ -445,6 +452,7 @@ class StreamingPlayback extends StreamEngine {
this.absolutePitch = 0;
this.lastAbsYaw = 0;
this.lastAbsPitch = 0;
this.firstPerson = this.initialBlock.firstPerson;
this.lastControlType =
detectControlObjectType(this.initialBlock.controlObjectData) ?? "player";
this.isPiloting =
@ -454,6 +462,31 @@ class StreamingPlayback extends StreamEngine {
this.initialBlock.controlObjectData?.controlObjectGhost != null
)
: false;
this.lastPilotGhostIndex =
this.isPiloting &&
typeof this.initialBlock.controlObjectData?.controlObjectGhost === "number"
? this.initialBlock.controlObjectData.controlObjectGhost
: undefined;
if (this.isPiloting) {
const nested = this.initialBlock.controlObjectData?.controlObjectData as
| Record<string, unknown>
| undefined;
const ang = nested?.angPosition as
| { x: number; y: number; z: number; w: number }
| undefined;
if (ang && typeof ang.w === "number") {
this.lastVehicleHeading = torqueQuatHeading(ang);
this.lastVehiclePitch = torqueQuatPitch(ang);
const threeQ = torqueQuatToThreeJS(ang);
if (threeQ) {
const [qx, qy, qz, qw] = threeQ;
const fx = 1 - 2 * (qy * qy + qz * qz);
const fy = 2 * (qx * qy + qz * qw);
const fz = 2 * (qx * qz - qy * qw);
this.lastVehicleOrbitDir = [-fx, -fy, -fz];
}
}
}
this.lastCameraMode =
this.lastControlType === "camera" &&
typeof this.initialBlock.controlObjectData?.cameraMode === "number"
@ -609,7 +642,7 @@ class StreamingPlayback extends StreamEngine {
const isPlayerChat = hasChatColor && fullText.includes(": ");
if (isPlayerChat) {
const colonIdx = fullText.indexOf(": ");
this.chatMessages.push({
this.pushChatMessage({
timeSec: 0,
sender: fullText.slice(0, colonIdx),
text: fullText.slice(colonIdx + 2),
@ -618,7 +651,7 @@ class StreamingPlayback extends StreamEngine {
segments,
});
} else {
this.chatMessages.push({
this.pushChatMessage({
timeSec: 0,
sender: "",
text: fullText,
@ -749,6 +782,10 @@ class StreamingPlayback extends StreamEngine {
// Apply ghost rotation to absolute tracking. This must happen before
// the next move delta so that our tracking stays calibrated to V12.
// During piloting, rotationZ/headX are relative to the vehicle (reset
// to 0 on mount). We still accept the reset so move deltas accumulate
// from the correct base; the vehicle heading offset is added later in
// updateCameraAndHud.
const controlData = packet.gameState.controlObjectData;
if (controlData) {
const absRot = this.getAbsoluteRotation(controlData);
@ -773,6 +810,9 @@ class StreamingPlayback extends StreamEngine {
}
if (block.type === BlockTypeInfo && this.isInfoData(block.parsed)) {
// InfoBlock: value1 byte 0 = $firstPerson flag, value2 = FOV.
// Verified against Tribes2.exe GameConnection::handleRecordedBlock.
this.firstPerson = (block.parsed.value1 & 0xff) !== 0;
if (Number.isFinite(block.parsed.value2)) {
this.latestFov = block.parsed.value2;
}
@ -919,10 +959,14 @@ class StreamingPlayback extends StreamEngine {
return !!parsed && typeof parsed === "object" && "yaw" in parsed;
}
private isInfoData(parsed: unknown): parsed is { value2: number } {
private isInfoData(
parsed: unknown,
): parsed is { value1: number; value2: number } {
return (
!!parsed &&
typeof parsed === "object" &&
"value1" in parsed &&
typeof (parsed as { value1?: unknown }).value1 === "number" &&
"value2" in parsed &&
typeof (parsed as { value2?: unknown }).value2 === "number"
);

View file

@ -101,6 +101,9 @@ export function streamEntityToGameEntity(
shapeName: entity.dataBlock,
dataBlock: entity.dataBlock,
weaponShape: entity.weaponShape,
packShape: entity.packShape,
falling: entity.falling,
jetting: entity.jetting,
playerName: entity.playerName,
iffColor: entity.iffColor,
threads: entity.threads,

View file

@ -354,16 +354,19 @@ export class LiveStreamAdapter extends StreamEngine {
this.handleGhostingMessage(event.parsedData);
const type = event.parsedData.type as string;
// Log events in early packets
// Always log RemoteCommandEvents (chat, server messages, HUD).
if (type === "RemoteCommandEvent") {
const funcName = this.resolveNetString(event.parsedData.funcName as string ?? "");
console.log(`[live] remote: ${funcName}`);
}
// Log other events in early packets
if (isEarlyPacket) {
if (type !== "NetStringEvent") {
if (type !== "NetStringEvent" && type !== "RemoteCommandEvent") {
console.log(
`[live] event: ${type}`,
type === "RemoteCommandEvent"
? { funcName: this.resolveNetString(event.parsedData.funcName as string ?? "") }
: type === "SimDataBlockEvent"
? { id: event.parsedData.objectId, className: event.parsedData.dataBlockClassName }
: undefined,
type === "SimDataBlockEvent"
? { id: event.parsedData.objectId, className: event.parsedData.dataBlockClassName }
: undefined,
);
}
}
@ -491,8 +494,18 @@ export class LiveStreamAdapter extends StreamEngine {
);
}
const prevMode = this.camera?.mode;
this.updateCameraAndHud();
// Log camera mode transitions (always, not just early packets).
if (this.camera && this.camera.mode !== prevMode) {
console.log(
`[live] camera mode: ${prevMode ?? "none"}${this.camera.mode}` +
(this.camera.mode === "third-person"
? ` orbit=${this.camera.orbitTargetId ?? "?"} dist=${this.camera.orbitDistance ?? "?"}`
: ""),
);
}
// Log camera position for early packets
if (this.tickCount <= 5 && this.camera) {
const [cx, cy, cz] = this.camera.position;

View file

@ -1,17 +1,22 @@
/**
* Movement animation selection logic replicating Torque's
* Player::pickActionAnimation() (player.cc:2280).
* Player::pickActionAnimation() (Tribes2.exe FUN_005d6210).
*
* The server does NOT transmit table animation indices (0-7) over the
* network. Each client independently derives the movement animation from
* the ghost's velocity, body rotation, and state flags (mFalling, jetting).
*/
/** Torque falling threshold: Z velocity below this = falling. */
const FALLING_THRESHOLD = -10;
/** Minimum velocity dot product to count as intentional movement. */
const MOVE_THRESHOLD = 0.1;
export interface MoveAnimationResult {
/** Engine alias name (e.g. "root", "run", "back", "side", "fall"). */
/** Engine alias name (e.g. "root", "run", "back", "side", "fall", "jet"). */
animation: string;
/** 1 for forward playback, -1 for reversed (right strafe). */
timeScale: number;
}
/**
* Extract body yaw (Torque rotationZ) from a Three.js quaternion produced by
* `playerYawToQuaternion()`. That function builds a Y-axis rotation:
@ -21,41 +26,65 @@ export interface MoveAnimationResult {
function quaternionToBodyYaw(q: [number, number, number, number]): number {
return -2 * Math.atan2(q[1], q[3]);
}
/**
* Pick the movement animation for a player based on their velocity and
* body orientation, matching Torque's pickActionAnimation().
* Pick the movement animation for a player based on their velocity, body
* orientation, and movement state flags.
*
* @param velocity Torque world-space velocity [x, y, z], or undefined for idle.
* @param rotation Three.js quaternion from playerYawToQuaternion().
* Matches the Tribes2.exe binary (build 25034) pickActionAnimation at
* 0x005d6210. The binary checks in order:
* 1. mFalling FallAnim (4)
* 2. contactTimer < 30 velocity-based selection (run/back/side/root)
* 3. jetting JetAnim (5)
* 4. else RootAnim (0)
*
* Since we don't have contactTimer, falling=false + no velocity uses root.
*/
export function pickMoveAnimation(
velocity: [number, number, number] | undefined,
rotation: [number, number, number, number],
falling?: boolean,
jetting?: boolean,
): MoveAnimationResult {
if (!velocity) {
return { animation: "root", timeScale: 1 };
}
const [vx, vy, vz] = velocity;
// Falling: Torque Z velocity below threshold.
if (vz < FALLING_THRESHOLD) {
// Falling overrides everything.
if (falling) {
return { animation: "fall", timeScale: 1 };
}
if (!velocity) {
// No velocity data at all — use jetting or idle.
if (jetting) return { animation: "jet", timeScale: 1 };
return { animation: "root", timeScale: 1 };
}
const [vx, vy, _vz] = velocity;
// Convert world velocity to player object space using body yaw.
// mWorldToObj.mulV(mVelocity) with a pure Z-axis rotation:
// localX = vx*cos(rotZ) + vy*sin(rotZ)
// localY = -vx*sin(rotZ) + vy*cos(rotZ)
const yaw = quaternionToBodyYaw(rotation);
const cosY = Math.cos(yaw);
const sinY = Math.sin(yaw);
// Torque object space: localY = forward, localX = right.
const localX = vx * cosY + vy * sinY;
const localY = -vx * sinY + vy * cosY;
// Pick direction with largest dot product.
// Dot products against animation direction vectors:
// run dir = (0, 1, 0) → dot = localY
// back dir = (0,-1, 0) → dot = -localY
// side dir = (-1,0, 0) → dot = -localX (left), +localX (right reversed)
const forwardDot = localY;
const backDot = -localY;
const leftDot = -localX;
const rightDot = localX;
const maxDot = Math.max(forwardDot, backDot, leftDot, rightDot);
if (maxDot < MOVE_THRESHOLD) {
// Below movement threshold — jetting or idle.
if (jetting) return { animation: "jet", timeScale: 1 };
return { animation: "root", timeScale: 1 };
}
if (maxDot === forwardDot) {
return { animation: "run", timeScale: 1 };
}

View file

@ -116,9 +116,9 @@ export class RelayClient {
}
/** Join a specific game server. */
joinServer(address: string): void {
joinServer(address: string, warriorName?: string): void {
console.log("[relay] Joining server:", address);
this.send({ type: "joinServer", address });
this.send({ type: "joinServer", address, warriorName });
}
/** Disconnect from the current game server. */

View file

@ -83,6 +83,31 @@ export function torqueQuatToThreeJS(q: {
return [x * invLen, y * invLen, z * invLen, w * invLen];
}
/** Extract heading (yaw around Torque Z axis) from a Torque quaternion. */
export function torqueQuatHeading(q: {
x: number;
y: number;
z: number;
w: number;
}): number {
return Math.atan2(
2 * (q.w * q.z + q.x * q.y),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
);
}
/** Extract pitch (rotation around Torque X axis) from a Torque quaternion. */
export function torqueQuatPitch(q: {
x: number;
y: number;
z: number;
w: number;
}): number {
const sinp = 2 * (q.w * q.x - q.y * q.z);
// Clamp for numerical stability near poles.
return Math.asin(Math.max(-1, Math.min(1, sinp)));
}
// ── Position / type guards ──
export function isValidPosition(

View file

@ -130,6 +130,12 @@ export interface StreamEntity {
weaponImageState?: WeaponImageState;
/** Weapon image state machine states from the ShapeBaseImageData datablock. */
weaponImageStates?: WeaponImageDataBlockState[];
/** DTS shape name for the mounted pack (slot 2, Mount1 bone). */
packShape?: string;
/** True when the player has no ground contact and is falling. */
falling?: boolean;
/** True when the player is using jetpack thrust. */
jetting?: boolean;
/** Head pitch for blend animations, normalized [-1,1]. -1 = max down, 1 = max up. */
headPitch?: number;
/** Head yaw for blend animations (freelook), normalized [-1,1]. -1 = max right, 1 = max left. */
@ -166,6 +172,8 @@ export interface StreamCamera {
yaw?: number;
/** Absolute control-object pitch in Torque radians (rotX/headX). */
pitch?: number;
/** Explicit orbit pullback direction in Three.js space (overrides yaw/pitch). */
orbitDirection?: [number, number, number];
}
/** A colored text segment from inline \c color switching. */
@ -176,6 +184,7 @@ export interface ChatSegment {
}
export interface ChatMessage {
id: number;
timeSec: number;
sender: string;
text: string;