mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-03-15 18:31:01 +00:00
cleanup input handling, fix server move packets
This commit is contained in:
parent
409df9fcaa
commit
e9125951e4
84 changed files with 2085 additions and 1808 deletions
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
import { Suspense } from "react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
// import { LiveConnectionProvider } from "@/src/components/LiveConnection";
|
||||
import { FeaturesProvider } from "@/src/components/FeaturesProvider";
|
||||
import { MapInspector } from "@/src/components/MapInspector";
|
||||
import { SettingsProvider } from "@/src/components/SettingsProvider";
|
||||
|
||||
// Three.js has its own loaders for textures and models, but we need to load other
|
||||
// stuff too, e.g. missions, terrains, and more. This client is used for those.
|
||||
|
|
@ -14,7 +14,9 @@ export default function HomePage() {
|
|||
<Suspense>
|
||||
<FeaturesProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MapInspector />
|
||||
<SettingsProvider>
|
||||
<MapInspector />
|
||||
</SettingsProvider>
|
||||
</QueryClientProvider>
|
||||
</FeaturesProvider>
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ function ShapeInspector() {
|
|||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<main>
|
||||
<SettingsProvider onClearFogEnabledOverride={() => {}}>
|
||||
<SettingsProvider>
|
||||
<div className={styles.CanvasContainer}>
|
||||
{showLoading && (
|
||||
<div
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,10 +1,10 @@
|
|||
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/1273ef014eba2bd5.js","/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","/t2-mapper/_next/static/chunks/63afa42c92661c50.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js","/t2-mapper/_next/static/chunks/57517f0359971c33.js","/t2-mapper/_next/static/chunks/0a364447adf881eb.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/cc36ef62835b35ab.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/fcfc8a45de71c4a4.js"],"default"]
|
||||
3:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","/t2-mapper/_next/static/chunks/990cfb71eaf1c762.js","/t2-mapper/_next/static/chunks/b30d580062e7b044.js","/t2-mapper/_next/static/chunks/e01e6e76b521dd3c.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/16e2b7e83646cebc.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js","/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","/t2-mapper/_next/static/chunks/0a364447adf881eb.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js"],"default"]
|
||||
6:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
|
||||
7:"$Sreact.suspense"
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/1273ef014eba2bd5.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/63afa42c92661c50.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/57517f0359971c33.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/0a364447adf881eb.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/cc36ef62835b35ab.js","async":true}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","async":true}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/fcfc8a45de71c4a4.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
|
||||
:HL["/t2-mapper/_next/static/chunks/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"buildId":"GVV-bte23-C1OKGsIpGF4","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/3ec6b524f05ae0b6.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/990cfb71eaf1c762.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/b30d580062e7b044.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/e01e6e76b521dd3c.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/16e2b7e83646cebc.js","async":true}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.js","async":true}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","async":true}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/0a364447adf881eb.js","async":true}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.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
|
||||
|
|
|
|||
|
|
@ -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[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/1273ef014eba2bd5.js","/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","/t2-mapper/_next/static/chunks/63afa42c92661c50.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js","/t2-mapper/_next/static/chunks/57517f0359971c33.js","/t2-mapper/_next/static/chunks/0a364447adf881eb.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/cc36ef62835b35ab.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/fcfc8a45de71c4a4.js"],"default"]
|
||||
6:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","/t2-mapper/_next/static/chunks/990cfb71eaf1c762.js","/t2-mapper/_next/static/chunks/b30d580062e7b044.js","/t2-mapper/_next/static/chunks/e01e6e76b521dd3c.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/16e2b7e83646cebc.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js","/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","/t2-mapper/_next/static/chunks/0a364447adf881eb.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.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/ad52ebedad251428.css","style"]
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"P":null,"b":"HUIHRvyaa6D1abkRRPPoG","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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/309d84bbb5b2092f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/1273ef014eba2bd5.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/63afa42c92661c50.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/57517f0359971c33.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/0a364447adf881eb.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/cc36ef62835b35ab.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/fcfc8a45de71c4a4.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/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"P":null,"b":"GVV-bte23-C1OKGsIpGF4","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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/3ec6b524f05ae0b6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/990cfb71eaf1c762.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/b30d580062e7b044.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/e01e6e76b521dd3c.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/16e2b7e83646cebc.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/0a364447adf881eb.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.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"}]]
|
||||
|
|
|
|||
|
|
@ -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":"HUIHRvyaa6D1abkRRPPoG","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":"GVV-bte23-C1OKGsIpGF4","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}
|
||||
|
|
|
|||
|
|
@ -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/ad52ebedad251428.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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":"GVV-bte23-C1OKGsIpGF4","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
:HL["/t2-mapper/_next/static/chunks/ad52ebedad251428.css","style"]
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","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/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"buildId":"GVV-bte23-C1OKGsIpGF4","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
521
docs/_next/static/chunks/16e2b7e83646cebc.js
Normal file
521
docs/_next/static/chunks/16e2b7e83646cebc.js
Normal file
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
211
docs/_next/static/chunks/28c57db7b25d3d02.js
Normal file
211
docs/_next/static/chunks/28c57db7b25d3d02.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,66069,e=>{"use strict";var r=e.i(932),t=e.i(71645),n=e.i(90072),c=e.i(71753),u=e.i(15080),o=e.i(79123),i=e.i(66093);let l=Math.PI/2-.01;function a(){let e,a,h,d,f,v,E,y,x,g,S,p=(0,r.c)(27),{speedMultiplier:M,touchMode:T,invertDrag:q,invertJoystick:b}=(0,o.useControls)(),L=(0,u.useThree)(m),_=(0,u.useThree)(s),{moveState:k,lookState:F}=(0,i.useJoystick)();p[0]===Symbol.for("react.memo_cache_sentinel")?(e=new n.Euler(0,0,0,"YXZ"),p[0]=e):e=p[0];let R=(0,t.useRef)(e),X=(0,t.useRef)(null);p[1]===Symbol.for("react.memo_cache_sentinel")?(a={x:0,y:0},p[1]=a):a=p[1];let Y=(0,t.useRef)(a);p[2]!==q?(h=()=>q,p[2]=q,p[3]=h):h=p[3];let V=(0,t.useEffectEvent)(h);p[4]===Symbol.for("react.memo_cache_sentinel")?(d=new n.Vector3,p[4]=d):d=p[4];let Z=(0,t.useRef)(d);p[5]===Symbol.for("react.memo_cache_sentinel")?(f=new n.Vector3,p[5]=f):f=p[5];let w=(0,t.useRef)(f);p[6]===Symbol.for("react.memo_cache_sentinel")?(v=new n.Vector3,p[6]=v):v=p[6];let z=(0,t.useRef)(v);return p[7]!==L.quaternion?(E=()=>{R.current.setFromQuaternion(L.quaternion,"YXZ")},p[7]=L.quaternion,p[8]=E):E=p[8],p[9]!==L?(y=[L],p[9]=L,p[10]=y):y=p[10],(0,t.useEffect)(E,y),p[11]!==L.quaternion||p[12]!==V||p[13]!==_.domElement||p[14]!==T?(x=()=>{if("moveLookStick"!==T)return;let e=_.domElement,r=e=>{if(null===X.current)for(;0<e.changedTouches.length;){let r=e.changedTouches[0];X.current=r.identifier,Y.current={x:r.clientX,y:r.clientY};break}},t=e=>{if(null!==X.current)for(let r=0;r<e.changedTouches.length;r++){let t=e.changedTouches[r];if(t.identifier===X.current){let e=t.clientX-Y.current.x,r=t.clientY-Y.current.y;Y.current={x:t.clientX,y:t.clientY};let n=V()?-1:1;R.current.setFromQuaternion(L.quaternion,"YXZ"),R.current.y=R.current.y+n*e*.004,R.current.x=R.current.x+n*r*.004,R.current.x=Math.max(-l,Math.min(l,R.current.x)),L.quaternion.setFromEuler(R.current);break}}},n=e=>{for(let r=0;r<e.changedTouches.length;r++)if(e.changedTouches[r].identifier===X.current){X.current=null;break}};return e.addEventListener("touchstart",r,{passive:!0}),e.addEventListener("touchmove",t,{passive:!0}),e.addEventListener("touchend",n,{passive:!0}),e.addEventListener("touchcancel",n,{passive:!0}),()=>{e.removeEventListener("touchstart",r),e.removeEventListener("touchmove",t),e.removeEventListener("touchend",n),e.removeEventListener("touchcancel",n),X.current=null}},p[11]=L.quaternion,p[12]=V,p[13]=_.domElement,p[14]=T,p[15]=x):x=p[15],p[16]!==L||p[17]!==_.domElement||p[18]!==T?(g=[L,_.domElement,T],p[16]=L,p[17]=_.domElement,p[18]=T,p[19]=g):g=p[19],(0,t.useEffect)(x,g),p[20]!==L||p[21]!==b||p[22]!==F.current||p[23]!==k.current||p[24]!==M||p[25]!==T?(S=(e,r)=>{let{force:t,angle:n}=k.current,{force:c,angle:u}=F.current;if("dualStick"===T){if(c>.15){let e=(c-.15)/.85,t=Math.cos(u),n=Math.sin(u),o=b?-1:1;R.current.setFromQuaternion(L.quaternion,"YXZ"),R.current.y=R.current.y-o*t*e*2.5*r,R.current.x=R.current.x+o*n*e*2.5*r,R.current.x=Math.max(-l,Math.min(l,R.current.x)),L.quaternion.setFromEuler(R.current)}if(t>.08){let e=80*M*((t-.08)/.92),c=Math.cos(n),u=Math.sin(n);L.getWorldDirection(Z.current),Z.current.normalize(),w.current.crossVectors(L.up,Z.current).normalize(),z.current.set(0,0,0).addScaledVector(Z.current,u).addScaledVector(w.current,-c),z.current.lengthSq()>0&&(z.current.normalize().multiplyScalar(e*r),L.position.add(z.current))}}else if("moveLookStick"===T&&t>0){let e=80*M*.5;if(L.getWorldDirection(Z.current),Z.current.normalize(),z.current.copy(Z.current).multiplyScalar(e*r),L.position.add(z.current),t>=.15){let e=Math.cos(n),c=Math.sin(n),u=(t-.15)/.85,o=b?-1:1;R.current.setFromQuaternion(L.quaternion,"YXZ"),R.current.y=R.current.y-o*e*u*1.25*r,R.current.x=R.current.x+o*c*u*1.25*r,R.current.x=Math.max(-l,Math.min(l,R.current.x)),L.quaternion.setFromEuler(R.current)}}},p[20]=L,p[21]=b,p[22]=F.current,p[23]=k.current,p[24]=M,p[25]=T,p[26]=S):S=p[26],(0,c.useFrame)(S),null}function s(e){return e.gl}function m(e){return e.camera}e.s(["TouchHandler",()=>a])}]);
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -4,7 +4,7 @@
|
|||
.JoinServerButton-module__DIR70a__Root{}.JoinServerButton-module__DIR70a__TextLabel{}.JoinServerButton-module__DIR70a__PingLabel{margin-right:2px;display:flex!important;}.JoinServerButton-module__DIR70a__Pulsing{animation:1.2s ease-out infinite JoinServerButton-module__DIR70a__blink}@keyframes JoinServerButton-module__DIR70a__blink{0%{opacity:1}to{opacity:.25}}.JoinServerButton-module__DIR70a__ButtonHint{}
|
||||
.Accordion-module__rN-DYq__AccordionGroup{flex-direction:column;gap:1px;display:flex}.Accordion-module__rN-DYq__Trigger{color:#fff;text-align:left;text-transform:uppercase;letter-spacing:.0417em;background:#ffffff1a;border:0;align-items:center;gap:4px;width:100%;padding:6px 8px;font-family:inherit;font-size:12px;font-weight:400;display:flex}.Accordion-module__rN-DYq__TriggerIcon{opacity:.5;font-size:12px;transition:transform .2s;transform:rotate(0)}.Accordion-module__rN-DYq__Trigger[data-state=open] .Accordion-module__rN-DYq__TriggerIcon{transform:rotate(90deg)}.Accordion-module__rN-DYq__Content{overflow:hidden}.Accordion-module__rN-DYq__Content[data-state=open]{animation:.3s Accordion-module__rN-DYq__slideDown}.Accordion-module__rN-DYq__Content[data-state=closed]{animation:.3s Accordion-module__rN-DYq__slideUp}.Accordion-module__rN-DYq__Body{padding:10px}@keyframes Accordion-module__rN-DYq__slideDown{0%{height:0}to{height:var(--radix-accordion-content-height)}}@keyframes Accordion-module__rN-DYq__slideUp{0%{height:var(--radix-accordion-content-height)}to{height:0}}
|
||||
.MissionSelect-module__N_AIjG__InputWrapper{z-index:2;align-items:center;margin:10px 10px 10px 8px;display:flex;position:relative}.MissionSelect-module__N_AIjG__Shortcut{color:#fff9;pointer-events:none;background:#ffffff26;border-radius:3px;padding:1px 4px;font-family:system-ui,sans-serif;font-size:11px;position:absolute;right:7px}.MissionSelect-module__N_AIjG__Input[aria-expanded=true]~.MissionSelect-module__N_AIjG__Shortcut{display:none}.MissionSelect-module__N_AIjG__Input{color:#fff;-webkit-user-select:text;user-select:text;background:#0009;border:1px solid #ffffff4d;border-radius:3px;outline:none;width:280px;padding:6px 36px 6px 8px;font-size:14px}.MissionSelect-module__N_AIjG__Input[aria-expanded=true]{padding-right:8px}.MissionSelect-module__N_AIjG__Input:focus{border-color:#fff9}.MissionSelect-module__N_AIjG__Input::placeholder{color:#777;font-family:inherit;font-size:12px}.MissionSelect-module__N_AIjG__SelectedValue{pointer-events:none;align-items:center;gap:6px;display:flex;position:absolute;left:8px;right:36px;overflow:hidden}.MissionSelect-module__N_AIjG__Input[aria-expanded=true]~.MissionSelect-module__N_AIjG__SelectedValue{display:none}.MissionSelect-module__N_AIjG__SelectedName{color:#fff;white-space:nowrap;text-overflow:ellipsis;flex-shrink:1;min-width:0;font-size:14px;font-weight:600;line-height:1.28571;overflow:hidden}.MissionSelect-module__N_AIjG__SelectedValue>.MissionSelect-module__N_AIjG__ItemType{flex-shrink:0}.MissionSelect-module__N_AIjG__Popover{z-index:100;min-width:320px;max-height:var(--popover-available-height,90vh);overscroll-behavior:contain;background:#141414f2;border:1px solid #ffffff80;border-radius:3px;overflow-y:auto;box-shadow:0 8px 24px #0009}.MissionSelect-module__N_AIjG__List{padding:4px 0}.MissionSelect-module__N_AIjG__List:has(>.MissionSelect-module__N_AIjG__Group:first-child){padding-top:0}.MissionSelect-module__N_AIjG__Group{padding-bottom:4px}.MissionSelect-module__N_AIjG__GroupLabel{color:#c6caca;z-index:1;background:#3a4548f2;border-bottom:1px solid #ffffff4d;padding:6px 8px 6px 12px;font-size:13px;font-weight:600;position:sticky;top:0}.MissionSelect-module__N_AIjG__Group:not(:last-child){border-bottom:1px solid #ffffff4d}.MissionSelect-module__N_AIjG__Item{cursor:pointer;border-radius:4px;outline:none;flex-direction:column;gap:1px;margin:4px 4px 0;padding:6px 8px;scroll-margin-top:32px;display:flex}.MissionSelect-module__N_AIjG__List>.MissionSelect-module__N_AIjG__Item:first-child{margin-top:0}.MissionSelect-module__N_AIjG__Item[data-active-item]{background:#ffffff26}.MissionSelect-module__N_AIjG__Item[aria-selected=true]{background:#6496ff4d}.MissionSelect-module__N_AIjG__ItemHeader{align-items:center;gap:6px;display:flex}.MissionSelect-module__N_AIjG__ItemName{color:#fff;font-size:14px;font-weight:600}.MissionSelect-module__N_AIjG__ItemTypes{gap:3px;display:flex}.MissionSelect-module__N_AIjG__ItemType{color:#fff;background:#ff9d0066;border-radius:3px;padding:2px 5px;font-size:10px;font-weight:600;line-height:1.3}.MissionSelect-module__N_AIjG__ItemType:hover{background:#ff9d00b3}.MissionSelect-module__N_AIjG__ItemMissionName{color:#ffffff80;font-size:12px}.MissionSelect-module__N_AIjG__NoResults{color:#ffffff80;text-align:center;padding:12px 8px;font-size:13px}.MissionSelect-module__N_AIjG__Backdrop{z-index:1;background:#00000080;position:fixed;inset:0}@media (max-width:899px){.MissionSelect-module__N_AIjG__InputWrapper{margin-left:4px}}
|
||||
.StreamingMissionInfo-module__hEaQnW__Header{flex:auto;align-items:center;display:flex}.StreamingMissionInfo-module__hEaQnW__MissionInfo{color:#fff;background:0 0;border:1px solid #fff0;border-radius:3px;align-items:center;gap:6px;margin:10px auto 10px 4px;padding:5px 8px;display:flex}.StreamingMissionInfo-module__hEaQnW__MissionName{}.StreamingMissionInfo-module__hEaQnW__MissionType{pointer-events:none;}.StreamingMissionInfo-module__hEaQnW__MissionTypeDisplayName{opacity:.5;font-size:12px}.StreamingMissionInfo-module__hEaQnW__Metadata{text-align:right;flex-direction:column;gap:2px;margin:0 0 0 auto;padding:8px 12px;font-size:12px;line-height:1.16667;display:flex}.StreamingMissionInfo-module__hEaQnW__Attribution,.StreamingMissionInfo-module__hEaQnW__ServerInfo{color:#83938b}.StreamingMissionInfo-module__hEaQnW__PlayerName,.StreamingMissionInfo-module__hEaQnW__RecordingDate,.StreamingMissionInfo-module__hEaQnW__ServerName{color:#eceae7}.StreamingMissionInfo-module__hEaQnW__ActionButton{flex:none;min-width:28px;min-height:28px;margin:0 10px 0 0;padding:2px;font-size:16px;}.StreamingMissionInfo-module__hEaQnW__EjectIcon{margin-top:-.5px;font-size:21px}@media (max-width:899px){.StreamingMissionInfo-module__hEaQnW__Metadata{display:none}.StreamingMissionInfo-module__hEaQnW__MissionInfo{margin-left:0}}
|
||||
.StreamingMissionInfo-module__hEaQnW__Header{flex:auto;align-items:center;display:flex}.StreamingMissionInfo-module__hEaQnW__MissionInfo{color:#fff;background:0 0;border:1px solid #fff0;border-radius:3px;align-items:center;gap:6px;margin:10px auto 10px 4px;padding:5px 8px;display:flex}.StreamingMissionInfo-module__hEaQnW__MissionName{}.StreamingMissionInfo-module__hEaQnW__MissionType{pointer-events:none;}.StreamingMissionInfo-module__hEaQnW__MissionTypeDisplayName{opacity:.5;font-size:12px}.StreamingMissionInfo-module__hEaQnW__Metadata{text-align:right;flex-direction:column;gap:2px;margin:0 0 0 auto;padding:8px 12px;font-size:12px;line-height:1.16667;display:flex}.StreamingMissionInfo-module__hEaQnW__Attribution,.StreamingMissionInfo-module__hEaQnW__ServerInfo{color:#83938b}.StreamingMissionInfo-module__hEaQnW__PlayerName,.StreamingMissionInfo-module__hEaQnW__RecordingDate,.StreamingMissionInfo-module__hEaQnW__ServerName{color:#eceae7}.StreamingMissionInfo-module__hEaQnW__ActionButton{flex:none;min-width:28px;min-height:28px;margin:0 10px 0 0;padding:2px;font-size:16px;}.StreamingMissionInfo-module__hEaQnW__EjectIcon{margin-top:-.5px;font-size:21px}.StreamingMissionInfo-module__hEaQnW__Error{color:#ff6a45}@media (max-width:899px){.StreamingMissionInfo-module__hEaQnW__Metadata{display:none}.StreamingMissionInfo-module__hEaQnW__MissionInfo{margin-left:0}}
|
||||
.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}
|
||||
File diff suppressed because one or more lines are too long
1
docs/_next/static/chunks/44a6df9214eeac58.js
Normal file
1
docs/_next/static/chunks/44a6df9214eeac58.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/_next/static/chunks/4e0d5bbc5104adf2.js
Normal file
1
docs/_next/static/chunks/4e0d5bbc5104adf2.js
Normal file
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
1
docs/_next/static/chunks/7dcb40c178014bc9.js
Normal file
1
docs/_next/static/chunks/7dcb40c178014bc9.js
Normal file
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
1
docs/_next/static/chunks/967c4aa315f919d7.js
Normal file
1
docs/_next/static/chunks/967c4aa315f919d7.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,66069,e=>{"use strict";var t=e.i(932),n=e.i(71645),r=e.i(71753),c=e.i(15080),u=e.i(79123),i=e.i(66093),l=e.i(65349);function o(){let e,o,a,h,m,d=(0,t.c)(17),{speedMultiplier:f,touchMode:v,invertDrag:E,invertJoystick:g}=(0,u.useControls)(),M=(0,c.useThree)(s),{moveState:T,lookState:L}=(0,i.useJoystick)(),k=(0,l.useOnInput)(),p=(0,n.useRef)(null);d[0]===Symbol.for("react.memo_cache_sentinel")?(e={x:0,y:0},d[0]=e):e=d[0];let b=(0,n.useRef)(e);d[1]!==E?(o=()=>E,d[1]=E,d[2]=o):o=d[2];let x=(0,n.useEffectEvent)(o),y=(0,n.useRef)(0),R=(0,n.useRef)(0);return d[3]!==x||d[4]!==M.domElement||d[5]!==v?(a=()=>{if("moveLookStick"!==v)return;let e=M.domElement,t=e=>{if(null===p.current)for(;0<e.changedTouches.length;){let t=e.changedTouches[0];p.current=t.identifier,b.current={x:t.clientX,y:t.clientY};break}},n=e=>{if(null!==p.current)for(let t=0;t<e.changedTouches.length;t++){let n=e.changedTouches[t];if(n.identifier===p.current){let e=n.clientX-b.current.x,t=n.clientY-b.current.y;b.current={x:n.clientX,y:n.clientY};let r=x()?-1:1;y.current=y.current+r*e*.004,R.current=R.current+r*t*.004;break}}},r=e=>{for(let t=0;t<e.changedTouches.length;t++)if(e.changedTouches[t].identifier===p.current){p.current=null;break}};return e.addEventListener("touchstart",t,{passive:!0}),e.addEventListener("touchmove",n,{passive:!0}),e.addEventListener("touchend",r,{passive:!0}),e.addEventListener("touchcancel",r,{passive:!0}),()=>{e.removeEventListener("touchstart",t),e.removeEventListener("touchmove",n),e.removeEventListener("touchend",r),e.removeEventListener("touchcancel",r),p.current=null}},d[3]=x,d[4]=M.domElement,d[5]=v,d[6]=a):a=d[6],d[7]!==M.domElement||d[8]!==v?(h=[M.domElement,v],d[7]=M.domElement,d[8]=v,d[9]=h):h=d[9],(0,n.useEffect)(a,h),d[10]!==g||d[11]!==L.current||d[12]!==T.current||d[13]!==k||d[14]!==f||d[15]!==v?(m=(e,t)=>{let{force:n,angle:r}=T.current,{force:c,angle:u}=L.current,i=y.current,l=R.current;y.current=0,R.current=0;let o=0,s=0;if("dualStick"===v){if(c>.15){let e=(c-.15)/.85,n=Math.cos(u),r=Math.sin(u),o=g?-1:1;i-=o*n*e*2.5*t,l+=o*r*e*2.5*t}if(n>.08){let e=(n-.08)/.92,t=Math.cos(r),c=Math.sin(r);o=Math.max(-1,Math.min(1,-t*e*f)),s=Math.max(-1,Math.min(1,c*e*f))}}else if("moveLookStick"===v&&n>0&&(s=Math.max(-1,Math.min(1,.5*f)),n>=.15)){let e=Math.cos(r),c=Math.sin(r),u=(n-.15)/.85,o=g?-1:1;i-=o*e*u*1.25*t,l+=o*c*u*1.25*t}let a=0!==o||0!==s;(0!==i||0!==l||a)&&k({deltaYaw:i,deltaPitch:l,x:o,y:s,z:0,triggers:[],delta:t})},d[10]=g,d[11]=L.current,d[12]=T.current,d[13]=k,d[14]=f,d[15]=v,d[16]=m):m=d[16],(0,r.useFrame)(m),null}function s(e){return e.gl}e.s(["TouchHandler",()=>o])}]);
|
||||
1
docs/_next/static/chunks/990cfb71eaf1c762.js
Normal file
1
docs/_next/static/chunks/990cfb71eaf1c762.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
docs/_next/static/chunks/ad4fd30929cca23f.js
Normal file
1
docs/_next/static/chunks/ad4fd30929cca23f.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/_next/static/chunks/b30d580062e7b044.js
Normal file
1
docs/_next/static/chunks/b30d580062e7b044.js
Normal file
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
|
|
@ -1,2 +1,2 @@
|
|||
.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:10px;right:10px}.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__TeamScores{font-family:monospace;font-size:12px;position:absolute;bottom:8px;left:8px}.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:8px;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}
|
||||
.ChatWindow-module__mz35vq__ChatContainer{pointer-events:auto;border:1px solid #2cacb566;flex-direction:column;width:400px;max-width:50%;display:flex;position:absolute;top:8px;left:8px}.ChatWindow-module__mz35vq__ChatWindow{-webkit-user-select:text;user-select:text;scrollbar-width:thin;scrollbar-color:#2cacb566 transparent;background:#00323ca6;min-height:4em;max-height:12.5em;padding:6px;font-size:12px;line-height:1.25;overflow-y:auto}.ChatWindow-module__mz35vq__ChatMessage{color:#2cacb5;padding:2px 0}.ChatWindow-module__mz35vq__ChatColor0{color:#2cacb5}.ChatWindow-module__mz35vq__ChatColor1{color:#04eb69}.ChatWindow-module__mz35vq__ChatColor2{color:#dbc880}.ChatWindow-module__mz35vq__ChatColor3{color:#4dfd5f}.ChatWindow-module__mz35vq__ChatColor4{color:#28e7f0}.ChatWindow-module__mz35vq__ChatColor5{color:#c8c832}.ChatWindow-module__mz35vq__ChatColor6{color:#c8c8c8}.ChatWindow-module__mz35vq__ChatColor7{color:#dcdc14}.ChatWindow-module__mz35vq__ChatColor8{color:#9696fa}.ChatWindow-module__mz35vq__ChatColor9{color:#3cdc96}
|
||||
.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:10px;right:10px}.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:6px;transform:translateY(-50%)}.PlayerHUD-module__-E1Scq__WeaponSeparator{height:6px}.PlayerHUD-module__-E1Scq__TeamInfo{flex-direction:column;gap:2px;display:flex}.PlayerHUD-module__-E1Scq__TeamScores{border:1px solid #80ffc826;font-size:12px;position:absolute;bottom:6px;left:6px}.PlayerHUD-module__-E1Scq__TeamRow{background:#00323ca6;flex:1 0 auto;justify-content:space-between;align-items:center;gap:6px;padding:4px 8px 4px 6px;display:flex}.PlayerHUD-module__-E1Scq__TeamRow+.PlayerHUD-module__-E1Scq__TeamRow{border-top:1px solid #80ffc826}.PlayerHUD-module__-E1Scq__TeamName{min-width:6em;font-size:12px;font-weight:500}.PlayerHUD-module__-E1Scq__TeamNameFriendly{color:#2de46a;}.PlayerHUD-module__-E1Scq__TeamNameEnemy{color:#79cbd4;}.PlayerHUD-module__-E1Scq__TeamScore{color:#fff;text-align:right;font-weight:500}.PlayerHUD-module__-E1Scq__TeamCount{color:#9ba;font-size:9px}.PlayerHUD-module__-E1Scq__PackInventoryHUD{align-items:center;gap:4px;display:flex;position:absolute;bottom:6px;right:6px}.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-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}
|
||||
.ChatWindow-module__mz35vq__ChatContainer{pointer-events:auto;border:1px solid #2cacb566;flex-direction:column;width:400px;max-width:50%;display:flex;position:absolute;top:6px;left:6px}.ChatWindow-module__mz35vq__ChatWindow{-webkit-user-select:text;user-select:text;scrollbar-width:thin;scrollbar-color:#2cacb566 transparent;background:#00323ca6;min-height:4em;max-height:12.5em;padding:6px;font-size:12px;line-height:1.25;overflow-y:auto}.ChatWindow-module__mz35vq__ChatMessage{color:#2cacb5;padding:2px 0}.ChatWindow-module__mz35vq__ChatColor0{color:#2cacb5}.ChatWindow-module__mz35vq__ChatColor1{color:#04eb69}.ChatWindow-module__mz35vq__ChatColor2{color:#dbc880}.ChatWindow-module__mz35vq__ChatColor3{color:#4dfd5f}.ChatWindow-module__mz35vq__ChatColor4{color:#28e7f0}.ChatWindow-module__mz35vq__ChatColor5{color:#c8c832}.ChatWindow-module__mz35vq__ChatColor6{color:#c8c8c8}.ChatWindow-module__mz35vq__ChatColor7{color:#dcdc14}.ChatWindow-module__mz35vq__ChatColor8{color:#9696fa}.ChatWindow-module__mz35vq__ChatColor9{color:#3cdc96}
|
||||
File diff suppressed because one or more lines are too long
8
docs/_next/static/chunks/e01e6e76b521dd3c.js
Normal file
8
docs/_next/static/chunks/e01e6e76b521dd3c.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/_next/static/chunks/f6808786c34b74b0.js
Normal file
1
docs/_next/static/chunks/f6808786c34b74b0.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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/ad52ebedad251428.css","style"]
|
||||
0:{"P":null,"b":"HUIHRvyaa6D1abkRRPPoG","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/ad52ebedad251428.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":"GVV-bte23-C1OKGsIpGF4","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/ad52ebedad251428.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
|
||||
|
|
|
|||
|
|
@ -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":"HUIHRvyaa6D1abkRRPPoG","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":"GVV-bte23-C1OKGsIpGF4","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}
|
||||
|
|
|
|||
|
|
@ -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/ad52ebedad251428.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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":"GVV-bte23-C1OKGsIpGF4","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
1:"$Sreact.fragment"
|
||||
2:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
|
||||
3:"$Sreact.suspense"
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","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":"GVV-bte23-C1OKGsIpGF4","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
|
||||
|
|
|
|||
|
|
@ -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":"HUIHRvyaa6D1abkRRPPoG","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
|
||||
0:{"buildId":"GVV-bte23-C1OKGsIpGF4","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
:HL["/t2-mapper/_next/static/chunks/ad52ebedad251428.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","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":"GVV-bte23-C1OKGsIpGF4","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
|
|
@ -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/ad52ebedad251428.css","style"]
|
||||
0:{"P":null,"b":"HUIHRvyaa6D1abkRRPPoG","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/ad52ebedad251428.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":"GVV-bte23-C1OKGsIpGF4","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/ad52ebedad251428.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
|
|
@ -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[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/1273ef014eba2bd5.js","/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","/t2-mapper/_next/static/chunks/63afa42c92661c50.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js","/t2-mapper/_next/static/chunks/57517f0359971c33.js","/t2-mapper/_next/static/chunks/0a364447adf881eb.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/cc36ef62835b35ab.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/fcfc8a45de71c4a4.js"],"default"]
|
||||
6:I[31713,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","/t2-mapper/_next/static/chunks/990cfb71eaf1c762.js","/t2-mapper/_next/static/chunks/b30d580062e7b044.js","/t2-mapper/_next/static/chunks/e01e6e76b521dd3c.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/16e2b7e83646cebc.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js","/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","/t2-mapper/_next/static/chunks/0a364447adf881eb.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.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/ad52ebedad251428.css","style"]
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"P":null,"b":"HUIHRvyaa6D1abkRRPPoG","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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/309d84bbb5b2092f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/1273ef014eba2bd5.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/63afa42c92661c50.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/57517f0359971c33.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/0a364447adf881eb.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/cc36ef62835b35ab.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/fcfc8a45de71c4a4.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/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"P":null,"b":"GVV-bte23-C1OKGsIpGF4","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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/3ec6b524f05ae0b6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/990cfb71eaf1c762.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/b30d580062e7b044.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/e01e6e76b521dd3c.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/16e2b7e83646cebc.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/af18e4f3fa33de6b.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/t2-mapper/_next/static/chunks/0a364447adf881eb.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.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"}]]
|
||||
|
|
|
|||
|
|
@ -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/39402d7e86cbc15f.js","/t2-mapper/_next/static/chunks/df1e111d87e0ccf8.js","/t2-mapper/_next/static/chunks/57517f0359971c33.js","/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","/t2-mapper/_next/static/chunks/9236f2f78e6373a1.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/acace6aadc879a08.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js"],"default"]
|
||||
6:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/ad4fd30929cca23f.js","/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","/t2-mapper/_next/static/chunks/44a6df9214eeac58.js","/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","/t2-mapper/_next/static/chunks/d712fc4c15db64e6.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/9549e76d03bf90ea.js","/t2-mapper/_next/static/chunks/727710e55f003daf.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/ad52ebedad251428.css","style"]
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"P":null,"b":"HUIHRvyaa6D1abkRRPPoG","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/ad52ebedad251428.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/309d84bbb5b2092f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/39402d7e86cbc15f.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/df1e111d87e0ccf8.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/57517f0359971c33.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/9236f2f78e6373a1.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.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/acace6aadc879a08.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.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/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"P":null,"b":"GVV-bte23-C1OKGsIpGF4","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/ad52ebedad251428.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/3ec6b524f05ae0b6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/ad4fd30929cca23f.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/44a6df9214eeac58.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/d712fc4c15db64e6.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9549e76d03bf90ea.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.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"}]]
|
||||
|
|
|
|||
|
|
@ -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":"HUIHRvyaa6D1abkRRPPoG","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":"GVV-bte23-C1OKGsIpGF4","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}
|
||||
|
|
|
|||
|
|
@ -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/ad52ebedad251428.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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":"GVV-bte23-C1OKGsIpGF4","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/ad52ebedad251428.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}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
:HL["/t2-mapper/_next/static/chunks/ad52ebedad251428.css","style"]
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","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/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"buildId":"GVV-bte23-C1OKGsIpGF4","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}
|
||||
|
|
|
|||
|
|
@ -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/39402d7e86cbc15f.js","/t2-mapper/_next/static/chunks/df1e111d87e0ccf8.js","/t2-mapper/_next/static/chunks/57517f0359971c33.js","/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","/t2-mapper/_next/static/chunks/9236f2f78e6373a1.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/acace6aadc879a08.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js"],"default"]
|
||||
3:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/ad4fd30929cca23f.js","/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","/t2-mapper/_next/static/chunks/44a6df9214eeac58.js","/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","/t2-mapper/_next/static/chunks/d712fc4c15db64e6.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/9549e76d03bf90ea.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js"],"default"]
|
||||
6:I[97367,["/t2-mapper/_next/static/chunks/2f236954d6a65e12.js"],"OutletBoundary"]
|
||||
7:"$Sreact.suspense"
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"buildId":"HUIHRvyaa6D1abkRRPPoG","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/39402d7e86cbc15f.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/df1e111d87e0ccf8.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/57517f0359971c33.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/9236f2f78e6373a1.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.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/acace6aadc879a08.js","async":true}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
|
||||
:HL["/t2-mapper/_next/static/chunks/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"buildId":"GVV-bte23-C1OKGsIpGF4","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/t2-mapper/_next/static/chunks/3ec6b524f05ae0b6.css","precedence":"next"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/ad4fd30929cca23f.js","async":true}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","async":true}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/44a6df9214eeac58.js","async":true}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","async":true}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/d712fc4c15db64e6.js","async":true}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9549e76d03bf90ea.js","async":true}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.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
|
||||
|
|
|
|||
|
|
@ -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":"HUIHRvyaa6D1abkRRPPoG","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
|
||||
0:{"buildId":"GVV-bte23-C1OKGsIpGF4","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
|
|
@ -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/39402d7e86cbc15f.js","/t2-mapper/_next/static/chunks/df1e111d87e0ccf8.js","/t2-mapper/_next/static/chunks/57517f0359971c33.js","/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","/t2-mapper/_next/static/chunks/9236f2f78e6373a1.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/acace6aadc879a08.js","/t2-mapper/_next/static/chunks/727710e55f003daf.js"],"default"]
|
||||
6:I[39724,["/t2-mapper/_next/static/chunks/89fcb9c19e93d0ef.js","/t2-mapper/_next/static/chunks/ad4fd30929cca23f.js","/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","/t2-mapper/_next/static/chunks/44a6df9214eeac58.js","/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","/t2-mapper/_next/static/chunks/d712fc4c15db64e6.js","/t2-mapper/_next/static/chunks/9a99559140e82f06.js","/t2-mapper/_next/static/chunks/153d5796298dee1e.js","/t2-mapper/_next/static/chunks/21659079be7af0ab.js","/t2-mapper/_next/static/chunks/9549e76d03bf90ea.js","/t2-mapper/_next/static/chunks/727710e55f003daf.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/ad52ebedad251428.css","style"]
|
||||
:HL["/t2-mapper/_next/static/chunks/309d84bbb5b2092f.css","style"]
|
||||
0:{"P":null,"b":"HUIHRvyaa6D1abkRRPPoG","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/ad52ebedad251428.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/309d84bbb5b2092f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/39402d7e86cbc15f.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/df1e111d87e0ccf8.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/57517f0359971c33.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/926fdfc108de2b2e.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/9236f2f78e6373a1.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.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/acace6aadc879a08.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.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/3ec6b524f05ae0b6.css","style"]
|
||||
0:{"P":null,"b":"GVV-bte23-C1OKGsIpGF4","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/ad52ebedad251428.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/3ec6b524f05ae0b6.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/t2-mapper/_next/static/chunks/ad4fd30929cca23f.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/t2-mapper/_next/static/chunks/f6808786c34b74b0.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/t2-mapper/_next/static/chunks/44a6df9214eeac58.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/t2-mapper/_next/static/chunks/28c57db7b25d3d02.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/t2-mapper/_next/static/chunks/d712fc4c15db64e6.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/t2-mapper/_next/static/chunks/9a99559140e82f06.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/t2-mapper/_next/static/chunks/153d5796298dee1e.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/t2-mapper/_next/static/chunks/21659079be7af0ab.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/t2-mapper/_next/static/chunks/9549e76d03bf90ea.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/t2-mapper/_next/static/chunks/727710e55f003daf.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"}]]
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -14,16 +14,16 @@
|
|||
"deploy": "npm run build && git add -f docs && git commit -m \"Deploy\" && git push",
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint .",
|
||||
"login": "node --env-file-if-exists=.env --env-file-if-exists=.env.local --import=tsx/esm scripts/t2-login.ts",
|
||||
"login": "node --env-file-if-exists=.env.development.local --import=tsx/esm scripts/t2-login.ts",
|
||||
"postbuild": "git checkout -- public/base && touch docs/.nojekyll",
|
||||
"prebuild": "git checkout -- docs && rimraf public/base && mv docs/base public/",
|
||||
"relay:dev": "node --env-file-if-exists=.env.local --watch --import=tsx/esm relay/server.ts",
|
||||
"relay": "node --env-file-if-exists=.env.local --import=tsx/esm relay/server.ts",
|
||||
"relay:dev": "node --env-file-if-exists=.env.development.local --watch --import=tsx/esm relay/server.ts",
|
||||
"relay": "node --env-file-if-exists=.env.development.local --import=tsx/esm relay/server.ts",
|
||||
"serve:static": "tsx scripts/serve-static.ts",
|
||||
"server-list": "node --env-file-if-exists=.env --env-file-if-exists=.env.local --import=tsx/esm scripts/t2-server-list.ts",
|
||||
"server-list": "node --env-file-if-exists=.env.development.local --import=tsx/esm scripts/t2-server-list.ts",
|
||||
"start:both": "concurrently npm:start npm:relay:dev",
|
||||
"start": "next dev --turbopack",
|
||||
"test-connect": "node --env-file-if-exists=.env.local --import=tsx/esm scripts/test-connect.ts",
|
||||
"test-connect": "node --env-file-if-exists=.env.development.local --import=tsx/esm scripts/test-connect.ts",
|
||||
"test:watch": "vitest",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
|
|
|||
|
|
@ -65,17 +65,10 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
|
|||
/** Events waiting to be sent (new or retransmitted from lost packets). */
|
||||
private eventSendQueue: { seq: number; event: ClientEvent }[] = [];
|
||||
private stringTable = new ClientNetStringTable();
|
||||
/** Incrementing move index so the server doesn't deduplicate our moves. */
|
||||
private moveIndex = 0;
|
||||
private dataPacketCount = 0;
|
||||
private rawMessageCount = 0;
|
||||
private sendMoveCount = 0;
|
||||
private _mapName?: string;
|
||||
private observerEnforced = false;
|
||||
/** Buffered move state — merged into the next keepalive tick. */
|
||||
private bufferedMove: ClientMoveData | null = null;
|
||||
/** Ticks remaining to hold the current trigger state before clearing. */
|
||||
private triggerHoldTicks = 0;
|
||||
/** Send timestamps by sequence number for RTT measurement. */
|
||||
private sendTimestamps = new Map<number, number>();
|
||||
/** Smoothed RTT in ms (exponential moving average). */
|
||||
|
|
@ -631,60 +624,12 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
|
|||
this.flushEvents();
|
||||
}
|
||||
|
||||
/** Flush pending events in a data packet. */
|
||||
/** Flush pending events in a data packet immediately. */
|
||||
private flushEvents(): void {
|
||||
// Assign sequence numbers to new pending events and add to send queue.
|
||||
for (const event of this.pendingEvents.splice(0)) {
|
||||
const seq = this.nextSendEventSeq++;
|
||||
this.eventSendQueue.push({ seq, event });
|
||||
if (this.pendingEvents.length === 0 && this.eventSendQueue.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (this.eventSendQueue.length === 0) return;
|
||||
|
||||
this.sendDataPacketWithEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and send a data packet that includes events from the send queue.
|
||||
* Events stay tracked per-packet so they can be re-queued on loss.
|
||||
*/
|
||||
private sendDataPacketWithEvents(move?: ClientMoveData): void {
|
||||
const events = this.eventSendQueue.splice(0);
|
||||
if (events.length === 0) return;
|
||||
|
||||
const startSeq = events[0].seq;
|
||||
|
||||
connLog.debug(
|
||||
{
|
||||
eventCount: events.length,
|
||||
seqRange: `${startSeq}-${events[events.length - 1].seq}`,
|
||||
sendSeq: this.protocol.lastSendSeq + 1,
|
||||
},
|
||||
"Sending data packet with guaranteed events",
|
||||
);
|
||||
|
||||
// Track which events are in this packet for ack/loss handling.
|
||||
// lastSendSeq+1 because buildSendPacketHeader increments it.
|
||||
const packetSeq = this.protocol.lastSendSeq + 1;
|
||||
this.sentEventsByPacket.set(packetSeq, events);
|
||||
|
||||
const moveData = move ?? {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
roll: 0,
|
||||
freeLook: false,
|
||||
trigger: [false, false, false, false, false, false],
|
||||
};
|
||||
|
||||
const packet = buildClientGamePacket(this.protocol, {
|
||||
moves: [moveData],
|
||||
moveStartIndex: this.moveIndex++,
|
||||
events: events.map((e) => e.event),
|
||||
nextSendEventSeq: startSeq,
|
||||
});
|
||||
this.sendRaw(packet);
|
||||
this.emitDataPacket([], 0);
|
||||
}
|
||||
|
||||
/** Handle packet delivery notification from the protocol layer. */
|
||||
|
|
@ -733,62 +678,23 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Buffer a move to be sent in the next keepalive tick.
|
||||
* Moves are merged into the 32ms keepalive cadence rather than sent as
|
||||
* separate packets, because the server's Camera control object processes
|
||||
* moves from the regular tick stream (separate extra packets can be
|
||||
* ignored or cause trigger edge detection issues).
|
||||
* Send moves immediately to the game server.
|
||||
* The browser owns move indices and re-sends all unacked moves each tick,
|
||||
* just like real Tribes 2's moveWritePacket.
|
||||
*/
|
||||
sendMove(move: ClientMoveData): void {
|
||||
this.sendMoveCount++;
|
||||
if (this.sendMoveCount <= 5 || this.sendMoveCount % 100 === 0) {
|
||||
connLog.debug(
|
||||
{
|
||||
yaw: move.yaw,
|
||||
pitch: move.pitch,
|
||||
x: move.x,
|
||||
y: move.y,
|
||||
z: move.z,
|
||||
total: this.sendMoveCount,
|
||||
},
|
||||
"Sending move",
|
||||
);
|
||||
}
|
||||
// During trigger hold, merge trigger flags so rapid move updates
|
||||
// (e.g. from useFrame at 60fps) can't overwrite a pending trigger
|
||||
// before the server sees it.
|
||||
if (this.triggerHoldTicks > 0 && this.bufferedMove) {
|
||||
move = {
|
||||
...move,
|
||||
trigger: this.bufferedMove.trigger.map(
|
||||
(held, i) => held || (move.trigger[i] ?? false),
|
||||
),
|
||||
};
|
||||
}
|
||||
this.bufferedMove = move;
|
||||
// If any trigger is set, hold it for 2 ticks to ensure the server
|
||||
// sees the edge (true then false on the next tick).
|
||||
if (move.trigger.some(Boolean)) {
|
||||
this.triggerHoldTicks = 2;
|
||||
}
|
||||
sendMoves(moves: ClientMoveData[], moveStartIndex: number): void {
|
||||
this.emitDataPacket(moves, moveStartIndex);
|
||||
}
|
||||
|
||||
/** Send the current move state as a keepalive packet at the tick rate. */
|
||||
private sendTickMove(): void {
|
||||
const move: ClientMoveData = this.bufferedMove ?? {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
roll: 0,
|
||||
freeLook: false,
|
||||
trigger: [false, false, false, false, false, false],
|
||||
};
|
||||
|
||||
// Record send time keyed by the 9-bit sequence number (0–511) that the
|
||||
// server will echo back in highestAck. lastSendSeq is the full counter;
|
||||
// the wire format uses only the low 9 bits.
|
||||
/**
|
||||
* Internal: build and send a data packet with moves and/or pending events.
|
||||
* Used by both sendMoves (browser-initiated) and keepalive (idle).
|
||||
*/
|
||||
private emitDataPacket(
|
||||
moves: ClientMoveData[],
|
||||
moveStartIndex: number,
|
||||
): void {
|
||||
// Record send time for RTT measurement.
|
||||
const nextSeq9 = (this.protocol.lastSendSeq + 1) & 0x1ff;
|
||||
this.sendTimestamps.set(nextSeq9, Date.now());
|
||||
|
||||
|
|
@ -798,37 +704,37 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
|
|||
this.eventSendQueue.push({ seq, event });
|
||||
}
|
||||
|
||||
// If we have events waiting to be sent (new or re-queued from lost
|
||||
// packets), include them in this tick's data packet.
|
||||
let events: { seq: number; event: ClientEvent }[] | undefined;
|
||||
if (this.eventSendQueue.length > 0) {
|
||||
this.sendDataPacketWithEvents(move);
|
||||
} else {
|
||||
const packet = buildClientGamePacket(this.protocol, {
|
||||
moves: [move],
|
||||
moveStartIndex: this.moveIndex++,
|
||||
});
|
||||
this.sendRaw(packet);
|
||||
events = this.eventSendQueue.splice(0);
|
||||
const packetSeq = this.protocol.lastSendSeq + 1;
|
||||
this.sentEventsByPacket.set(packetSeq, events);
|
||||
}
|
||||
|
||||
// Count down trigger hold, then clear triggers.
|
||||
if (this.triggerHoldTicks > 0) {
|
||||
this.triggerHoldTicks--;
|
||||
if (this.triggerHoldTicks === 0 && this.bufferedMove) {
|
||||
this.bufferedMove = {
|
||||
...this.bufferedMove,
|
||||
trigger: [false, false, false, false, false, false],
|
||||
};
|
||||
}
|
||||
}
|
||||
const packet = buildClientGamePacket(this.protocol, {
|
||||
moves,
|
||||
moveStartIndex,
|
||||
...(events
|
||||
? {
|
||||
events: events.map((e) => e.event),
|
||||
nextSendEventSeq: events[0].seq,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
this.sendRaw(packet);
|
||||
}
|
||||
|
||||
/** Start keepalive timer. */
|
||||
/** Send an idle keepalive packet (no moves, but flushes pending events). */
|
||||
private sendKeepalive(): void {
|
||||
this.emitDataPacket([], 0);
|
||||
}
|
||||
|
||||
/** Start keepalive timer. Sends idle packets when the browser isn't sending moves. */
|
||||
private startKeepalive(): void {
|
||||
let keepaliveCount = 0;
|
||||
this.keepaliveTimer = setInterval(() => {
|
||||
keepaliveCount++;
|
||||
if (keepaliveCount % 300 === 0) {
|
||||
// ~10s at 32ms tick rate
|
||||
connLog.info(
|
||||
{
|
||||
dataPackets: this.dataPacketCount,
|
||||
|
|
@ -840,7 +746,7 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
|
|||
"Connection status",
|
||||
);
|
||||
}
|
||||
this.sendTickMove();
|
||||
this.sendKeepalive();
|
||||
}, KEEPALIVE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
|
|
@ -863,7 +769,10 @@ export class GameConnection extends EventEmitter<GameConnectionEvents> {
|
|||
// Send a Disconnect packet so the server knows we're leaving
|
||||
if (this.socket && this.serverConnectSequence !== 0) {
|
||||
try {
|
||||
const packet = buildDisconnectPacket(this.connectSequence);
|
||||
const packet = buildDisconnectPacket(
|
||||
this.serverConnectSequence,
|
||||
this.clientConnectSequence,
|
||||
);
|
||||
this.socket.send(packet, this.port, this.host);
|
||||
connLog.info("Sent Disconnect packet to server");
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -274,10 +274,13 @@ export interface ClientEvent {
|
|||
*/
|
||||
function writeMove(bs: BitStreamWriter, move: ClientMoveData): void {
|
||||
// Rotation (flag + optional 16-bit signed).
|
||||
// Pack: int16 = (int)(radians * 65536). Server unpacks: float = (short)int16 / 65536.
|
||||
const pyaw = Math.round(move.yaw * 65536) | 0;
|
||||
const ppitch = Math.round(move.pitch * 65536) | 0;
|
||||
const proll = Math.round(move.roll * 65536) | 0;
|
||||
// Matches Torque's Move::clamp(): pyaw = (yaw / M_2PI) * 0x10000.
|
||||
// Server's Move::unclamp() reverses: yaw = (short)pyaw * M_2PI / 0x10000.
|
||||
// Input values are in radians; divide by 2π to get fractional turns for packing.
|
||||
const M_2PI = 2 * Math.PI;
|
||||
const pyaw = Math.round((move.yaw / M_2PI) * 65536) | 0;
|
||||
const ppitch = Math.round((move.pitch / M_2PI) * 65536) | 0;
|
||||
const proll = Math.round((move.roll / M_2PI) * 65536) | 0;
|
||||
|
||||
if (pyaw !== 0) {
|
||||
bs.writeFlag(true);
|
||||
|
|
@ -480,10 +483,14 @@ export function buildConnectRequest(
|
|||
}
|
||||
|
||||
/** Build a Disconnect (type 38) OOB packet. */
|
||||
export function buildDisconnectPacket(connectSequence: number): Uint8Array {
|
||||
export function buildDisconnectPacket(
|
||||
serverConnectSequence: number,
|
||||
clientConnectSequence: number,
|
||||
): Uint8Array {
|
||||
const bs = new BitStreamWriter(64);
|
||||
bs.writeU8(38); // Disconnect type
|
||||
bs.writeU32(connectSequence);
|
||||
bs.writeU32(serverConnectSequence);
|
||||
bs.writeU32(clientConnectSequence);
|
||||
writeString(bs, ""); // reason
|
||||
return bs.getBuffer();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,18 +355,21 @@ wss.on("connection", (ws) => {
|
|||
break;
|
||||
}
|
||||
|
||||
case "sendMove": {
|
||||
case "sendMoves": {
|
||||
if (gameConnection) {
|
||||
gameConnection.sendMove({
|
||||
x: message.move.x,
|
||||
y: message.move.y,
|
||||
z: message.move.z,
|
||||
yaw: message.move.yaw,
|
||||
pitch: message.move.pitch,
|
||||
roll: message.move.roll,
|
||||
freeLook: message.move.freeLook,
|
||||
trigger: message.move.trigger,
|
||||
});
|
||||
gameConnection.sendMoves(
|
||||
message.moves.map((m) => ({
|
||||
x: m.x,
|
||||
y: m.y,
|
||||
z: m.z,
|
||||
yaw: m.yaw,
|
||||
pitch: m.pitch,
|
||||
roll: m.roll,
|
||||
freeLook: m.freeLook,
|
||||
trigger: m.trigger,
|
||||
})),
|
||||
message.moveStartIndex,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export type ClientMessage =
|
|||
| { type: "listServers" }
|
||||
| { type: "joinServer"; address: string; warriorName?: string }
|
||||
| { type: "disconnect" }
|
||||
| { type: "sendMove"; move: ClientMove }
|
||||
| { type: "sendMoves"; moves: ClientMove[]; moveStartIndex: number }
|
||||
| { type: "sendCommand"; command: string; args: string[] }
|
||||
| {
|
||||
type: "sendCRCResponse";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.ChatContainer {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 400px;
|
||||
max-width: 50%;
|
||||
display: flex;
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
}
|
||||
|
||||
.ChatWindow {
|
||||
max-height: 12.5em;
|
||||
min-height: 4em;
|
||||
max-height: 12.5em;
|
||||
overflow-y: auto;
|
||||
background: rgba(0, 50, 60, 0.65);
|
||||
padding: 6px;
|
||||
|
|
|
|||
610
src/components/InputConsumer.tsx
Normal file
610
src/components/InputConsumer.tsx
Normal file
|
|
@ -0,0 +1,610 @@
|
|||
import { useRef, useEffect } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { Camera, Euler, Vector3 } from "three";
|
||||
import { createLogger } from "../logger";
|
||||
import {
|
||||
liveConnectionStore,
|
||||
useLiveSelector,
|
||||
} from "../state/liveConnectionStore";
|
||||
import { useEngineStoreApi } from "../state/engineStore";
|
||||
import { streamPlaybackStore } from "../state/streamPlaybackStore";
|
||||
import { useInputContext } from "./InputContext";
|
||||
import { useTick, useGetTickFraction } from "./TickProvider";
|
||||
import { yawPitchToQuaternion, MAX_PITCH } from "../stream/streamHelpers";
|
||||
import type { StreamRecording, StreamCamera } from "../stream/types";
|
||||
import type { LiveStreamAdapter } from "../stream/liveStreaming";
|
||||
import type { ClientMove } from "../../relay/types";
|
||||
|
||||
const log = createLogger("InputConsumer");
|
||||
|
||||
const MAX_SPEED = 300;
|
||||
const LOCAL_MAX_PITCH = Math.PI / 2 - 0.01; // ~89°
|
||||
|
||||
/**
|
||||
* Max moves in the unacked buffer. Matches Torque's MaxMoveQueueSize.
|
||||
* Also the max we can send per packet (5-bit count = 31), but we keep
|
||||
* a slightly larger buffer to avoid losing moves during high latency.
|
||||
*/
|
||||
const MAX_MOVE_BUFFER = 45;
|
||||
|
||||
/** Max moves per packet (MoveCountBits = 5 → 2^5 - 1). */
|
||||
const MAX_MOVES_PER_PACKET = 31;
|
||||
|
||||
/**
|
||||
* $Camera::movementSpeed (default 40 in Tribes2.exe at _DAT_0079abe8).
|
||||
* With trigger[1] (altTrigger) always set, the effective speed is 80.
|
||||
*/
|
||||
const CAMERA_SPEED = 40;
|
||||
|
||||
/** Torque tick duration in seconds (1/32 = 0.03125). */
|
||||
const TICK_SEC = 1 / 32;
|
||||
|
||||
const M_2PI = 2 * Math.PI;
|
||||
|
||||
/**
|
||||
* Quantize a rotation delta through Torque's Move clamp/unclamp round-trip.
|
||||
* This ensures our predicted rotation exactly matches the server's decoded value.
|
||||
*
|
||||
* clamp: pyaw = (radians / 2π) * 65536, masked to 16 bits
|
||||
* unclamp: radians = (short)pyaw * 2π / 65536
|
||||
*/
|
||||
function quantizeRotation(radians: number): number {
|
||||
const packed = Math.round((radians / M_2PI) * 65536) | 0;
|
||||
// Sign-extend 16-bit (like C's (short) cast).
|
||||
const signed = (packed << 16) >> 16;
|
||||
return (signed * M_2PI) / 65536;
|
||||
}
|
||||
|
||||
// Scratch objects to avoid per-frame allocations.
|
||||
const _forwardVec = new Vector3();
|
||||
const _sideVec = new Vector3();
|
||||
const _moveVec = new Vector3();
|
||||
const _lookEuler = new Euler(0, 0, 0, "YXZ");
|
||||
const _orbitDir = new Vector3();
|
||||
const _orbitTarget = new Vector3();
|
||||
|
||||
/** A buffered move sent to the server, awaiting acknowledgment. */
|
||||
interface BufferedMove {
|
||||
/** Browser-assigned move index. */
|
||||
moveIndex: number;
|
||||
/** The full move data for re-sending to the server. */
|
||||
move: ClientMove;
|
||||
/** Rotation deltas for prediction replay. */
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
/** Movement axes for position prediction replay. */
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Camera::processTick position update.
|
||||
*
|
||||
* Matches Tribes2.exe FUN_005cbc80: builds Rz(yaw)*Rx(pitch) orientation
|
||||
* matrix, transforms local move axes to world space, applies at tick rate.
|
||||
*
|
||||
* Torque uses (cos,+sin;-sin,cos) rotation convention, NOT standard math.
|
||||
* The resulting Rz(y)*Rx(p) row-major matrix columns are:
|
||||
* right (move.x): { cy, -sy, 0 }
|
||||
* forward (move.y): { sy*cp, cy*cp, -sp }
|
||||
* up (move.z): { sy*sp, cy*sp, cp }
|
||||
*
|
||||
* IMPORTANT: `yaw`/`pitch` must be the rotation state from the PREVIOUS
|
||||
* tick, not the current one. Tribes2.exe reads the old transform matrix
|
||||
* (built at end of previous tick) for position computation.
|
||||
*/
|
||||
function applyProcessTickPosition(
|
||||
pos: { x: number; y: number; z: number },
|
||||
yaw: number,
|
||||
pitch: number,
|
||||
mx: number,
|
||||
my: number,
|
||||
mz: number,
|
||||
speed: number,
|
||||
): void {
|
||||
if (mx === 0 && my === 0 && mz === 0) return;
|
||||
|
||||
const sy = Math.sin(yaw);
|
||||
const cy = Math.cos(yaw);
|
||||
const sp = Math.sin(pitch);
|
||||
const cp = Math.cos(pitch);
|
||||
|
||||
// pos += (right*mx + forward*my + up*mz) * speed * TickSec
|
||||
const scale = speed * TICK_SEC;
|
||||
pos.x += (cy * mx + sy * cp * my + sy * sp * mz) * scale;
|
||||
pos.y += (-sy * mx + cy * cp * my + cy * sp * mz) * scale;
|
||||
pos.z += (-sp * my + cp * mz) * scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes input frames from the move queue and applies them.
|
||||
*
|
||||
* Implements the same client-side prediction strategy as the real Tribes 2
|
||||
* client (verified against Tribes2.exe):
|
||||
*
|
||||
* 1. Camera::processTick — apply rotation + position at tick rate.
|
||||
* 2. Camera::interpolateTick — interpolate between tick states for smooth
|
||||
* frame-rate rendering.
|
||||
* 3. Send ALL unacked moves to the server at tick rate (like moveWritePacket).
|
||||
* 4. On server correction (readPacketData), snap to authoritative state and
|
||||
* replay all unacknowledged moves.
|
||||
*
|
||||
* The browser owns the move index counter and re-sends unacked moves for
|
||||
* UDP reliability, just like the real Tribes 2 client.
|
||||
*/
|
||||
export function InputConsumer() {
|
||||
const { moveQueue, mode, setMode } = useInputContext();
|
||||
const adapter = useLiveSelector((s) => s.adapter);
|
||||
const gameStatus = useLiveSelector((s) => s.gameStatus);
|
||||
const sendMoves = useLiveSelector((s) => s.sendMoves);
|
||||
const store = useEngineStoreApi();
|
||||
const camera = useThree((state) => state.camera);
|
||||
const getTickFraction = useGetTickFraction();
|
||||
const activeAdapterRef = useRef<LiveStreamAdapter | null>(null);
|
||||
|
||||
// ── Move buffer (unacked moves) ──
|
||||
const moveBuffer = useRef<BufferedMove[]>([]);
|
||||
// Browser-owned move index counter.
|
||||
const nextMoveIndex = useRef(0);
|
||||
// The last lastMoveAck we processed (to detect new server corrections).
|
||||
const lastProcessedAck = useRef(0);
|
||||
// The last server camera snapshot we reconciled from (identity check).
|
||||
const lastReconciledCamera = useRef<StreamCamera | null>(null);
|
||||
|
||||
// ── Local predicted state (Torque coordinates) ──
|
||||
// Absolute predicted yaw/pitch in Torque radians.
|
||||
const predYaw = useRef(0);
|
||||
const predPitch = useRef(0);
|
||||
// Predicted position in Torque world coords (x=east, y=north, z=up).
|
||||
const predPos = useRef({ x: 0, y: 0, z: 0 });
|
||||
|
||||
// ── Previous tick state for interpolateTick ──
|
||||
const prevYaw = useRef(0);
|
||||
const prevPitch = useRef(0);
|
||||
const prevPos = useRef({ x: 0, y: 0, z: 0 });
|
||||
|
||||
// Whether prediction has been initialized from a server snapshot.
|
||||
const predInitialized = useRef(false);
|
||||
|
||||
// ── Accumulated input for current tick (live mode) ──
|
||||
const tickDeltaYaw = useRef(0);
|
||||
const tickDeltaPitch = useRef(0);
|
||||
const tickMoveX = useRef(0);
|
||||
const tickMoveY = useRef(0);
|
||||
const tickMoveZ = useRef(0);
|
||||
const tickTriggers = useRef([false, false, false, false, false, false]);
|
||||
|
||||
// Previous trigger state for edge detection.
|
||||
const prevTriggers = useRef([false, false, false, false, false, false]);
|
||||
|
||||
const isLive =
|
||||
!!adapter &&
|
||||
(gameStatus === "connected" || gameStatus === "authenticating");
|
||||
|
||||
// Wire adapter to engine store.
|
||||
useEffect(() => {
|
||||
if (isLive && adapter) {
|
||||
if (activeAdapterRef.current === adapter) return;
|
||||
|
||||
log.info("wiring adapter to engine store");
|
||||
const liveState = liveConnectionStore.getState();
|
||||
const liveRecording: StreamRecording = {
|
||||
source: "live",
|
||||
duration: Infinity,
|
||||
missionName: liveState.mapName ?? null,
|
||||
gameType: null,
|
||||
serverDisplayName: liveState.serverName ?? null,
|
||||
recorderName: liveState.warriorName ?? null,
|
||||
recordingDate: null,
|
||||
streamingPlayback: adapter,
|
||||
};
|
||||
|
||||
store.getState().setRecording(liveRecording);
|
||||
store.getState().setPlaybackStatus("playing");
|
||||
activeAdapterRef.current = adapter;
|
||||
|
||||
// Reset prediction state for new connection.
|
||||
predInitialized.current = false;
|
||||
moveBuffer.current.length = 0;
|
||||
nextMoveIndex.current = 0;
|
||||
lastProcessedAck.current = 0;
|
||||
lastReconciledCamera.current = null;
|
||||
|
||||
setMode("fly");
|
||||
} else if (!isLive && activeAdapterRef.current) {
|
||||
const current = store.getState().playback.recording;
|
||||
if (current?.source === "live") {
|
||||
store.getState().setRecording(null);
|
||||
}
|
||||
activeAdapterRef.current = null;
|
||||
predInitialized.current = false;
|
||||
moveBuffer.current.length = 0;
|
||||
|
||||
setMode("local");
|
||||
}
|
||||
}, [isLive, adapter, store, setMode]);
|
||||
|
||||
// ── processTick: send moves at the Torque tick rate (32Hz). ──
|
||||
useTick(() => {
|
||||
if (!activeAdapterRef.current || gameStatus !== "connected") return;
|
||||
|
||||
// Consume accumulated deltas.
|
||||
const yaw = tickDeltaYaw.current;
|
||||
const pitch = tickDeltaPitch.current;
|
||||
tickDeltaYaw.current = 0;
|
||||
tickDeltaPitch.current = 0;
|
||||
|
||||
const mx = tickMoveX.current;
|
||||
const my = tickMoveY.current;
|
||||
const mz = tickMoveZ.current;
|
||||
tickMoveX.current = 0;
|
||||
tickMoveY.current = 0;
|
||||
tickMoveZ.current = 0;
|
||||
|
||||
const triggers = [...tickTriggers.current];
|
||||
tickTriggers.current.fill(false);
|
||||
|
||||
// Trigger edge detection.
|
||||
if (triggers[2] && !prevTriggers.current[2]) {
|
||||
activeAdapterRef.current.toggleObserverMode();
|
||||
log.info("observer mode: %s", activeAdapterRef.current.observerMode);
|
||||
setMode(
|
||||
activeAdapterRef.current.observerMode === "follow" ? "follow" : "fly",
|
||||
);
|
||||
}
|
||||
prevTriggers.current = triggers;
|
||||
|
||||
// ── Camera::processTick equivalent ──
|
||||
|
||||
// Quantize rotation to match server's Move::clamp/unclamp round-trip.
|
||||
// useFrame already applied raw deltas for responsiveness; correct to
|
||||
// the quantized value the server will actually use.
|
||||
const qYaw = quantizeRotation(yaw);
|
||||
const qPitch = quantizeRotation(pitch);
|
||||
predYaw.current += qYaw - yaw;
|
||||
predPitch.current += qPitch - pitch;
|
||||
|
||||
// Save previous tick state for interpolateTick.
|
||||
prevYaw.current = predYaw.current;
|
||||
prevPitch.current = predPitch.current;
|
||||
prevPos.current = { ...predPos.current };
|
||||
|
||||
// NOTE: Rotation is NOT re-applied here — useFrame already applied
|
||||
// (now corrected to quantized). useTick only buffers and sends.
|
||||
|
||||
// Apply position using the PREVIOUS tick's rotation, matching Tribes2.exe:
|
||||
// processTick reads the old transform matrix (built at end of previous
|
||||
// tick) for position computation, even though mRot is updated first.
|
||||
// Since predYaw/predPitch already include this tick's quantized deltas,
|
||||
// subtract them to get the old rotation.
|
||||
const speed = CAMERA_SPEED * 2;
|
||||
const posRotYaw = predYaw.current - qYaw;
|
||||
const posRotPitch = predPitch.current - qPitch;
|
||||
applyProcessTickPosition(
|
||||
predPos.current,
|
||||
posRotYaw,
|
||||
posRotPitch,
|
||||
mx,
|
||||
my,
|
||||
mz,
|
||||
speed,
|
||||
);
|
||||
|
||||
// Always set trigger[1] (altTrigger) — the Torque Camera doubles its
|
||||
// movement speed when this trigger is active. Our speedMultiplier is
|
||||
// a fraction of this faster base speed, already applied by the input
|
||||
// producer (KeyboardAndMouseHandler) to the movement axes.
|
||||
triggers[1] = true;
|
||||
|
||||
// Build the move and assign a browser-owned index.
|
||||
const moveIndex = nextMoveIndex.current++;
|
||||
const move: ClientMove = {
|
||||
x: mx,
|
||||
y: my,
|
||||
z: mz,
|
||||
yaw,
|
||||
pitch,
|
||||
roll: 0,
|
||||
trigger: triggers,
|
||||
freeLook: false,
|
||||
};
|
||||
|
||||
// Buffer for prediction replay and re-sending.
|
||||
const buffer = moveBuffer.current;
|
||||
buffer.push({ moveIndex, move, yaw: qYaw, pitch: qPitch, x: mx, y: my, z: mz });
|
||||
|
||||
// Cap buffer size.
|
||||
if (buffer.length > MAX_MOVE_BUFFER) {
|
||||
buffer.splice(0, buffer.length - MAX_MOVE_BUFFER);
|
||||
}
|
||||
|
||||
// Prune acknowledged moves before sending.
|
||||
const ack = activeAdapterRef.current.lastMoveAck;
|
||||
while (buffer.length > 0 && buffer[0].moveIndex < ack) {
|
||||
buffer.shift();
|
||||
}
|
||||
|
||||
// Send ALL unacked moves, just like Tribes 2's moveWritePacket.
|
||||
// The server deduplicates based on moveStartIndex.
|
||||
if (buffer.length > 0) {
|
||||
const movesToSend = buffer.slice(0, MAX_MOVES_PER_PACKET);
|
||||
sendMoves(
|
||||
movesToSend.map((m) => m.move),
|
||||
movesToSend[0].moveIndex,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ── useFrame: drain moveQueue, reconcile, interpolateTick + render. ──
|
||||
useFrame((state, delta) => {
|
||||
const frames = moveQueue.current;
|
||||
if (frames.length > 0) {
|
||||
// Drain the move queue.
|
||||
let dYaw = 0;
|
||||
let dPitch = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let z = 0;
|
||||
let frameDelta = 0;
|
||||
const frameTriggers = [false, false, false, false, false, false];
|
||||
|
||||
for (const frame of frames) {
|
||||
dYaw += frame.deltaYaw;
|
||||
dPitch += frame.deltaPitch;
|
||||
x = frame.x; // latest wins
|
||||
y = frame.y;
|
||||
z = frame.z;
|
||||
frameDelta += frame.delta;
|
||||
for (let i = 0; i < frame.triggers.length; i++) {
|
||||
if (frame.triggers[i]) frameTriggers[i] = true;
|
||||
}
|
||||
}
|
||||
moveQueue.current.length = 0;
|
||||
|
||||
if (isLive && activeAdapterRef.current && gameStatus === "connected") {
|
||||
// Live mode: accumulate for useTick to consume and send.
|
||||
tickDeltaYaw.current += dYaw;
|
||||
tickDeltaPitch.current += dPitch;
|
||||
tickMoveX.current = x;
|
||||
tickMoveY.current = y;
|
||||
tickMoveZ.current = z;
|
||||
for (let i = 0; i < frameTriggers.length; i++) {
|
||||
if (frameTriggers[i]) tickTriggers.current[i] = true;
|
||||
}
|
||||
|
||||
// Apply look deltas to prediction immediately for frame-rate
|
||||
// responsiveness (the pending deltas haven't been consumed by useTick
|
||||
// yet, but we still want them to affect the camera this frame).
|
||||
predYaw.current += dYaw;
|
||||
predPitch.current = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, predPitch.current + dPitch),
|
||||
);
|
||||
} else {
|
||||
// Local mode: apply input directly to camera.
|
||||
const spState = streamPlaybackStore.getState();
|
||||
if (spState.playback && !spState.freeFlyCamera) return;
|
||||
|
||||
applyLocalCamera(camera, dYaw, dPitch, x, y, z, frameDelta);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Live mode: server reconciliation + interpolateTick ──
|
||||
|
||||
if (!isLive || !activeAdapterRef.current || gameStatus !== "connected") {
|
||||
return;
|
||||
}
|
||||
|
||||
const adapterRef = activeAdapterRef.current;
|
||||
const snapshot = adapterRef.getSnapshot();
|
||||
const serverCam = snapshot?.camera;
|
||||
|
||||
// Check for new server correction.
|
||||
if (
|
||||
serverCam &&
|
||||
serverCam !== lastReconciledCamera.current &&
|
||||
typeof serverCam.yaw === "number" &&
|
||||
typeof serverCam.pitch === "number"
|
||||
) {
|
||||
lastReconciledCamera.current = serverCam;
|
||||
|
||||
// Prune acknowledged moves from the buffer.
|
||||
const ack = adapterRef.lastMoveAck;
|
||||
if (ack > lastProcessedAck.current) {
|
||||
lastProcessedAck.current = ack;
|
||||
const buffer = moveBuffer.current;
|
||||
while (buffer.length > 0 && buffer[0].moveIndex < ack) {
|
||||
buffer.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// Snap to server's authoritative state (rotation + position).
|
||||
predYaw.current = serverCam.yaw;
|
||||
predPitch.current = serverCam.pitch;
|
||||
predPos.current = {
|
||||
x: serverCam.position[0],
|
||||
y: serverCam.position[1],
|
||||
z: serverCam.position[2],
|
||||
};
|
||||
|
||||
// Replay all unacknowledged moves on top of server state.
|
||||
// This is exactly what ProcessList::advanceClientTime does:
|
||||
// for each pending move, call control->processTick(&move).
|
||||
// Position uses the rotation from BEFORE each move (old transform),
|
||||
// then rotation is updated for the next move.
|
||||
const speed = CAMERA_SPEED * 2;
|
||||
for (const move of moveBuffer.current) {
|
||||
// Position first, using pre-move rotation (matches Tribes2.exe).
|
||||
applyProcessTickPosition(
|
||||
predPos.current,
|
||||
predYaw.current,
|
||||
predPitch.current,
|
||||
move.x,
|
||||
move.y,
|
||||
move.z,
|
||||
speed,
|
||||
);
|
||||
// Then update rotation for the next move.
|
||||
predYaw.current += move.yaw;
|
||||
predPitch.current = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, predPitch.current + move.pitch),
|
||||
);
|
||||
}
|
||||
|
||||
// Also add any pending deltas not yet consumed by useTick.
|
||||
predYaw.current += tickDeltaYaw.current;
|
||||
predPitch.current = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, predPitch.current + tickDeltaPitch.current),
|
||||
);
|
||||
|
||||
// After reconciliation, snap prev state to match (no interpolation
|
||||
// glitch — we want the corrected state to appear immediately).
|
||||
prevYaw.current = predYaw.current;
|
||||
prevPitch.current = predPitch.current;
|
||||
prevPos.current = { ...predPos.current };
|
||||
|
||||
predInitialized.current = true;
|
||||
}
|
||||
|
||||
if (!predInitialized.current) return;
|
||||
|
||||
if (mode === "fly") {
|
||||
// ── Camera::interpolateTick equivalent for position ──
|
||||
// Torque interpolates between prev and current tick states:
|
||||
// renderState = prevState + (currentState - prevState) * tickFrac
|
||||
// tickFrac goes 0→1 between ticks (0 = just after tick, 1 = next tick).
|
||||
const tickFrac = getTickFraction();
|
||||
const pp = prevPos.current;
|
||||
const cp = predPos.current;
|
||||
const renderX = pp.x + (cp.x - pp.x) * tickFrac;
|
||||
const renderY = pp.y + (cp.y - pp.y) * tickFrac;
|
||||
const renderZ = pp.z + (cp.z - pp.z) * tickFrac;
|
||||
|
||||
// Convert Torque coords (x=east, y=north, z=up) to Three.js (x=north, y=up, z=east).
|
||||
state.camera.position.set(renderY, renderZ, renderX);
|
||||
|
||||
// Rotation uses predicted values directly (already includes pending
|
||||
// deltas from useFrame above for immediate responsiveness).
|
||||
const [qx, qy, qz, qw] = yawPitchToQuaternion(
|
||||
predYaw.current,
|
||||
predPitch.current,
|
||||
);
|
||||
state.camera.quaternion.set(qx, qy, qz, qw);
|
||||
} else if (mode === "follow") {
|
||||
// Follow/orbit mode: use server position for orbit target,
|
||||
// but apply our predicted rotation for responsive orbit camera.
|
||||
applyOrbitCamera(state, serverCam, predYaw.current, predPitch.current);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up on unmount.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (activeAdapterRef.current) {
|
||||
const current = store.getState().playback.recording;
|
||||
if (current?.source === "live") {
|
||||
store.getState().setRecording(null);
|
||||
}
|
||||
activeAdapterRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [store]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Apply rotation and movement to the camera locally (local/demo mode). */
|
||||
function applyLocalCamera(
|
||||
camera: Camera,
|
||||
dYaw: number,
|
||||
dPitch: number,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
delta: number,
|
||||
) {
|
||||
if (dYaw !== 0 || dPitch !== 0) {
|
||||
_lookEuler.setFromQuaternion(camera.quaternion, "YXZ");
|
||||
_lookEuler.y -= dYaw;
|
||||
_lookEuler.x -= dPitch;
|
||||
_lookEuler.x = Math.max(
|
||||
-LOCAL_MAX_PITCH,
|
||||
Math.min(LOCAL_MAX_PITCH, _lookEuler.x),
|
||||
);
|
||||
camera.quaternion.setFromEuler(_lookEuler);
|
||||
}
|
||||
|
||||
if (x !== 0 || y !== 0 || z !== 0) {
|
||||
camera.getWorldDirection(_forwardVec);
|
||||
_forwardVec.normalize();
|
||||
_sideVec.crossVectors(camera.up, _forwardVec).normalize();
|
||||
|
||||
_moveVec.set(0, 0, 0);
|
||||
if (y !== 0) _moveVec.addScaledVector(_forwardVec, y);
|
||||
if (x !== 0) _moveVec.addScaledVector(_sideVec, -x);
|
||||
if (z !== 0) _moveVec.y += z;
|
||||
|
||||
const len = _moveVec.length();
|
||||
if (len > 0) {
|
||||
// Clamp length to 1 so diagonal movement isn't faster, but preserve
|
||||
// sub-1 magnitudes from speedMultiplier.
|
||||
_moveVec.multiplyScalar((Math.min(1, len) / len) * MAX_SPEED * delta);
|
||||
camera.position.add(_moveVec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In follow/orbit mode, recompute orbit camera position from the server's
|
||||
* orbit target using our predicted rotation.
|
||||
*/
|
||||
function applyOrbitCamera(
|
||||
state: { camera: Camera },
|
||||
serverCam: StreamCamera | undefined,
|
||||
predYaw: number,
|
||||
predPitch: number,
|
||||
) {
|
||||
if (
|
||||
!serverCam ||
|
||||
serverCam.mode !== "third-person" ||
|
||||
!serverCam.orbitTargetId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = streamPlaybackStore.getState().root;
|
||||
if (!root) return;
|
||||
|
||||
const targetGroup = root.children.find(
|
||||
(child) => child.name === serverCam.orbitTargetId,
|
||||
);
|
||||
if (!targetGroup) return;
|
||||
|
||||
_orbitTarget.copy(targetGroup.position);
|
||||
const entities = streamPlaybackStore.getState().entities;
|
||||
const orbitEntity = entities.get(serverCam.orbitTargetId);
|
||||
if (orbitEntity?.renderType === "Player") {
|
||||
_orbitTarget.y += 1.0;
|
||||
}
|
||||
|
||||
const sx = Math.sin(predPitch);
|
||||
const cx = Math.cos(predPitch);
|
||||
const sz = Math.sin(predYaw);
|
||||
const cz = Math.cos(predYaw);
|
||||
// Camera pulls back along negative forward (Torque Rz*Rx column 1,
|
||||
// converted to Three.js coords).
|
||||
_orbitDir.set(-cz * cx, -sx, sz * cx);
|
||||
|
||||
if (_orbitDir.lengthSq() > 1e-8) {
|
||||
_orbitDir.normalize();
|
||||
const orbitDistance = Math.max(0.1, serverCam.orbitDistance ?? 4);
|
||||
state.camera.position
|
||||
.copy(_orbitTarget)
|
||||
.addScaledVector(_orbitDir, orbitDistance);
|
||||
state.camera.lookAt(_orbitTarget);
|
||||
}
|
||||
}
|
||||
48
src/components/InputContext.tsx
Normal file
48
src/components/InputContext.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { createContext, useContext } from "react";
|
||||
|
||||
export type InputMode = "local" | "fly" | "follow";
|
||||
|
||||
export interface InputFrame {
|
||||
/** Look rotation deltas (radians). */
|
||||
deltaYaw: number;
|
||||
deltaPitch: number;
|
||||
/** Movement axes [-1, 1], pre-scaled by speedMultiplier. */
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
/** Trigger states. OR'd by consumer between ticks. */
|
||||
triggers: boolean[];
|
||||
/** Frame delta time (seconds). */
|
||||
delta: number;
|
||||
}
|
||||
|
||||
export type OnInput = (frame: InputFrame) => void;
|
||||
|
||||
export interface InputContextValue {
|
||||
/** Ref to accumulated input frames. Consumer reads and clears. */
|
||||
moveQueue: React.RefObject<InputFrame[]>;
|
||||
/** Callback for producers to report input each frame. */
|
||||
onInput: OnInput;
|
||||
/** Current input mode. Set by InputConsumer, read by producers. */
|
||||
mode: InputMode;
|
||||
/** Setter for mode (called by InputConsumer). */
|
||||
setMode: (mode: InputMode) => void;
|
||||
}
|
||||
|
||||
export const InputContext = createContext<InputContextValue | null>(null);
|
||||
|
||||
export function useInputContext(): InputContextValue {
|
||||
const ctx = useContext(InputContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useInputContext must be used within an InputProvider");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function useOnInput(): OnInput {
|
||||
return useInputContext().onInput;
|
||||
}
|
||||
|
||||
export function useInputMode(): InputMode {
|
||||
return useInputContext().mode;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { lazy, ReactNode, Suspense } from "react";
|
||||
import { lazy, ReactNode, Suspense, useCallback, useRef, useState } from "react";
|
||||
import { KeyboardControls } from "@react-three/drei";
|
||||
import { JoystickProvider } from "./JoystickContext";
|
||||
import { useTouchDevice } from "./useTouchDevice";
|
||||
|
|
@ -6,6 +6,12 @@ import {
|
|||
KeyboardAndMouseHandler,
|
||||
KEYBOARD_CONTROLS,
|
||||
} from "./KeyboardAndMouseHandler";
|
||||
import {
|
||||
InputContext,
|
||||
type InputFrame,
|
||||
type InputMode,
|
||||
type OnInput,
|
||||
} from "./InputContext";
|
||||
|
||||
const TouchHandler = lazy(() =>
|
||||
import("@/src/components/TouchHandler").then((mod) => ({
|
||||
|
|
@ -14,14 +20,23 @@ const TouchHandler = lazy(() =>
|
|||
);
|
||||
|
||||
export function InputProvider({ children }: { children: ReactNode }) {
|
||||
const moveQueue = useRef<InputFrame[]>([]);
|
||||
const [mode, setMode] = useState<InputMode>("local");
|
||||
|
||||
const onInput: OnInput = useCallback((frame: InputFrame) => {
|
||||
moveQueue.current.push(frame);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<KeyboardControls map={KEYBOARD_CONTROLS}>
|
||||
<JoystickProvider>{children}</JoystickProvider>
|
||||
</KeyboardControls>
|
||||
<InputContext.Provider value={{ moveQueue, onInput, mode, setMode }}>
|
||||
<KeyboardControls map={KEYBOARD_CONTROLS}>
|
||||
<JoystickProvider>{children}</JoystickProvider>
|
||||
</KeyboardControls>
|
||||
</InputContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function InputHandlers() {
|
||||
export function InputProducers() {
|
||||
const isTouch = useTouchDevice();
|
||||
|
||||
return (
|
||||
|
|
@ -35,3 +50,6 @@ export function InputHandlers() {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** @deprecated Use `InputProducers` instead. */
|
||||
export const InputHandlers = InputProducers;
|
||||
|
|
|
|||
|
|
@ -170,12 +170,12 @@ export function InspectorControls({
|
|||
<input
|
||||
id="speedInput"
|
||||
type="range"
|
||||
min={0.1}
|
||||
max={5}
|
||||
step={0.05}
|
||||
value={speedMultiplier}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={Math.round(speedMultiplier * 100)}
|
||||
onChange={(event) =>
|
||||
setSpeedMultiplier(parseFloat(event.target.value))
|
||||
setSpeedMultiplier(parseFloat(event.target.value) / 100)
|
||||
}
|
||||
/>
|
||||
<p className={styles.Description}>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { useEffect, useEffectEvent, useRef } from "react";
|
||||
import { Euler, Vector3 } from "three";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useKeyboardControls } from "@react-three/drei";
|
||||
import { PointerLockControls } from "three-stdlib";
|
||||
import { useControls } from "./SettingsProvider";
|
||||
import {
|
||||
MAX_SPEED_MULTIPLIER,
|
||||
MIN_SPEED_MULTIPLIER,
|
||||
useControls,
|
||||
} from "./SettingsProvider";
|
||||
import { useCameras } from "./CamerasProvider";
|
||||
import { streamPlaybackStore } from "../state/streamPlaybackStore";
|
||||
import { useInputContext } from "./InputContext";
|
||||
|
||||
export enum Controls {
|
||||
forward = "forward",
|
||||
|
|
@ -51,16 +54,22 @@ export const KEYBOARD_CONTROLS = [
|
|||
{ name: Controls.camera9, keys: ["Digit9"] },
|
||||
];
|
||||
|
||||
const BASE_SPEED = 80;
|
||||
const MIN_SPEED_ADJUSTMENT = 0.05;
|
||||
const MAX_SPEED_ADJUSTMENT = 0.5;
|
||||
const MAX_PITCH = Math.PI / 2 - 0.01; // ~89°
|
||||
const MIN_SPEED_ADJUSTMENT = 2;
|
||||
const MAX_SPEED_ADJUSTMENT = 10;
|
||||
const DRAG_THRESHOLD = 3; // px of movement before it counts as a drag
|
||||
|
||||
/** Shared mouse/look sensitivity used across all modes (.mis, .rec, live). */
|
||||
export const MOUSE_SENSITIVITY = 0.003;
|
||||
export const ARROW_LOOK_SPEED = 1; // radians/sec
|
||||
|
||||
function quantizeSpeed(speedMultiplier: number): number {
|
||||
// Map [0.01, 1] → [1/16, 1], snapped to the 6-bit grid (multiples of 1/16).
|
||||
const t =
|
||||
(speedMultiplier - MIN_SPEED_MULTIPLIER) / (1 - MIN_SPEED_MULTIPLIER);
|
||||
const steps = Math.round(t * 15); // 0..15 → 16 levels (1/16 to 16/16)
|
||||
return (steps + 1) / 16;
|
||||
}
|
||||
|
||||
export function KeyboardAndMouseHandler() {
|
||||
// Don't let KeyboardControls handle stuff when metaKey is held.
|
||||
useEffect(() => {
|
||||
|
|
@ -85,6 +94,7 @@ export function KeyboardAndMouseHandler() {
|
|||
|
||||
const { speedMultiplier, setSpeedMultiplier, invertScroll, invertDrag } =
|
||||
useControls();
|
||||
const { onInput, mode } = useInputContext();
|
||||
const [subscribe, getKeys] = useKeyboardControls<Controls>();
|
||||
const camera = useThree((state) => state.camera);
|
||||
const gl = useThree((state) => state.gl);
|
||||
|
|
@ -94,11 +104,13 @@ export function KeyboardAndMouseHandler() {
|
|||
const getInvertScroll = useEffectEvent(() => invertScroll);
|
||||
const getInvertDrag = useEffectEvent(() => invertDrag);
|
||||
|
||||
// Scratch vectors/euler to avoid allocations each frame
|
||||
const forwardVec = useRef(new Vector3());
|
||||
const sideVec = useRef(new Vector3());
|
||||
const moveVec = useRef(new Vector3());
|
||||
const lookEuler = useRef(new Euler(0, 0, 0, "YXZ"));
|
||||
// Accumulated mouse deltas between frames.
|
||||
const mouseDeltaYaw = useRef(0);
|
||||
const mouseDeltaPitch = useRef(0);
|
||||
|
||||
// Trigger flags set by event handlers, consumed in useFrame.
|
||||
const triggerFire = useRef(false);
|
||||
const triggerObserve = useRef(false);
|
||||
|
||||
// Setup pointer lock controls
|
||||
useEffect(() => {
|
||||
|
|
@ -110,11 +122,11 @@ export function KeyboardAndMouseHandler() {
|
|||
};
|
||||
}, [camera, gl.domElement]);
|
||||
|
||||
// When pointer is locked: click cycles camera.
|
||||
// When pointer is unlocked: drag rotates camera, click locks pointer.
|
||||
// Mouse handling: accumulate deltas for input frames.
|
||||
// In local mode, drag-to-look works without pointer lock.
|
||||
// Pointer lock and click behavior depend on mode.
|
||||
useEffect(() => {
|
||||
const canvas = gl.domElement;
|
||||
const euler = new Euler(0, 0, 0, "YXZ");
|
||||
let dragging = false;
|
||||
let didDrag = false;
|
||||
let startX = 0;
|
||||
|
|
@ -130,6 +142,13 @@ export function KeyboardAndMouseHandler() {
|
|||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (controlsRef.current?.isLocked) {
|
||||
// Pointer is locked: accumulate raw deltas.
|
||||
mouseDeltaYaw.current += e.movementX * MOUSE_SENSITIVITY;
|
||||
mouseDeltaPitch.current += e.movementY * MOUSE_SENSITIVITY;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dragging) return;
|
||||
if (
|
||||
!didDrag &&
|
||||
|
|
@ -141,11 +160,8 @@ export function KeyboardAndMouseHandler() {
|
|||
didDrag = true;
|
||||
|
||||
const dragSign = getInvertDrag() ? -1 : 1;
|
||||
euler.setFromQuaternion(camera.quaternion, "YXZ");
|
||||
euler.y += dragSign * e.movementX * MOUSE_SENSITIVITY;
|
||||
euler.x += dragSign * e.movementY * MOUSE_SENSITIVITY;
|
||||
euler.x = Math.max(-MAX_PITCH, Math.min(MAX_PITCH, euler.x));
|
||||
camera.quaternion.setFromEuler(euler);
|
||||
mouseDeltaYaw.current += dragSign * e.movementX * MOUSE_SENSITIVITY;
|
||||
mouseDeltaPitch.current += dragSign * e.movementY * MOUSE_SENSITIVITY;
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
|
|
@ -154,10 +170,17 @@ export function KeyboardAndMouseHandler() {
|
|||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const controls = controlsRef.current;
|
||||
if (!controls || controls.isLocked) {
|
||||
nextCamera();
|
||||
if (controls?.isLocked) {
|
||||
if (mode === "follow") {
|
||||
// In follow mode, click cycles to next player (trigger 0).
|
||||
triggerFire.current = true;
|
||||
} else if (mode === "local") {
|
||||
// In local mode, click while locked cycles preset cameras.
|
||||
nextCamera();
|
||||
}
|
||||
// In fly mode, clicks while locked do nothing special.
|
||||
} else if (e.target === canvas && !didDrag) {
|
||||
controls.lock();
|
||||
controls?.lock();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -172,9 +195,9 @@ export function KeyboardAndMouseHandler() {
|
|||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
document.removeEventListener("click", handleClick);
|
||||
};
|
||||
}, [camera, gl.domElement, nextCamera]);
|
||||
}, [camera, gl.domElement, nextCamera, mode]);
|
||||
|
||||
// Handle number keys 1-9 for camera selection
|
||||
// Handle number keys 1-9 for camera selection (local-only UI action).
|
||||
useEffect(() => {
|
||||
const cameraControls = [
|
||||
Controls.camera1,
|
||||
|
|
@ -198,7 +221,7 @@ export function KeyboardAndMouseHandler() {
|
|||
});
|
||||
}, [subscribe, setCameraIndex, cameraCount]);
|
||||
|
||||
// Handle mousewheel for speed adjustment
|
||||
// Handle mousewheel for speed adjustment (local setting, stays in KMH).
|
||||
useEffect(() => {
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -207,16 +230,17 @@ export function KeyboardAndMouseHandler() {
|
|||
const direction = (e.deltaY > 0 ? -1 : 1) * scrollSign;
|
||||
|
||||
const delta =
|
||||
// Helps normalize sensitivity; trackpad scrolling will have many small
|
||||
// updates while mouse wheels have fewer updates but large deltas.
|
||||
Math.max(
|
||||
MIN_SPEED_ADJUSTMENT,
|
||||
Math.min(MAX_SPEED_ADJUSTMENT, Math.abs(e.deltaY * 0.01)),
|
||||
) * direction;
|
||||
|
||||
setSpeedMultiplier((prev) => {
|
||||
const newSpeed = Math.round((prev + delta) * 20) / 20;
|
||||
return Math.max(0.1, Math.min(5, newSpeed));
|
||||
const newSpeed = Math.round(prev * 100) + delta;
|
||||
return Math.max(
|
||||
MIN_SPEED_MULTIPLIER,
|
||||
Math.min(MAX_SPEED_MULTIPLIER, newSpeed / 100),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -228,12 +252,28 @@ export function KeyboardAndMouseHandler() {
|
|||
};
|
||||
}, [gl.domElement, setSpeedMultiplier]);
|
||||
|
||||
useFrame((state, delta) => {
|
||||
// When streaming is active and not in free-fly mode, the stream
|
||||
// (StreamingController) drives the camera — skip our movement.
|
||||
const spState = streamPlaybackStore.getState();
|
||||
if (spState.playback && !spState.freeFlyCamera) return;
|
||||
// 'O' key: toggle observer mode (sets trigger 2).
|
||||
useEffect(() => {
|
||||
if (mode === "local") return;
|
||||
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.code !== "KeyO" || e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === "INPUT" ||
|
||||
target.tagName === "TEXTAREA" ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
triggerObserve.current = true;
|
||||
};
|
||||
window.addEventListener("keydown", handleKey);
|
||||
return () => window.removeEventListener("keydown", handleKey);
|
||||
}, [mode]);
|
||||
|
||||
// Build and emit InputFrame each render frame.
|
||||
useFrame((_state, delta) => {
|
||||
const {
|
||||
forward,
|
||||
backward,
|
||||
|
|
@ -247,58 +287,60 @@ export function KeyboardAndMouseHandler() {
|
|||
lookRight,
|
||||
} = getKeys();
|
||||
|
||||
// Arrow keys: rotate camera look direction
|
||||
if (lookUp || lookDown || lookLeft || lookRight) {
|
||||
lookEuler.current.setFromQuaternion(camera.quaternion, "YXZ");
|
||||
if (lookLeft) lookEuler.current.y += ARROW_LOOK_SPEED * delta;
|
||||
if (lookRight) lookEuler.current.y -= ARROW_LOOK_SPEED * delta;
|
||||
if (lookUp) lookEuler.current.x += ARROW_LOOK_SPEED * delta;
|
||||
if (lookDown) lookEuler.current.x -= ARROW_LOOK_SPEED * delta;
|
||||
lookEuler.current.x = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, lookEuler.current.x),
|
||||
);
|
||||
camera.quaternion.setFromEuler(lookEuler.current);
|
||||
// Arrow keys contribute to look deltas.
|
||||
let deltaYaw = mouseDeltaYaw.current;
|
||||
let deltaPitch = mouseDeltaPitch.current;
|
||||
mouseDeltaYaw.current = 0;
|
||||
mouseDeltaPitch.current = 0;
|
||||
|
||||
if (lookLeft) deltaYaw -= ARROW_LOOK_SPEED * delta;
|
||||
if (lookRight) deltaYaw += ARROW_LOOK_SPEED * delta;
|
||||
if (lookUp) deltaPitch -= ARROW_LOOK_SPEED * delta;
|
||||
if (lookDown) deltaPitch += ARROW_LOOK_SPEED * delta;
|
||||
|
||||
// Movement axes, pre-scaled by speedMultiplier (clamped to [-1, 1]).
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let z = 0;
|
||||
if (left) x -= 1;
|
||||
if (right) x += 1;
|
||||
if (forward) y += 1;
|
||||
if (backward) y -= 1;
|
||||
if (up) z += 1;
|
||||
if (down) z -= 1;
|
||||
|
||||
const quantizedSpeedMultiplier = quantizeSpeed(speedMultiplier);
|
||||
|
||||
x = Math.max(-1, Math.min(1, x * quantizedSpeedMultiplier));
|
||||
y = Math.max(-1, Math.min(1, y * quantizedSpeedMultiplier));
|
||||
z = Math.max(-1, Math.min(1, z * quantizedSpeedMultiplier));
|
||||
|
||||
// Triggers.
|
||||
const triggers = [false, false, false, false, false, false];
|
||||
if (triggerFire.current) {
|
||||
triggers[0] = true;
|
||||
triggerFire.current = false;
|
||||
}
|
||||
if (triggerObserve.current) {
|
||||
triggers[2] = true;
|
||||
triggerObserve.current = false;
|
||||
}
|
||||
|
||||
if (!forward && !backward && !left && !right && !up && !down) {
|
||||
return;
|
||||
}
|
||||
// Only emit if there's actual input.
|
||||
const hasLook = deltaYaw !== 0 || deltaPitch !== 0;
|
||||
const hasMove = x !== 0 || y !== 0 || z !== 0;
|
||||
const hasTriggers = triggers.some(Boolean);
|
||||
if (!hasLook && !hasMove && !hasTriggers) return;
|
||||
|
||||
const speed = BASE_SPEED * speedMultiplier;
|
||||
|
||||
// Forward/backward: take complete camera angle into account (including Y)
|
||||
camera.getWorldDirection(forwardVec.current);
|
||||
forwardVec.current.normalize();
|
||||
|
||||
// Left/right: move along XZ plane
|
||||
sideVec.current.crossVectors(camera.up, forwardVec.current).normalize();
|
||||
|
||||
moveVec.current.set(0, 0, 0);
|
||||
|
||||
if (forward) {
|
||||
moveVec.current.add(forwardVec.current);
|
||||
}
|
||||
if (backward) {
|
||||
moveVec.current.sub(forwardVec.current);
|
||||
}
|
||||
if (left) {
|
||||
moveVec.current.add(sideVec.current);
|
||||
}
|
||||
if (right) {
|
||||
moveVec.current.sub(sideVec.current);
|
||||
}
|
||||
if (up) {
|
||||
moveVec.current.y += 1;
|
||||
}
|
||||
if (down) {
|
||||
moveVec.current.y -= 1;
|
||||
}
|
||||
|
||||
if (moveVec.current.lengthSq() > 0) {
|
||||
moveVec.current.normalize().multiplyScalar(speed * delta);
|
||||
camera.position.add(moveVec.current);
|
||||
}
|
||||
onInput({
|
||||
deltaYaw,
|
||||
deltaPitch,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
triggers,
|
||||
delta,
|
||||
});
|
||||
});
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,365 +0,0 @@
|
|||
import { useRef, useEffect } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { Vector3 } from "three";
|
||||
import { useKeyboardControls } from "@react-three/drei";
|
||||
import { createLogger } from "../logger";
|
||||
import {
|
||||
liveConnectionStore,
|
||||
useLiveSelector,
|
||||
} from "../state/liveConnectionStore";
|
||||
import { useEngineStoreApi } from "../state/engineStore";
|
||||
import { streamPlaybackStore } from "../state/streamPlaybackStore";
|
||||
import {
|
||||
Controls,
|
||||
MOUSE_SENSITIVITY,
|
||||
ARROW_LOOK_SPEED,
|
||||
} from "./KeyboardAndMouseHandler";
|
||||
import { useControls } from "./SettingsProvider";
|
||||
import { useTick, TICK_RATE } from "./TickProvider";
|
||||
import { yawPitchToQuaternion, MAX_PITCH } from "../stream/streamHelpers";
|
||||
import type { StreamRecording, StreamCamera } from "../stream/types";
|
||||
import type { LiveStreamAdapter } from "../stream/liveStreaming";
|
||||
|
||||
const log = createLogger("LiveObserver");
|
||||
|
||||
const TICK_INTERVAL = 1 / TICK_RATE;
|
||||
|
||||
// Scratch objects to avoid per-frame allocations.
|
||||
const _orbitDir = new Vector3();
|
||||
const _orbitTarget = new Vector3();
|
||||
|
||||
/** Predicted camera rotation state for client-side prediction. */
|
||||
interface PredictionState {
|
||||
/** Absolute predicted yaw (Torque radians). */
|
||||
yaw: number;
|
||||
/** Absolute predicted pitch (Torque radians). */
|
||||
pitch: number;
|
||||
/** Previous tick's yaw, for inter-tick interpolation. */
|
||||
prevYaw: number;
|
||||
/** Previous tick's pitch, for inter-tick interpolation. */
|
||||
prevPitch: number;
|
||||
/** Whether prediction has been initialized from a server snapshot. */
|
||||
initialized: boolean;
|
||||
/** Last server camera snapshot we synced from (identity check for new data). */
|
||||
lastSyncedCamera: StreamCamera | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridges the LiveStreamAdapter into the playback pipeline.
|
||||
* Sends Move structs to the relay and applies client-side rotation prediction
|
||||
* so camera look feels responsive at frame rate, matching how the real
|
||||
* Tribes 2 client works (predict locally, correct from server).
|
||||
*/
|
||||
export function LiveObserver() {
|
||||
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);
|
||||
const gl = useThree((state) => state.gl);
|
||||
const [, getKeys] = useKeyboardControls<Controls>();
|
||||
|
||||
// Accumulated rotation deltas since last move was sent. Mouse events and
|
||||
// arrow keys both add to these; consumed at the tick rate (32ms).
|
||||
const deltaYawRef = useRef(0);
|
||||
const deltaPitchRef = useRef(0);
|
||||
|
||||
// Client-side prediction state.
|
||||
const predRef = useRef<PredictionState>({
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
prevYaw: 0,
|
||||
prevPitch: 0,
|
||||
initialized: false,
|
||||
lastSyncedCamera: null,
|
||||
});
|
||||
|
||||
// Sub-tick accumulator for interpolation (0..TICK_INTERVAL).
|
||||
const tickAccRef = useRef(0);
|
||||
|
||||
// Wire adapter to engine store.
|
||||
useEffect(() => {
|
||||
if (
|
||||
adapter &&
|
||||
(gameStatus === "connected" || gameStatus === "authenticating")
|
||||
) {
|
||||
if (activeAdapterRef.current === adapter) return;
|
||||
|
||||
log.info("wiring adapter to engine store");
|
||||
const liveState = liveConnectionStore.getState();
|
||||
const liveRecording: StreamRecording = {
|
||||
source: "live",
|
||||
duration: Infinity,
|
||||
missionName: liveState.mapName ?? null,
|
||||
gameType: null,
|
||||
serverDisplayName: liveState.serverName ?? null,
|
||||
recorderName: liveState.warriorName ?? null,
|
||||
recordingDate: null,
|
||||
streamingPlayback: adapter,
|
||||
};
|
||||
|
||||
store.getState().setRecording(liveRecording);
|
||||
store.getState().setPlaybackStatus("playing");
|
||||
activeAdapterRef.current = adapter;
|
||||
// Reset prediction when connecting to a new server.
|
||||
predRef.current.initialized = false;
|
||||
predRef.current.lastSyncedCamera = null;
|
||||
} else if (!adapter && activeAdapterRef.current) {
|
||||
// Only clear the recording if it's still the live one we set.
|
||||
const current = store.getState().playback.recording;
|
||||
if (current?.source === "live") {
|
||||
store.getState().setRecording(null);
|
||||
}
|
||||
activeAdapterRef.current = null;
|
||||
predRef.current.initialized = false;
|
||||
}
|
||||
}, [adapter, gameStatus, store]);
|
||||
|
||||
// Accumulate mouse deltas when pointer is locked or dragging.
|
||||
useEffect(() => {
|
||||
let dragging = false;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (document.pointerLockElement) {
|
||||
// Match Three.js PointerLockControls default (0.002).
|
||||
deltaYawRef.current += e.movementX * 0.002;
|
||||
deltaPitchRef.current += e.movementY * 0.002;
|
||||
} else if (dragging) {
|
||||
deltaYawRef.current += e.movementX * MOUSE_SENSITIVITY;
|
||||
deltaPitchRef.current += e.movementY * MOUSE_SENSITIVITY;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (!document.pointerLockElement && e.target === gl.domElement) {
|
||||
dragging = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
dragging = false;
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mousedown", handleMouseDown);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mousedown", handleMouseDown);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
}, [gl.domElement]);
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClick, { capture: true });
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClick, { capture: true });
|
||||
};
|
||||
}, [gl.domElement]);
|
||||
|
||||
// 'O' toggles between follow and free-fly observer modes on the server.
|
||||
useEffect(() => {
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.code !== "KeyO" || e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === "INPUT" ||
|
||||
target.tagName === "TEXTAREA" ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!activeAdapterRef.current) return;
|
||||
|
||||
activeAdapterRef.current.toggleObserverMode();
|
||||
log.info("observer mode: %s", activeAdapterRef.current.observerMode);
|
||||
};
|
||||
window.addEventListener("keydown", handleKey);
|
||||
return () => window.removeEventListener("keydown", handleKey);
|
||||
}, []);
|
||||
|
||||
// Accumulate arrow-key rotation each render frame (frame-rate independent).
|
||||
useFrame((_, delta) => {
|
||||
if (!activeAdapterRef.current || gameStatus !== "connected") return;
|
||||
const { lookUp, lookDown, lookLeft, lookRight } = getKeys();
|
||||
if (lookRight) deltaYawRef.current += ARROW_LOOK_SPEED * delta;
|
||||
if (lookLeft) deltaYawRef.current -= ARROW_LOOK_SPEED * delta;
|
||||
if (lookDown) deltaPitchRef.current += ARROW_LOOK_SPEED * delta;
|
||||
if (lookUp) deltaPitchRef.current -= ARROW_LOOK_SPEED * delta;
|
||||
});
|
||||
|
||||
// Send moves at the Torque tick rate (32Hz) and apply rotation prediction.
|
||||
useTick(() => {
|
||||
if (!activeAdapterRef.current || gameStatus !== "connected") return;
|
||||
|
||||
const { forward, backward, left, right, up, down } = getKeys();
|
||||
|
||||
// Torque Camera axes: x = strafe (+ right), y = forward/back, z = up/down.
|
||||
let mx = 0;
|
||||
let my = 0;
|
||||
let mz = 0;
|
||||
if (forward) my += 1;
|
||||
if (backward) my -= 1;
|
||||
if (left) mx -= 1;
|
||||
if (right) mx += 1;
|
||||
if (up) mz += 1;
|
||||
if (down) mz -= 1;
|
||||
|
||||
// Consume accumulated rotation deltas.
|
||||
const yaw = deltaYawRef.current;
|
||||
const pitch = deltaPitchRef.current;
|
||||
deltaYawRef.current = 0;
|
||||
deltaPitchRef.current = 0;
|
||||
|
||||
// Apply prediction: save previous tick state, then advance.
|
||||
const pred = predRef.current;
|
||||
pred.prevYaw = pred.yaw;
|
||||
pred.prevPitch = pred.pitch;
|
||||
pred.yaw += yaw;
|
||||
pred.pitch = Math.max(-MAX_PITCH, Math.min(MAX_PITCH, pred.pitch + pitch));
|
||||
// Reset sub-tick accumulator for interpolation.
|
||||
tickAccRef.current = 0;
|
||||
|
||||
// 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,
|
||||
y: my * speed,
|
||||
z: mz * speed,
|
||||
yaw,
|
||||
pitch,
|
||||
roll: 0,
|
||||
trigger: [false, true, false, false, false, false],
|
||||
freeLook: false,
|
||||
});
|
||||
});
|
||||
|
||||
// Override camera rotation with predicted values at frame rate.
|
||||
// Priority 1 ensures this runs AFTER StreamingController (priority 0),
|
||||
// which handles position from server snapshots.
|
||||
useFrame((state, delta) => {
|
||||
if (!activeAdapterRef.current || gameStatus !== "connected") return;
|
||||
|
||||
const pred = predRef.current;
|
||||
|
||||
// Sync prediction base from each new server snapshot. The server's
|
||||
// yaw/pitch is authoritative; we layer any pending (unconsumed) mouse
|
||||
// deltas on top so the camera feels responsive between server updates.
|
||||
const snapshot = activeAdapterRef.current.getSnapshot();
|
||||
const serverCam = snapshot?.camera;
|
||||
if (
|
||||
serverCam &&
|
||||
serverCam !== pred.lastSyncedCamera &&
|
||||
typeof serverCam.yaw === "number" &&
|
||||
typeof serverCam.pitch === "number"
|
||||
) {
|
||||
// Pending deltas not yet consumed by useTick — replay on top of server.
|
||||
const pendingYaw = deltaYawRef.current;
|
||||
const pendingPitch = deltaPitchRef.current;
|
||||
|
||||
pred.prevYaw = pred.initialized ? pred.yaw : serverCam.yaw;
|
||||
pred.prevPitch = pred.initialized ? pred.pitch : serverCam.pitch;
|
||||
pred.yaw = serverCam.yaw + pendingYaw;
|
||||
pred.pitch = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, serverCam.pitch + pendingPitch),
|
||||
);
|
||||
pred.lastSyncedCamera = serverCam;
|
||||
pred.initialized = true;
|
||||
}
|
||||
|
||||
if (!pred.initialized) return;
|
||||
|
||||
// Advance sub-tick accumulator for interpolation.
|
||||
tickAccRef.current += delta;
|
||||
const t = Math.min(1, tickAccRef.current / TICK_INTERVAL);
|
||||
|
||||
// Interpolate between previous and current tick prediction, then add
|
||||
// pending (unconsumed) mouse/arrow deltas so rotation responds at frame
|
||||
// rate rather than waiting for the next useTick to consume them.
|
||||
const interpYaw =
|
||||
pred.prevYaw + (pred.yaw - pred.prevYaw) * t + deltaYawRef.current;
|
||||
const interpPitch = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(
|
||||
MAX_PITCH,
|
||||
pred.prevPitch +
|
||||
(pred.pitch - pred.prevPitch) * t +
|
||||
deltaPitchRef.current,
|
||||
),
|
||||
);
|
||||
|
||||
// Convert predicted rotation to Three.js quaternion and apply.
|
||||
const [qx, qy, qz, qw] = yawPitchToQuaternion(interpYaw, interpPitch);
|
||||
|
||||
// For third-person (orbit) mode, recompute orbit position from predicted
|
||||
// angles so the orbit responds at frame rate too.
|
||||
if (serverCam?.mode === "third-person" && serverCam.orbitTargetId) {
|
||||
const root = streamPlaybackStore.getState().root;
|
||||
const targetGroup = root?.children.find(
|
||||
(child) => child.name === serverCam.orbitTargetId,
|
||||
);
|
||||
if (targetGroup) {
|
||||
_orbitTarget.copy(targetGroup.position);
|
||||
const entities = streamPlaybackStore.getState().entities;
|
||||
const orbitEntity = entities.get(serverCam.orbitTargetId);
|
||||
if (orbitEntity?.renderType === "Player") {
|
||||
_orbitTarget.y += 1.0;
|
||||
}
|
||||
|
||||
const sx = Math.sin(interpPitch);
|
||||
const cx = Math.cos(interpPitch);
|
||||
const sz = Math.sin(interpYaw);
|
||||
const cz = Math.cos(interpYaw);
|
||||
// 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();
|
||||
const orbitDistance = Math.max(0.1, serverCam.orbitDistance ?? 4);
|
||||
state.camera.position
|
||||
.copy(_orbitTarget)
|
||||
.addScaledVector(_orbitDir, orbitDistance);
|
||||
state.camera.lookAt(_orbitTarget);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Observer fly or first-person: override rotation only (position comes
|
||||
// from StreamingController's server snapshot interpolation).
|
||||
state.camera.quaternion.set(qx, qy, qz, qw);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up on unmount.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (activeAdapterRef.current) {
|
||||
const current = store.getState().playback.recording;
|
||||
if (current?.source === "live") {
|
||||
store.getState().setRecording(null);
|
||||
}
|
||||
activeAdapterRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [store]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -125,9 +125,12 @@ function MusicPlayer({ track }: { track: string }) {
|
|||
const url = `${RESOURCE_ROOT_URL}music/${track.toLowerCase()}.mp3`;
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
audioRef.current?.pause();
|
||||
};
|
||||
const audio = audioRef.current;
|
||||
if (audio) {
|
||||
return () => {
|
||||
audio.pause();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const toggle = () => {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,11 @@ import { Camera } from "three";
|
|||
import { InspectorControls } from "@/src/components/InspectorControls";
|
||||
import { MissionSelect } from "@/src/components/MissionSelect";
|
||||
import { StreamingMissionInfo } from "@/src/components/StreamingMissionInfo";
|
||||
import { SettingsProvider } from "@/src/components/SettingsProvider";
|
||||
import { useSettings } from "@/src/components/SettingsProvider";
|
||||
import { ObserverCamera } from "@/src/components/ObserverCamera";
|
||||
import { AudioProvider } from "@/src/components/AudioContext";
|
||||
import { CamerasProvider } from "@/src/components/CamerasProvider";
|
||||
import { InputConsumer } from "./InputConsumer";
|
||||
import {
|
||||
RecordingProvider,
|
||||
useRecording,
|
||||
|
|
@ -33,11 +34,10 @@ import {
|
|||
import { usePublicWindowAPI } from "@/src/components/usePublicWindowAPI";
|
||||
import {
|
||||
CurrentMission,
|
||||
useFogQueryState,
|
||||
useMissionQueryState,
|
||||
} from "@/src/components/useQueryParams";
|
||||
import { ThreeCanvas, InvalidateFunction } from "@/src/components/ThreeCanvas";
|
||||
import { InputHandlers, InputProvider } from "./InputHandlers";
|
||||
import { InputProducers, InputProvider } from "./InputHandlers";
|
||||
import { VisualInput } from "./VisualInput";
|
||||
import { LoadingIndicator } from "./LoadingIndicator";
|
||||
import { AudioEnabled } from "./AudioEnabled";
|
||||
|
|
@ -81,10 +81,6 @@ const DebugElements = createLazy(
|
|||
() => import("@/src/components/DebugElements"),
|
||||
);
|
||||
const Mission = createLazy("Mission", () => import("@/src/components/Mission"));
|
||||
const LiveObserver = createLazy(
|
||||
"LiveObserver",
|
||||
() => import("@/src/components/LiveObserver"),
|
||||
);
|
||||
const ChatSoundPlayer = createLazy(
|
||||
"ChatSoundPlayer",
|
||||
() => import("@/src/components/ChatSoundPlayer"),
|
||||
|
|
@ -104,16 +100,12 @@ const ServerBrowser = createLazy(
|
|||
|
||||
export function MapInspector() {
|
||||
const [currentMission, setCurrentMission] = useMissionQueryState();
|
||||
const [fogEnabledOverride, setFogEnabledOverride] = useFogQueryState();
|
||||
|
||||
const clearFogEnabledOverride = useCallback(() => {
|
||||
setFogEnabledOverride(null);
|
||||
}, [setFogEnabledOverride]);
|
||||
const features = useFeatures();
|
||||
const { clearFogEnabledOverride, sidebarOpen, setSidebarOpen } =
|
||||
useSettings();
|
||||
const { missionName, missionType } = currentMission;
|
||||
const [mapInfoOpen, setMapInfoOpen] = useState(false);
|
||||
const [serverBrowserOpen, setServerBrowserOpen] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [choosingMap, setChoosingMap] = useState(false);
|
||||
const [missionLoadingProgress, setMissionLoadingProgress] = useState(0);
|
||||
const [showLoadingIndicator, setShowLoadingIndicator] = useState(true);
|
||||
|
|
@ -136,7 +128,7 @@ export function MapInspector() {
|
|||
setSidebarOpen(false);
|
||||
}
|
||||
},
|
||||
[clearFogEnabledOverride, setCurrentMission, isTouch],
|
||||
[clearFogEnabledOverride, setCurrentMission, isTouch, setSidebarOpen],
|
||||
);
|
||||
|
||||
usePublicWindowAPI({ onChangeMission: changeMission });
|
||||
|
|
@ -144,8 +136,6 @@ export function MapInspector() {
|
|||
const recording = useRecording();
|
||||
const dataSource = useDataSource();
|
||||
const hasStreamData = dataSource === "demo" || dataSource === "live";
|
||||
const hasLiveAdapter = useLiveSelector((s) => s.adapter != null);
|
||||
|
||||
// Sync the mission query param when streaming data provides a mission name.
|
||||
const streamMissionName = useMissionName();
|
||||
const streamMissionType = useMissionType();
|
||||
|
|
@ -180,13 +170,13 @@ export function MapInspector() {
|
|||
if (gameStatus === "connected" && isTouch) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
}, [gameStatus, isTouch]);
|
||||
}, [gameStatus, isTouch, setSidebarOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (recording && isTouch) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
}, [isTouch, recording]);
|
||||
}, [isTouch, recording, setSidebarOpen]);
|
||||
|
||||
const loadingProgress = missionLoadingProgress;
|
||||
const isLoading = loadingProgress < 1;
|
||||
|
|
@ -214,196 +204,183 @@ export function MapInspector() {
|
|||
return (
|
||||
<main className={styles.Frame}>
|
||||
<RecordingProvider>
|
||||
<SettingsProvider
|
||||
fogEnabledOverride={fogEnabledOverride}
|
||||
onClearFogEnabledOverride={clearFogEnabledOverride}
|
||||
<header
|
||||
className={styles.Toolbar}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<header
|
||||
className={styles.Toolbar}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
<button
|
||||
type="button"
|
||||
className={styles.ToggleSidebarButton}
|
||||
data-orientation="top"
|
||||
aria-label={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
title={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
onClick={(event) => {
|
||||
startTransition(() => setSidebarOpen((open) => !open));
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.ToggleSidebarButton}
|
||||
data-orientation="top"
|
||||
aria-label={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
title={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
onClick={(event) => {
|
||||
startTransition(() => setSidebarOpen((open) => !open));
|
||||
}}
|
||||
>
|
||||
{sidebarOpen ? <LuPanelTopClose /> : <LuPanelTopOpen />}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.ToggleSidebarButton}
|
||||
data-orientation="left"
|
||||
aria-label={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
title={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
onClick={(event) => {
|
||||
startTransition(() => setSidebarOpen((open) => !open));
|
||||
}}
|
||||
>
|
||||
{sidebarOpen ? <LuPanelLeftClose /> : <LuPanelLeftOpen />}
|
||||
</button>
|
||||
<Activity
|
||||
mode={hasStreamData && !choosingMap ? "visible" : "hidden"}
|
||||
>
|
||||
<StreamingMissionInfo />
|
||||
</Activity>
|
||||
<Activity
|
||||
mode={!hasStreamData || choosingMap ? "visible" : "hidden"}
|
||||
>
|
||||
<MissionSelect
|
||||
value={choosingMap ? "" : missionName}
|
||||
missionType={choosingMap ? "" : missionType}
|
||||
onChange={changeMission}
|
||||
autoFocus={choosingMap}
|
||||
/>
|
||||
{choosingMap && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.CancelButton}
|
||||
onClick={() => {
|
||||
setChoosingMap(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</Activity>
|
||||
</header>
|
||||
{sidebarOpen ? <div className={styles.Backdrop} /> : null}
|
||||
<Activity mode={sidebarOpen ? "visible" : "hidden"}>
|
||||
<ViewTransition>
|
||||
<div
|
||||
className={styles.Sidebar}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
data-open={sidebarOpen}
|
||||
>
|
||||
<InspectorControls
|
||||
missionName={missionName}
|
||||
missionType={missionType}
|
||||
onOpenMapInfo={() => setMapInfoOpen(true)}
|
||||
onOpenServerBrowser={
|
||||
features.live ? () => setServerBrowserOpen(true) : undefined
|
||||
}
|
||||
onChooseMap={
|
||||
hasStreamData
|
||||
? () => {
|
||||
setChoosingMap(true);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onCancelChoosingMap={() => {
|
||||
setChoosingMap(false);
|
||||
}}
|
||||
choosingMap={choosingMap}
|
||||
cameraRef={cameraRef}
|
||||
invalidateRef={invalidateRef}
|
||||
/>
|
||||
</div>
|
||||
</ViewTransition>
|
||||
{sidebarOpen ? <LuPanelTopClose /> : <LuPanelTopOpen />}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.ToggleSidebarButton}
|
||||
data-orientation="left"
|
||||
aria-label={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
title={sidebarOpen ? "Close sidebar" : "Open sidebar"}
|
||||
onClick={(event) => {
|
||||
startTransition(() => setSidebarOpen((open) => !open));
|
||||
}}
|
||||
>
|
||||
{sidebarOpen ? <LuPanelLeftClose /> : <LuPanelLeftOpen />}
|
||||
</button>
|
||||
<Activity mode={hasStreamData && !choosingMap ? "visible" : "hidden"}>
|
||||
<StreamingMissionInfo />
|
||||
</Activity>
|
||||
<InputProvider>
|
||||
<div className={styles.Content}>
|
||||
<div className={styles.ThreeView}>
|
||||
<ThreeCanvas
|
||||
dpr={mapInfoOpen || serverBrowserOpen ? 0.25 : undefined}
|
||||
onCreated={(state) => {
|
||||
cameraRef.current = state.camera;
|
||||
invalidateRef.current = state.invalidate;
|
||||
}}
|
||||
>
|
||||
<TickProvider>
|
||||
<CamerasProvider>
|
||||
<InputHandlers />
|
||||
<AudioProvider>
|
||||
<SceneLighting />
|
||||
<Suspense>
|
||||
<EntityScene />
|
||||
</Suspense>
|
||||
<ObserverCamera />
|
||||
<AudioEnabled>
|
||||
<ChatSoundPlayer />
|
||||
</AudioEnabled>
|
||||
<DebugEnabled>
|
||||
<DebugElements />
|
||||
</DebugEnabled>
|
||||
{recording ? (
|
||||
<Suspense>
|
||||
<StreamingController recording={recording} />
|
||||
</Suspense>
|
||||
) : null}
|
||||
{!hasStreamData ? (
|
||||
<Suspense>
|
||||
<Mission
|
||||
key={`${missionName}~${missionType}`}
|
||||
name={missionName}
|
||||
missionType={missionType}
|
||||
onLoadingChange={handleLoadingChange}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
{hasLiveAdapter ? (
|
||||
<Suspense>
|
||||
<LiveObserver />
|
||||
</Suspense>
|
||||
) : null}
|
||||
</AudioProvider>
|
||||
</CamerasProvider>
|
||||
</TickProvider>
|
||||
</ThreeCanvas>
|
||||
</div>
|
||||
{hasStreamData ? (
|
||||
<Suspense>
|
||||
<PlayerHUD />
|
||||
</Suspense>
|
||||
) : null}
|
||||
<VisualInput />
|
||||
{showLoadingIndicator && (
|
||||
<LoadingIndicator
|
||||
isLoading={isLoading}
|
||||
progress={loadingProgress}
|
||||
/>
|
||||
)}
|
||||
<Activity mode={!hasStreamData || choosingMap ? "visible" : "hidden"}>
|
||||
<MissionSelect
|
||||
value={choosingMap ? "" : missionName}
|
||||
missionType={choosingMap ? "" : missionType}
|
||||
onChange={changeMission}
|
||||
autoFocus={choosingMap}
|
||||
/>
|
||||
{choosingMap && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.CancelButton}
|
||||
onClick={() => {
|
||||
setChoosingMap(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</Activity>
|
||||
</header>
|
||||
{sidebarOpen ? <div className={styles.Backdrop} /> : null}
|
||||
<Activity mode={sidebarOpen ? "visible" : "hidden"}>
|
||||
<ViewTransition>
|
||||
<div
|
||||
className={styles.Sidebar}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
data-open={sidebarOpen}
|
||||
>
|
||||
<InspectorControls
|
||||
missionName={missionName}
|
||||
missionType={missionType}
|
||||
onOpenMapInfo={() => setMapInfoOpen(true)}
|
||||
onOpenServerBrowser={
|
||||
features.live ? () => setServerBrowserOpen(true) : undefined
|
||||
}
|
||||
onChooseMap={
|
||||
hasStreamData
|
||||
? () => {
|
||||
setChoosingMap(true);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onCancelChoosingMap={() => {
|
||||
setChoosingMap(false);
|
||||
}}
|
||||
choosingMap={choosingMap}
|
||||
cameraRef={cameraRef}
|
||||
invalidateRef={invalidateRef}
|
||||
/>
|
||||
</div>
|
||||
</InputProvider>
|
||||
<footer
|
||||
className={styles.PlayerBar}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{recording?.source === "demo" ? (
|
||||
</ViewTransition>
|
||||
</Activity>
|
||||
<InputProvider>
|
||||
<div className={styles.Content}>
|
||||
<div className={styles.ThreeView}>
|
||||
<ThreeCanvas
|
||||
dpr={mapInfoOpen || serverBrowserOpen ? 0.25 : undefined}
|
||||
onCreated={(state) => {
|
||||
cameraRef.current = state.camera;
|
||||
invalidateRef.current = state.invalidate;
|
||||
}}
|
||||
>
|
||||
<TickProvider>
|
||||
<CamerasProvider>
|
||||
<InputProducers />
|
||||
<AudioProvider>
|
||||
<SceneLighting />
|
||||
<Suspense>
|
||||
<EntityScene />
|
||||
</Suspense>
|
||||
<ObserverCamera />
|
||||
<AudioEnabled>
|
||||
<ChatSoundPlayer />
|
||||
</AudioEnabled>
|
||||
<DebugEnabled>
|
||||
<DebugElements />
|
||||
</DebugEnabled>
|
||||
{recording ? (
|
||||
<Suspense>
|
||||
<StreamingController recording={recording} />
|
||||
</Suspense>
|
||||
) : null}
|
||||
{!hasStreamData ? (
|
||||
<Suspense>
|
||||
<Mission
|
||||
key={`${missionName}~${missionType}`}
|
||||
name={missionName}
|
||||
missionType={missionType}
|
||||
onLoadingChange={handleLoadingChange}
|
||||
/>
|
||||
</Suspense>
|
||||
) : null}
|
||||
<InputConsumer />
|
||||
</AudioProvider>
|
||||
</CamerasProvider>
|
||||
</TickProvider>
|
||||
</ThreeCanvas>
|
||||
</div>
|
||||
{hasStreamData ? (
|
||||
<Suspense>
|
||||
<DemoPlaybackControls />
|
||||
<PlayerHUD />
|
||||
</Suspense>
|
||||
) : null}
|
||||
</footer>
|
||||
{mapInfoOpen ? (
|
||||
<ViewTransition>
|
||||
<Suspense>
|
||||
<MapInfoDialog
|
||||
onClose={() => setMapInfoOpen(false)}
|
||||
missionName={missionName}
|
||||
missionType={missionType ?? ""}
|
||||
/>
|
||||
</Suspense>
|
||||
</ViewTransition>
|
||||
<VisualInput />
|
||||
{showLoadingIndicator && (
|
||||
<LoadingIndicator
|
||||
isLoading={isLoading}
|
||||
progress={loadingProgress}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</InputProvider>
|
||||
<footer
|
||||
className={styles.PlayerBar}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{recording?.source === "demo" ? (
|
||||
<Suspense>
|
||||
<DemoPlaybackControls />
|
||||
</Suspense>
|
||||
) : null}
|
||||
{serverBrowserOpen ? (
|
||||
<ViewTransition>
|
||||
<Suspense>
|
||||
<ServerBrowser onClose={() => setServerBrowserOpen(false)} />
|
||||
</Suspense>
|
||||
</ViewTransition>
|
||||
) : null}
|
||||
</SettingsProvider>
|
||||
</footer>
|
||||
{mapInfoOpen ? (
|
||||
<ViewTransition>
|
||||
<Suspense>
|
||||
<MapInfoDialog
|
||||
onClose={() => setMapInfoOpen(false)}
|
||||
missionName={missionName}
|
||||
missionType={missionType ?? ""}
|
||||
/>
|
||||
</Suspense>
|
||||
</ViewTransition>
|
||||
) : null}
|
||||
{serverBrowserOpen ? (
|
||||
<ViewTransition>
|
||||
<Suspense>
|
||||
<ServerBrowser onClose={() => setServerBrowserOpen(false)} />
|
||||
</Suspense>
|
||||
</ViewTransition>
|
||||
) : null}
|
||||
</RecordingProvider>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
|
||||
.WeaponHUD {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
right: 6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
|
|
@ -87,18 +87,27 @@
|
|||
|
||||
/* ── Team Scores (bottom-left) ── */
|
||||
|
||||
.TeamInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.TeamScores {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
font-family: monospace;
|
||||
bottom: 6px;
|
||||
left: 6px;
|
||||
font-size: 12px;
|
||||
border: 1px solid rgba(128, 255, 200, 0.15);
|
||||
}
|
||||
|
||||
.TeamRow {
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
padding: 2px 8px;
|
||||
padding: 4px 8px 4px 6px;
|
||||
background: rgba(0, 50, 60, 0.65);
|
||||
}
|
||||
|
||||
|
|
@ -106,35 +115,39 @@
|
|||
border-top: 1px solid rgba(128, 255, 200, 0.15);
|
||||
}
|
||||
|
||||
.TeamName {
|
||||
min-width: 6em;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.TeamNameFriendly {
|
||||
color: #2ecc40;
|
||||
min-width: 60px;
|
||||
composes: TeamName;
|
||||
color: #2de46a;
|
||||
}
|
||||
|
||||
.TeamNameEnemy {
|
||||
color: #e44;
|
||||
min-width: 60px;
|
||||
composes: TeamName;
|
||||
color: rgb(121, 203, 212);
|
||||
}
|
||||
|
||||
.TeamScore {
|
||||
color: #fff;
|
||||
min-width: 24px;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.TeamCount {
|
||||
color: #9ba;
|
||||
min-width: 24px;
|
||||
text-align: right;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
/* ── Pack + Inventory HUD (bottom-right) ── */
|
||||
|
||||
.PackInventoryHUD {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
|
@ -166,7 +179,6 @@
|
|||
}
|
||||
|
||||
.PackInvCount {
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
color: #bfe;
|
||||
min-width: 12px;
|
||||
|
|
|
|||
|
|
@ -235,15 +235,20 @@ function TeamScores() {
|
|||
(DEFAULT_TEAM_NAMES[team.teamId] ?? `Team ${team.teamId}`);
|
||||
return (
|
||||
<div key={team.teamId} className={styles.TeamRow}>
|
||||
<span
|
||||
className={
|
||||
isFriendly ? styles.TeamNameFriendly : styles.TeamNameEnemy
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
<div className={styles.TeamInfo}>
|
||||
<span
|
||||
className={
|
||||
isFriendly ? styles.TeamNameFriendly : styles.TeamNameEnemy
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</span>{" "}
|
||||
<span className={styles.TeamCount}>
|
||||
{team.playerCount}{" "}
|
||||
{team.playerCount === 1 ? "player" : "players"}
|
||||
</span>
|
||||
</div>
|
||||
<span className={styles.TeamScore}>{team.score}</span>
|
||||
<span className={styles.TeamCount}>({team.playerCount})</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ const IFF_ENEMY_URL = textureToUrl("gui/hud_enemytriangle");
|
|||
|
||||
const _tmpVec = new Vector3();
|
||||
|
||||
const EMPTY_KEYFRAMES = [];
|
||||
|
||||
/**
|
||||
* Floating nameplate above a player model showing the entity name and a health
|
||||
* bar. Fades out with distance.
|
||||
|
|
@ -45,7 +47,7 @@ export function PlayerNameplate({ entity }: { entity: PlayerEntity }) {
|
|||
}, [gltf.scene]);
|
||||
|
||||
// Check whether this entity has any health data at all.
|
||||
const keyframes = entity.keyframes ?? [];
|
||||
const keyframes = entity.keyframes ?? EMPTY_KEYFRAMES;
|
||||
const hasHealthData = useMemo(
|
||||
() => keyframes.some((kf) => kf.health != null),
|
||||
[keyframes],
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useFogQueryState } from "./useQueryParams";
|
||||
import { useTouchDevice } from "./useTouchDevice";
|
||||
|
||||
type StateSetter<T> = ReturnType<typeof useState<T>>[1];
|
||||
|
||||
|
|
@ -16,6 +18,7 @@ export type TouchMode = "dualStick" | "moveLookStick";
|
|||
type SettingsContext = {
|
||||
fogEnabled: boolean;
|
||||
setFogEnabled: StateSetter<boolean>;
|
||||
clearFogEnabledOverride: () => void;
|
||||
highQualityFog: boolean;
|
||||
setHighQualityFog: StateSetter<boolean>;
|
||||
fov: number;
|
||||
|
|
@ -28,6 +31,8 @@ type SettingsContext = {
|
|||
setWarriorName: StateSetter<string>;
|
||||
audioVolume: number;
|
||||
setAudioVolume: StateSetter<number>;
|
||||
sidebarOpen: boolean;
|
||||
setSidebarOpen: StateSetter<boolean>;
|
||||
};
|
||||
|
||||
type DebugContext = {
|
||||
|
|
@ -50,6 +55,9 @@ type ControlsContext = {
|
|||
setInvertJoystick: StateSetter<boolean>;
|
||||
};
|
||||
|
||||
export const MIN_SPEED_MULTIPLIER = 0.01;
|
||||
export const MAX_SPEED_MULTIPLIER = 1;
|
||||
|
||||
const SettingsContext = createContext<SettingsContext | null>(null);
|
||||
const DebugContext = createContext<DebugContext | null>(null);
|
||||
const ControlsContext = createContext<ControlsContext | null>(null);
|
||||
|
|
@ -68,6 +76,7 @@ type PersistedSettings = {
|
|||
invertScroll?: boolean;
|
||||
invertDrag?: boolean;
|
||||
invertJoystick?: boolean;
|
||||
sidebarOpen?: boolean;
|
||||
};
|
||||
|
||||
export function useSettings() {
|
||||
|
|
@ -82,18 +91,10 @@ export function useControls() {
|
|||
return useContext(ControlsContext);
|
||||
}
|
||||
|
||||
export function SettingsProvider({
|
||||
children,
|
||||
fogEnabledOverride,
|
||||
onClearFogEnabledOverride,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
fogEnabledOverride?: boolean | null;
|
||||
onClearFogEnabledOverride: () => void;
|
||||
}) {
|
||||
export function SettingsProvider({ children }: { children: ReactNode }) {
|
||||
const [fogEnabled, setFogEnabled] = useState(true);
|
||||
const [highQualityFog, setHighQualityFog] = useState(false);
|
||||
const [speedMultiplier, setSpeedMultiplier] = useState(1);
|
||||
const [speedMultiplier, setSpeedMultiplier] = useState(0.15);
|
||||
const [fov, setFov] = useState(90);
|
||||
const [audioEnabled, setAudioEnabled] = useState(false);
|
||||
const [audioVolume, setAudioVolume] = useState(0.75);
|
||||
|
|
@ -104,20 +105,27 @@ export function SettingsProvider({
|
|||
const [invertScroll, setInvertScroll] = useState(false);
|
||||
const [invertDrag, setInvertDrag] = useState(false);
|
||||
const [invertJoystick, setInvertJoystick] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [renderOnDemand, setRenderOnDemand] = useState(false);
|
||||
|
||||
const [fogEnabledOverride, setFogEnabledOverride] = useFogQueryState();
|
||||
const clearFogEnabledOverride = useCallback(() => {
|
||||
setFogEnabledOverride(null);
|
||||
}, [setFogEnabledOverride]);
|
||||
|
||||
const setFogEnabledWithoutOverride: StateSetter<boolean> = useCallback(
|
||||
(value) => {
|
||||
setFogEnabled(value);
|
||||
onClearFogEnabledOverride();
|
||||
clearFogEnabledOverride();
|
||||
},
|
||||
[onClearFogEnabledOverride],
|
||||
[clearFogEnabledOverride],
|
||||
);
|
||||
|
||||
const settingsContext: SettingsContext = useMemo(
|
||||
() => ({
|
||||
fogEnabled: fogEnabledOverride ?? fogEnabled,
|
||||
setFogEnabled: setFogEnabledWithoutOverride,
|
||||
clearFogEnabledOverride,
|
||||
highQualityFog,
|
||||
setHighQualityFog,
|
||||
fov,
|
||||
|
|
@ -130,17 +138,21 @@ export function SettingsProvider({
|
|||
setWarriorName,
|
||||
audioVolume,
|
||||
setAudioVolume,
|
||||
sidebarOpen,
|
||||
setSidebarOpen,
|
||||
}),
|
||||
[
|
||||
fogEnabled,
|
||||
fogEnabledOverride,
|
||||
setFogEnabledWithoutOverride,
|
||||
clearFogEnabledOverride,
|
||||
highQualityFog,
|
||||
fov,
|
||||
audioEnabled,
|
||||
animationEnabled,
|
||||
warriorName,
|
||||
audioVolume,
|
||||
sidebarOpen,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
@ -178,8 +190,13 @@ export function SettingsProvider({
|
|||
],
|
||||
);
|
||||
|
||||
const isTouch = useTouchDevice();
|
||||
|
||||
// Read persisted settings from localStorage.
|
||||
useEffect(() => {
|
||||
// Defer until we know whether or not we're on a touch device...
|
||||
if (isTouch == null) return;
|
||||
|
||||
let savedSettings: PersistedSettings = {};
|
||||
try {
|
||||
savedSettings = JSON.parse(localStorage.getItem("settings")) || {};
|
||||
|
|
@ -203,7 +220,10 @@ export function SettingsProvider({
|
|||
}
|
||||
if (savedSettings.speedMultiplier != null) {
|
||||
setSpeedMultiplier(
|
||||
Math.max(0, Math.min(1, savedSettings.speedMultiplier)),
|
||||
Math.max(
|
||||
MIN_SPEED_MULTIPLIER,
|
||||
Math.min(MAX_SPEED_MULTIPLIER, savedSettings.speedMultiplier),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (savedSettings.fov != null) {
|
||||
|
|
@ -227,7 +247,13 @@ export function SettingsProvider({
|
|||
if (savedSettings.invertJoystick != null) {
|
||||
setInvertJoystick(savedSettings.invertJoystick);
|
||||
}
|
||||
}, []);
|
||||
if (savedSettings.sidebarOpen != null) {
|
||||
// Don't restore on touch devices!
|
||||
if (!isTouch) {
|
||||
setSidebarOpen(savedSettings.sidebarOpen);
|
||||
}
|
||||
}
|
||||
}, [isTouch]);
|
||||
|
||||
// Persist settings to localStorage with debouncing to avoid excessive writes
|
||||
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
|
@ -254,6 +280,7 @@ export function SettingsProvider({
|
|||
invertScroll,
|
||||
invertDrag,
|
||||
invertJoystick,
|
||||
sidebarOpen,
|
||||
};
|
||||
try {
|
||||
localStorage.setItem("settings", JSON.stringify(settingsToSave));
|
||||
|
|
@ -281,6 +308,7 @@ export function SettingsProvider({
|
|||
invertScroll,
|
||||
invertDrag,
|
||||
invertJoystick,
|
||||
sidebarOpen,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -418,36 +418,40 @@ export function StreamingController({
|
|||
// When freeFlyCamera is active, skip stream camera positioning so
|
||||
// ObserverControls drives the camera instead.
|
||||
const freeFly = streamPlaybackStore.getState().freeFlyCamera;
|
||||
// In live mode, LiveObserver owns camera rotation (client-side prediction).
|
||||
// StreamingController still handles position, FOV, and entity interpolation.
|
||||
// In live mode, InputConsumer owns camera position and rotation
|
||||
// (moves are applied locally, matching how the real Tribes 2 client
|
||||
// handles its control Camera). StreamingController still handles
|
||||
// entity interpolation, FOV, and orbit target positioning.
|
||||
const isLive = recording.source === "live";
|
||||
|
||||
if (currentCamera && !freeFly) {
|
||||
if (previousCamera) {
|
||||
const px = previousCamera.position[0];
|
||||
const py = previousCamera.position[1];
|
||||
const pz = previousCamera.position[2];
|
||||
const cx = currentCamera.position[0];
|
||||
const cy = currentCamera.position[1];
|
||||
const cz = currentCamera.position[2];
|
||||
const ix = px + (cx - px) * interpT;
|
||||
const iy = py + (cy - py) * interpT;
|
||||
const iz = pz + (cz - pz) * interpT;
|
||||
state.camera.position.set(iy, iz, ix);
|
||||
// In live mode, InputConsumer owns both camera position and rotation
|
||||
// (client-side prediction with server reconciliation + interpolateTick,
|
||||
// matching Tribes 2's Camera behavior). StreamingController only
|
||||
// handles entity interpolation, FOV, and orbit target positioning.
|
||||
if (!isLive) {
|
||||
if (previousCamera) {
|
||||
const px = previousCamera.position[0];
|
||||
const py = previousCamera.position[1];
|
||||
const pz = previousCamera.position[2];
|
||||
const cx = currentCamera.position[0];
|
||||
const cy = currentCamera.position[1];
|
||||
const cz = currentCamera.position[2];
|
||||
const ix = px + (cx - px) * interpT;
|
||||
const iy = py + (cy - py) * interpT;
|
||||
const iz = pz + (cz - pz) * interpT;
|
||||
state.camera.position.set(iy, iz, ix);
|
||||
|
||||
if (!isLive) {
|
||||
_interpQuatA.set(...previousCamera.rotation);
|
||||
_interpQuatB.set(...currentCamera.rotation);
|
||||
_interpQuatA.slerp(_interpQuatB, interpT);
|
||||
state.camera.quaternion.copy(_interpQuatA);
|
||||
}
|
||||
} else {
|
||||
state.camera.position.set(
|
||||
currentCamera.position[1],
|
||||
currentCamera.position[2],
|
||||
currentCamera.position[0],
|
||||
);
|
||||
if (!isLive) {
|
||||
} else {
|
||||
state.camera.position.set(
|
||||
currentCamera.position[1],
|
||||
currentCamera.position[2],
|
||||
currentCamera.position[0],
|
||||
);
|
||||
state.camera.quaternion.set(...currentCamera.rotation);
|
||||
}
|
||||
}
|
||||
|
|
@ -550,8 +554,8 @@ export function StreamingController({
|
|||
}
|
||||
|
||||
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.
|
||||
// In live mode, InputConsumer handles orbit positioning from local rotation
|
||||
// so the orbit responds at frame rate. Skip here to avoid fighting.
|
||||
if (
|
||||
!freeFly &&
|
||||
!isLive &&
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@
|
|||
margin-top: -0.5px;
|
||||
}
|
||||
|
||||
.Error {
|
||||
color: rgb(255, 106, 69);
|
||||
}
|
||||
|
||||
@media (max-width: 899px) {
|
||||
.Metadata {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -68,12 +68,16 @@ export function StreamingMissionInfo() {
|
|||
</div>
|
||||
<div className={styles.Metadata}>
|
||||
{isLive ? (
|
||||
playerName ? (
|
||||
<div className={styles.Attribution}>
|
||||
Connected as{" "}
|
||||
<span className={styles.PlayerName}>{playerName}</span>
|
||||
</div>
|
||||
) : null
|
||||
isLiveConnected ? (
|
||||
playerName ? (
|
||||
<div className={styles.Attribution}>
|
||||
Connected as{" "}
|
||||
<span className={styles.PlayerName}>{playerName}</span>
|
||||
</div>
|
||||
) : null
|
||||
) : (
|
||||
<div className={styles.Error}>Disconnected</div>
|
||||
)
|
||||
) : playerName && dateString ? (
|
||||
<div className={styles.Attribution}>
|
||||
Recorded by <span className={styles.PlayerName}>{playerName}</span>{" "}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ export type TickCallback = (tick: number) => void;
|
|||
interface TickContextValue {
|
||||
subscribe: (callback: TickCallback) => () => void;
|
||||
getTick: () => number;
|
||||
/** Fraction [0, 1) of the current tick elapsed since the last tick fired. */
|
||||
getTickFraction: () => number;
|
||||
}
|
||||
|
||||
const TickContext = createContext<TickContextValue | null>(null);
|
||||
|
|
@ -56,7 +58,15 @@ export function TickProvider({ children }: TickProviderProps) {
|
|||
|
||||
const getTick = useCallback(() => tickRef.current, []);
|
||||
|
||||
const context = useMemo(() => ({ subscribe, getTick }), [subscribe, getTick]);
|
||||
const getTickFraction = useCallback(
|
||||
() => accumulatorRef.current / TICK_INTERVAL,
|
||||
[],
|
||||
);
|
||||
|
||||
const context = useMemo(
|
||||
() => ({ subscribe, getTick, getTickFraction }),
|
||||
[subscribe, getTick, getTickFraction],
|
||||
);
|
||||
|
||||
return (
|
||||
<TickContext.Provider value={context}>{children}</TickContext.Provider>
|
||||
|
|
@ -83,3 +93,11 @@ export function useGetTick() {
|
|||
}
|
||||
return context.getTick;
|
||||
}
|
||||
|
||||
export function useGetTickFraction() {
|
||||
const context = useContext(TickContext);
|
||||
if (!context) {
|
||||
throw new Error("useGetTickFraction must be used within a TickProvider");
|
||||
}
|
||||
return context.getTickFraction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import { useEffect, useEffectEvent, useRef } from "react";
|
||||
import { Euler, Vector3 } from "three";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useControls } from "./SettingsProvider";
|
||||
import { useJoystick } from "./JoystickContext";
|
||||
import { useOnInput } from "./InputContext";
|
||||
|
||||
const BASE_SPEED = 80;
|
||||
const LOOK_SENSITIVITY = 0.004;
|
||||
const STICK_LOOK_SENSITIVITY = 2.5;
|
||||
const DUAL_MOVE_DEADZONE = 0.08;
|
||||
const DUAL_LOOK_DEADZONE = 0.15;
|
||||
const SINGLE_STICK_DEADZONE = 0.15;
|
||||
const MAX_PITCH = Math.PI / 2 - 0.01; // ~89°
|
||||
|
||||
export type JoystickState = {
|
||||
angle: number;
|
||||
|
|
@ -21,25 +19,18 @@ export type JoystickState = {
|
|||
export function TouchHandler() {
|
||||
const { speedMultiplier, touchMode, invertDrag, invertJoystick } =
|
||||
useControls();
|
||||
const camera = useThree((state) => state.camera);
|
||||
const gl = useThree((state) => state.gl);
|
||||
const { moveState, lookState } = useJoystick();
|
||||
const onInput = useOnInput();
|
||||
|
||||
// Touch look state
|
||||
const euler = useRef(new Euler(0, 0, 0, "YXZ"));
|
||||
const lookTouchId = useRef<number | null>(null);
|
||||
const lastTouchPos = useRef({ x: 0, y: 0 });
|
||||
const getInvertDrag = useEffectEvent(() => invertDrag);
|
||||
|
||||
// Scratch vectors
|
||||
const forwardVec = useRef(new Vector3());
|
||||
const sideVec = useRef(new Vector3());
|
||||
const moveVec = useRef(new Vector3());
|
||||
|
||||
// Initialize euler from current camera rotation on mount
|
||||
useEffect(() => {
|
||||
euler.current.setFromQuaternion(camera.quaternion, "YXZ");
|
||||
}, [camera]);
|
||||
// Accumulated touch-drag deltas between frames.
|
||||
const touchDeltaYaw = useRef(0);
|
||||
const touchDeltaPitch = useRef(0);
|
||||
|
||||
// Touch-drag look handling (moveLookStick mode)
|
||||
useEffect(() => {
|
||||
|
|
@ -47,27 +38,13 @@ export function TouchHandler() {
|
|||
|
||||
const canvas = gl.domElement;
|
||||
|
||||
// const isTouchOnJoystick = (touch: Touch) => {
|
||||
// const zone = joystickZone.current;
|
||||
// if (!zone) return false;
|
||||
// const rect = zone.getBoundingClientRect();
|
||||
// return (
|
||||
// touch.clientX >= rect.left &&
|
||||
// touch.clientX <= rect.right &&
|
||||
// touch.clientY >= rect.top &&
|
||||
// touch.clientY <= rect.bottom
|
||||
// );
|
||||
// };
|
||||
|
||||
const handleTouchStart = (e: TouchEvent) => {
|
||||
if (lookTouchId.current !== null) return;
|
||||
for (let i = 0; i < e.changedTouches.length; i++) {
|
||||
const touch = e.changedTouches[i];
|
||||
// if (!isTouchOnJoystick(touch)) {
|
||||
lookTouchId.current = touch.identifier;
|
||||
lastTouchPos.current = { x: touch.clientX, y: touch.clientY };
|
||||
break;
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -81,14 +58,8 @@ export function TouchHandler() {
|
|||
lastTouchPos.current = { x: touch.clientX, y: touch.clientY };
|
||||
|
||||
const dragSign = getInvertDrag() ? -1 : 1;
|
||||
euler.current.setFromQuaternion(camera.quaternion, "YXZ");
|
||||
euler.current.y += dragSign * dx * LOOK_SENSITIVITY;
|
||||
euler.current.x += dragSign * dy * LOOK_SENSITIVITY;
|
||||
euler.current.x = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, euler.current.x),
|
||||
);
|
||||
camera.quaternion.setFromEuler(euler.current);
|
||||
touchDeltaYaw.current += dragSign * dx * LOOK_SENSITIVITY;
|
||||
touchDeltaPitch.current += dragSign * dy * LOOK_SENSITIVITY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -115,14 +86,23 @@ export function TouchHandler() {
|
|||
canvas.removeEventListener("touchcancel", handleTouchEnd);
|
||||
lookTouchId.current = null;
|
||||
};
|
||||
}, [camera, gl.domElement, touchMode]);
|
||||
}, [gl.domElement, touchMode]);
|
||||
|
||||
useFrame((_state, delta) => {
|
||||
const { force: moveForce, angle: moveAngle } = moveState.current;
|
||||
const { force: lookForce, angle: lookAngle } = lookState.current;
|
||||
|
||||
let deltaYaw = touchDeltaYaw.current;
|
||||
let deltaPitch = touchDeltaPitch.current;
|
||||
touchDeltaYaw.current = 0;
|
||||
touchDeltaPitch.current = 0;
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
const z = 0;
|
||||
|
||||
if (touchMode === "dualStick") {
|
||||
// Right stick → camera rotation
|
||||
// Right stick -> camera rotation
|
||||
if (lookForce > DUAL_LOOK_DEADZONE) {
|
||||
const normalizedLookForce =
|
||||
(lookForce - DUAL_LOOK_DEADZONE) / (1 - DUAL_LOOK_DEADZONE);
|
||||
|
|
@ -130,56 +110,41 @@ export function TouchHandler() {
|
|||
const lookY = Math.sin(lookAngle);
|
||||
|
||||
const joySign = invertJoystick ? -1 : 1;
|
||||
euler.current.setFromQuaternion(camera.quaternion, "YXZ");
|
||||
euler.current.y -=
|
||||
deltaYaw -=
|
||||
joySign *
|
||||
lookX *
|
||||
normalizedLookForce *
|
||||
STICK_LOOK_SENSITIVITY *
|
||||
delta;
|
||||
euler.current.x +=
|
||||
deltaPitch +=
|
||||
joySign *
|
||||
lookY *
|
||||
normalizedLookForce *
|
||||
STICK_LOOK_SENSITIVITY *
|
||||
delta;
|
||||
euler.current.x = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, euler.current.x),
|
||||
);
|
||||
camera.quaternion.setFromEuler(euler.current);
|
||||
}
|
||||
|
||||
// Left stick → movement
|
||||
// Left stick -> movement
|
||||
if (moveForce > DUAL_MOVE_DEADZONE) {
|
||||
const normalizedMoveForce =
|
||||
(moveForce - DUAL_MOVE_DEADZONE) / (1 - DUAL_MOVE_DEADZONE);
|
||||
const speed = BASE_SPEED * speedMultiplier * normalizedMoveForce;
|
||||
const joyX = Math.cos(moveAngle);
|
||||
const joyY = Math.sin(moveAngle);
|
||||
|
||||
camera.getWorldDirection(forwardVec.current);
|
||||
forwardVec.current.normalize();
|
||||
sideVec.current.crossVectors(camera.up, forwardVec.current).normalize();
|
||||
|
||||
moveVec.current
|
||||
.set(0, 0, 0)
|
||||
.addScaledVector(forwardVec.current, joyY)
|
||||
.addScaledVector(sideVec.current, -joyX);
|
||||
|
||||
if (moveVec.current.lengthSq() > 0) {
|
||||
moveVec.current.normalize().multiplyScalar(speed * delta);
|
||||
camera.position.add(moveVec.current);
|
||||
}
|
||||
// Map joystick to movement axes, pre-scaled by speedMultiplier.
|
||||
x = Math.max(
|
||||
-1,
|
||||
Math.min(1, -joyX * normalizedMoveForce * speedMultiplier),
|
||||
);
|
||||
y = Math.max(
|
||||
-1,
|
||||
Math.min(1, joyY * normalizedMoveForce * speedMultiplier),
|
||||
);
|
||||
}
|
||||
} else if (touchMode === "moveLookStick") {
|
||||
if (moveForce > 0) {
|
||||
// Move forward at half the configured speed.
|
||||
const speed = BASE_SPEED * speedMultiplier * 0.5;
|
||||
camera.getWorldDirection(forwardVec.current);
|
||||
forwardVec.current.normalize();
|
||||
moveVec.current.copy(forwardVec.current).multiplyScalar(speed * delta);
|
||||
camera.position.add(moveVec.current);
|
||||
// Move forward at half speed.
|
||||
y = Math.max(-1, Math.min(1, 0.5 * speedMultiplier));
|
||||
|
||||
if (moveForce >= SINGLE_STICK_DEADZONE) {
|
||||
// Outer zone: also control camera look (yaw + pitch).
|
||||
|
|
@ -189,29 +154,38 @@ export function TouchHandler() {
|
|||
(moveForce - SINGLE_STICK_DEADZONE) / (1 - SINGLE_STICK_DEADZONE);
|
||||
|
||||
const singleJoySign = invertJoystick ? -1 : 1;
|
||||
euler.current.setFromQuaternion(camera.quaternion, "YXZ");
|
||||
euler.current.y -=
|
||||
deltaYaw -=
|
||||
singleJoySign *
|
||||
lookX *
|
||||
normalizedLookForce *
|
||||
STICK_LOOK_SENSITIVITY *
|
||||
0.5 *
|
||||
delta;
|
||||
euler.current.x +=
|
||||
deltaPitch +=
|
||||
singleJoySign *
|
||||
lookY *
|
||||
normalizedLookForce *
|
||||
STICK_LOOK_SENSITIVITY *
|
||||
0.5 *
|
||||
delta;
|
||||
euler.current.x = Math.max(
|
||||
-MAX_PITCH,
|
||||
Math.min(MAX_PITCH, euler.current.x),
|
||||
);
|
||||
camera.quaternion.setFromEuler(euler.current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only emit if there's actual input.
|
||||
const hasLook = deltaYaw !== 0 || deltaPitch !== 0;
|
||||
const hasMove = x !== 0 || y !== 0 || z !== 0;
|
||||
if (!hasLook && !hasMove) return;
|
||||
|
||||
onInput({
|
||||
deltaYaw,
|
||||
deltaPitch,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
triggers: [],
|
||||
delta,
|
||||
});
|
||||
});
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export interface LiveConnectionStore extends LiveConnectionState {
|
|||
listServers(): void;
|
||||
joinServer(address: string, warriorName?: string): void;
|
||||
disconnectServer(): void;
|
||||
sendMove(move: ClientMove): void;
|
||||
sendMoves(moves: ClientMove[], moveStartIndex: number): void;
|
||||
sendCommand(command: string, ...args: string[]): void;
|
||||
}
|
||||
|
||||
|
|
@ -254,8 +254,8 @@ export const liveConnectionStore = createStore<LiveConnectionStore>(
|
|||
});
|
||||
},
|
||||
|
||||
sendMove(move) {
|
||||
get()._relay?.sendMove(move);
|
||||
sendMoves(moves, moveStartIndex) {
|
||||
get()._relay?.sendMoves(moves, moveStartIndex);
|
||||
},
|
||||
|
||||
sendCommand(command, ...args) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ export class LiveStreamAdapter extends StreamEngine {
|
|||
/** Current mission name as reported by the server. */
|
||||
missionName: string | null = null;
|
||||
|
||||
/** Server's latest move acknowledgment (which moveIndex it has processed). */
|
||||
lastMoveAck = 0;
|
||||
|
||||
constructor(relay: RelayClient) {
|
||||
super();
|
||||
this.relay = relay;
|
||||
|
|
@ -306,57 +309,17 @@ export class LiveStreamAdapter extends StreamEngine {
|
|||
*/
|
||||
observerMode: "fly" | "follow" = "fly";
|
||||
|
||||
/** Enter follow mode (from fly) or cycle to next player (in follow). */
|
||||
cycleObserveNext(): void {
|
||||
if (this.observerMode === "fly") {
|
||||
// Jump trigger enters observerFollow from observerFly
|
||||
log.info("observer: fly → follow (jump trigger)");
|
||||
this.sendTrigger(2);
|
||||
this.observerMode = "follow";
|
||||
} else {
|
||||
// Fire trigger cycles to next player in observerFollow
|
||||
log.info("observer: cycle next (fire trigger)");
|
||||
this.sendTrigger(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggle between follow and free-fly observer modes. */
|
||||
/** Toggle between follow and free-fly observer modes (local state only). */
|
||||
toggleObserverMode(): void {
|
||||
if (this.observerMode === "fly") {
|
||||
// Jump trigger enters observerFollow from observerFly
|
||||
log.info("observer: fly → follow (jump trigger)");
|
||||
this.sendTrigger(2);
|
||||
log.info("observer: fly → follow");
|
||||
this.observerMode = "follow";
|
||||
} else {
|
||||
// Jump trigger returns to observerFly from observerFollow
|
||||
log.info("observer: follow → fly (jump trigger)");
|
||||
this.sendTrigger(2);
|
||||
log.info("observer: follow → fly");
|
||||
this.observerMode = "fly";
|
||||
}
|
||||
}
|
||||
|
||||
private sendTrigger(index: number): void {
|
||||
const trigger: [boolean, boolean, boolean, boolean, boolean, boolean] = [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
];
|
||||
trigger[index] = true;
|
||||
this.relay.sendMove({
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
roll: 0,
|
||||
trigger,
|
||||
freeLook: false,
|
||||
});
|
||||
}
|
||||
|
||||
/** Get the player list (for observer cycling UI). */
|
||||
getPlayerList(): PlayerListEntry[] {
|
||||
const entries: PlayerListEntry[] = [];
|
||||
|
|
@ -409,6 +372,9 @@ export class LiveStreamAdapter extends StreamEngine {
|
|||
);
|
||||
}
|
||||
|
||||
// Track move acknowledgments for client-side prediction replay.
|
||||
this.lastMoveAck = parsed.gameState.lastMoveAck;
|
||||
|
||||
// Control object state
|
||||
this.processControlObject(parsed.gameState);
|
||||
|
||||
|
|
|
|||
|
|
@ -175,9 +175,9 @@ export class RelayClient {
|
|||
this.send({ type: "sendGhostAck", sequence, ghostCount });
|
||||
}
|
||||
|
||||
/** Send a move struct to the relay for forwarding to the game server. */
|
||||
sendMove(move: ClientMove): void {
|
||||
this.send({ type: "sendMove", move });
|
||||
/** Send moves to the relay for immediate forwarding to the game server. */
|
||||
sendMoves(moves: ClientMove[], moveStartIndex: number): void {
|
||||
this.send({ type: "sendMoves", moves, moveStartIndex });
|
||||
}
|
||||
|
||||
/** Close the WebSocket connection entirely. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue