input and tour improvements, bug fixes

This commit is contained in:
Brian Beck 2026-03-22 21:11:02 -07:00
parent fe90146e1e
commit 90ec7cbae2
110 changed files with 2802 additions and 1286 deletions

View file

@ -1 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{o as i}from"./react-three-fiber.esm-dhSWjERg.js";import{a}from"./SettingsProvider-xrmxG700.js";import{l as o,u as s}from"./three.module-CwgFV8Kd.js";import{r as c}from"./engineStore-Cio8vU1L.js";var l=t(),u=e(n(),1),d=r(),f=(0,u.createContext)(void 0);function p(e){let t=(0,l.c)(11),{children:n}=e,r=i(g),{audioVolume:p}=a(),_;t[0]===Symbol.for(`react.memo_cache_sentinel`)?(_={audioLoader:null,audioListener:null},t[0]=_):_=t[0];let[v,y]=(0,u.useState)(_),b,x;t[1]===r?(b=t[2],x=t[3]):(b=()=>{let e=new s,t=r.children.find(h);t||(t=new o,r.add(t)),y({audioLoader:e,audioListener:t});let n=()=>{let e=t.context;!e||e.state!==`suspended`||e.resume().finally(()=>{document.removeEventListener(`click`,n),document.removeEventListener(`keydown`,n),document.removeEventListener(`touchend`,n)})};document.addEventListener(`click`,n),document.addEventListener(`keydown`,n),document.addEventListener(`touchend`,n);let i=c.subscribe(m,e=>{let n=t.context;n&&(e===`paused`?n.suspend():n.state===`suspended`&&n.resume())});return()=>{document.removeEventListener(`click`,n),document.removeEventListener(`keydown`,n),document.removeEventListener(`touchend`,n),i(),t&&r.remove(t)}},x=[r],t[1]=r,t[2]=b,t[3]=x),(0,u.useEffect)(b,x);let S,C;t[4]!==v.audioListener||t[5]!==p?(S=()=>{v.audioListener?.setMasterVolume(p)},C=[p,v.audioListener],t[4]=v.audioListener,t[5]=p,t[6]=S,t[7]=C):(S=t[6],C=t[7]),(0,u.useEffect)(S,C);let w;return t[8]!==v||t[9]!==n?(w=(0,d.jsx)(f.Provider,{value:v,children:n}),t[8]=v,t[9]=n,t[10]=w):w=t[10],w}function m(e){return e.playback.status}function h(e){return e instanceof o}function g(e){return e.camera}function _(){let e=(0,u.useContext)(f);if(e===void 0)throw Error(`useAudio must be used within AudioProvider`);return e}export{_ as n,p as t};
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{o as i}from"./react-three-fiber.esm-CgPHUpXo.js";import{a}from"./SettingsProvider-CCHVZuSg.js";import{l as o,u as s}from"./three.module-07hRbor4.js";import{r as c}from"./engineStore-Dkm20jvr.js";var l=t(),u=e(n(),1),d=r(),f=(0,u.createContext)(void 0);function p(e){let t=(0,l.c)(11),{children:n}=e,r=i(g),{audioVolume:p}=a(),_;t[0]===Symbol.for(`react.memo_cache_sentinel`)?(_={audioLoader:null,audioListener:null},t[0]=_):_=t[0];let[v,y]=(0,u.useState)(_),b,x;t[1]===r?(b=t[2],x=t[3]):(b=()=>{let e=new s,t=r.children.find(h);t||(t=new o,r.add(t)),y({audioLoader:e,audioListener:t});let n=()=>{let e=t.context;!e||e.state!==`suspended`||e.resume().finally(()=>{document.removeEventListener(`click`,n),document.removeEventListener(`keydown`,n),document.removeEventListener(`touchend`,n)})};document.addEventListener(`click`,n),document.addEventListener(`keydown`,n),document.addEventListener(`touchend`,n);let i=c.subscribe(m,e=>{let n=t.context;n&&(e===`paused`?n.suspend():n.state===`suspended`&&n.resume())});return()=>{document.removeEventListener(`click`,n),document.removeEventListener(`keydown`,n),document.removeEventListener(`touchend`,n),i(),t&&r.remove(t)}},x=[r],t[1]=r,t[2]=b,t[3]=x),(0,u.useEffect)(b,x);let S,C;t[4]!==v.audioListener||t[5]!==p?(S=()=>{v.audioListener?.setMasterVolume(p)},C=[p,v.audioListener],t[4]=v.audioListener,t[5]=p,t[6]=S,t[7]=C):(S=t[6],C=t[7]),(0,u.useEffect)(S,C);let w;return t[8]!==v||t[9]!==n?(w=(0,d.jsx)(f.Provider,{value:v,children:n}),t[8]=v,t[9]=n,t[10]=w):w=t[10],w}function m(e){return e.playback.status}function h(e){return e instanceof o}function g(e){return e.camera}function _(){let e=(0,u.useContext)(f);if(e===void 0)throw Error(`useAudio must be used within AudioProvider`);return e}export{_ as n,p as t};

View file

@ -1 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{r as t,t as n}from"./jsx-runtime-BpGWiA-R.js";import{i as r,o as i}from"./react-three-fiber.esm-dhSWjERg.js";import{a,i as o}from"./SettingsProvider-xrmxG700.js";import{t as s}from"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{Ut as c,bt as l,c as u}from"./three.module-CwgFV8Kd.js";import"./mission-C2iDKeMo.js";import{r as d}from"./engineStore-Cio8vU1L.js";import{r as f}from"./loaders-0nekNxyt.js";import{n as p}from"./AudioContext-Dn9ry4KA.js";import{t as m}from"./FloatingLabel-D3hPNzZS.js";var h=e(t(),1),g=n(),_=s(`AudioEmitter`),v=new Map,y=new Map;function b(e,t=1){y.set(e,t)}function x(e){y.delete(e)}var S=0;function C(){return S}function w(){S++;for(let[e]of y){try{e.stop()}catch{}try{e.disconnect()}catch{}}y.clear()}d.subscribe(e=>e.playback.rate,e=>{for(let[t,n]of y)try{t.setPlaybackRate(n*e)}catch{}});function T(e,t){let n=t(e),r=n?.filename;if(!r)return null;let i=r.endsWith(`.wav`)?r:`${r}.wav`,a=n.description,o=a==null?void 0:t(a);return{filename:i,is3D:o?.is3D??!0,isLooping:o?.isLooping??!1,refDist:o?.referenceDistance??20,maxDist:o?.maxDistance??100,volume:o?.volume??1}}function E(e,t,n,r,i){let a;try{a=f(e.filename)}catch{return}let o=d.getState().playback.rate,s=S;D(a,n,n=>{if(s===S)try{if(e.is3D&&i){let a=new l(t);a.setBuffer(n),a.setDistanceModel(`inverse`),a.setRefDistance(e.refDist),a.setMaxDistance(e.maxDist),a.setRolloffFactor(1),a.setVolume(e.volume),a.setPlaybackRate(o),r&&a.position.copy(r),i.add(a),y.set(a,1),a.play(),a.source.onended=()=>{y.delete(a);try{a.disconnect()}catch{}i.remove(a)}}else{let r=new u(t);r.setBuffer(n),r.setVolume(e.volume),r.setPlaybackRate(o),y.set(r,1),r.play(),r.source.onended=()=>{y.delete(r);try{r.disconnect()}catch{}}}}catch{}})}function D(e,t,n){v.has(e)?n(v.get(e)):t.load(e,t=>{v.set(e,t),n(t)},void 0,t=>{_.error(`Audio load error %s: %o`,e,t)})}var O=(0,h.memo)(function({entity:e}){let{debugMode:t}=o(),n=e.audioFileName??``,s=e.audioVolume??1,d=e.audioMinDistance??1,_=e.audioMaxDistance??1,v=e.audioMinLoopGap??0,y=e.audioMaxLoopGap??0,b=e.audioIs3D??!0?1:0,x=e.audioIsLooping??!0,[S,C,w]=e.position??[0,0,0],T=i(e=>e.scene),E=i(e=>e.camera),{audioLoader:O,audioListener:k}=p(),{audioEnabled:A}=a(),j=(0,h.useRef)(null),M=(0,h.useRef)(null),N=(0,h.useRef)(null),P=(0,h.useRef)(!1),F=(0,h.useRef)(!1),I=(0,h.useRef)(new c(S,C,w)),L=(0,h.useRef)(0),R=()=>{M.current!=null&&(clearTimeout(M.current),M.current=null),N.current!=null&&(clearTimeout(N.current),N.current=null)},[z]=(0,h.useState)(()=>Math.random());(0,h.useEffect)(()=>{if(!O||!k)return;L.current++;let e;if(b){let t=new l(k);t.position.copy(I.current),t.setDistanceModel(`inverse`),t.setRefDistance(d),t.setMaxDistance(_),t.setRolloffFactor(1),t.setVolume(s),e=t,T.add(e)}else e=new u(k),e.setVolume(s);return j.current=e,()=>{R();try{e.stop()}catch{}try{e.disconnect()}catch{}b&&T.remove(e),j.current=null,P.current=!1,F.current=!1}},[O,k,b,d,_,s,T]);let B=(e,t)=>{if(x)if(v>0||y>0){let n=Math.max(0,v),r=Math.max(n,y),i=n===r?n:z*(r-n)+n;e.loop=!1;let a=()=>{t===L.current&&(e.isPlaying===!1?M.current=setTimeout(()=>{if(t===L.current)try{e.play(),B(e,t)}catch{}},i):N.current=setTimeout(a,100))};N.current=setTimeout(a,100)}else e.setLoop(!0)},V=(0,h.useEffectEvent)(e=>{if(!O)return;let t=L.current;if(P.current)try{e.isPlaying||(e.play(),B(e,t))}catch{}else{let r;try{r=f(n)}catch{return}D(r,O,n=>{if(t===L.current&&!e.buffer){e.setBuffer(n),P.current=!0;try{e.play(),B(e,t)}catch{}}})}});return(0,h.useEffect)(()=>{let e=j.current;!e||b||!A||!n||V(e)},[A,b,n,O,k]),r(()=>{let e=j.current;if(!e||!b||!A||!n)return;let t=E.position.distanceTo(I.current),r=F.current,i=t<=_;if(i&&!r)F.current=!0,V(e);else if(!i&&r){F.current=!1,R();try{e.stop()}catch{}}}),(0,h.useEffect)(()=>{let e=j.current;if(e&&!A){R();try{e.stop()}catch{}F.current=!1}},[A]),t?(0,g.jsxs)(`mesh`,{position:I.current,children:[(0,g.jsx)(`sphereGeometry`,{args:[d,12,12]}),(0,g.jsx)(`meshBasicMaterial`,{color:`#00ff00`,wireframe:!0,opacity:.05,transparent:!0,toneMapped:!1}),(0,g.jsx)(m,{color:`#00ff00`,position:[0,d+1,0],children:n})]}):null});export{O as AudioEmitter,v as audioBufferCache,D as getCachedAudioBuffer,C as getSoundGeneration,E as playOneShotSound,T as resolveAudioProfile,w as stopAllTrackedSounds,b as trackSound,x as untrackSound};
import{r as e}from"./chunk-DECur_0Z.js";import{r as t,t as n}from"./jsx-runtime-BpGWiA-R.js";import{i as r,o as i}from"./react-three-fiber.esm-CgPHUpXo.js";import{a,i as o}from"./SettingsProvider-CCHVZuSg.js";import{t as s}from"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{Ut as c,bt as l,c as u}from"./three.module-07hRbor4.js";import"./mission-D8vr00S1.js";import{r as d}from"./engineStore-Dkm20jvr.js";import{r as f}from"./loaders-VxR5Bl13.js";import{n as p}from"./AudioContext-CMp1T7r9.js";import{t as m}from"./FloatingLabel-C7nyg5oz.js";var h=e(t(),1),g=n(),_=s(`AudioEmitter`),v=new Map,y=new Map;function b(e,t=1){y.set(e,t)}function x(e){y.delete(e)}var S=0;function C(){return S}function w(){S++;for(let[e]of y){try{e.stop()}catch{}try{e.disconnect()}catch{}}y.clear()}d.subscribe(e=>e.playback.rate,e=>{for(let[t,n]of y)try{t.setPlaybackRate(n*e)}catch{}});function T(e,t){let n=t(e),r=n?.filename;if(!r)return null;let i=r.endsWith(`.wav`)?r:`${r}.wav`,a=n.description,o=a==null?void 0:t(a);return{filename:i,is3D:o?.is3D??!0,isLooping:o?.isLooping??!1,refDist:o?.referenceDistance??20,maxDist:o?.maxDistance??100,volume:o?.volume??1}}function E(e,t,n,r,i){let a;try{a=f(e.filename)}catch{return}let o=d.getState().playback.rate,s=S;D(a,n,n=>{if(s===S)try{if(e.is3D&&i){let a=new l(t);a.setBuffer(n),a.setDistanceModel(`inverse`),a.setRefDistance(e.refDist),a.setMaxDistance(e.maxDist),a.setRolloffFactor(1),a.setVolume(e.volume),a.setPlaybackRate(o),r&&a.position.copy(r),i.add(a),y.set(a,1),a.play(),a.source.onended=()=>{y.delete(a);try{a.disconnect()}catch{}i.remove(a)}}else{let r=new u(t);r.setBuffer(n),r.setVolume(e.volume),r.setPlaybackRate(o),y.set(r,1),r.play(),r.source.onended=()=>{y.delete(r);try{r.disconnect()}catch{}}}}catch{}})}function D(e,t,n){v.has(e)?n(v.get(e)):t.load(e,t=>{v.set(e,t),n(t)},void 0,t=>{_.error(`Audio load error %s: %o`,e,t)})}var O=(0,h.memo)(function({entity:e}){let{debugMode:t}=o(),n=e.audioFileName??``,s=e.audioVolume??1,d=e.audioMinDistance??1,_=e.audioMaxDistance??1,v=e.audioMinLoopGap??0,y=e.audioMaxLoopGap??0,b=e.audioIs3D??!0?1:0,x=e.audioIsLooping??!0,[S,C,w]=e.position??[0,0,0],T=i(e=>e.scene),E=i(e=>e.camera),{audioLoader:O,audioListener:k}=p(),{audioEnabled:A}=a(),j=(0,h.useRef)(null),M=(0,h.useRef)(null),N=(0,h.useRef)(null),P=(0,h.useRef)(!1),F=(0,h.useRef)(!1),I=(0,h.useRef)(new c(S,C,w)),L=(0,h.useRef)(0),R=()=>{M.current!=null&&(clearTimeout(M.current),M.current=null),N.current!=null&&(clearTimeout(N.current),N.current=null)},[z]=(0,h.useState)(()=>Math.random());(0,h.useEffect)(()=>{if(!O||!k)return;L.current++;let e;if(b){let t=new l(k);t.position.copy(I.current),t.setDistanceModel(`inverse`),t.setRefDistance(d),t.setMaxDistance(_),t.setRolloffFactor(1),t.setVolume(s),e=t,T.add(e)}else e=new u(k),e.setVolume(s);return j.current=e,()=>{R();try{e.stop()}catch{}try{e.disconnect()}catch{}b&&T.remove(e),j.current=null,P.current=!1,F.current=!1}},[O,k,b,d,_,s,T]);let B=(e,t)=>{if(x)if(v>0||y>0){let n=Math.max(0,v),r=Math.max(n,y),i=n===r?n:z*(r-n)+n;e.loop=!1;let a=()=>{t===L.current&&(e.isPlaying===!1?M.current=setTimeout(()=>{if(t===L.current)try{e.play(),B(e,t)}catch{}},i):N.current=setTimeout(a,100))};N.current=setTimeout(a,100)}else e.setLoop(!0)},V=(0,h.useEffectEvent)(e=>{if(!O)return;let t=L.current;if(P.current)try{e.isPlaying||(e.play(),B(e,t))}catch{}else{let r;try{r=f(n)}catch{return}D(r,O,n=>{if(t===L.current&&!e.buffer){e.setBuffer(n),P.current=!0;try{e.play(),B(e,t)}catch{}}})}});return(0,h.useEffect)(()=>{let e=j.current;!e||b||!A||!n||V(e)},[A,b,n,O,k]),r(()=>{let e=j.current;if(!e||!b||!A||!n)return;let t=E.position.distanceTo(I.current),r=F.current,i=t<=_;if(i&&!r)F.current=!0,V(e);else if(!i&&r){F.current=!1,R();try{e.stop()}catch{}}}),(0,h.useEffect)(()=>{let e=j.current;if(e&&!A){R();try{e.stop()}catch{}F.current=!1}},[A]),t?(0,g.jsxs)(`mesh`,{position:I.current,children:[(0,g.jsx)(`sphereGeometry`,{args:[d,12,12]}),(0,g.jsx)(`meshBasicMaterial`,{color:`#00ff00`,wireframe:!0,opacity:.05,transparent:!0,toneMapped:!1}),(0,g.jsx)(m,{color:`#00ff00`,position:[0,d+1,0],children:n})]}):null});export{O as AudioEmitter,v as audioBufferCache,D as getCachedAudioBuffer,C as getSoundGeneration,E as playOneShotSound,T as resolveAudioProfile,w as stopAllTrackedSounds,b as trackSound,x as untrackSound};

View file

@ -1 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import"./streamHelpers-Be29sBCp.js";import"./scene-C7IIl-c0.js";import"./mission-C2iDKeMo.js";import{g as i}from"./index-BMiY1uIn.js";var a=t(),o=e(n(),1),s={InputForm:`_InputForm_18kom_1`,Input:`_Input_18kom_1`},c=r();function l(){let e=(0,a.c)(8),[t,n]=(0,o.useState)(``),r;e[0]===t?r=e[1]:(r=e=>{e.preventDefault();let r=t.trim();r&&(i.getState().sendCommand(`messageSent`,r),n(``))},e[0]=t,e[1]=r);let l=r,f;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(f=e=>n(e.target.value),e[2]=f):f=e[2];let p;e[3]===t?p=e[4]:(p=(0,c.jsx)(`input`,{className:s.Input,type:`text`,placeholder:`Say something…`,value:t,onChange:f,onKeyDown:d,onKeyUp:u,maxLength:255}),e[3]=t,e[4]=p);let m;return e[5]!==l||e[6]!==p?(m=(0,c.jsx)(`form`,{className:s.InputForm,onSubmit:l,children:p}),e[5]=l,e[6]=p,e[7]=m):m=e[7],m}function u(e){return e.stopPropagation()}function d(e){return e.stopPropagation()}export{l as ChatInput};
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import"./streamHelpers-AIec78DP.js";import"./scene-BdOVRsxo.js";import"./mission-D8vr00S1.js";import{O as i}from"./index-DBtsNu05.js";var a=t(),o=e(n(),1),s={InputForm:`_InputForm_18kom_1`,Input:`_Input_18kom_1`},c=r();function l(){let e=(0,a.c)(8),[t,n]=(0,o.useState)(``),r;e[0]===t?r=e[1]:(r=e=>{e.preventDefault();let r=t.trim();r&&(i.getState().sendCommand(`messageSent`,r),n(``))},e[0]=t,e[1]=r);let l=r,f;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(f=e=>n(e.target.value),e[2]=f):f=e[2];let p;e[3]===t?p=e[4]:(p=(0,c.jsx)(`input`,{className:s.Input,type:`text`,placeholder:`Say something…`,value:t,onChange:f,onKeyDown:d,onKeyUp:u,maxLength:255}),e[3]=t,e[4]=p);let m;return e[5]!==l||e[6]!==p?(m=(0,c.jsx)(`form`,{className:s.InputForm,onSubmit:l,children:p}),e[5]=l,e[6]=p,e[7]=m):m=e[7],m}function u(e){return e.stopPropagation()}function d(e){return e.stopPropagation()}export{l as ChatInput};

View file

@ -1 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{r as t}from"./jsx-runtime-BpGWiA-R.js";import"./react-three-fiber.esm-dhSWjERg.js";import{a as n}from"./SettingsProvider-xrmxG700.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{c as r}from"./three.module-CwgFV8Kd.js";import"./mission-C2iDKeMo.js";import{i,r as a}from"./engineStore-Cio8vU1L.js";import{r as o}from"./loaders-0nekNxyt.js";import{n as s}from"./AudioContext-Dn9ry4KA.js";import"./FloatingLabel-D3hPNzZS.js";import{getCachedAudioBuffer as c,getSoundGeneration as l,trackSound as u,untrackSound as d}from"./AudioEmitter-_djhBYQd.js";var f=e(t(),1);function p(){let{audioLoader:e,audioListener:t}=s(),{audioEnabled:p}=n(),m=i(e=>e.playback.streamSnapshot?.chatMessages),h=i(e=>e.playback.streamSnapshot?.timeSec),g=(0,f.useRef)(new WeakSet),_=(0,f.useRef)(new Map);return(0,f.useEffect)(()=>{if(!p||!e||!t||!m?.length||h==null)return;let n=g.current,i=_.current;for(let s of m)if(!n.has(s)&&(n.add(s),s.soundPath&&!(Math.abs(h-s.timeSec)>2)))try{let n=o(s.soundPath),f=s.soundPitch??1,p=a.getState().playback.rate,m=s.sender,h=l();c(n,e,e=>{if(h!==l())return;if(m){let e=i.get(m);if(e){try{e.stop()}catch{}d(e);try{e.disconnect()}catch{}i.delete(m)}}let n=new r(t);n.setBuffer(e),n.setPlaybackRate(f*p),u(n,f),m&&i.set(m,n),n.play(),n.source.onended=()=>{d(n);try{n.disconnect()}catch{}m&&i.get(m)===n&&i.delete(m)}})}catch{}},[p,e,t,m,h]),null}export{p as ChatSoundPlayer};
import{r as e}from"./chunk-DECur_0Z.js";import{r as t}from"./jsx-runtime-BpGWiA-R.js";import"./react-three-fiber.esm-CgPHUpXo.js";import{a as n}from"./SettingsProvider-CCHVZuSg.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{c as r}from"./three.module-07hRbor4.js";import"./mission-D8vr00S1.js";import{i,r as a}from"./engineStore-Dkm20jvr.js";import{r as o}from"./loaders-VxR5Bl13.js";import{n as s}from"./AudioContext-CMp1T7r9.js";import"./FloatingLabel-C7nyg5oz.js";import{getCachedAudioBuffer as c,getSoundGeneration as l,trackSound as u,untrackSound as d}from"./AudioEmitter-awEqEuEC.js";var f=e(t(),1);function p(){let{audioLoader:e,audioListener:t}=s(),{audioEnabled:p}=n(),m=i(e=>e.playback.streamSnapshot?.chatMessages),h=i(e=>e.playback.streamSnapshot?.timeSec),g=(0,f.useRef)(new WeakSet),_=(0,f.useRef)(new Map);return(0,f.useEffect)(()=>{if(!p||!e||!t||!m?.length||h==null)return;let n=g.current,i=_.current;for(let s of m)if(!n.has(s)&&(n.add(s),s.soundPath&&!(Math.abs(h-s.timeSec)>2)))try{let n=o(s.soundPath),f=s.soundPitch??1,p=a.getState().playback.rate,m=s.sender,h=l();c(n,e,e=>{if(h!==l())return;if(m){let e=i.get(m);if(e){try{e.stop()}catch{}d(e);try{e.disconnect()}catch{}i.delete(m)}}let n=new r(t);n.setBuffer(e),n.setPlaybackRate(f*p),u(n,f),m&&i.set(m,n),n.play(),n.source.onended=()=>{d(n);try{n.disconnect()}catch{}m&&i.get(m)===n&&i.delete(m)}})}catch{}},[p,e,t,m,h]),null}export{p as ChatSoundPlayer};

View file

@ -1 +1 @@
import{r as e,t}from"./chunk-DECur_0Z.js";import{n,r,t as i}from"./jsx-runtime-BpGWiA-R.js";import{n as a,r as o}from"./react-three-fiber.esm-dhSWjERg.js";import"./traditional-BTL5qX2E.js";import{t as s}from"./Html-B3dXZ0LN.js";var c=t(((e,t)=>{(function(n,r){typeof e==`object`&&t!==void 0?t.exports=r():typeof define==`function`&&define.amd?define(r):n.Stats=r()})(e,function(){var e=function(){function t(e){return i.appendChild(e.dom),e}function n(e){for(var t=0;t<i.children.length;t++)i.children[t].style.display=t===e?`block`:`none`;r=e}var r=0,i=document.createElement(`div`);i.style.cssText=`position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000`,i.addEventListener(`click`,function(e){e.preventDefault(),n(++r%i.children.length)},!1);var a=(performance||Date).now(),o=a,s=0,c=t(new e.Panel(`FPS`,`#0ff`,`#002`)),l=t(new e.Panel(`MS`,`#0f0`,`#020`));if(self.performance&&self.performance.memory)var u=t(new e.Panel(`MB`,`#f08`,`#201`));return n(0),{REVISION:16,dom:i,addPanel:t,showPanel:n,begin:function(){a=(performance||Date).now()},end:function(){s++;var e=(performance||Date).now();if(l.update(e-a,200),e>o+1e3&&(c.update(1e3*s/(e-o),100),o=e,s=0,u)){var t=performance.memory;u.update(t.usedJSHeapSize/1048576,t.jsHeapSizeLimit/1048576)}return e},update:function(){a=this.end()},domElement:i,setMode:n}};return e.Panel=function(e,t,n){var r=1/0,i=0,a=Math.round,o=a(window.devicePixelRatio||1),s=80*o,c=48*o,l=3*o,u=2*o,d=3*o,f=15*o,p=74*o,m=30*o,h=document.createElement(`canvas`);h.width=s,h.height=c,h.style.cssText=`width:80px;height:48px`;var g=h.getContext(`2d`);return g.font=`bold `+9*o+`px Helvetica,Arial,sans-serif`,g.textBaseline=`top`,g.fillStyle=n,g.fillRect(0,0,s,c),g.fillStyle=t,g.fillText(e,l,u),g.fillRect(d,f,p,m),g.fillStyle=n,g.globalAlpha=.9,g.fillRect(d,f,p,m),{dom:h,update:function(c,_){r=Math.min(r,c),i=Math.max(i,c),g.fillStyle=n,g.globalAlpha=1,g.fillRect(0,0,s,f),g.fillStyle=t,g.fillText(a(c)+` `+e+` (`+a(r)+`-`+a(i)+`)`,l,u),g.drawImage(h,d+o,f,p-o,m,d,f,p-o,m),g.fillRect(d+p-o,f,o,m),g.fillStyle=n,g.globalAlpha=.9,g.fillRect(d+p-o,f,o,a((1-c/_)*m))}}},e})})),l=e(r());function u(e,t){typeof e==`function`?e(t):e!=null&&(e.current=t)}function d(e,t=[],n){let[r,i]=l.useState();return l.useLayoutEffect(()=>{let t=e();return i(t),u(n,t),()=>u(n,null)},t),r}var f=e(c());function p({showPanel:e=0,className:t,parent:n}){let r=d(()=>new f.default,[]);return l.useEffect(()=>{if(r){let i=n&&n.current||document.body;r.showPanel(e),i?.appendChild(r.dom);let s=(t??``).split(` `).filter(e=>e);s.length&&r.dom.classList.add(...s);let c=o(()=>r.begin()),l=a(()=>r.end());return()=>{s.length&&r.dom.classList.remove(...s),i?.removeChild(r.dom),c(),l()}}},[n,r,t,e]),null}var m=n(),h={StatsPanel:`_StatsPanel_10m5i_1`,AxisLabel:`_AxisLabel_10m5i_8`},g=i();function _(){let e=(0,m.c)(10),t=(0,l.useRef)(null),n;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(n=()=>{let e=t.current;e&&e.setColors(`rgb(153, 255, 0)`,`rgb(0, 153, 255)`,`rgb(255, 153, 0)`)},e[0]=n):n=e[0],(0,l.useEffect)(n);let r;e[1]===Symbol.for(`react.memo_cache_sentinel`)?(r=(0,g.jsx)(p,{className:h.StatsPanel}),e[1]=r):r=e[1];let i;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(i=[70],e[2]=i):i=e[2];let a;e[3]===Symbol.for(`react.memo_cache_sentinel`)?(a=(0,g.jsx)(`axesHelper`,{ref:t,args:i,renderOrder:999,children:(0,g.jsx)(`lineBasicMaterial`,{depthTest:!1,depthWrite:!1,fog:!1,vertexColors:!0})}),e[3]=a):a=e[3];let o;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(o=[80,0,0],e[4]=o):o=e[4];let c;e[5]===Symbol.for(`react.memo_cache_sentinel`)?(c=(0,g.jsx)(s,{position:o,center:!0,children:(0,g.jsx)(`span`,{className:h.AxisLabel,"data-axis":`y`,children:`Y`})}),e[5]=c):c=e[5];let u;e[6]===Symbol.for(`react.memo_cache_sentinel`)?(u=[0,80,0],e[6]=u):u=e[6];let d;e[7]===Symbol.for(`react.memo_cache_sentinel`)?(d=(0,g.jsx)(s,{position:u,center:!0,children:(0,g.jsx)(`span`,{className:h.AxisLabel,"data-axis":`z`,children:`Z`})}),e[7]=d):d=e[7];let f;e[8]===Symbol.for(`react.memo_cache_sentinel`)?(f=[0,0,80],e[8]=f):f=e[8];let _;return e[9]===Symbol.for(`react.memo_cache_sentinel`)?(_=(0,g.jsxs)(g.Fragment,{children:[r,a,c,d,(0,g.jsx)(s,{position:f,center:!0,children:(0,g.jsx)(`span`,{className:h.AxisLabel,"data-axis":`x`,children:`X`})})]}),e[9]=_):_=e[9],_}export{_ as DebugElements};
import{r as e,t}from"./chunk-DECur_0Z.js";import{n,r,t as i}from"./jsx-runtime-BpGWiA-R.js";import{n as a,r as o}from"./react-three-fiber.esm-CgPHUpXo.js";import"./traditional-BTL5qX2E.js";import{t as s}from"./Html-BLmmc0n9.js";var c=t(((e,t)=>{(function(n,r){typeof e==`object`&&t!==void 0?t.exports=r():typeof define==`function`&&define.amd?define(r):n.Stats=r()})(e,function(){var e=function(){function t(e){return i.appendChild(e.dom),e}function n(e){for(var t=0;t<i.children.length;t++)i.children[t].style.display=t===e?`block`:`none`;r=e}var r=0,i=document.createElement(`div`);i.style.cssText=`position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000`,i.addEventListener(`click`,function(e){e.preventDefault(),n(++r%i.children.length)},!1);var a=(performance||Date).now(),o=a,s=0,c=t(new e.Panel(`FPS`,`#0ff`,`#002`)),l=t(new e.Panel(`MS`,`#0f0`,`#020`));if(self.performance&&self.performance.memory)var u=t(new e.Panel(`MB`,`#f08`,`#201`));return n(0),{REVISION:16,dom:i,addPanel:t,showPanel:n,begin:function(){a=(performance||Date).now()},end:function(){s++;var e=(performance||Date).now();if(l.update(e-a,200),e>o+1e3&&(c.update(1e3*s/(e-o),100),o=e,s=0,u)){var t=performance.memory;u.update(t.usedJSHeapSize/1048576,t.jsHeapSizeLimit/1048576)}return e},update:function(){a=this.end()},domElement:i,setMode:n}};return e.Panel=function(e,t,n){var r=1/0,i=0,a=Math.round,o=a(window.devicePixelRatio||1),s=80*o,c=48*o,l=3*o,u=2*o,d=3*o,f=15*o,p=74*o,m=30*o,h=document.createElement(`canvas`);h.width=s,h.height=c,h.style.cssText=`width:80px;height:48px`;var g=h.getContext(`2d`);return g.font=`bold `+9*o+`px Helvetica,Arial,sans-serif`,g.textBaseline=`top`,g.fillStyle=n,g.fillRect(0,0,s,c),g.fillStyle=t,g.fillText(e,l,u),g.fillRect(d,f,p,m),g.fillStyle=n,g.globalAlpha=.9,g.fillRect(d,f,p,m),{dom:h,update:function(c,_){r=Math.min(r,c),i=Math.max(i,c),g.fillStyle=n,g.globalAlpha=1,g.fillRect(0,0,s,f),g.fillStyle=t,g.fillText(a(c)+` `+e+` (`+a(r)+`-`+a(i)+`)`,l,u),g.drawImage(h,d+o,f,p-o,m,d,f,p-o,m),g.fillRect(d+p-o,f,o,m),g.fillStyle=n,g.globalAlpha=.9,g.fillRect(d+p-o,f,o,a((1-c/_)*m))}}},e})})),l=e(r());function u(e,t){typeof e==`function`?e(t):e!=null&&(e.current=t)}function d(e,t=[],n){let[r,i]=l.useState();return l.useLayoutEffect(()=>{let t=e();return i(t),u(n,t),()=>u(n,null)},t),r}var f=e(c());function p({showPanel:e=0,className:t,parent:n}){let r=d(()=>new f.default,[]);return l.useEffect(()=>{if(r){let i=n&&n.current||document.body;r.showPanel(e),i?.appendChild(r.dom);let s=(t??``).split(` `).filter(e=>e);s.length&&r.dom.classList.add(...s);let c=o(()=>r.begin()),l=a(()=>r.end());return()=>{s.length&&r.dom.classList.remove(...s),i?.removeChild(r.dom),c(),l()}}},[n,r,t,e]),null}var m=n(),h={StatsPanel:`_StatsPanel_10m5i_1`,AxisLabel:`_AxisLabel_10m5i_8`},g=i();function _(){let e=(0,m.c)(10),t=(0,l.useRef)(null),n;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(n=()=>{let e=t.current;e&&e.setColors(`rgb(153, 255, 0)`,`rgb(0, 153, 255)`,`rgb(255, 153, 0)`)},e[0]=n):n=e[0],(0,l.useEffect)(n);let r;e[1]===Symbol.for(`react.memo_cache_sentinel`)?(r=(0,g.jsx)(p,{className:h.StatsPanel}),e[1]=r):r=e[1];let i;e[2]===Symbol.for(`react.memo_cache_sentinel`)?(i=[70],e[2]=i):i=e[2];let a;e[3]===Symbol.for(`react.memo_cache_sentinel`)?(a=(0,g.jsx)(`axesHelper`,{ref:t,args:i,renderOrder:999,children:(0,g.jsx)(`lineBasicMaterial`,{depthTest:!1,depthWrite:!1,fog:!1,vertexColors:!0})}),e[3]=a):a=e[3];let o;e[4]===Symbol.for(`react.memo_cache_sentinel`)?(o=[80,0,0],e[4]=o):o=e[4];let c;e[5]===Symbol.for(`react.memo_cache_sentinel`)?(c=(0,g.jsx)(s,{position:o,center:!0,children:(0,g.jsx)(`span`,{className:h.AxisLabel,"data-axis":`y`,children:`Y`})}),e[5]=c):c=e[5];let u;e[6]===Symbol.for(`react.memo_cache_sentinel`)?(u=[0,80,0],e[6]=u):u=e[6];let d;e[7]===Symbol.for(`react.memo_cache_sentinel`)?(d=(0,g.jsx)(s,{position:u,center:!0,children:(0,g.jsx)(`span`,{className:h.AxisLabel,"data-axis":`z`,children:`Z`})}),e[7]=d):d=e[7];let f;e[8]===Symbol.for(`react.memo_cache_sentinel`)?(f=[0,0,80],e[8]=f):f=e[8];let _;return e[9]===Symbol.for(`react.memo_cache_sentinel`)?(_=(0,g.jsxs)(g.Fragment,{children:[r,a,c,d,(0,g.jsx)(s,{position:f,center:!0,children:(0,g.jsx)(`span`,{className:h.AxisLabel,"data-axis":`x`,children:`X`})})]}),e[9]=_):_=e[9],_}export{_ as DebugElements};

View file

@ -1 +0,0 @@
._Root_16j0q_1{z-index:2;align-items:center;gap:10px;padding:8px 12px;font-size:13px;display:flex}._PlayPause_16j0q_10{color:#fff;cursor:pointer;background:#03529399;border:1px solid #ffffff4d;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;width:32px;height:32px;padding:0;font-size:14px;display:flex}@media (hover:hover){._PlayPause_16j0q_10:hover{background:#0062b3cc}}._Time_16j0q_32{font-variant-numeric:tabular-nums;white-space:nowrap;flex-shrink:0}._Seek_16j0q_38[type=range]{flex:1 1 0;min-width:0;max-width:none}._Speed_16j0q_44{color:#fff;background:#0009;border:1px solid #ffffff4d;border-radius:3px;flex-shrink:0;padding:2px 4px;font-size:12px}

View file

@ -0,0 +1 @@
._Root_i1hot_1{z-index:2;align-items:center;gap:10px;padding:8px 12px;font-size:13px;display:flex}._PlayPause_i1hot_10{color:#fff;cursor:pointer;background:#03529399;border:1px solid #ffffff4d;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;width:32px;height:32px;padding:0;font-size:11px;display:flex}@media (hover:hover){._PlayPause_i1hot_10:hover{background:#0062b3cc}}._Time_i1hot_32{font-variant-numeric:tabular-nums;white-space:nowrap;flex-shrink:0}._Seek_i1hot_38[type=range]{flex:1 1 0;min-width:0;max-width:none}._Speed_i1hot_44,._CameraMode_i1hot_45{color:#fff;background:#0009;border:1px solid #ffffff4d;border-radius:3px;flex-shrink:0;padding:3px 4px;font-size:13px}._Field_i1hot_55{align-items:center;gap:8px;display:flex}._Field_i1hot_55 label{text-transform:uppercase;color:#fff9;font-size:11px;font-weight:500}

View file

@ -0,0 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import"./traditional-BTL5qX2E.js";import"./engineStore-Dkm20jvr.js";import{C as i,D as a,E as o,S as s,T as c,u as l,w as u,x as d}from"./index-DBtsNu05.js";import{n as f,t as p}from"./gr-1PcjgMj0.js";var m=t(),h=e(n(),1),g={Root:`_Root_i1hot_1`,PlayPause:`_PlayPause_i1hot_10`,Time:`_Time_i1hot_32`,Seek:`_Seek_i1hot_38`,Speed:`_Speed_i1hot_44`,CameraMode:`_CameraMode_i1hot_45`,Field:`_Field_i1hot_55`},_=r();function v(e){return`${Math.floor(e/60)}:${Math.floor(e%60).toString().padStart(2,`0`)}`}function y(){let e=(0,m.c)(42),t=o(),n=u(),r=s(),y=i(),x=a(),{play:S,pause:C,seek:w,setSpeed:T}=c(),E,D;e[0]!==n||e[1]!==C||e[2]!==S||e[3]!==t?(E=()=>{if(!t)return;let e=e=>{if(e.code!==`Space`)return;let t=e.target;t.tagName===`INPUT`||t.tagName===`TEXTAREA`||t.tagName===`SELECT`||t.tagName===`BUTTON`||t.isContentEditable||(e.preventDefault(),n?C():S())};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},D=[t,n,S,C],e[0]=n,e[1]=C,e[2]=S,e[3]=t,e[4]=E,e[5]=D):(E=e[4],D=e[5]),(0,h.useEffect)(E,D);let O;e[6]!==T||e[7]!==x?(O=()=>{let e=d.indexOf(x);e>0&&T(d[e-1])},e[6]=T,e[7]=x,e[8]=O):O=e[8],l(`decreasePlaybackSpeed`,O);let k;e[9]!==T||e[10]!==x?(k=()=>{let e=d.indexOf(x);e<d.length-1&&T(d[e+1])},e[9]=T,e[10]=x,e[11]=k):k=e[11],l(`increasePlaybackSpeed`,k);let A;e[12]===w?A=e[13]:(A=e=>{w(parseFloat(e.target.value))},e[12]=w,e[13]=A);let j=A,M;e[14]===T?M=e[15]:(M=e=>{T(parseFloat(e.target.value))},e[14]=T,e[15]=M);let N=M;if(!t||!Number.isFinite(t.duration))return null;let P=n?C:S,F=n?`Pause`:`Play`,I;e[16]===n?I=e[17]:(I=n?(0,_.jsx)(p,{}):(0,_.jsx)(f,{}),e[16]=n,e[17]=I);let L;e[18]!==P||e[19]!==F||e[20]!==I?(L=(0,_.jsx)(`button`,{className:g.PlayPause,onClick:P,"aria-label":F,autoFocus:!0,children:I}),e[18]=P,e[19]=F,e[20]=I,e[21]=L):L=e[21];let R;e[22]===r?R=e[23]:(R=v(r),e[22]=r,e[23]=R);let z;e[24]===y?z=e[25]:(z=v(y),e[24]=y,e[25]=z);let B=`${R} / ${z}`,V;e[26]===B?V=e[27]:(V=(0,_.jsx)(`span`,{className:g.Time,children:B}),e[26]=B,e[27]=V);let H;e[28]!==r||e[29]!==y||e[30]!==j?(H=(0,_.jsx)(`input`,{className:g.Seek,type:`range`,min:0,max:y,step:.01,value:r,onChange:j}),e[28]=r,e[29]=y,e[30]=j,e[31]=H):H=e[31];let U;e[32]===Symbol.for(`react.memo_cache_sentinel`)?(U=(0,_.jsx)(`label`,{htmlFor:`playbackSpeed`,children:`Speed`}),e[32]=U):U=e[32];let W;e[33]===Symbol.for(`react.memo_cache_sentinel`)?(W=d.map(b),e[33]=W):W=e[33];let G;e[34]!==N||e[35]!==x?(G=(0,_.jsxs)(`div`,{className:g.Field,children:[U,(0,_.jsx)(`select`,{id:`playbackSpeed`,className:g.Speed,value:x,onChange:N,children:W})]}),e[34]=N,e[35]=x,e[36]=G):G=e[36];let K;return e[37]!==V||e[38]!==H||e[39]!==G||e[40]!==L?(K=(0,_.jsxs)(`div`,{className:g.Root,children:[L,V,H,G]}),e[37]=V,e[38]=H,e[39]=G,e[40]=L,e[41]=K):K=e[41],K}function b(e){return(0,_.jsxs)(`option`,{value:e,children:[e,`x`]},e)}export{y as DemoPlaybackControls};

View file

@ -1 +0,0 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import"./traditional-BTL5qX2E.js";import"./engineStore-Cio8vU1L.js";import{d as i,f as a,h as o,m as s,p as c,u as l}from"./index-BMiY1uIn.js";var u=t(),d=e(n(),1),f={Root:`_Root_16j0q_1`,PlayPause:`_PlayPause_16j0q_10`,Time:`_Time_16j0q_32`,Seek:`_Seek_16j0q_38`,Speed:`_Speed_16j0q_44`},p=r(),m=[.25,.5,1,2,4];function h(e){return`${Math.floor(e/60)}:${Math.floor(e%60).toString().padStart(2,`0`)}`}function g(){let e=(0,u.c)(33),t=s(),n=a(),r=l(),g=i(),x=o(),{play:S,pause:C,seek:w,setSpeed:T}=c(),E,D;e[0]!==n||e[1]!==C||e[2]!==S||e[3]!==t?(E=()=>{if(!t)return;let e=e=>{if(e.code!==`Space`)return;let t=e.target;t.tagName===`INPUT`||t.tagName===`TEXTAREA`||t.tagName===`SELECT`||t.tagName===`BUTTON`||t.isContentEditable||(e.preventDefault(),n?C():S())};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},D=[t,n,S,C],e[0]=n,e[1]=C,e[2]=S,e[3]=t,e[4]=E,e[5]=D):(E=e[4],D=e[5]),(0,d.useEffect)(E,D);let O;e[6]===w?O=e[7]:(O=e=>{w(parseFloat(e.target.value))},e[6]=w,e[7]=O);let k=O,A;e[8]===T?A=e[9]:(A=e=>{T(parseFloat(e.target.value))},e[8]=T,e[9]=A);let j=A;if(!t||!Number.isFinite(t.duration))return null;let M=n?C:S,N=n?`Pause`:`Play`,P=n?`❚❚`:``,F;e[10]!==M||e[11]!==N||e[12]!==P?(F=(0,p.jsx)(`button`,{className:f.PlayPause,onClick:M,"aria-label":N,autoFocus:!0,children:P}),e[10]=M,e[11]=N,e[12]=P,e[13]=F):F=e[13];let I;e[14]===r?I=e[15]:(I=h(r),e[14]=r,e[15]=I);let L;e[16]===g?L=e[17]:(L=h(g),e[16]=g,e[17]=L);let R=`${I} / ${L}`,z;e[18]===R?z=e[19]:(z=(0,p.jsx)(`span`,{className:f.Time,children:R}),e[18]=R,e[19]=z);let B;e[20]!==r||e[21]!==g||e[22]!==k?(B=(0,p.jsx)(`input`,{className:f.Seek,type:`range`,min:0,max:g,step:.01,value:r,onChange:k}),e[20]=r,e[21]=g,e[22]=k,e[23]=B):B=e[23];let V;e[24]===Symbol.for(`react.memo_cache_sentinel`)?(V=m.map(_),e[24]=V):V=e[24];let H;e[25]!==j||e[26]!==x?(H=(0,p.jsx)(`select`,{className:f.Speed,value:x,onChange:j,children:V}),e[25]=j,e[26]=x,e[27]=H):H=e[27];let U;return e[28]!==z||e[29]!==B||e[30]!==H||e[31]!==F?(U=(0,p.jsxs)(`div`,{className:f.Root,onKeyDown:b,onPointerDown:y,onClick:v,children:[F,z,B,H]}),e[28]=z,e[29]=B,e[30]=H,e[31]=F,e[32]=U):U=e[32],U}function _(e){return(0,p.jsxs)(`option`,{value:e,children:[e,`x`]},e)}function v(e){return e.stopPropagation()}function y(e){return e.stopPropagation()}function b(e){return e.stopPropagation()}export{g as DemoPlaybackControls};

View file

@ -1 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{i}from"./react-three-fiber.esm-dhSWjERg.js";import{Ut as a}from"./three.module-CwgFV8Kd.js";import{t as o}from"./Html-B3dXZ0LN.js";var s={Label:`_Label_8rn2m_1`},c=t(),l=e(n(),1),u=r(),d=[0,0,0],f=new a;function p(e,t,n,r){let i=e.matrixWorld.elements;return(t-i[12])*-i[8]+(n-i[13])*-i[9]+(r-i[14])*-i[10]<0}var m=(0,l.memo)(function(e){let t=(0,c.c)(11),{children:n,color:r,position:a,opacity:m}=e,h=r===void 0?`white`:r,g=a===void 0?d:a,_=m===void 0?`fadeWithDistance`:m,v=_===`fadeWithDistance`,y=(0,l.useRef)(null),[b,x]=(0,l.useState)(_!==0),S=(0,l.useRef)(null),C;t[0]!==v||t[1]!==b||t[2]!==_?(C=e=>{let{camera:t}=e,n=y.current;if(!n)return;n.getWorldPosition(f);let r=p(t,f.x,f.y,f.z);if(v){let e=r?1/0:t.position.distanceTo(f),n=e<200;if(b!==n&&x(n),S.current&&n){let t=Math.max(0,Math.min(1,1-e/200));S.current.style.opacity=t.toString()}}else{let e=!r&&_!==0;b!==e&&x(e),S.current&&(S.current.style.opacity=_.toString())}},t[0]=v,t[1]=b,t[2]=_,t[3]=C):C=t[3],i(C);let w;t[4]!==n||t[5]!==h||t[6]!==b||t[7]!==g?(w=b?(0,u.jsx)(o,{position:g,center:!0,children:(0,u.jsx)(`div`,{ref:S,className:s.Label,style:{color:h},children:n})}):null,t[4]=n,t[5]=h,t[6]=b,t[7]=g,t[8]=w):w=t[8];let T;return t[9]===w?T=t[10]:(T=(0,u.jsx)(`group`,{ref:y,children:w}),t[9]=w,t[10]=T),T});export{m as t};
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{i}from"./react-three-fiber.esm-CgPHUpXo.js";import{Ut as a}from"./three.module-07hRbor4.js";import{t as o}from"./Html-BLmmc0n9.js";var s={Label:`_Label_8rn2m_1`},c=t(),l=e(n(),1),u=r(),d=[0,0,0],f=new a;function p(e,t,n,r){let i=e.matrixWorld.elements;return(t-i[12])*-i[8]+(n-i[13])*-i[9]+(r-i[14])*-i[10]<0}var m=(0,l.memo)(function(e){let t=(0,c.c)(12),{children:n,color:r,position:a,opacity:m}=e,h=r===void 0?`white`:r,g=a===void 0?d:a,_=m===void 0?`fadeWithDistance`:m,v=_===`fadeWithDistance`,y=(0,l.useRef)(null),[b,x]=(0,l.useState)(_!==0),S=(0,l.useRef)(null),C;t[0]!==v||t[1]!==b||t[2]!==_?(C=e=>{let{camera:t}=e,n=y.current;if(!n)return;n.getWorldPosition(f);let r=p(t,f.x,f.y,f.z);if(v){let e=r?1/0:t.position.distanceTo(f),n=e<200;if(b!==n&&x(n),S.current&&n){let t=Math.max(0,Math.min(1,1-e/200));S.current.style.opacity=t.toString()}}else{let e=!r&&_!==0;b!==e&&x(e),S.current&&(S.current.style.opacity=_.toString())}},t[0]=v,t[1]=b,t[2]=_,t[3]=C):C=t[3],i(C);let w;t[4]!==n||t[5]!==h||t[6]!==v||t[7]!==b||t[8]!==g?(w=b?(0,u.jsx)(o,{position:g,center:!0,children:(0,u.jsx)(`div`,{ref:S,className:s.Label,style:{color:h,opacity:v?0:void 0},children:n})}):null,t[4]=n,t[5]=h,t[6]=v,t[7]=b,t[8]=g,t[9]=w):w=t[9];let T;return t[10]===w?T=t[11]:(T=(0,u.jsx)(`group`,{ref:y,children:w}),t[10]=w,t[11]=T),T});export{m as t};

View file

@ -1,4 +1,4 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{i}from"./react-three-fiber.esm-dhSWjERg.js";import{a}from"./SettingsProvider-xrmxG700.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{At as o,Dt as s,Ht as c,b as l,p as u}from"./three.module-CwgFV8Kd.js";import"./mission-C2iDKeMo.js";import{t as d}from"./Texture-CUOilM1U.js";import{p as f}from"./loaders-0nekNxyt.js";import{t as p}from"./DebugSuspense-evFO3StN.js";var m=t(),h=e(n(),1),g=`
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{i}from"./react-three-fiber.esm-CgPHUpXo.js";import{a}from"./SettingsProvider-CCHVZuSg.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{At as o,Dt as s,Ht as c,b as l,p as u}from"./three.module-07hRbor4.js";import"./mission-D8vr00S1.js";import{p as d}from"./loaders-VxR5Bl13.js";import{t as f}from"./Texture-CIsc25mc.js";import{t as p}from"./DebugSuspense-Dk4MzcWf.js";var m=t(),h=e(n(),1),g=`
#include <fog_pars_vertex>
varying vec2 vUv;
@ -72,4 +72,4 @@ void main() {
gl_FragColor.a *= 1.0 - fogFactor;
#endif
}
`;function v({textures:e,scale:t,umapping:n,vmapping:r,color:i,baseTranslucency:a}){let s=[...t].sort((e,t)=>t-e),u=new c(s[0]*n,s[1]*r),d=e[0];return new o({uniforms:{frame0:{value:d},frame1:{value:e[1]??d},frame2:{value:e[2]??d},frame3:{value:e[3]??d},frame4:{value:e[4]??d},currentFrame:{value:0},vScroll:{value:0},uvScale:{value:u},tintColor:{value:new l(...i)},opacity:{value:a},opacityFactor:{value:1},fogColor:{value:new l},fogNear:{value:1},fogFar:{value:2e3}},vertexShader:g,fragmentShader:_,transparent:!0,blending:2,side:2,depthWrite:!1,fog:!0})}var y=r();function b(e){e.wrapS=e.wrapT=s,e.colorSpace=``,e.flipY=!1,e.needsUpdate=!0}function x(e){let t=(0,m.c)(7),[n,r,i]=e,a;t[0]!==n||t[1]!==r||t[2]!==i?(a=new u(n,r,i),a.translate(n/2,r/2,i/2),t[0]=n,t[1]=r,t[2]=i,t[3]=a):a=t[3];let o=a,s,c;return t[4]===o?(s=t[5],c=t[6]):(s=()=>()=>o.dispose(),c=[o],t[4]=o,t[5]=s,t[6]=c),(0,h.useEffect)(s,c),o}function S(e){let t=(0,m.c)(10),{scale:n,color:r,baseTranslucency:i}=e,a=x(n),o;t[0]!==r[0]||t[1]!==r[1]||t[2]!==r[2]?(o=new l(r[0],r[1],r[2]),t[0]=r[0],t[1]=r[1],t[2]=r[2],t[3]=o):o=t[3];let s=o,c=i*1,u;t[4]!==s||t[5]!==c?(u=(0,y.jsx)(`meshBasicMaterial`,{color:s,transparent:!0,opacity:c,blending:2,side:2,depthWrite:!1,fog:!1}),t[4]=s,t[5]=c,t[6]=u):u=t[6];let d;return t[7]!==a||t[8]!==u?(d=(0,y.jsx)(`mesh`,{geometry:a,renderOrder:1,children:u}),t[7]=a,t[8]=u,t[9]=d):d=t[9],d}function C({scale:e,data:t}){let{animationEnabled:n}=a(),r=x(e),o=d((0,h.useMemo)(()=>t.textures.map(e=>f(e)),[t.textures]),e=>{e.forEach(e=>b(e))}),s=(0,h.useMemo)(()=>v({textures:o,scale:e,umapping:t.umapping,vmapping:t.vmapping,color:t.color,baseTranslucency:t.baseTranslucency}),[o,e,t]);(0,h.useEffect)(()=>()=>s.dispose(),[s]);let c=(0,h.useRef)(0);return i((e,r)=>{if(!n){c.current=0,s.uniforms.currentFrame.value=0,s.uniforms.vScroll.value=0;return}c.current+=r,s.uniforms.currentFrame.value=Math.floor(c.current*t.framesPerSec)%t.numFrames,s.uniforms.vScroll.value=c.current*t.scrollSpeed}),(0,y.jsx)(`mesh`,{geometry:r,material:s,renderOrder:1})}function w(e){let t=(0,m.c)(14),{entity:n}=e,r=n.forceFieldData,i=r.dimensions;if(r.textures.map(T).length===0){let e;return t[0]!==r.baseTranslucency||t[1]!==r.color||t[2]!==i?(e=(0,y.jsx)(S,{scale:i,color:r.color,baseTranslucency:r.baseTranslucency}),t[0]=r.baseTranslucency,t[1]=r.color,t[2]=i,t[3]=e):e=t[3],e}let a;t[4]!==r.baseTranslucency||t[5]!==r.color||t[6]!==i?(a=(0,y.jsx)(S,{scale:i,color:r.color,baseTranslucency:r.baseTranslucency}),t[4]=r.baseTranslucency,t[5]=r.color,t[6]=i,t[7]=a):a=t[7];let o;t[8]!==r||t[9]!==i?(o=(0,y.jsx)(C,{scale:i,data:r}),t[8]=r,t[9]=i,t[10]=o):o=t[10];let s;return t[11]!==a||t[12]!==o?(s=(0,y.jsx)(p,{name:`ForceField`,fallback:a,children:o}),t[11]=a,t[12]=o,t[13]=s):s=t[13],s}function T(e){return f(e)}export{w as ForceFieldBare};
`;function v({textures:e,scale:t,umapping:n,vmapping:r,color:i,baseTranslucency:a}){let s=[...t].sort((e,t)=>t-e),u=new c(s[0]*n,s[1]*r),d=e[0];return new o({uniforms:{frame0:{value:d},frame1:{value:e[1]??d},frame2:{value:e[2]??d},frame3:{value:e[3]??d},frame4:{value:e[4]??d},currentFrame:{value:0},vScroll:{value:0},uvScale:{value:u},tintColor:{value:new l(...i)},opacity:{value:a},opacityFactor:{value:1},fogColor:{value:new l},fogNear:{value:1},fogFar:{value:2e3}},vertexShader:g,fragmentShader:_,transparent:!0,blending:2,side:2,depthWrite:!1,fog:!0})}var y=r();function b(e){e.wrapS=e.wrapT=s,e.colorSpace=``,e.flipY=!1,e.needsUpdate=!0}function x(e){let t=(0,m.c)(7),[n,r,i]=e,a;t[0]!==n||t[1]!==r||t[2]!==i?(a=new u(n,r,i),a.translate(n/2,r/2,i/2),t[0]=n,t[1]=r,t[2]=i,t[3]=a):a=t[3];let o=a,s,c;return t[4]===o?(s=t[5],c=t[6]):(s=()=>()=>o.dispose(),c=[o],t[4]=o,t[5]=s,t[6]=c),(0,h.useEffect)(s,c),o}function S(e){let t=(0,m.c)(10),{scale:n,color:r,baseTranslucency:i}=e,a=x(n),o;t[0]!==r[0]||t[1]!==r[1]||t[2]!==r[2]?(o=new l(r[0],r[1],r[2]),t[0]=r[0],t[1]=r[1],t[2]=r[2],t[3]=o):o=t[3];let s=o,c=i*1,u;t[4]!==s||t[5]!==c?(u=(0,y.jsx)(`meshBasicMaterial`,{color:s,transparent:!0,opacity:c,blending:2,side:2,depthWrite:!1,fog:!1}),t[4]=s,t[5]=c,t[6]=u):u=t[6];let d;return t[7]!==a||t[8]!==u?(d=(0,y.jsx)(`mesh`,{geometry:a,renderOrder:1,children:u}),t[7]=a,t[8]=u,t[9]=d):d=t[9],d}function C({scale:e,data:t}){let{animationEnabled:n}=a(),r=x(e),o=f((0,h.useMemo)(()=>t.textures.map(e=>d(e)),[t.textures]),e=>{e.forEach(e=>b(e))}),s=(0,h.useMemo)(()=>v({textures:o,scale:e,umapping:t.umapping,vmapping:t.vmapping,color:t.color,baseTranslucency:t.baseTranslucency}),[o,e,t]);(0,h.useEffect)(()=>()=>s.dispose(),[s]);let c=(0,h.useRef)(0);return i((e,r)=>{if(!n){c.current=0,s.uniforms.currentFrame.value=0,s.uniforms.vScroll.value=0;return}c.current+=r,s.uniforms.currentFrame.value=Math.floor(c.current*t.framesPerSec)%t.numFrames,s.uniforms.vScroll.value=c.current*t.scrollSpeed}),(0,y.jsx)(`mesh`,{geometry:r,material:s,renderOrder:1})}function w(e){let t=(0,m.c)(14),{entity:n}=e,r=n.forceFieldData,i=r.dimensions;if(r.textures.map(T).length===0){let e;return t[0]!==r.baseTranslucency||t[1]!==r.color||t[2]!==i?(e=(0,y.jsx)(S,{scale:i,color:r.color,baseTranslucency:r.baseTranslucency}),t[0]=r.baseTranslucency,t[1]=r.color,t[2]=i,t[3]=e):e=t[3],e}let a;t[4]!==r.baseTranslucency||t[5]!==r.color||t[6]!==i?(a=(0,y.jsx)(S,{scale:i,color:r.color,baseTranslucency:r.baseTranslucency}),t[4]=r.baseTranslucency,t[5]=r.color,t[6]=i,t[7]=a):a=t[7];let o;t[8]!==r||t[9]!==i?(o=(0,y.jsx)(C,{scale:i,data:r}),t[8]=r,t[9]=i,t[10]=o):o=t[10];let s;return t[11]!==a||t[12]!==o?(s=(0,y.jsx)(p,{name:`ForceField`,fallback:a,children:o}),t[11]=a,t[12]=o,t[13]=s):s=t[13],s}function T(e){return d(e)}export{w as ForceFieldBare};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";var i=t(),a=e(n(),1),o=r(),s=(0,a.createContext)(null);function c(){let e=(0,a.useContext)(s);if(!e)throw Error(`No JoystickContext found. Did you forget to add a <JoystickProvider>?`);return e}function l(e){let t=(0,i.c)(7),{children:n}=e,r;t[0]===Symbol.for(`react.memo_cache_sentinel`)?(r={angle:0,force:0},t[0]=r):r=t[0];let c=(0,a.useRef)(r),l;t[1]===Symbol.for(`react.memo_cache_sentinel`)?(l={angle:0,force:0},t[1]=l):l=t[1];let u=(0,a.useRef)(l),d;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(d=e=>{let{angle:t,force:n}=e;t!=null&&(c.current.angle=t),n!=null&&(c.current.force=n)},t[2]=d):d=t[2];let f=d,p;t[3]===Symbol.for(`react.memo_cache_sentinel`)?(p=e=>{let{angle:t,force:n}=e;t!=null&&(u.current.angle=t),n!=null&&(u.current.force=n)},t[3]=p):p=t[3];let m=p,h;t[4]===Symbol.for(`react.memo_cache_sentinel`)?(h={moveState:c,lookState:u,setMoveState:f,setLookState:m},t[4]=h):h=t[4];let g=h,_;return t[5]===n?_=t[6]:(_=(0,o.jsx)(s,{value:g,children:n}),t[5]=n,t[6]=_),_}export{c as n,l as t};

View file

@ -1 +0,0 @@
import{n as e,t}from"./jsx-runtime-BpGWiA-R.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import"./streamHelpers-Be29sBCp.js";import"./scene-C7IIl-c0.js";import"./mission-C2iDKeMo.js";import"./engineStore-Cio8vU1L.js";import{_ as n,m as r,s as i}from"./index-BMiY1uIn.js";var a=e(),o={Root:`_Root_uktvs_1`,Column:`_Column_uktvs_13`,Row:`_Row_uktvs_20`,Spacer:`_Spacer_uktvs_26`,Key:`_Key_uktvs_30`,Arrow:`_Arrow_uktvs_53`},s=t();function c(){let e=(0,a.c)(51),t=r(),c=n(y),b=i(v),x=i(_),S=i(g),C=i(h),w=i(m),T=i(p),E=i(f),D=i(d),O=i(u),k=i(l);if(t&&t.source!==`live`||t?.source===`live`&&!c)return null;let A;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(A=(0,s.jsx)(`div`,{className:o.Spacer}),e[0]=A):A=e[0];let j;e[1]===b?j=e[2]:(j=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":b,children:`W`}),e[1]=b,e[2]=j);let M;e[3]===Symbol.for(`react.memo_cache_sentinel`)?(M=(0,s.jsx)(`div`,{className:o.Spacer}),e[3]=M):M=e[3];let N;e[4]===j?N=e[5]:(N=(0,s.jsxs)(`div`,{className:o.Row,children:[A,j,M]}),e[4]=j,e[5]=N);let P;e[6]===S?P=e[7]:(P=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":S,children:`A`}),e[6]=S,e[7]=P);let F;e[8]===x?F=e[9]:(F=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":x,children:`S`}),e[8]=x,e[9]=F);let I;e[10]===C?I=e[11]:(I=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":C,children:`D`}),e[10]=C,e[11]=I);let L;e[12]!==P||e[13]!==F||e[14]!==I?(L=(0,s.jsxs)(`div`,{className:o.Row,children:[P,F,I]}),e[12]=P,e[13]=F,e[14]=I,e[15]=L):L=e[15];let R;e[16]!==N||e[17]!==L?(R=(0,s.jsxs)(`div`,{className:o.Column,children:[N,L]}),e[16]=N,e[17]=L,e[18]=R):R=e[18];let z;e[19]===Symbol.for(`react.memo_cache_sentinel`)?(z=(0,s.jsx)(`span`,{className:o.Arrow,children:``}),e[19]=z):z=e[19];let B;e[20]===w?B=e[21]:(B=(0,s.jsx)(`div`,{className:o.Row,children:(0,s.jsxs)(`div`,{className:o.Key,"data-pressed":w,children:[z,` Space`]})}),e[20]=w,e[21]=B);let V;e[22]===Symbol.for(`react.memo_cache_sentinel`)?(V=(0,s.jsx)(`span`,{className:o.Arrow,children:``}),e[22]=V):V=e[22];let H;e[23]===T?H=e[24]:(H=(0,s.jsx)(`div`,{className:o.Row,children:(0,s.jsxs)(`div`,{className:o.Key,"data-pressed":T,children:[V,` Shift`]})}),e[23]=T,e[24]=H);let U;e[25]!==B||e[26]!==H?(U=(0,s.jsxs)(`div`,{className:o.Column,children:[B,H]}),e[25]=B,e[26]=H,e[27]=U):U=e[27];let W;e[28]===Symbol.for(`react.memo_cache_sentinel`)?(W=(0,s.jsx)(`div`,{className:o.Spacer}),e[28]=W):W=e[28];let G;e[29]===E?G=e[30]:(G=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":E,children:``}),e[29]=E,e[30]=G);let K;e[31]===Symbol.for(`react.memo_cache_sentinel`)?(K=(0,s.jsx)(`div`,{className:o.Spacer}),e[31]=K):K=e[31];let q;e[32]===G?q=e[33]:(q=(0,s.jsxs)(`div`,{className:o.Row,children:[W,G,K]}),e[32]=G,e[33]=q);let J;e[34]===O?J=e[35]:(J=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":O,children:``}),e[34]=O,e[35]=J);let Y;e[36]===D?Y=e[37]:(Y=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":D,children:``}),e[36]=D,e[37]=Y);let X;e[38]===k?X=e[39]:(X=(0,s.jsx)(`div`,{className:o.Key,"data-pressed":k,children:``}),e[38]=k,e[39]=X);let Z;e[40]!==J||e[41]!==Y||e[42]!==X?(Z=(0,s.jsxs)(`div`,{className:o.Row,children:[J,Y,X]}),e[40]=J,e[41]=Y,e[42]=X,e[43]=Z):Z=e[43];let Q;e[44]!==q||e[45]!==Z?(Q=(0,s.jsxs)(`div`,{className:o.Column,children:[q,Z]}),e[44]=q,e[45]=Z,e[46]=Q):Q=e[46];let $;return e[47]!==U||e[48]!==Q||e[49]!==R?($=(0,s.jsxs)(`div`,{className:o.Root,children:[R,U,Q]}),e[47]=U,e[48]=Q,e[49]=R,e[50]=$):$=e[50],$}function l(e){return e.lookRight}function u(e){return e.lookLeft}function d(e){return e.lookDown}function f(e){return e.lookUp}function p(e){return e.down}function m(e){return e.up}function h(e){return e.right}function g(e){return e.left}function _(e){return e.backward}function v(e){return e.forward}function y(e){return e.liveReady}export{c as KeyboardOverlay};

View file

@ -1 +0,0 @@
._Root_uktvs_1{pointer-events:none;z-index:1;align-items:flex-end;gap:10px;display:flex;position:absolute;bottom:16px;left:50%;transform:translate(-50%)}._Column_uktvs_13{flex-direction:column;justify-content:center;gap:4px;display:flex}._Row_uktvs_20{justify-content:stretch;gap:4px;display:flex}._Spacer_uktvs_26{width:32px}._Key_uktvs_30{color:#ffffff80;white-space:nowrap;background:#0006;border:1px solid #fff3;border-radius:4px;flex:1 0 0;justify-content:center;align-items:center;min-width:32px;height:32px;padding:0 8px;font-size:11px;font-weight:600;display:flex}._Key_uktvs_30[data-pressed=true]{color:#fff;background:#34bbab99;border-color:#23fddc80}._Arrow_uktvs_53{margin-right:3px}

View file

@ -0,0 +1 @@
._Root_88qkf_1{pointer-events:none;z-index:1;align-items:flex-end;gap:10px;display:flex;position:absolute;bottom:16px;left:50%;transform:translate(-50%)}._Column_88qkf_13{flex-direction:column;justify-content:flex-end;align-items:stretch;gap:4px;display:flex}._Row_88qkf_25{justify-content:stretch;gap:4px;display:flex}._Spacer_88qkf_31{width:32px}._Sep_88qkf_35{opacity:.5}._Key_88qkf_39{color:#ffffff80;white-space:nowrap;background:#00000080;border:1px solid #ffffff4d;border-radius:4px;align-items:stretch;min-width:32px;height:32px;margin:0 auto;font-size:11px;font-weight:600;display:flex;overflow:hidden}._Key_88qkf_39[data-size=auto]{flex:none}._Key_88qkf_39[data-size=fill]{flex:1 0 auto}._Key_88qkf_39[data-disabled=true]{opacity:.6}._Key_88qkf_39[data-pressed=true]:not([data-disabled=true]){background:#23918499;border-color:#18c5ab66}._Label_88qkf_78{background:#c8c8c81a;justify-content:center;align-items:center;min-width:30px;padding:0 6px;display:flex}._Key_88qkf_39[data-pressed=true]:not([data-disabled=true]) ._Label_88qkf_78{color:#ffffffb3}._Label_88qkf_78[data-size=auto]{flex:none}._Label_88qkf_78[data-size=fill]{flex:1 0 auto}._Label_88qkf_78:first-child{border-right:1px solid #fff3}._Label_88qkf_78:last-child{border-left:1px solid #fff3}._Key_88qkf_39[data-pressed=true]:not([data-disabled=true]) ._Label_88qkf_78{color:#a2ffdecc;border-color:#18c5ab66}._MultiInput_88qkf_120{font-family:var(--monospace-font);flex-direction:column;justify-content:center;align-items:center;display:flex}._Input_88qkf_128{justify-content:center;align-items:center;min-width:30px;padding:0 8px;line-height:.8;display:flex}._Input_88qkf_128[data-size=auto],._MultiInput_88qkf_120[data-size=auto]{flex:none}._Input_88qkf_128[data-size=fill],._MultiInput_88qkf_120[data-size=fill]{flex:1 0 auto}._Key_88qkf_39[data-pressed=true]:not([data-disabled=true]) ._Input_88qkf_128{color:#fff}._MultiInput_88qkf_120 ._Input_88qkf_128{background:0 0;border:0;flex:none}._ColumnLabel_88qkf_161{color:#ffffff80;text-shadow:0 0 2px #0009;text-transform:uppercase;text-align:center;padding:2px 0;font-size:9px}._PlayPauseIcon_88qkf_170{font-size:9px}._MouseIcon_88qkf_174{font-size:19px}._Input_88qkf_128:has(._MouseIcon_88qkf_174){padding:0 4px}

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{t as i}from"./useQuery-BgNfxBEt.js";import{c as a}from"./manifest-CirqV3Ls.js";import"./logger-z_EpIdIa.js";import"./mission-C2iDKeMo.js";import{D as o,O as s}from"./index-BMiY1uIn.js";import{i as c,l,n as u}from"./loaders-0nekNxyt.js";var d=t(),f=e(n(),1),p={GuiMarkup:`_GuiMarkup_jg4va_1`,Bullet:`_Bullet_jg4va_12`},m=r(),h=new Set([`spop`,`spush`,`lmargin`,`font`,`color`,`bitmap`,`a`,`/a`]);function g(e){return e.split(/<([^><]+)>/g).map((e,t)=>{if(t%2==0)return e?{type:`text`,value:e}:null;{let[t,...n]=e.split(`:`);return h.has(t.toLowerCase())?{type:`tag`,name:t,args:n}:{type:`text`,value:`<${e}>`}}}).filter(e=>e!=null)}function _(e){let[t,n]=e;return{fontDescription:t,fontSize:n?Math.max(11,Math.min(parseInt(n.trim(),10),16)):void 0}}function v(e){let t=g(e),n={type:`span`,source:`root`,style:{},children:[]},r=n,i=[r],a=e=>e.children!=null&&e.children.some(e=>typeof e==`string`||a(e));for(let e of t)switch(e.type){case`text`:r.children.push(e.value);break;case`tag`:switch(e.name){case`spush`:{let e={type:`span`,source:`spush`,style:{},children:[]};r.children.push(e),r=e,i.push(r);break}case`spop`:if(r.source!==`root`){let e=i.pop();for(;e.source!==`spush`;)e=i.pop();r=i[i.length-1]}break;case`lmargin`:break;case`font`:{let t=_(e.args).fontSize;if(!a(r))r.style.fontSize=t;else{let e={type:`span`,source:`spush`,style:{fontSize:t},children:[]};r.children.push(e),r=e,i.push(r)}break}case`color`:if(!a(r))r.style.color=`#${e.args[0].trim()}`;else{let t={type:`span`,source:`spush`,style:{color:`#${e.args[0].trim()}`},children:[]};r.children.push(t),r=t,i.push(r)}break;case`bitmap`:{let t={type:`bitmap`,value:e.args[0]};r.children.push(t);break}case`a`:{let t=e.args[0].trim().split(` `),n={type:`a`,source:`a`,value:`http://${t.length===2&&t[0]===`wwwlink`?t[1]:t[0]}`,style:{},children:[]};r.children.push(n),r=n,i.push(r);break}case`/a`:{let e=i.pop();for(;e.source!==`a`;)e=i.pop();r=i[i.length-1];break}}}return y(n)}function y(e){switch(e.type){case`span`:return f.createElement(`span`,{style:Object.keys(e.style).length===0?void 0:e.style},...e.children.map(e=>typeof e==`string`?e:y(e)));case`a`:return f.createElement(`a`,{href:e.value,style:Object.keys(e.style).length===0?void 0:e.style,rel:`noopener noreferrer`,target:`_blank`},...e.children.map(e=>typeof e==`string`?e:y(e)));case`bitmap`:return(0,m.jsx)(S,{name:e.value})}}var b=new Map;function x(e){if(b.has(e))return b.get(e);let t;try{t=c(a(`textures/gui/${e}`))}catch{t=null}return b.set(e,t),t}function S(e){let t=(0,d.c)(5),{name:n}=e,r;t[0]===n?r=t[1]:(r=x(n),t[0]=n,t[1]=r);let i=r;if(i){let e;return t[2]===i?e=t[3]:(e=(0,m.jsx)(`img`,{src:i,alt:``,className:p.Bitmap}),t[2]=i,t[3]=e),e}if(/bullet/i.test(n)){let e;return t[4]===Symbol.for(`react.memo_cache_sentinel`)?(e=(0,m.jsx)(`span`,{className:p.Bullet,children:``}),t[4]=e):e=t[4],e}return null}var C=/<(?:font|color|bitmap|just|lmargin|a):/i;function w(e){return C.test(e)}function T(e,t){let n=t.toUpperCase();return e.split(`
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{t as i}from"./useQuery-B-4HMtqx.js";import{c as a}from"./manifest-CirqV3Ls.js";import"./logger-z_EpIdIa.js";import"./mission-D8vr00S1.js";import{H as o,U as s}from"./index-DBtsNu05.js";import{i as c,l,n as u}from"./loaders-VxR5Bl13.js";var d=t(),f=e(n(),1),p={GuiMarkup:`_GuiMarkup_jg4va_1`,Bullet:`_Bullet_jg4va_12`},m=r(),h=new Set([`spop`,`spush`,`lmargin`,`font`,`color`,`bitmap`,`a`,`/a`]);function g(e){return e.split(/<([^><]+)>/g).map((e,t)=>{if(t%2==0)return e?{type:`text`,value:e}:null;{let[t,...n]=e.split(`:`);return h.has(t.toLowerCase())?{type:`tag`,name:t,args:n}:{type:`text`,value:`<${e}>`}}}).filter(e=>e!=null)}function _(e){let[t,n]=e;return{fontDescription:t,fontSize:n?Math.max(11,Math.min(parseInt(n.trim(),10),16)):void 0}}function v(e){let t=g(e),n={type:`span`,source:`root`,style:{},children:[]},r=n,i=[r],a=e=>e.children!=null&&e.children.some(e=>typeof e==`string`||a(e));for(let e of t)switch(e.type){case`text`:r.children.push(e.value);break;case`tag`:switch(e.name){case`spush`:{let e={type:`span`,source:`spush`,style:{},children:[]};r.children.push(e),r=e,i.push(r);break}case`spop`:if(r.source!==`root`){let e=i.pop();for(;e.source!==`spush`;)e=i.pop();r=i[i.length-1]}break;case`lmargin`:break;case`font`:{let t=_(e.args).fontSize;if(!a(r))r.style.fontSize=t;else{let e={type:`span`,source:`spush`,style:{fontSize:t},children:[]};r.children.push(e),r=e,i.push(r)}break}case`color`:if(!a(r))r.style.color=`#${e.args[0].trim()}`;else{let t={type:`span`,source:`spush`,style:{color:`#${e.args[0].trim()}`},children:[]};r.children.push(t),r=t,i.push(r)}break;case`bitmap`:{let t={type:`bitmap`,value:e.args[0]};r.children.push(t);break}case`a`:{let t=e.args[0].trim().split(` `),n={type:`a`,source:`a`,value:`http://${t.length===2&&t[0]===`wwwlink`?t[1]:t[0]}`,style:{},children:[]};r.children.push(n),r=n,i.push(r);break}case`/a`:{let e=i.pop();for(;e.source!==`a`;)e=i.pop();r=i[i.length-1];break}}}return y(n)}function y(e){switch(e.type){case`span`:return f.createElement(`span`,{style:Object.keys(e.style).length===0?void 0:e.style},...e.children.map(e=>typeof e==`string`?e:y(e)));case`a`:return f.createElement(`a`,{href:e.value,style:Object.keys(e.style).length===0?void 0:e.style,rel:`noopener noreferrer`,target:`_blank`},...e.children.map(e=>typeof e==`string`?e:y(e)));case`bitmap`:return(0,m.jsx)(S,{name:e.value})}}var b=new Map;function x(e){if(b.has(e))return b.get(e);let t;try{t=c(a(`textures/gui/${e}`))}catch{t=null}return b.set(e,t),t}function S(e){let t=(0,d.c)(5),{name:n}=e,r;t[0]===n?r=t[1]:(r=x(n),t[0]=n,t[1]=r);let i=r;if(i){let e;return t[2]===i?e=t[3]:(e=(0,m.jsx)(`img`,{src:i,alt:``,className:p.Bitmap}),t[2]=i,t[3]=e),e}if(/bullet/i.test(n)){let e;return t[4]===Symbol.for(`react.memo_cache_sentinel`)?(e=(0,m.jsx)(`span`,{className:p.Bullet,children:``}),t[4]=e):e=t[4],e}return null}var C=/<(?:font|color|bitmap|just|lmargin|a):/i;function w(e){return C.test(e)}function T(e,t){let n=t.toUpperCase();return e.split(`
`).flatMap(e=>{let t=e.match(/^\[([^\]]+)\]/);return t&&!t[1].toUpperCase().split(/\s+/).includes(n)?[]:[e.replace(/^\[[^\]]+\]/,``)]}).join(`
`)}function E(e){let t=(0,d.c)(4),{markup:n}=e,r;t[0]===n?r=t[1]:(r=v(n),t[0]=n,t[1]=r);let i=r,a;return t[2]===i?a=t[3]:(a=(0,m.jsx)(`div`,{className:p.GuiMarkup,children:i}),t[2]=i,t[3]=a),a}var D={Dialog:`_Dialog_tbn5d_1 _Dialog_6c89x_1`,Overlay:`_Overlay_tbn5d_10 _Overlay_6c89x_20`,Body:`_Body_tbn5d_14`,Left:`_Left_tbn5d_22`,PreviewImage:`_PreviewImage_tbn5d_29`,PreviewImageFloating:`_PreviewImageFloating_tbn5d_35`,Title:`_Title_tbn5d_45`,MapMeta:`_MapMeta_tbn5d_53`,MapPlanet:`_MapPlanet_tbn5d_63`,MapQuote:`_MapQuote_tbn5d_67`,MapBlurb:`_MapBlurb_tbn5d_86`,Section:`_Section_tbn5d_91`,SectionTitle:`_SectionTitle_tbn5d_95`,MusicTrack:`_MusicTrack_tbn5d_105`,MusicButton:`_MusicButton_tbn5d_119`,Footer:`_Footer_tbn5d_146`,CloseButton:`_CloseButton_tbn5d_158 _DialogButton_6c89x_31`,Hint:`_Hint_tbn5d_162`,MusicTrackName:`_MusicTrackName_tbn5d_168`};function ee(e){let t=(0,d.c)(2),n;return t[0]===e?n=t[1]:(n={queryKey:[`parsedMission`,e],queryFn:()=>l(e)},t[0]=e,t[1]=n),i(n)}function te(e){for(let t of e.body){if(t.type!==`ObjectDeclaration`)continue;let{instanceName:e,body:n}=t;if(e&&e.type===`Identifier`&&e.name.toLowerCase()===`missiongroup`){let e={};for(let t of n){if(t.type!==`Assignment`)continue;let{target:n,value:r}=t;n.type===`Identifier`&&r.type===`StringLiteral`&&(e[n.name.toLowerCase()]=r.value)}return e}}return{}}function ne(e,t){if(e)try{return c(a(`textures/gui/${e}`))}catch{}try{return c(a(`textures/gui/Load_${t}`))}catch{}return null}function re(e){let t=(0,d.c)(7),{src:n,alt:r,className:i}=e,a=i===void 0?D.PreviewImage:i,[o,s]=(0,f.useState)(null),c,l;if(t[0]===n?(c=t[1],l=t[2]):(c=()=>{let e=!1,t;return fetch(n).then(j).then(A).then(k).then(n=>{e||!n||(t=URL.createObjectURL(n),s(t))}).catch(O),()=>{e=!0,t&&URL.revokeObjectURL(t)}},l=[n],t[0]=n,t[1]=c,t[2]=l),(0,f.useEffect)(c,l),!o)return null;let u;return t[3]!==r||t[4]!==a||t[5]!==o?(u=(0,m.jsx)(`img`,{src:o,alt:r,className:a}),t[3]=r,t[4]=a,t[5]=o,t[6]=u):u=t[6],u}function O(){}function k(e){return new Promise(t=>{let n=document.createElement(`canvas`);n.width=e.width,n.height=e.height,n.getContext(`2d`)?.drawImage(e,0,0),e.close(),n.toBlob(t)})}function A(e){return createImageBitmap(e,{colorSpaceConversion:`none`})}function j(e){return e.blob()}function ie(e){let t=(0,d.c)(22),{track:n}=e,[r,i]=(0,f.useState)(!1),[a,c]=(0,f.useState)(!0),l=(0,f.useRef)(null),p;t[0]===n?p=t[1]:(p=n.toLowerCase(),t[0]=n,t[1]=p);let h=`${u}music/${p}.mp3`,g,_;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(g=()=>{let e=l.current;if(e)return()=>{e.pause()}},_=[],t[2]=g,t[3]=_):(g=t[2],_=t[3]),(0,f.useEffect)(g,_);let v;t[4]===r?v=t[5]:(v=()=>{let e=l.current;e&&(r?e.pause():e.play().catch(()=>c(!1)))},t[4]=r,t[5]=v);let y=v,b,x,S;t[6]===Symbol.for(`react.memo_cache_sentinel`)?(b=()=>i(!0),x=()=>i(!1),S=()=>c(!1),t[6]=b,t[7]=x,t[8]=S):(b=t[6],x=t[7],S=t[8]);let C;t[9]===h?C=t[10]:(C=(0,m.jsx)(`audio`,{ref:l,src:h,loop:!0,onPlay:b,onPause:x,onError:S}),t[9]=h,t[10]=C);let w;t[11]===n?w=t[12]:(w=(0,m.jsx)(`span`,{className:D.MusicTrackName,children:n}),t[11]=n,t[12]=w);let T;t[13]!==a||t[14]!==r||t[15]!==y?(T=a&&(0,m.jsx)(`button`,{className:D.MusicButton,onClick:y,"aria-label":r?`Pause music`:`Play music`,children:r?(0,m.jsx)(s,{}):(0,m.jsx)(o,{})}),t[13]=a,t[14]=r,t[15]=y,t[16]=T):T=t[16];let E;return t[17]!==r||t[18]!==T||t[19]!==C||t[20]!==w?(E=(0,m.jsxs)(`div`,{className:D.MusicTrack,"data-playing":r,children:[C,w,T]}),t[17]=r,t[18]=T,t[19]=C,t[20]=w,t[21]=E):E=t[21],E}function M(e){let t=(0,d.c)(100),{onClose:n,missionName:r,missionType:i}=e,{data:a}=ee(r),o=(0,f.useRef)(null),s,c;t[0]===Symbol.for(`react.memo_cache_sentinel`)?(s=()=>{o.current?.focus();try{document.exitPointerLock()}catch{}},c=[],t[0]=s,t[1]=c):(s=t[0],c=t[1]),(0,f.useEffect)(s,c);let l,u;t[2]===n?(l=t[3],u=t[4]):(l=()=>{let e=e=>{if(e.key===`Escape`)n();else if(e.key===`k`&&(e.metaKey||e.ctrlKey)){n();return}e.stopImmediatePropagation()},t=se;return window.addEventListener(`keydown`,e,{capture:!0}),window.addEventListener(`keyup`,t,{capture:!0}),()=>{window.removeEventListener(`keydown`,e,{capture:!0}),window.removeEventListener(`keyup`,t,{capture:!0})}},u=[n],t[2]=n,t[3]=l,t[4]=u),(0,f.useEffect)(l,u);let p;t[5]===a?p=t[6]:(p=a?te(a.ast):{},t[5]=a,t[6]=p);let h=p,g;t[7]!==r||t[8]!==a?(g=a?ne(a.bitmap,r):null,t[7]=r,t[8]=a,t[9]=g):g=t[9];let _=g,v=a?.displayName??r,y;t[10]===i?y=t[11]:(y=i.toLowerCase(),t[10]=i,t[11]=y);let b=y===`singleplayer`,x=h.musictrack,S,C,O,k,A,j,M,N,P,F,I,L,R,z,B,V,H,U;if(t[12]!==_||t[13]!==v||t[14]!==b||t[15]!==i||t[16]!==n||t[17]!==a){let e=a?.missionString?T(a.missionString,i):null,r,s,c,l;if(t[36]!==a?.missionQuote){if(l=a?.missionQuote?.trim()??``,s=w(l),c=``,r=``,!s)for(let e of l.split(`
`)){let t=e.trim();t.match(/^--[^-]/)?r=t.replace(/^-+\s*/,``).trim():t&&(c+=(c?`

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{i}from"./react-three-fiber.esm-CgPHUpXo.js";import"./SettingsProvider-CCHVZuSg.js";import{_ as a,h as o,m as s}from"./GenericShape-PntybIni.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{Ot as c,St as l,Ut as u,b as d}from"./three.module-07hRbor4.js";import"./mission-D8vr00S1.js";import"./engineStore-Dkm20jvr.js";import{p as f}from"./loaders-VxR5Bl13.js";import{t as p}from"./Texture-CIsc25mc.js";import"./FloatingLabel-C7nyg5oz.js";import"./globalFogUniforms-CtxQvsRj.js";var m=t(),h=e(n(),1),g=r(),_=new u,v=new u,y=new u,b=new u,x=new u,S=new u,C=new u(0,1,0);function w(e){let t=(0,m.c)(14),{entity:n}=e,{visual:r}=n,i;t[0]===r.texture?i=t[1]:(i=f(r.texture),t[0]=r.texture,t[1]=i);let a=p(i,T),o=Array.isArray(a)?a[0]:a,s;t[2]!==r.color.b||t[3]!==r.color.g||t[4]!==r.color.r?(s=new d().setRGB(r.color.r,r.color.g,r.color.b,c),t[2]=r.color.b,t[3]=r.color.g,t[4]=r.color.r,t[5]=s):s=t[5];let l=s,u;t[6]===r.size?u=t[7]:(u=[r.size,r.size,1],t[6]=r.size,t[7]=u);let h;t[8]!==l||t[9]!==o?(h=(0,g.jsx)(`spriteMaterial`,{map:o,color:l,transparent:!0,blending:2,depthWrite:!1,toneMapped:!1}),t[8]=l,t[9]=o,t[10]=h):h=t[10];let _;return t[11]!==u||t[12]!==h?(_=(0,g.jsx)(`sprite`,{scale:u,children:h}),t[11]=u,t[12]=h,t[13]=_):_=t[13],_}function T(e){o(Array.isArray(e)?e[0]:e)}function E(e){let t=(0,m.c)(29),{entity:n}=e,{visual:r}=n,o=(0,h.useRef)(null),c=(0,h.useRef)(null),u=(0,h.useRef)(null),d;t[0]===Symbol.for(`react.memo_cache_sentinel`)?(d=new l,t[0]=d):d=t[0];let w=(0,h.useRef)(d),T;t[1]===r.texture?T=t[2]:(T=f(r.texture),t[1]=r.texture,t[2]=T);let E=r.crossTexture??r.texture,O;t[3]===E?O=t[4]:(O=f(E),t[3]=E,t[4]=O);let k;t[5]!==T||t[6]!==O?(k=[T,O],t[5]=T,t[6]=O,t[7]=k):k=t[7];let A=p(k,D),j;t[8]===A?j=t[9]:(j=Array.isArray(A)?A:[A,A],t[8]=A,t[9]=j);let[M,N]=j,P;t[10]!==n.direction||t[11]!==n.keyframes?.[0]||t[12]!==r.crossSize||t[13]!==r.crossViewAng||t[14]!==r.renderCross||t[15]!==r.tracerLength||t[16]!==r.tracerWidth?(P=e=>{let{camera:t}=e,i=o.current,l=c.current;if(!i||!l)return;let d=n.keyframes?.[0],f=d?.position,p=n.direction??d?.velocity;if(!f||!p){i.visible=!1,u.current&&(u.current.visible=!1);return}if(a(p,_),_.lengthSq()<1e-8){i.visible=!1,u.current&&(u.current.visible=!1);return}_.normalize(),i.visible=!0,a(f,S),v.copy(S).sub(t.position),y.crossVectors(v,_),y.lengthSq()<1e-8&&(y.crossVectors(C,_),y.lengthSq()<1e-8&&y.set(1,0,0)),y.normalize().multiplyScalar(r.tracerWidth);let m=r.tracerLength*.5;b.copy(_).multiplyScalar(-m),x.copy(_).multiplyScalar(m);let h=l.array;h[0]=b.x+y.x,h[1]=b.y+y.y,h[2]=b.z+y.z,h[3]=b.x-y.x,h[4]=b.y-y.y,h[5]=b.z-y.z,h[6]=x.x-y.x,h[7]=x.y-y.y,h[8]=x.z-y.z,h[9]=x.x+y.x,h[10]=x.y+y.y,h[11]=x.z+y.z,l.needsUpdate=!0;let g=u.current;if(!g)return;if(!r.renderCross){g.visible=!1;return}v.normalize();let T=_.dot(v);if(T>-r.crossViewAng&&T<r.crossViewAng){g.visible=!1;return}g.visible=!0,s(_,w.current),g.quaternion.copy(w.current),g.scale.setScalar(r.crossSize)},t[10]=n.direction,t[11]=n.keyframes?.[0],t[12]=r.crossSize,t[13]=r.crossViewAng,t[14]=r.renderCross,t[15]=r.tracerLength,t[16]=r.tracerWidth,t[17]=P):P=t[17],i(P);let F;t[18]===Symbol.for(`react.memo_cache_sentinel`)?(F=(0,g.jsx)(`bufferAttribute`,{ref:c,attach:`attributes-position`,args:[new Float32Array(12),3]}),t[18]=F):F=t[18];let I;t[19]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,g.jsx)(`bufferAttribute`,{attach:`attributes-uv`,args:[new Float32Array([0,0,0,1,1,1,1,0]),2]}),t[19]=I):I=t[19];let L;t[20]===Symbol.for(`react.memo_cache_sentinel`)?(L=(0,g.jsxs)(`bufferGeometry`,{children:[F,I,(0,g.jsx)(`bufferAttribute`,{attach:`index`,args:[new Uint16Array([0,1,2,0,2,3]),1]})]}),t[20]=L):L=t[20];let R;t[21]===M?R=t[22]:(R=(0,g.jsxs)(`mesh`,{ref:o,children:[L,(0,g.jsx)(`meshBasicMaterial`,{map:M,transparent:!0,blending:2,side:2,depthWrite:!1,toneMapped:!1})]}),t[21]=M,t[22]=R);let z;t[23]!==N||t[24]!==r.renderCross?(z=r.renderCross?(0,g.jsxs)(`mesh`,{ref:u,children:[(0,g.jsxs)(`bufferGeometry`,{children:[(0,g.jsx)(`bufferAttribute`,{attach:`attributes-position`,args:[new Float32Array([-.5,0,-.5,.5,0,-.5,.5,0,.5,-.5,0,.5]),3]}),(0,g.jsx)(`bufferAttribute`,{attach:`attributes-uv`,args:[new Float32Array([0,0,0,1,1,1,1,0]),2]}),(0,g.jsx)(`bufferAttribute`,{attach:`index`,args:[new Uint16Array([0,1,2,0,2,3]),1]})]}),(0,g.jsx)(`meshBasicMaterial`,{map:N,transparent:!0,blending:2,side:2,depthWrite:!1,toneMapped:!1})]}):null,t[23]=N,t[24]=r.renderCross,t[25]=z):z=t[25];let B;return t[26]!==R||t[27]!==z?(B=(0,g.jsxs)(g.Fragment,{children:[R,z]}),t[26]=R,t[27]=z,t[28]=B):B=t[28],B}function D(e){let t=Array.isArray(e)?e:[e];for(let e of t)o(e)}export{w as SpriteProjectile,E as TracerProjectile};

View file

@ -1 +0,0 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n,t as r}from"./jsx-runtime-BpGWiA-R.js";import{i}from"./react-three-fiber.esm-dhSWjERg.js";import"./SettingsProvider-xrmxG700.js";import{_ as a,h as o,m as s}from"./GenericShape-C-0r5jzJ.js";import"./logger-z_EpIdIa.js";import"./traditional-BTL5qX2E.js";import{Ot as c,St as l,Ut as u,b as d}from"./three.module-CwgFV8Kd.js";import"./mission-C2iDKeMo.js";import"./engineStore-Cio8vU1L.js";import{t as f}from"./Texture-CUOilM1U.js";import{p}from"./loaders-0nekNxyt.js";import"./FloatingLabel-D3hPNzZS.js";import"./globalFogUniforms-CaAkHfiE.js";var m=t(),h=e(n(),1),g=r(),_=new u,v=new u,y=new u,b=new u,x=new u,S=new u,C=new u(0,1,0);function w(e){let t=(0,m.c)(14),{entity:n}=e,{visual:r}=n,i;t[0]===r.texture?i=t[1]:(i=p(r.texture),t[0]=r.texture,t[1]=i);let a=f(i,T),o=Array.isArray(a)?a[0]:a,s;t[2]!==r.color.b||t[3]!==r.color.g||t[4]!==r.color.r?(s=new d().setRGB(r.color.r,r.color.g,r.color.b,c),t[2]=r.color.b,t[3]=r.color.g,t[4]=r.color.r,t[5]=s):s=t[5];let l=s,u;t[6]===r.size?u=t[7]:(u=[r.size,r.size,1],t[6]=r.size,t[7]=u);let h;t[8]!==l||t[9]!==o?(h=(0,g.jsx)(`spriteMaterial`,{map:o,color:l,transparent:!0,blending:2,depthWrite:!1,toneMapped:!1}),t[8]=l,t[9]=o,t[10]=h):h=t[10];let _;return t[11]!==u||t[12]!==h?(_=(0,g.jsx)(`sprite`,{scale:u,children:h}),t[11]=u,t[12]=h,t[13]=_):_=t[13],_}function T(e){o(Array.isArray(e)?e[0]:e)}function E(e){let t=(0,m.c)(29),{entity:n}=e,{visual:r}=n,o=(0,h.useRef)(null),c=(0,h.useRef)(null),u=(0,h.useRef)(null),d;t[0]===Symbol.for(`react.memo_cache_sentinel`)?(d=new l,t[0]=d):d=t[0];let w=(0,h.useRef)(d),T;t[1]===r.texture?T=t[2]:(T=p(r.texture),t[1]=r.texture,t[2]=T);let E=r.crossTexture??r.texture,O;t[3]===E?O=t[4]:(O=p(E),t[3]=E,t[4]=O);let k;t[5]!==T||t[6]!==O?(k=[T,O],t[5]=T,t[6]=O,t[7]=k):k=t[7];let A=f(k,D),j;t[8]===A?j=t[9]:(j=Array.isArray(A)?A:[A,A],t[8]=A,t[9]=j);let[M,N]=j,P;t[10]!==n.direction||t[11]!==n.keyframes?.[0]||t[12]!==r.crossSize||t[13]!==r.crossViewAng||t[14]!==r.renderCross||t[15]!==r.tracerLength||t[16]!==r.tracerWidth?(P=e=>{let{camera:t}=e,i=o.current,l=c.current;if(!i||!l)return;let d=n.keyframes?.[0],f=d?.position,p=n.direction??d?.velocity;if(!f||!p){i.visible=!1,u.current&&(u.current.visible=!1);return}if(a(p,_),_.lengthSq()<1e-8){i.visible=!1,u.current&&(u.current.visible=!1);return}_.normalize(),i.visible=!0,a(f,S),v.copy(S).sub(t.position),y.crossVectors(v,_),y.lengthSq()<1e-8&&(y.crossVectors(C,_),y.lengthSq()<1e-8&&y.set(1,0,0)),y.normalize().multiplyScalar(r.tracerWidth);let m=r.tracerLength*.5;b.copy(_).multiplyScalar(-m),x.copy(_).multiplyScalar(m);let h=l.array;h[0]=b.x+y.x,h[1]=b.y+y.y,h[2]=b.z+y.z,h[3]=b.x-y.x,h[4]=b.y-y.y,h[5]=b.z-y.z,h[6]=x.x-y.x,h[7]=x.y-y.y,h[8]=x.z-y.z,h[9]=x.x+y.x,h[10]=x.y+y.y,h[11]=x.z+y.z,l.needsUpdate=!0;let g=u.current;if(!g)return;if(!r.renderCross){g.visible=!1;return}v.normalize();let T=_.dot(v);if(T>-r.crossViewAng&&T<r.crossViewAng){g.visible=!1;return}g.visible=!0,s(_,w.current),g.quaternion.copy(w.current),g.scale.setScalar(r.crossSize)},t[10]=n.direction,t[11]=n.keyframes?.[0],t[12]=r.crossSize,t[13]=r.crossViewAng,t[14]=r.renderCross,t[15]=r.tracerLength,t[16]=r.tracerWidth,t[17]=P):P=t[17],i(P);let F;t[18]===Symbol.for(`react.memo_cache_sentinel`)?(F=(0,g.jsx)(`bufferAttribute`,{ref:c,attach:`attributes-position`,args:[new Float32Array(12),3]}),t[18]=F):F=t[18];let I;t[19]===Symbol.for(`react.memo_cache_sentinel`)?(I=(0,g.jsx)(`bufferAttribute`,{attach:`attributes-uv`,args:[new Float32Array([0,0,0,1,1,1,1,0]),2]}),t[19]=I):I=t[19];let L;t[20]===Symbol.for(`react.memo_cache_sentinel`)?(L=(0,g.jsxs)(`bufferGeometry`,{children:[F,I,(0,g.jsx)(`bufferAttribute`,{attach:`index`,args:[new Uint16Array([0,1,2,0,2,3]),1]})]}),t[20]=L):L=t[20];let R;t[21]===M?R=t[22]:(R=(0,g.jsxs)(`mesh`,{ref:o,children:[L,(0,g.jsx)(`meshBasicMaterial`,{map:M,transparent:!0,blending:2,side:2,depthWrite:!1,toneMapped:!1})]}),t[21]=M,t[22]=R);let z;t[23]!==N||t[24]!==r.renderCross?(z=r.renderCross?(0,g.jsxs)(`mesh`,{ref:u,children:[(0,g.jsxs)(`bufferGeometry`,{children:[(0,g.jsx)(`bufferAttribute`,{attach:`attributes-position`,args:[new Float32Array([-.5,0,-.5,.5,0,-.5,.5,0,.5,-.5,0,.5]),3]}),(0,g.jsx)(`bufferAttribute`,{attach:`attributes-uv`,args:[new Float32Array([0,0,0,1,1,1,1,0]),2]}),(0,g.jsx)(`bufferAttribute`,{attach:`index`,args:[new Uint16Array([0,1,2,0,2,3]),1]})]}),(0,g.jsx)(`meshBasicMaterial`,{map:N,transparent:!0,blending:2,side:2,depthWrite:!1,toneMapped:!1})]}):null,t[23]=N,t[24]=r.renderCross,t[25]=z):z=t[25];let B;return t[26]!==R||t[27]!==z?(B=(0,g.jsxs)(g.Fragment,{children:[R,z]}),t[26]=R,t[27]=z,t[28]=B):B=t[28],B}function D(e){let t=Array.isArray(e)?e:[e];for(let e of t)o(e)}export{w as SpriteProjectile,E as TracerProjectile};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
import{r as e}from"./chunk-DECur_0Z.js";import{r as t}from"./jsx-runtime-BpGWiA-R.js";import{a as n,o as r}from"./react-three-fiber.esm-dhSWjERg.js";import{Rt as i,zt as a}from"./three.module-CwgFV8Kd.js";var o=e(t()),s=e=>e===Object(e)&&!Array.isArray(e)&&typeof e!=`function`;function c(e,t){let c=r(e=>e.gl),l=n(a,s(e)?Object.values(e):e);return(0,o.useLayoutEffect)(()=>{t?.(l)},[t]),(0,o.useEffect)(()=>{if(`initTexture`in c){let e=[];Array.isArray(l)?e=l:l instanceof i?e=[l]:s(l)&&(e=Object.values(l)),e.forEach(e=>{e instanceof i&&c.initTexture(e)})}},[c,l]),(0,o.useMemo)(()=>{if(s(e)){let t={},n=0;for(let r in e)t[r]=l[n++];return t}else return l},[e,l])}c.preload=e=>n.preload(a,e),c.clear=e=>n.clear(a,e);export{c as t};
import{r as e}from"./chunk-DECur_0Z.js";import{r as t}from"./jsx-runtime-BpGWiA-R.js";import{a as n,o as r}from"./react-three-fiber.esm-CgPHUpXo.js";import{Rt as i,zt as a}from"./three.module-07hRbor4.js";var o=e(t()),s=e=>e===Object(e)&&!Array.isArray(e)&&typeof e!=`function`;function c(e,t){let c=r(e=>e.gl),l=n(a,s(e)?Object.values(e):e);return(0,o.useLayoutEffect)(()=>{t?.(l)},[t]),(0,o.useEffect)(()=>{if(`initTexture`in c){let e=[];Array.isArray(l)?e=l:l instanceof i?e=[l]:s(l)&&(e=Object.values(l)),e.forEach(e=>{e instanceof i&&c.initTexture(e)})}},[c,l]),(0,o.useMemo)(()=>{if(s(e)){let t={},n=0;for(let r in e)t[r]=l[n++];return t}else return l},[e,l])}c.preload=e=>n.preload(a,e),c.clear=e=>n.clear(a,e);export{c as t};

View file

@ -1 +0,0 @@
import{r as e}from"./chunk-DECur_0Z.js";import{n as t,r as n}from"./jsx-runtime-BpGWiA-R.js";import{i as r,o as i}from"./react-three-fiber.esm-dhSWjERg.js";import{r as a}from"./SettingsProvider-xrmxG700.js";import"./traditional-BTL5qX2E.js";import{o,r as s}from"./index-BMiY1uIn.js";var c=t(),l=e(n(),1),u=.004,d=2.5,f=.08,p=.15,m=.15;function h(){let e=(0,c.c)(17),{speedMultiplier:t,touchMode:n,invertDrag:h,invertJoystick:_}=a(),v=i(g),{moveState:y,lookState:b}=o(),x=s(),S=(0,l.useRef)(null),C;e[0]===Symbol.for(`react.memo_cache_sentinel`)?(C={x:0,y:0},e[0]=C):C=e[0];let w=(0,l.useRef)(C),T;e[1]===h?T=e[2]:(T=()=>h,e[1]=h,e[2]=T);let E=(0,l.useEffectEvent)(T),D=(0,l.useRef)(0),O=(0,l.useRef)(0),k;e[3]!==E||e[4]!==v.domElement||e[5]!==n?(k=()=>{if(n!==`moveLookStick`)return;let e=v.domElement,t=e=>{if(S.current===null)for(;0<e.changedTouches.length;){let t=e.changedTouches[0];S.current=t.identifier,w.current={x:t.clientX,y:t.clientY};break}},r=e=>{if(S.current!==null)for(let t=0;t<e.changedTouches.length;t++){let n=e.changedTouches[t];if(n.identifier===S.current){let e=n.clientX-w.current.x,t=n.clientY-w.current.y;w.current={x:n.clientX,y:n.clientY};let r=E()?1:-1;D.current+=r*e*u,O.current+=r*t*u;break}}},i=e=>{for(let t=0;t<e.changedTouches.length;t++)if(e.changedTouches[t].identifier===S.current){S.current=null;break}};return e.addEventListener(`touchstart`,t,{passive:!0}),e.addEventListener(`touchmove`,r,{passive:!0}),e.addEventListener(`touchend`,i,{passive:!0}),e.addEventListener(`touchcancel`,i,{passive:!0}),()=>{e.removeEventListener(`touchstart`,t),e.removeEventListener(`touchmove`,r),e.removeEventListener(`touchend`,i),e.removeEventListener(`touchcancel`,i),S.current=null}},e[3]=E,e[4]=v.domElement,e[5]=n,e[6]=k):k=e[6];let A;e[7]!==v.domElement||e[8]!==n?(A=[v.domElement,n],e[7]=v.domElement,e[8]=n,e[9]=A):A=e[9],(0,l.useEffect)(k,A);let j;return e[10]!==_||e[11]!==b.current||e[12]!==y.current||e[13]!==x||e[14]!==t||e[15]!==n?(j=(e,r)=>{let{force:i,angle:a}=y.current,{force:o,angle:s}=b.current,c=D.current,l=O.current;D.current=0,O.current=0;let u=0,h=0;if(n===`dualStick`){if(o>p){let e=(o-p)/(1-p),t=Math.cos(s),n=Math.sin(s),i=_?1:-1;c-=i*t*e*d*r,l+=i*n*e*d*r}if(i>f){let e=(i-f)/(1-f),n=Math.cos(a),r=Math.sin(a);u=Math.max(-1,Math.min(1,n*e*t)),h=Math.max(-1,Math.min(1,r*e*t))}}else if(n===`moveLookStick`&&i>0&&(h=Math.max(-1,Math.min(1,.5*t)),i>=m)){let e=Math.cos(a),t=Math.sin(a),n=(i-m)/(1-m),o=_?1:-1;c-=o*e*n*d*.5*r,l+=o*t*n*d*.5*r}!(c!==0||l!==0)&&!(u!==0||h!==0)||x({deltaYaw:c,deltaPitch:l,x:u,y:h,z:0,triggers:[],delta:r})},e[10]=_,e[11]=b.current,e[12]=y.current,e[13]=x,e[14]=t,e[15]=n,e[16]=j):j=e[16],r(j),null}function g(e){return e.gl}export{h as TouchHandler};

View file

@ -0,0 +1 @@
import{n as e}from"./jsx-runtime-BpGWiA-R.js";import{i as t}from"./react-three-fiber.esm-CgPHUpXo.js";import{r as n}from"./SettingsProvider-CCHVZuSg.js";import"./traditional-BTL5qX2E.js";import{n as r}from"./JoystickContext-YJ6eVLFP.js";import{f as i,h as a}from"./index-DBtsNu05.js";var o=e(),s=.004,c=2.5,l=.08,u=.15,d=.15;function f(){let e=(0,o.c)(9),{speedMultiplier:f,touchMode:p,invertDrag:m,invertJoystick:h}=n(),{moveState:g,lookState:_}=r(),v=a(),[,y]=i(),b;return e[0]!==y||e[1]!==m||e[2]!==h||e[3]!==_.current||e[4]!==g.current||e[5]!==v||e[6]!==f||e[7]!==p?(b=(e,t)=>{let{force:n,angle:r}=g.current,{force:i,angle:a}=_.current,o=y().touchLook,b=m?1:-1,x=0,S=0;p===`moveLookStick`&&o&&o.dragging&&(x=b*o.deltaX*s,S=b*o.deltaY*s);let C=0,w=0;if(p===`dualStick`){if(i>u){let e=(i-u)/(1-u),n=Math.cos(a),r=Math.sin(a),o=h?1:-1;x-=o*n*e*c*t,S+=o*r*e*c*t}if(n>l){let e=(n-l)/(1-l),t=Math.cos(r),i=Math.sin(r);C=Math.max(-1,Math.min(1,t*e*f)),w=Math.max(-1,Math.min(1,i*e*f))}}else if(p===`moveLookStick`&&n>0&&(w=Math.max(-1,Math.min(1,.5*f)),n>=d)){let e=Math.cos(r),i=Math.sin(r),a=(n-d)/(1-d),o=h?1:-1;x-=o*e*a*c*.5*t,S+=o*i*a*c*.5*t}!(x!==0||S!==0)&&!(C!==0||w!==0)||v({deltaYaw:x,deltaPitch:S,x:C,y:w,z:0,triggers:[],delta:t})},e[0]=y,e[1]=m,e[2]=h,e[3]=_.current,e[4]=g.current,e[5]=v,e[6]=f,e[7]=p,e[8]=b):b=e[8],t(b),null}export{f as TouchHandler};

View file

@ -1,2 +0,0 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/nipplejs-TUj5E3G4.js","assets/chunk-DECur_0Z.js"])))=>i.map(i=>d[i]);
import{r as e}from"./chunk-DECur_0Z.js";import{r as t,t as n}from"./jsx-runtime-BpGWiA-R.js";import{r}from"./SettingsProvider-xrmxG700.js";import{l as i,o as a}from"./index-BMiY1uIn.js";var o=e(t(),1),s={Joystick:`_Joystick_155b9_1`,Left:`_Left_155b9_11 _Joystick_155b9_1`,Right:`_Right_155b9_17 _Joystick_155b9_1`},c=n();function l(e){let t=e.querySelector(`.back`);t&&(t.style.background=`rgba(3, 79, 76, 0.6)`,t.style.border=`1px solid rgba(0, 219, 223, 0.5)`,t.style.boxShadow=`inset 0 0 10px rgba(0, 0, 0, 0.7)`);let n=e.querySelector(`.front`);n&&(n.style.background=`radial-gradient(circle at 50% 50%, rgba(23, 247, 198, 0.9) 0%, rgba(9, 184, 170, 0.95) 100%)`,n.style.border=`2px solid rgba(255, 255, 255, 0.4)`,n.style.boxShadow=`0 2px 4px rgba(0, 0, 0, 0.5), 0 1px 1px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15), inset 0 -1px 2px rgba(0, 0, 0, 0.3)`)}function u(){let{touchMode:t}=r(),[n,u]=(0,o.useState)(null),[d,f]=(0,o.useState)(null),{moveState:p,lookState:m,setMoveState:h,setLookState:g}=a();(0,o.useEffect)(()=>{if(!n)return;let t=null,r=!1;return i(()=>import(`./nipplejs-TUj5E3G4.js`).then(t=>e(t.default,1)).then(e=>{r||(t=e.default.create({zone:n,mode:`static`,position:{left:`70px`,bottom:`70px`},size:120,restOpacity:.9,dynamicPage:!0}),l(n),t.on(`move`,(e,t)=>{h({angle:t.angle.radian,force:Math.min(1,t.force)})}),t.on(`end`,()=>{h({force:0})}))}),__vite__mapDeps([0,1])),()=>{r=!0,t?.destroy()}},[p,n,h]),(0,o.useEffect)(()=>{if(!d)return;let t=null,n=!1;return i(()=>import(`./nipplejs-TUj5E3G4.js`).then(t=>e(t.default,1)).then(e=>{n||(t=e.default.create({zone:d,mode:`static`,position:{right:`70px`,bottom:`70px`},size:120,restOpacity:.9,dynamicPage:!0}),l(d),t.on(`move`,(e,t)=>{g({angle:t.angle.radian,force:Math.min(1,t.force)})}),t.on(`end`,()=>{g({force:0})}))}),__vite__mapDeps([0,1])),()=>{n=!0,t?.destroy()}},[m,d,g]);let _=()=>{document.activeElement instanceof HTMLElement&&document.activeElement.blur()};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(`div`,{ref:u,className:t===`dualStick`?s.Left:s.Joystick,onContextMenu:e=>e.preventDefault(),onTouchStart:_},t),t===`dualStick`?(0,c.jsx)(`div`,{ref:f,className:s.Right,onContextMenu:e=>e.preventDefault(),onTouchStart:_}):null]})}export{u as TouchJoystick};

View file

@ -0,0 +1,2 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/nipplejs-l2bgT5Z7.js","assets/chunk-DECur_0Z.js"])))=>i.map(i=>d[i]);
import{r as e}from"./chunk-DECur_0Z.js";import{r as t,t as n}from"./jsx-runtime-BpGWiA-R.js";import{r}from"./SettingsProvider-CCHVZuSg.js";import{t as i}from"./preload-helper-CwUjIIrH.js";import{n as a}from"./JoystickContext-YJ6eVLFP.js";var o=e(t(),1),s={Joystick:`_Joystick_155b9_1`,Left:`_Left_155b9_11 _Joystick_155b9_1`,Right:`_Right_155b9_17 _Joystick_155b9_1`},c=n();function l(e){let t=e.querySelector(`.back`);t&&(t.style.background=`rgba(3, 79, 76, 0.6)`,t.style.border=`1px solid rgba(0, 219, 223, 0.5)`,t.style.boxShadow=`inset 0 0 10px rgba(0, 0, 0, 0.7)`);let n=e.querySelector(`.front`);n&&(n.style.background=`radial-gradient(circle at 50% 50%, rgba(23, 247, 198, 0.9) 0%, rgba(9, 184, 170, 0.95) 100%)`,n.style.border=`2px solid rgba(255, 255, 255, 0.4)`,n.style.boxShadow=`0 2px 4px rgba(0, 0, 0, 0.5), 0 1px 1px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15), inset 0 -1px 2px rgba(0, 0, 0, 0.3)`)}function u(){let{touchMode:t}=r(),[n,u]=(0,o.useState)(null),[d,f]=(0,o.useState)(null),{moveState:p,lookState:m,setMoveState:h,setLookState:g}=a();(0,o.useEffect)(()=>{if(!n)return;let t=null,r=!1;return i(()=>import(`./nipplejs-l2bgT5Z7.js`).then(t=>e(t.default,1)).then(e=>{r||(t=e.default.create({zone:n,mode:`static`,position:{left:`70px`,bottom:`70px`},size:120,restOpacity:.9,dynamicPage:!0}),l(n),t.on(`move`,(e,t)=>{h({angle:t.angle.radian,force:Math.min(1,t.force)})}),t.on(`end`,()=>{h({force:0})}))}),__vite__mapDeps([0,1])),()=>{r=!0,t?.destroy()}},[p,n,h]),(0,o.useEffect)(()=>{if(!d)return;let t=null,n=!1;return i(()=>import(`./nipplejs-l2bgT5Z7.js`).then(t=>e(t.default,1)).then(e=>{n||(t=e.default.create({zone:d,mode:`static`,position:{right:`70px`,bottom:`70px`},size:120,restOpacity:.9,dynamicPage:!0}),l(d),t.on(`move`,(e,t)=>{g({angle:t.angle.radian,force:Math.min(1,t.force)})}),t.on(`end`,()=>{g({force:0})}))}),__vite__mapDeps([0,1])),()=>{n=!0,t?.destroy()}},[m,d,g]);let _=()=>{document.activeElement instanceof HTMLElement&&document.activeElement.blur()};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(`div`,{ref:u,className:t===`dualStick`?s.Left:s.Joystick,onContextMenu:e=>e.preventDefault(),onTouchStart:_},t),t===`dualStick`?(0,c.jsx)(`div`,{ref:f,className:s.Right,onContextMenu:e=>e.preventDefault(),onTouchStart:_}):null]})}export{u as TouchJoystick};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{i as e,n as t}from"./traditional-BTL5qX2E.js";function n(e,t=null){return{targets:e,categoryName:t,currentIndex:0,phase:`traveling`,elapsed:0,phaseDuration:0,curve:null,startPos:null,startQuat:null,orbitCenter:null,orbitRadius:null,orbitStartAngle:0}}var r=e(e=>({animation:null,flyTo(t){e({animation:n([t])})},startTour(t,r){t.length!==0&&e({animation:n(t,r)})},advanceTarget(){e(e=>e.animation?{animation:{...e.animation,currentIndex:e.animation.currentIndex+1,phase:`traveling`,elapsed:0,curve:null,startPos:null,startQuat:null,orbitCenter:null,orbitRadius:null,orbitStartAngle:0}}:e)},cancel(){e({animation:null})}}));function i(e,n){return t(r,e,n)}export{i as n,r as t};

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
import{t as e}from"./logger-z_EpIdIa.js";import{A as t,_ as n}from"./streamHelpers-Be29sBCp.js";var r=e(`demoTimelineScanner`),i=500,a=new Set([`msglegitkill`,`msgheadshotkill`,`msgteamkill`,`msgselfkill`,`msgexplosionkill`,`msgvehiclekill`,`msgvehiclecrash`,`msgvehiclespawnkill`,`msgturretkill`,`msgcturretkill`,`msgturretselfkill`,`msgoobkill`,`msgcampkill`,`msgrogueminekill`,`msglavakill`,`msglightningkill`]),o=new Set([`msgselfkill`,`msgturretselfkill`,`msgoobkill`,`msglavakill`,`msglightningkill`,`msgcampkill`]);function s(e,t){if(e.length>=2&&e.charCodeAt(0)===1){let n=parseInt(e.slice(1),10);if(Number.isFinite(n))return t.get(n)??e}return e}function c(e,t,r){let i=s(e,r);for(let e=0;e<t.length;e++){let a=`%${e+1}`;i.includes(a)&&(i=i.replaceAll(a,n(s(t[e],r))))}return i=i.replace(/%\d+/g,``),n(i)}async function l(e,l,u,d){let f=new t(new Uint8Array(e)),{initialBlock:p}=await f.load(),m=new Map;for(let[e,t]of p.taggedStrings)m.set(e,t);let h=f.getRegistry(),g=l?n(l).trim().toLowerCase():null,_=null,v=null;for(let e=0;e<p.demoValues.length;e++){if(p.demoValues[e]!==`readplayerinfo`)continue;let t=p.demoValues[e+1];if(t?.startsWith(`1 `)){let e=t.split(` `),n=parseInt(e[1],10);Number.isFinite(n)&&(_=n);break}}if(_!=null){let e=p.demoValues,t=parseInt(e[1]===`<BLANK>`?`0`:e[1]??`0`,10)||0;for(let n=0;n<t;n++){let t=(e[2+n]??``).split(` `);if(parseInt(t[2],10)===_){let e=parseInt(t[4],10);isNaN(e)||(v=e);break}}}let y=[],b=0,x=!1,S=0,C=f.blockCount;for(;!d?.aborted;){let e;try{e=f.nextBlock()}catch(e){r.warn(`Stopping scan at block %d due to read error: %o`,S,e);break}if(!e)break;if(S++,e.type===2){b++;continue}if(e.type!==0||!e.parsed)continue;let t=e.parsed;if(!t.events)continue;let l=32/1e3*b;for(let e of t.events)try{if(!e.parsedData)continue;let t=e.parsedData.type;if(t===`NetStringEvent`){let t=e.parsedData.id,n=e.parsedData.value;n!=null&&m.set(t,n);continue}let r=h.getEventParser(e.classId)?.name;if(t!==`RemoteCommandEvent`&&r!==`RemoteCommandEvent`||s(e.parsedData.funcName,m)!==`ServerMessage`)continue;let i=e.parsedData.args;if(!i||i.length<2)continue;let u=s(i[0],m).toLowerCase();if(u===`msgclientjointeam`&&_!=null&&i.length>=6&&parseInt(s(i[4],m),10)===_){let e=parseInt(s(i[5],m),10);isNaN(e)||(v=e)}if(u===`msgmissionstart`&&!x){x=!0,y.push({timeSec:l,type:`match-start`,description:`Match started`});continue}if(u===`msggameover`){y.push({timeSec:l,type:`match-end`,description:`Match ended`});continue}if(u===`msgctfflagcapped`&&i.length>=2){let e=c(i[1],i.slice(2),m),t=i.length>=3?n(s(i[2],m)).trim():void 0,r=i.length>=4?n(s(i[3],m)).trim():void 0,a=`neutral`;if(v!=null&&v>0&&i.length>=6){let e=parseInt(s(i[5],m),10);e===v?a=`friendly`:isNaN(e)||(a=`enemy`)}y.push({timeSec:l,type:`flag-cap`,description:e||`Flag captured`,teamAffinity:a,capturer:t,flagTeamName:r||void 0});continue}if(a.has(u)&&i.length>=6){if(o.has(u))continue;let e=n(s(i[5],m)).trim(),t=n(s(i[2],m)).trim(),r=i.length>=10?n(s(i[9],m)).trim():void 0;if(g&&e.toLowerCase()===g){let n=c(i[1],i.slice(2),m);y.push({timeSec:l,type:`kill`,description:n||`${e} got a kill`,killer:e,victim:t,weapon:r||void 0})}}}catch(e){r.warn(`Skipping malformed event in block %d: %o`,S,e)}S%i===0&&(C&&u&&u(Math.min(S/C,1)),await new Promise(e=>setTimeout(e,0)))}return r.info(`Scanned %d blocks, found %d events`,S,y.length),y}export{l as scanDemoTimeline};
import{t as e}from"./logger-z_EpIdIa.js";import{A as t,_ as n}from"./streamHelpers-AIec78DP.js";var r=e(`demoTimelineScanner`),i=500,a=new Set([`msglegitkill`,`msgheadshotkill`,`msgteamkill`,`msgselfkill`,`msgexplosionkill`,`msgvehiclekill`,`msgvehiclecrash`,`msgvehiclespawnkill`,`msgturretkill`,`msgcturretkill`,`msgturretselfkill`,`msgoobkill`,`msgcampkill`,`msgrogueminekill`,`msglavakill`,`msglightningkill`]),o=new Set([`msgselfkill`,`msgturretselfkill`,`msgoobkill`,`msglavakill`,`msglightningkill`,`msgcampkill`]);function s(e,t){if(e.length>=2&&e.charCodeAt(0)===1){let n=parseInt(e.slice(1),10);if(Number.isFinite(n))return t.get(n)??e}return e}function c(e,t,r){let i=s(e,r);for(let e=0;e<t.length;e++){let a=`%${e+1}`;i.includes(a)&&(i=i.replaceAll(a,n(s(t[e],r))))}return i=i.replace(/%\d+/g,``),n(i)}async function l(e,l,u,d){let f=new t(new Uint8Array(e)),{initialBlock:p}=await f.load(),m=new Map;for(let[e,t]of p.taggedStrings)m.set(e,t);let h=f.getRegistry(),g=l?n(l).trim().toLowerCase():null,_=null,v=null;for(let e=0;e<p.demoValues.length;e++){if(p.demoValues[e]!==`readplayerinfo`)continue;let t=p.demoValues[e+1];if(t?.startsWith(`1 `)){let e=t.split(` `),n=parseInt(e[1],10);Number.isFinite(n)&&(_=n);break}}if(_!=null){let e=p.demoValues,t=parseInt(e[1]===`<BLANK>`?`0`:e[1]??`0`,10)||0;for(let n=0;n<t;n++){let t=(e[2+n]??``).split(` `);if(parseInt(t[2],10)===_){let e=parseInt(t[4],10);isNaN(e)||(v=e);break}}}let y=[],b=0,x=!1,S=0,C=f.blockCount;for(;!d?.aborted;){let e;try{e=f.nextBlock()}catch(e){r.warn(`Stopping scan at block %d due to read error: %o`,S,e);break}if(!e)break;if(S++,e.type===2){b++;continue}if(e.type!==0||!e.parsed)continue;let t=e.parsed;if(!t.events)continue;let l=32/1e3*b;for(let e of t.events)try{if(!e.parsedData)continue;let t=e.parsedData.type;if(t===`NetStringEvent`){let t=e.parsedData.id,n=e.parsedData.value;n!=null&&m.set(t,n);continue}let r=h.getEventParser(e.classId)?.name;if(t!==`RemoteCommandEvent`&&r!==`RemoteCommandEvent`||s(e.parsedData.funcName,m)!==`ServerMessage`)continue;let i=e.parsedData.args;if(!i||i.length<2)continue;let u=s(i[0],m).toLowerCase();if(u===`msgclientjointeam`&&_!=null&&i.length>=6&&parseInt(s(i[4],m),10)===_){let e=parseInt(s(i[5],m),10);isNaN(e)||(v=e)}if(u===`msgmissionstart`&&!x){x=!0,y.push({timeSec:l,type:`match-start`,description:`Match started`});continue}if(u===`msggameover`){y.push({timeSec:l,type:`match-end`,description:`Match ended`});continue}if(u===`msgctfflagcapped`&&i.length>=2){let e=c(i[1],i.slice(2),m),t=i.length>=3?n(s(i[2],m)).trim():void 0,r=i.length>=4?n(s(i[3],m)).trim():void 0,a=`neutral`;if(v!=null&&v>0&&i.length>=6){let e=parseInt(s(i[5],m),10);e===v?a=`friendly`:isNaN(e)||(a=`enemy`)}y.push({timeSec:l,type:`flag-cap`,description:e||`Flag captured`,teamAffinity:a,capturer:t,flagTeamName:r||void 0});continue}if(a.has(u)&&i.length>=6){if(o.has(u))continue;let e=n(s(i[5],m)).trim(),t=n(s(i[2],m)).trim(),r=i.length>=10?n(s(i[9],m)).trim():void 0;if(g&&e.toLowerCase()===g){let n=c(i[1],i.slice(2),m);y.push({timeSec:l,type:`kill`,description:n||`${e} got a kill`,killer:e,victim:t,weapon:r||void 0})}}}catch(e){r.warn(`Skipping malformed event in block %d: %o`,S,e)}S%i===0&&(C&&u&&u(Math.min(S/C,1)),await new Promise(e=>setTimeout(e,0)))}return r.info(`Scanned %d blocks, found %d events`,S,y.length),y}export{l as scanDemoTimeline};

View file

@ -1 +0,0 @@
import{n as e}from"./jsx-runtime-BpGWiA-R.js";import{i as t,n}from"./traditional-BTL5qX2E.js";var r=e=>(t,n,r)=>{let i=r.subscribe;return r.subscribe=((e,t,n)=>{let a=e;if(t){let i=n?.equalityFn||Object.is,o=e(r.getState());a=n=>{let r=e(n);if(!i(o,r)){let e=o;t(o=r,e)}},n?.fireImmediately&&t(o,o)}return i(a)}),e(t,n,r)};function i(e){let t=new Map;for(let n of e.state.datablocks.values()){if(n._class!==`tsshapeconstructor`)continue;let e=n.baseshape;if(typeof e!=`string`)continue;let r=e.toLowerCase(),i=r.replace(/\.dts$/i,``)+`_`,a=new Map;for(let e=0;e<=127;e++){let t=n[`sequence${e}`];if(typeof t!=`string`)continue;let r=t.indexOf(` `);if(r===-1)continue;let o=t.slice(0,r).toLowerCase(),s=t.slice(r+1).trim().toLowerCase();if(!s||!o.startsWith(i)||!o.endsWith(`.dsq`))continue;let c=o.slice(i.length,-4);c&&a.set(s,c)}a.size>0&&t.set(r,a)}return t}function a(e,t,n){let r=new Map;for(let n of e){let e=t.clipAction(n);r.set(n.name.toLowerCase(),e)}if(n)for(let[e,t]of n){let n=r.get(t);n&&!r.has(e)&&r.set(e,n)}return r}e();function o(e){return e.toLowerCase()}function s(e){let t=o(e.trim());return t.startsWith(`$`)?t.slice(1):t}function c(e,t,n){return e<t?t:e>n?n:e}function l(e){let t={},n={},r={},i={};for(let n of e.state.objectsById.values())t[n._id]=0,n._name&&(r[o(n._name)]=n._id,n._isDatablock&&(i[o(n._name)]=n._id));for(let t of e.state.globals.keys())n[s(t)]=0;return{objectVersionById:t,globalVersionByName:n,objectIdsByName:r,datablockIdsByName:i}}var u={runtime:{runtime:null,sequenceAliases:new Map,objectVersionById:{},globalVersionByName:{},objectIdsByName:{},datablockIdsByName:{},lastRuntimeTick:0},playback:{recording:null,status:`stopped`,timeMs:0,rate:1,durationMs:0,streamSnapshot:null}},d=t()(r(e=>({...u,setRuntime(t){let n=l(t),r=i(t);e(e=>({...e,runtime:{runtime:t,sequenceAliases:r,objectVersionById:n.objectVersionById,globalVersionByName:n.globalVersionByName,objectIdsByName:n.objectIdsByName,datablockIdsByName:n.datablockIdsByName,lastRuntimeTick:0}}))},clearRuntime(){e(e=>({...e,runtime:{runtime:null,sequenceAliases:new Map,objectVersionById:{},globalVersionByName:{},objectIdsByName:{},datablockIdsByName:{},lastRuntimeTick:0}}))},applyRuntimeBatch(t,n){t.length!==0&&e(e=>{let r={...e.runtime.objectVersionById},i={...e.runtime.globalVersionByName},a={...e.runtime.objectIdsByName},c={...e.runtime.datablockIdsByName},l=e=>{e!=null&&(r[e]=(r[e]??0)+1)};for(let e of t){if(e.type===`object.created`){let t=e.object;if(l(e.objectId),t._name){let n=o(t._name);a[n]=e.objectId,t._isDatablock&&(c[n]=e.objectId)}l(t._parent?._id);continue}if(e.type===`object.deleted`){let t=e.object;if(delete r[e.objectId],t?._name){let e=o(t._name);delete a[e],t._isDatablock&&delete c[e]}l(t?._parent?._id);continue}if(e.type===`field.changed`){l(e.objectId);continue}if(e.type===`global.changed`){let t=s(e.name);i[t]=(i[t]??0)+1;continue}}let u=n?.tick??(e.runtime.lastRuntimeTick>0?e.runtime.lastRuntimeTick+1:1);return{...e,runtime:{...e.runtime,objectVersionById:r,globalVersionByName:i,objectIdsByName:a,datablockIdsByName:c,lastRuntimeTick:u}}})},setRecording(t){let n=Math.max(0,(t?.duration??0)*1e3);e(e=>({...e,playback:{recording:t,status:t?`stopped`:e.playback.status,timeMs:t?0:e.playback.timeMs,rate:t?1:e.playback.rate,durationMs:n,streamSnapshot:t?null:e.playback.streamSnapshot}}))},setPlaybackTime(t){e(e=>{let n=c(t,0,e.playback.durationMs);return{...e,playback:{...e.playback,timeMs:n}}})},setPlaybackStatus(t){e(e=>({...e,playback:{...e.playback,status:t}}))},setPlaybackRate(t){let n=Number.isFinite(t)?c(t,.01,16):1;e(e=>({...e,playback:{...e.playback,rate:n}}))},setPlaybackStreamSnapshot(t){e(e=>({...e,playback:{...e.playback,streamSnapshot:t}}))}}))),f=0;function p(){return f}function m(e,t){f+=e*t*1e3}function h(){f=0}d.subscribe(e=>e.playback.status,e=>{e===`stopped`&&h()});function g(){return d}function _(e,t){return n(d,e,t)}export{g as a,_ as i,p as n,a as o,d as r,r as s,m as t};

View file

@ -0,0 +1 @@
import{n as e}from"./jsx-runtime-BpGWiA-R.js";import{i as t,n}from"./traditional-BTL5qX2E.js";import{t as r}from"./middleware-DPacZrFu.js";function i(e){let t=new Map;for(let n of e.state.datablocks.values()){if(n._class!==`tsshapeconstructor`)continue;let e=n.baseshape;if(typeof e!=`string`)continue;let r=e.toLowerCase(),i=r.replace(/\.dts$/i,``)+`_`,a=new Map;for(let e=0;e<=127;e++){let t=n[`sequence${e}`];if(typeof t!=`string`)continue;let r=t.indexOf(` `);if(r===-1)continue;let o=t.slice(0,r).toLowerCase(),s=t.slice(r+1).trim().toLowerCase();if(!s||!o.startsWith(i)||!o.endsWith(`.dsq`))continue;let c=o.slice(i.length,-4);c&&a.set(s,c)}a.size>0&&t.set(r,a)}return t}function a(e,t,n){let r=new Map;for(let n of e){let e=t.clipAction(n);r.set(n.name.toLowerCase(),e)}if(n)for(let[e,t]of n){let n=r.get(t);n&&!r.has(e)&&r.set(e,n)}return r}e();function o(e){return e.toLowerCase()}function s(e){let t=o(e.trim());return t.startsWith(`$`)?t.slice(1):t}function c(e,t,n){return e<t?t:e>n?n:e}function l(e){let t={},n={},r={},i={};for(let n of e.state.objectsById.values())t[n._id]=0,n._name&&(r[o(n._name)]=n._id,n._isDatablock&&(i[o(n._name)]=n._id));for(let t of e.state.globals.keys())n[s(t)]=0;return{objectVersionById:t,globalVersionByName:n,objectIdsByName:r,datablockIdsByName:i}}var u={runtime:{runtime:null,sequenceAliases:new Map,objectVersionById:{},globalVersionByName:{},objectIdsByName:{},datablockIdsByName:{},lastRuntimeTick:0},playback:{recording:null,status:`stopped`,timeMs:0,rate:1,durationMs:0,streamSnapshot:null}},d=t()(r(e=>({...u,setRuntime(t){let n=l(t),r=i(t);e(e=>({...e,runtime:{runtime:t,sequenceAliases:r,objectVersionById:n.objectVersionById,globalVersionByName:n.globalVersionByName,objectIdsByName:n.objectIdsByName,datablockIdsByName:n.datablockIdsByName,lastRuntimeTick:0}}))},clearRuntime(){e(e=>({...e,runtime:{runtime:null,sequenceAliases:new Map,objectVersionById:{},globalVersionByName:{},objectIdsByName:{},datablockIdsByName:{},lastRuntimeTick:0}}))},applyRuntimeBatch(t,n){t.length!==0&&e(e=>{let r={...e.runtime.objectVersionById},i={...e.runtime.globalVersionByName},a={...e.runtime.objectIdsByName},c={...e.runtime.datablockIdsByName},l=e=>{e!=null&&(r[e]=(r[e]??0)+1)};for(let e of t){if(e.type===`object.created`){let t=e.object;if(l(e.objectId),t._name){let n=o(t._name);a[n]=e.objectId,t._isDatablock&&(c[n]=e.objectId)}l(t._parent?._id);continue}if(e.type===`object.deleted`){let t=e.object;if(delete r[e.objectId],t?._name){let e=o(t._name);delete a[e],t._isDatablock&&delete c[e]}l(t?._parent?._id);continue}if(e.type===`field.changed`){l(e.objectId);continue}if(e.type===`global.changed`){let t=s(e.name);i[t]=(i[t]??0)+1;continue}}let u=n?.tick??(e.runtime.lastRuntimeTick>0?e.runtime.lastRuntimeTick+1:1);return{...e,runtime:{...e.runtime,objectVersionById:r,globalVersionByName:i,objectIdsByName:a,datablockIdsByName:c,lastRuntimeTick:u}}})},setRecording(t){let n=Math.max(0,(t?.duration??0)*1e3);e(e=>({...e,playback:{recording:t,status:t?`stopped`:e.playback.status,timeMs:t?0:e.playback.timeMs,rate:t?1:e.playback.rate,durationMs:n,streamSnapshot:t?null:e.playback.streamSnapshot}}))},setPlaybackTime(t){e(e=>{let n=c(t,0,e.playback.durationMs);return{...e,playback:{...e.playback,timeMs:n}}})},setPlaybackStatus(t){e(e=>({...e,playback:{...e.playback,status:t}}))},setPlaybackRate(t){let n=Number.isFinite(t)?c(t,.01,16):1;e(e=>({...e,playback:{...e.playback,rate:n}}))},setPlaybackStreamSnapshot(t){e(e=>({...e,playback:{...e.playback,streamSnapshot:t}}))}}))),f=0;function p(){return f}function m(e,t){f+=e*t*1e3}function h(){f=0}d.subscribe(e=>e.playback.status,e=>{e===`stopped`&&h()});function g(){return d}function _(e,t){return n(d,e,t)}export{g as a,_ as i,p as n,a as o,d as r,m as t};

View file

@ -1,4 +1,4 @@
import{o as e}from"./react-three-fiber.esm-dhSWjERg.js";import{C as t,Dt as n,Et as r,K as i,N as a,Ot as o,Rt as s,Vt as c,q as l,t as u}from"./three.module-CwgFV8Kd.js";var d=new a,f=new Map;function p(e,t){let n=f.get(e);if(n)return t&&n.image&&t(n),n;let r=new s;return r.flipY=!1,f.set(e,r),d.load(e,e=>{r.image=e,r.needsUpdate=!0,t?.(r)}),r}function m(e){let t=f.get(e);return t?t.image?Promise.resolve(t):new Promise(e=>{let n=()=>{t.image?e(t):setTimeout(n,16)};n()}):new Promise((t,n)=>{let r=new s;r.flipY=!1,f.set(e,r),d.load(e,e=>{r.image=e,r.needsUpdate=!0,t(r)},void 0,n)})}function h(e,t={}){let{repeat:r=[1,1],disableMipmaps:a=!1,anisotropy:s}=t;return e.wrapS=e.wrapT=n,e.colorSpace=o,e.repeat.set(...r),e.flipY=!1,e.anisotropy=s??1,a?(e.generateMipmaps=!1,e.minFilter=i):(e.generateMipmaps=!0,e.minFilter=l),e.magFilter=i,e.needsUpdate=!0,e}function g(e){let a=new t(e,256,256,r,c);return a.colorSpace=``,a.wrapS=a.wrapT=n,a.generateMipmaps=!1,a.minFilter=i,a.magFilter=i,a.needsUpdate=!0,a}function _(){return e(v)}function v(e){return e.gl.capabilities.getMaxAnisotropy()}var y=`
import{o as e}from"./react-three-fiber.esm-CgPHUpXo.js";import{C as t,Dt as n,Et as r,K as i,N as a,Ot as o,Rt as s,Vt as c,q as l,t as u}from"./three.module-07hRbor4.js";var d=new a,f=new Map;function p(e,t){let n=f.get(e);if(n)return t&&n.image&&t(n),n;let r=new s;return r.flipY=!1,f.set(e,r),d.load(e,e=>{r.image=e,r.needsUpdate=!0,t?.(r)}),r}function m(e){let t=f.get(e);return t?t.image?Promise.resolve(t):new Promise(e=>{let n=()=>{t.image?e(t):setTimeout(n,16)};n()}):new Promise((t,n)=>{let r=new s;r.flipY=!1,f.set(e,r),d.load(e,e=>{r.image=e,r.needsUpdate=!0,t(r)},void 0,n)})}function h(e,t={}){let{repeat:r=[1,1],disableMipmaps:a=!1,anisotropy:s}=t;return e.wrapS=e.wrapT=n,e.colorSpace=o,e.repeat.set(...r),e.flipY=!1,e.anisotropy=s??1,a?(e.generateMipmaps=!1,e.minFilter=i):(e.generateMipmaps=!0,e.minFilter=l),e.magFilter=i,e.needsUpdate=!0,e}function g(e){let a=new t(e,256,256,r,c);return a.colorSpace=``,a.wrapS=a.wrapT=n,a.generateMipmaps=!1,a.minFilter=i,a.magFilter=i,a.needsUpdate=!0,a}function _(){return e(v)}function v(e){return e.gl.capabilities.getMaxAnisotropy()}var y=`
#ifdef USE_FOG
// Check fog enabled uniform - allows toggling without shader recompilation
#ifdef USE_VOLUMETRIC_FOG

View file

@ -0,0 +1 @@
import{t as e}from"./iconBase-BCRUFbxq.js";function t(t){return e({tag:`svg`,attr:{viewBox:`0 0 24 24`},child:[{tag:`path`,attr:{fill:`none`,strokeWidth:`2`,d:`M3,22.0000002 L21,12 L3,2 L3,22.0000002 Z M5,19 L17.5999998,11.9999999 L5,5 L5,19 Z M7,16 L14.1999999,12 L7,8 L7,16 Z M9,13 L10.8,12 L9,11 L9,13 Z`},child:[]}]})(t)}function n(t){return e({tag:`svg`,attr:{viewBox:`0 0 24 24`},child:[{tag:`path`,attr:{fill:`none`,strokeWidth:`2`,d:`M3,21 L9,21 L9,3 L3,3 L3,21 Z M4,19 L8,19 L8,5 L4,5 L4,19 Z M5,17 L7,17 L7,7 L5,7 L5,17 Z M15,21 L21,21 L21,3 L15,3 L15,21 Z M16,19 L20,19 L20,5 L16,5 L16,19 Z M17,17 L19,17 L19,7 L17,7 L17,17 Z`},child:[]}]})(t)}export{t as n,n as t};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
import{c as e,n as t,s as n,t as r}from"./manifest-CirqV3Ls.js";import{i}from"./stringUtils-EmGsjr9D.js";import{t as a}from"./logger-z_EpIdIa.js";import{a as o}from"./mission-C2iDKeMo.js";function s(e){return e.split(/(?:\r\n|\r|\n)/g).map(e=>e.trim()).filter(Boolean).filter(e=>!e.startsWith(`;`)).map(e=>{let t=e.match(/^(.+)\s(\d+)$/);if(t){let e=parseInt(t[2],10);return{name:t[1],frameCount:e}}else return{name:e,frameCount:1}})}var c=256;function l(e){let t=new DataView(e),n=0,r=t.getUint8(n++),i=new Uint16Array(c*c),a=[],o=e=>{let r=``;for(let i=0;i<e;i++){let e=t.getUint8(n+i);if(e===0)break;r+=String.fromCharCode(e)}return n+=e,r};for(let e=0;e<c*c;e++){let r=t.getUint16(n,!0);n+=2,i[e]=r}n+=256*256;let s=i;for(let e=0;e<8;e++){let r=t.getUint8(n++),i=o(r);e<6&&r>0&&a.push(i)}let l=[];for(let e of a){let e=new Uint8Array(c*c);for(let r=0;r<c*c;r++)e[r]=t.getUint8(n++);l.push(e)}return{version:r,textureNames:a,heightMap:s,alphaMaps:l}}var u=a(`loaders`),d=`/t2-mapper`,f=`${d}/base/`,p=`${d}/magenta.png`;function m(e,t){let i;try{i=r(e)}catch(n){if(t)return u.warn(`Resource "%s" not found — rendering fallback`,e),t;throw n}let[a,o]=n(i);return a?`${f}@vl2/${a}/${o}`:`${f}${o}`}function h(e){return m(`interiors/${e}`).replace(/\.dif$/i,`.glb`)}function g(e){return m(`shapes/${e}`).replace(/\.dts$/i,`.glb`)}function _(t){return t=t.replace(/^terrain\./,``),m(e(`textures/terrain/${t}`),p)}function v(t,n){let r=i(n).split(`/`);return m(e(`${r.length>1?r.slice(0,-1).join(`/`)+`/`:``}${t}`),p)}function y(t){return m(e(`textures/${t}`),p)}function b(e){return m(`audio/${e}`).replace(/\.wav$/i,`.m4a`)}async function x(e){let t=m(`textures/${e}`);return(await(await fetch(t)).text()).split(/(?:\r\n|\r|\n)/).map(e=>{if(e=e.trim(),!e.startsWith(`;`))return e}).filter(Boolean)}async function S(e){let n=t(e),r=await(await fetch(m(n.resourcePath))).arrayBuffer(),i;try{i=new TextDecoder(`utf-8`,{fatal:!0}).decode(r)}catch{i=new TextDecoder(`windows-1252`).decode(r)}return i=i.replaceAll(`<EFBFBD>`,`'`),o(i)}async function C(e){let t=m(`terrains/${e}`);u.debug(`Fetching terrain: %s`,t);let n=await fetch(t);if(!n.ok)throw Error(`[loadTerrain] Failed to fetch ${t}: ${n.status} ${n.statusText}`);let r=await n.arrayBuffer();return u.debug(`Loaded terrain %s: %d bytes`,e,r.byteLength),l(r)}async function w(e){let t=m(e);return s(await(await fetch(t)).text())}export{v as a,w as c,g as d,_ as f,m as i,S as l,f as n,h as o,y as p,b as r,x as s,p as t,C as u};
import{c as e,n as t,s as n,t as r}from"./manifest-CirqV3Ls.js";import{i}from"./stringUtils-EmGsjr9D.js";import{t as a}from"./logger-z_EpIdIa.js";import{a as o}from"./mission-D8vr00S1.js";function s(e){return e.split(/(?:\r\n|\r|\n)/g).map(e=>e.trim()).filter(Boolean).filter(e=>!e.startsWith(`;`)).map(e=>{let t=e.match(/^(.+)\s(\d+)$/);if(t){let e=parseInt(t[2],10);return{name:t[1],frameCount:e}}else return{name:e,frameCount:1}})}var c=256;function l(e){let t=new DataView(e),n=0,r=t.getUint8(n++),i=new Uint16Array(c*c),a=[],o=e=>{let r=``;for(let i=0;i<e;i++){let e=t.getUint8(n+i);if(e===0)break;r+=String.fromCharCode(e)}return n+=e,r};for(let e=0;e<c*c;e++){let r=t.getUint16(n,!0);n+=2,i[e]=r}n+=256*256;let s=i;for(let e=0;e<8;e++){let r=t.getUint8(n++),i=o(r);e<6&&r>0&&a.push(i)}let l=[];for(let e of a){let e=new Uint8Array(c*c);for(let r=0;r<c*c;r++)e[r]=t.getUint8(n++);l.push(e)}return{version:r,textureNames:a,heightMap:s,alphaMaps:l}}var u=a(`loaders`),d=`/t2-mapper`,f=`${d}/base/`,p=`${d}/magenta.png`;function m(e,t){let i;try{i=r(e)}catch(n){if(t)return u.warn(`Resource "%s" not found — rendering fallback`,e),t;throw n}let[a,o]=n(i);return a?`${f}@vl2/${a}/${o}`:`${f}${o}`}function h(e){return m(`interiors/${e}`).replace(/\.dif$/i,`.glb`)}function g(e){return m(`shapes/${e}`).replace(/\.dts$/i,`.glb`)}function _(t){return t=t.replace(/^terrain\./,``),m(e(`textures/terrain/${t}`),p)}function v(t,n){let r=i(n).split(`/`);return m(e(`${r.length>1?r.slice(0,-1).join(`/`)+`/`:``}${t}`),p)}function y(t){return m(e(`textures/${t}`),p)}function b(e){return m(`audio/${e}`).replace(/\.wav$/i,`.m4a`)}async function x(e){let t=m(`textures/${e}`);return(await(await fetch(t)).text()).split(/(?:\r\n|\r|\n)/).map(e=>{if(e=e.trim(),!e.startsWith(`;`))return e}).filter(Boolean)}async function S(e){let n=t(e),r=await(await fetch(m(n.resourcePath))).arrayBuffer(),i;try{i=new TextDecoder(`utf-8`,{fatal:!0}).decode(r)}catch{i=new TextDecoder(`windows-1252`).decode(r)}return i=i.replaceAll(`<EFBFBD>`,`'`),o(i)}async function C(e){let t=m(`terrains/${e}`);u.debug(`Fetching terrain: %s`,t);let n=await fetch(t);if(!n.ok)throw Error(`[loadTerrain] Failed to fetch ${t}: ${n.status} ${n.statusText}`);let r=await n.arrayBuffer();return u.debug(`Loaded terrain %s: %d bytes`,e,r.byteLength),l(r)}async function w(e){let t=m(e);return s(await(await fetch(t)).text())}export{v as a,w as c,g as d,_ as f,m as i,S as l,f as n,h as o,y as p,b as r,x as s,p as t,C as u};

View file

@ -0,0 +1 @@
var e=e=>(t,n,r)=>{let i=r.subscribe;return r.subscribe=((e,t,n)=>{let a=e;if(t){let i=n?.equalityFn||Object.is,o=e(r.getState());a=n=>{let r=e(n);if(!i(o,r)){let e=o;t(o=r,e)}},n?.fireImmediately&&t(o,o)}return i(a)}),e(t,n,r)};export{e as t};

View file

@ -0,0 +1 @@
var e=`modulepreload`,t=function(e){return`/t2-mapper/`+e},n={},r=function(r,i,a){let o=Promise.resolve();if(i&&i.length>0){let r=document.getElementsByTagName(`link`),s=document.querySelector(`meta[property=csp-nonce]`),c=s?.nonce||s?.getAttribute(`nonce`);function l(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}o=l(i.map(i=>{if(i=t(i,a),i in n)return;n[i]=!0;let o=i.endsWith(`.css`),s=o?`[rel="stylesheet"]`:``;if(a)for(let e=r.length-1;e>=0;e--){let t=r[e];if(t.href===i&&(!o||t.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${i}"]${s}`))return;let l=document.createElement(`link`);if(l.rel=o?`stylesheet`:e,o||(l.as=`script`),l.crossOrigin=``,l.href=i,c&&l.setAttribute(`nonce`,c),document.head.appendChild(l),o)return new Promise((e,t)=>{l.addEventListener(`load`,e),l.addEventListener(`error`,()=>t(Error(`Unable to preload CSS for ${i}`)))})}))}function s(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return o.then(e=>{for(let t of e||[])t.status===`rejected`&&s(t.reason);return r().catch(s)})};export{r as t};

View file

@ -1 +1 @@
import{t as e}from"./logger-z_EpIdIa.js";import{St as t,nt as n}from"./three.module-CwgFV8Kd.js";var r=e(`ghostToScene`);function i(e,t={x:0,y:0,z:0}){return e&&typeof e==`object`&&`x`in e?e:t}function a(e,t={r:0,g:0,b:0}){return e&&typeof e==`object`&&`r`in e?e:t}function o(e,t={r:.5,g:.5,b:.5,a:1}){return e&&typeof e==`object`&&`r`in e?e:t}function s(e){if(e&&typeof e==`object`&&`elements`in e&&Array.isArray(e.elements))return e;if(e&&typeof e==`object`&&`position`in e&&`rotation`in e){let{position:t,rotation:n}=e,r=n.x*n.x,i=n.y*n.y,a=n.z*n.z,o=n.x*n.y,s=n.x*n.z,c=n.y*n.z,l=n.w*n.x,u=n.w*n.y,d=n.w*n.z;return{elements:[1-2*(i+a),2*(o+d),2*(s-u),0,2*(o-d),1-2*(r+a),2*(c+l),0,2*(s+u),2*(c-l),1-2*(r+i),0,t.x,t.y,t.z,1],position:{x:t.x,y:t.y,z:t.z}}}return{elements:[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],position:{x:0,y:0,z:0}}}function c(e,t){return{className:`TerrainBlock`,ghostIndex:e,terrFileName:t.terrFileName??``,detailTextureName:t.detailTextureName??``,squareSize:t.squareSize??8,emptySquareRuns:t.emptySquareRuns}}function l(e,t){return{className:`InteriorInstance`,ghostIndex:e,interiorFile:t.interiorFile??``,transform:s(t.transform),scale:i(t.scale,{x:1,y:1,z:1}),showTerrainInside:t.showTerrainInside??!1,skinBase:t.skinBase??``,alarmState:t.alarmState??!1}}function u(e,t){return{className:`TSStatic`,ghostIndex:e,shapeName:t.shapeName??``,transform:s(t.transform),scale:i(t.scale,{x:1,y:1,z:1})}}function d(e,t){let n=Array.isArray(t.fogVolumes)?t.fogVolumes.map(e=>({visibleDistance:e.visibleDistance??0,minHeight:e.minHeight??0,maxHeight:e.maxHeight??0,color:a(e.color)})):[],r=Array.isArray(t.cloudLayers)?t.cloudLayers.map(e=>({texture:e.texture??``,heightPercent:e.heightPercent??0,speed:e.speed??0})):[];return{className:`Sky`,ghostIndex:e,materialList:t.materialList??``,fogColor:a(t.fogColor),visibleDistance:t.visibleDistance??1e3,fogDistance:t.fogDistance??0,skySolidColor:a(t.skySolidColor),useSkyTextures:t.useSkyTextures??!0,fogVolumes:n,cloudLayers:r,windVelocity:i(t.windVelocity)}}function f(e,t){return{className:`Sun`,ghostIndex:e,direction:i(t.direction,{x:.57735,y:.57735,z:-.57735}),color:o(t.color,{r:.7,g:.7,b:.7,a:1}),ambient:o(t.ambient,{r:.5,g:.5,b:.5,a:1}),textures:Array.isArray(t.textures)?t.textures:void 0}}function p(e,t){return{className:`MissionArea`,ghostIndex:e,area:t.area??{x:-512,y:-512,w:1024,h:1024},flightCeiling:t.flightCeiling??2e3,flightCeilingRange:t.flightCeilingRange??50}}function m(e,t){return{className:`WaterBlock`,ghostIndex:e,transform:s(t.transform),scale:i(t.scale,{x:1,y:1,z:1}),surfaceName:t.surfaceName??``,envMapName:t.envMapName??``,surfaceOpacity:t.surfaceOpacity??.75,waveMagnitude:t.waveMagnitude??1,envMapIntensity:t.envMapIntensity??1}}function h(e,t,n){let i;switch(e){case`TerrainBlock`:return i=c(t,n),r.debug(`TerrainBlock #%d: terrFileName=%s`,t,i.terrFileName),i;case`InteriorInstance`:return i=l(t,n),r.debug(`InteriorInstance #%d: interiorFile=%s`,t,i.interiorFile),i;case`TSStatic`:return u(t,n);case`Sky`:{i=d(t,n);let e=i;return r.debug(`Sky #%d: materialList=%s fogColor=(%s, %s, %s) visibleDist=%d fogDist=%d useSkyTextures=%s`,t,e.materialList,e.fogColor.r.toFixed(3),e.fogColor.g.toFixed(3),e.fogColor.b.toFixed(3),e.visibleDistance,e.fogDistance,e.useSkyTextures),i}case`Sun`:{i=f(t,n);let e=i;return r.debug(`Sun #%d: dir=(%s, %s, %s) color=(%s, %s, %s) ambient=(%s, %s, %s)`,t,e.direction.x.toFixed(3),e.direction.y.toFixed(3),e.direction.z.toFixed(3),e.color.r.toFixed(3),e.color.g.toFixed(3),e.color.b.toFixed(3),e.ambient.r.toFixed(3),e.ambient.g.toFixed(3),e.ambient.b.toFixed(3)),i}case`MissionArea`:return p(t,n);case`WaterBlock`:return m(t,n);default:return null}}function g(e){return[e.y,e.z,e.x]}function _(e){return[e.y,e.z,e.x]}function v(e){let r=e.elements,i=new n,a=i.elements;a[0]=r[5],a[1]=r[6],a[2]=r[4],a[3]=0,a[4]=r[9],a[5]=r[10],a[6]=r[8],a[7]=0,a[8]=r[1],a[9]=r[2],a[10]=r[0],a[11]=0,a[12]=0,a[13]=0,a[14]=0,a[15]=1;let o=new t;return o.setFromRotationMatrix(i),o.conjugate(),o}export{h as i,_ as n,g as r,v as t};
import{t as e}from"./logger-z_EpIdIa.js";import{St as t,nt as n}from"./three.module-07hRbor4.js";var r=e(`ghostToScene`);function i(e,t={x:0,y:0,z:0}){return e&&typeof e==`object`&&`x`in e?e:t}function a(e,t={r:0,g:0,b:0}){return e&&typeof e==`object`&&`r`in e?e:t}function o(e,t={r:.5,g:.5,b:.5,a:1}){return e&&typeof e==`object`&&`r`in e?e:t}function s(e){if(e&&typeof e==`object`&&`elements`in e&&Array.isArray(e.elements))return e;if(e&&typeof e==`object`&&`position`in e&&`rotation`in e){let{position:t,rotation:n}=e,r=n.x*n.x,i=n.y*n.y,a=n.z*n.z,o=n.x*n.y,s=n.x*n.z,c=n.y*n.z,l=n.w*n.x,u=n.w*n.y,d=n.w*n.z;return{elements:[1-2*(i+a),2*(o+d),2*(s-u),0,2*(o-d),1-2*(r+a),2*(c+l),0,2*(s+u),2*(c-l),1-2*(r+i),0,t.x,t.y,t.z,1],position:{x:t.x,y:t.y,z:t.z}}}return{elements:[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],position:{x:0,y:0,z:0}}}function c(e,t){return{className:`TerrainBlock`,ghostIndex:e,terrFileName:t.terrFileName??``,detailTextureName:t.detailTextureName??``,squareSize:t.squareSize??8,emptySquareRuns:t.emptySquareRuns}}function l(e,t){return{className:`InteriorInstance`,ghostIndex:e,interiorFile:t.interiorFile??``,transform:s(t.transform),scale:i(t.scale,{x:1,y:1,z:1}),showTerrainInside:t.showTerrainInside??!1,skinBase:t.skinBase??``,alarmState:t.alarmState??!1}}function u(e,t){return{className:`TSStatic`,ghostIndex:e,shapeName:t.shapeName??``,transform:s(t.transform),scale:i(t.scale,{x:1,y:1,z:1})}}function d(e,t){let n=Array.isArray(t.fogVolumes)?t.fogVolumes.map(e=>({visibleDistance:e.visibleDistance??0,minHeight:e.minHeight??0,maxHeight:e.maxHeight??0,color:a(e.color)})):[],r=Array.isArray(t.cloudLayers)?t.cloudLayers.map(e=>({texture:e.texture??``,heightPercent:e.heightPercent??0,speed:e.speed??0})):[];return{className:`Sky`,ghostIndex:e,materialList:t.materialList??``,fogColor:a(t.fogColor),visibleDistance:t.visibleDistance??1e3,fogDistance:t.fogDistance??0,skySolidColor:a(t.skySolidColor),useSkyTextures:t.useSkyTextures??!0,fogVolumes:n,cloudLayers:r,windVelocity:i(t.windVelocity)}}function f(e,t){return{className:`Sun`,ghostIndex:e,direction:i(t.direction,{x:.57735,y:.57735,z:-.57735}),color:o(t.color,{r:.7,g:.7,b:.7,a:1}),ambient:o(t.ambient,{r:.5,g:.5,b:.5,a:1}),textures:Array.isArray(t.textures)?t.textures:void 0}}function p(e,t){return{className:`MissionArea`,ghostIndex:e,area:t.area??{x:-512,y:-512,w:1024,h:1024},flightCeiling:t.flightCeiling??2e3,flightCeilingRange:t.flightCeilingRange??50}}function m(e,t){return{className:`WaterBlock`,ghostIndex:e,transform:s(t.transform),scale:i(t.scale,{x:1,y:1,z:1}),surfaceName:t.surfaceName??``,envMapName:t.envMapName??``,surfaceOpacity:t.surfaceOpacity??.75,waveMagnitude:t.waveMagnitude??1,envMapIntensity:t.envMapIntensity??1}}function h(e,t,n){let i;switch(e){case`TerrainBlock`:return i=c(t,n),r.debug(`TerrainBlock #%d: terrFileName=%s`,t,i.terrFileName),i;case`InteriorInstance`:return i=l(t,n),r.debug(`InteriorInstance #%d: interiorFile=%s`,t,i.interiorFile),i;case`TSStatic`:return u(t,n);case`Sky`:{i=d(t,n);let e=i;return r.debug(`Sky #%d: materialList=%s fogColor=(%s, %s, %s) visibleDist=%d fogDist=%d useSkyTextures=%s`,t,e.materialList,e.fogColor.r.toFixed(3),e.fogColor.g.toFixed(3),e.fogColor.b.toFixed(3),e.visibleDistance,e.fogDistance,e.useSkyTextures),i}case`Sun`:{i=f(t,n);let e=i;return r.debug(`Sun #%d: dir=(%s, %s, %s) color=(%s, %s, %s) ambient=(%s, %s, %s)`,t,e.direction.x.toFixed(3),e.direction.y.toFixed(3),e.direction.z.toFixed(3),e.color.r.toFixed(3),e.color.g.toFixed(3),e.color.b.toFixed(3),e.ambient.r.toFixed(3),e.ambient.g.toFixed(3),e.ambient.b.toFixed(3)),i}case`MissionArea`:return p(t,n);case`WaterBlock`:return m(t,n);default:return null}}function g(e){return[e.y,e.z,e.x]}function _(e){return[e.y,e.z,e.x]}function v(e){let r=e.elements,i=new n,a=i.elements;a[0]=r[5],a[1]=r[6],a[2]=r[4],a[3]=0,a[4]=r[9],a[5]=r[10],a[6]=r[8],a[7]=0,a[8]=r[1],a[9]=r[2],a[10]=r[0],a[11]=0,a[12]=0,a[13]=0,a[14]=0,a[15]=1;let o=new t;return o.setFromRotationMatrix(i),o.conjugate(),o}export{h as i,_ as n,g as r,v as t};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{i as e}from"./traditional-BTL5qX2E.js";var t=e()(()=>({time:0,playback:null,root:null,cameraMode:`original`,orbitOverrideYaw:0,orbitOverridePitch:0,entities:new Map}));function n(){t.setState({time:0,playback:null,cameraMode:`original`,orbitOverrideYaw:0,orbitOverridePitch:0})}export{t as n,n as t};

View file

@ -1 +0,0 @@
import{i as e}from"./traditional-BTL5qX2E.js";var t=e()(()=>({time:0,playback:null,root:null,freeFlyCamera:!1,entities:new Map}));function n(){t.setState({time:0,playback:null,freeFlyCamera:!1})}export{t as n,n as t};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{r as e,t}from"./useBaseQuery-BcnVzmUC.js";function n(n,r){return t(n,e,r)}export{n as t};

View file

@ -1 +0,0 @@
import{r as e,t}from"./useBaseQuery-BIpPgzSS.js";function n(n,r){return t(n,e,r)}export{n as t};

View file

@ -19,22 +19,26 @@
<link rel="apple-touch-icon" href="/t2-mapper/icon-512.png" />
<meta name="theme-color" content="#067272" />
<link rel="preconnect" href="https://www.gstatic.com" crossorigin />
<script type="module" crossorigin src="/t2-mapper/assets/index-BMiY1uIn.js"></script>
<script type="module" crossorigin src="/t2-mapper/assets/index-DBtsNu05.js"></script>
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/chunk-DECur_0Z.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/three.module-CwgFV8Kd.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/streamHelpers-Be29sBCp.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/preload-helper-CwUjIIrH.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/three.module-07hRbor4.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/streamHelpers-AIec78DP.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/logger-z_EpIdIa.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/mission-C2iDKeMo.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/mission-D8vr00S1.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/jsx-runtime-BpGWiA-R.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/traditional-BTL5qX2E.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/react-three-fiber.esm-dhSWjERg.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/react-three-fiber.esm-CgPHUpXo.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/stringUtils-EmGsjr9D.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/manifest-CirqV3Ls.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/SettingsProvider-xrmxG700.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/SettingsProvider-CCHVZuSg.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/iconBase-BCRUFbxq.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/engineStore-Cio8vU1L.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/scene-C7IIl-c0.js">
<link rel="stylesheet" crossorigin href="/t2-mapper/assets/index-UOHJQQT_.css">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/middleware-DPacZrFu.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/JoystickContext-YJ6eVLFP.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/scene-BdOVRsxo.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/cameraTourStore-PEzPVGnX.js">
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/engineStore-Dkm20jvr.js">
<link rel="stylesheet" crossorigin href="/t2-mapper/assets/index-DgQPeusO.css">
</head>
<body>
<div id="root"></div>

View file

@ -28,6 +28,7 @@ export default defineConfig([
"react-hooks/set-state-in-effect": "off",
"react-refresh/only-export-components": "warn",
"react-hooks/immutability": "warn",
"no-control-regex": "off",
},
},
]);

View file

@ -0,0 +1,53 @@
import { useRecording } from "./RecordingProvider";
import { useInputMode } from "./InputContext";
import { useCameraTour } from "../state/cameraTourStore";
import { InputBindings } from "./InputBindings";
import {
FREE_FLY_INPUT,
MOVABLE_CAMERA_INPUT,
POINTER_LOCKABLE_INPUT,
MAP_MODE_INPUT,
DEMO_MODE_INPUT,
LIVE_OBSERVER_INPUT,
LIVE_FOLLOW_INPUT,
TOUR_MODE_INPUT,
} from "./inputMap";
/**
* Mounts the appropriate `InputBindings` components based on the current
* app mode. Each binding group has its own lifecycle when a group
* unmounts, its actions are automatically cleaned up from the store.
*/
export function ActiveInputBindings() {
const recording = useRecording();
const inputMode = useInputMode();
const isTourActive = useCameraTour((s) => s.animation !== null);
const isDemo = recording?.source === "demo";
const isLive = recording?.source === "live";
const isMap = !recording;
// Free-fly movement: map mode (no tour) or live free-fly.
const showFreeFly =
(isMap && !isTourActive) || (isLive && inputMode === "fly");
// Camera can be moved by drag/touch in most modes.
const showMovableCamera = !isTourActive;
// Pointer lock: available when not touring.
const showPointerLockable = !isTourActive;
return (
<>
{showFreeFly && <InputBindings map={FREE_FLY_INPUT} />}
{showMovableCamera && <InputBindings map={MOVABLE_CAMERA_INPUT} />}
{showPointerLockable && <InputBindings map={POINTER_LOCKABLE_INPUT} />}
{isMap && !isTourActive && <InputBindings map={MAP_MODE_INPUT} />}
{isDemo && <InputBindings map={DEMO_MODE_INPUT} />}
{isLive && <InputBindings map={LIVE_OBSERVER_INPUT} />}
{isLive && inputMode === "follow" && (
<InputBindings map={LIVE_FOLLOW_INPUT} />
)}
{isTourActive && <InputBindings map={TOUR_MODE_INPUT} />}
</>
);
}

View file

@ -5,7 +5,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { FeaturesProvider } from "@/src/components/FeaturesProvider";
import { MapInspector } from "@/src/components/MapInspector";
import { SettingsProvider } from "@/src/components/SettingsProvider";
// Three.js has its own loaders for textures and models, but we need to load other
// stuff too, e.g. missions, terrains, and more. This client is used for those.
const queryClient = new QueryClient();

View file

@ -425,6 +425,7 @@ export const AudioEmitter = memo(function AudioEmitter({
}, [audioEnabled]);
return debugMode ? (
// eslint-disable-next-line react-hooks/refs
<mesh position={emitterPosRef.current}>
<sphereGeometry args={[minDistance, 12, 12]} />
<meshBasicMaterial

View file

@ -1,5 +1,6 @@
import { useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useInputAction } from "./InputControls";
import {
Box3,
Camera,
@ -23,9 +24,9 @@ function easeInOutCubic(t: number): number {
const FALLBACK_ORBIT_RADIUS = 3;
const DEFAULT_ORBIT_HEIGHT = 2;
const MIN_ORBIT_RADIUS = 1.5;
const MIN_ORBIT_RADIUS = 1.8;
/** Extra orbit radius added beyond half the object's height. */
const ORBIT_PAD_VERTICAL = 1.6;
const ORBIT_PAD_VERTICAL = 1.8;
/** Extra orbit radius added beyond half the object's spread (width/length). */
const ORBIT_PAD_HORIZONTAL = 1.2;
const ORBIT_ANGULAR_SPEED = 0.6; // rad/s
@ -337,6 +338,24 @@ export function CameraTourConsumer() {
const scene = useThree((s) => s.scene);
const prevAnimationRef = useRef<TourAnimation | null>(null);
// Click to advance to next stop, or exit if on the last target.
useInputAction("nextStop", () => {
const animation = cameraTourStore.getState().animation;
if (!animation) return;
const isLastTarget =
animation.currentIndex >= animation.targets.length - 1;
if (isLastTarget) {
cameraTourStore.getState().cancel();
} else {
cameraTourStore.getState().advanceTarget();
}
});
// Escape to exit tour.
useInputAction("exitTour", () => {
cameraTourStore.getState().cancel();
});
useFrame((_state, delta) => {
const animation = cameraTourStore.getState().animation;
if (!animation) {

View file

@ -19,7 +19,7 @@
border-radius: 4px;
background: rgba(3, 82, 147, 0.6);
color: #fff;
font-size: 14px;
font-size: 11px;
cursor: pointer;
}
@ -41,12 +41,26 @@
max-width: none;
}
.Speed {
.Speed,
.CameraMode {
flex-shrink: 0;
padding: 2px 4px;
padding: 3px 4px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 3px;
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 12px;
font-size: 13px;
}
.Field {
display: flex;
align-items: center;
gap: 8px;
}
.Field label {
text-transform: uppercase;
color: rgba(255, 255, 255, 0.6);
font-weight: 500;
font-size: 11px;
}

View file

@ -1,4 +1,6 @@
import { useCallback, useEffect, type ChangeEvent } from "react";
import { useInputAction } from "./InputControls";
// import { useStore } from "zustand";
import {
usePlaybackActions,
useCurrentTime,
@ -6,10 +8,21 @@ import {
useIsPlaying,
useRecording,
useSpeed,
SPEED_OPTIONS,
} from "./RecordingProvider";
// import {
// streamPlaybackStore,
// type DemoCameraMode,
// } from "../state/streamPlaybackStore";
// import { useEngineStoreApi } from "../state/engineStore";
import { GrPauseFill, GrPlayFill } from "react-icons/gr";
import styles from "./DemoPlaybackControls.module.css";
const SPEED_OPTIONS = [0.25, 0.5, 1, 2, 4];
// const CAMERA_MODE_OPTIONS: { value: DemoCameraMode; label: string }[] = [
// { value: "original", label: "Original" },
// { value: "freeFly", label: "Free Fly" },
// { value: "orbitOverride", label: "Orbit Target" },
// ];
function formatTime(seconds: number): string {
const m = Math.floor(seconds / 60);
@ -24,6 +37,8 @@ export function DemoPlaybackControls() {
const duration = useDuration();
const speed = useSpeed();
const { play, pause, seek, setSpeed } = usePlaybackActions();
// const cameraMode = useStore(streamPlaybackStore, (s) => s.cameraMode);
// const engineStore = useEngineStoreApi();
// Spacebar toggles play/pause during demo playback.
useEffect(() => {
@ -51,6 +66,16 @@ export function DemoPlaybackControls() {
return () => window.removeEventListener("keydown", handleKeyDown);
}, [recording, isPlaying, play, pause]);
useInputAction("decreasePlaybackSpeed", () => {
const idx = SPEED_OPTIONS.indexOf(speed);
if (idx > 0) setSpeed(SPEED_OPTIONS[idx - 1]);
});
useInputAction("increasePlaybackSpeed", () => {
const idx = SPEED_OPTIONS.indexOf(speed);
if (idx < SPEED_OPTIONS.length - 1) setSpeed(SPEED_OPTIONS[idx + 1]);
});
const handleSeek = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
seek(parseFloat(e.target.value));
@ -65,22 +90,36 @@ export function DemoPlaybackControls() {
[setSpeed],
);
// const handleCameraModeChange = useCallback(
// (e: ChangeEvent<HTMLSelectElement>) => {
// const newMode = e.target.value as DemoCameraMode;
// if (newMode === "orbitOverride") {
// // Seed yaw/pitch from current stream camera to avoid a jump.
// const cam =
// engineStore.getState().playback.streamSnapshot?.camera ?? null;
// streamPlaybackStore.setState({
// cameraMode: newMode,
// orbitOverrideYaw: cam?.yaw ?? 0,
// orbitOverridePitch: cam?.pitch ?? 0,
// });
// } else {
// streamPlaybackStore.setState({ cameraMode: newMode });
// }
// },
// [engineStore],
// );
if (!recording || !Number.isFinite(recording.duration)) return null;
return (
<div
className={styles.Root}
onKeyDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
<div className={styles.Root}>
<button
className={styles.PlayPause}
onClick={isPlaying ? pause : play}
aria-label={isPlaying ? "Pause" : "Play"}
autoFocus
>
{isPlaying ? "\u275A\u275A" : "\u25B6"}
{isPlaying ? <GrPauseFill /> : <GrPlayFill />}
</button>
<span className={styles.Time}>
{`${formatTime(currentTime)} / ${formatTime(duration)}`}
@ -94,17 +133,32 @@ export function DemoPlaybackControls() {
value={currentTime}
onChange={handleSeek}
/>
<select
className={styles.Speed}
value={speed}
onChange={handleSpeedChange}
<div className={styles.Field}>
<label htmlFor="playbackSpeed">Speed</label>
<select
id="playbackSpeed"
className={styles.Speed}
value={speed}
onChange={handleSpeedChange}
>
{SPEED_OPTIONS.map((s) => (
<option key={s} value={s}>
{s}x
</option>
))}
</select>
</div>
{/* <select
className={styles.CameraMode}
value={cameraMode}
onChange={handleCameraModeChange}
>
{SPEED_OPTIONS.map((s) => (
<option key={s} value={s}>
{s}x
{CAMERA_MODE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</select> */}
</div>
);
}

View file

@ -51,20 +51,24 @@ const EntityLayer = memo(function EntityLayer() {
const currentIds = new Set<string>();
for (const entity of entities) {
currentIds.add(entity.id);
cache.set(entity.id, entity);
cache.set(entity.id, entity); // eslint-disable-line react-hooks/refs
}
// Remove entities no longer in the set
// eslint-disable-next-line react-hooks/refs
for (const id of cache.keys()) {
if (!currentIds.has(id)) {
cache.delete(id);
cache.delete(id); // eslint-disable-line react-hooks/refs
}
}
return (
<>
{[...cache.values()].map((entity) => (
<EntityWrapper key={entity.id} entity={entity} />
))}
{
// eslint-disable-next-line react-hooks/refs
[...cache.values()].map((entity) => (
<EntityWrapper key={entity.id} entity={entity} />
))
}
</>
);
});
@ -115,7 +119,7 @@ function FlagMarkerSlot({ entity }: { entity: GameEntity }) {
: undefined;
return ((flags ?? 0) & 0x2) !== 0;
});
flagRef.current = isFlag;
flagRef.current = isFlag; // eslint-disable-line react-hooks/refs
useFrame(() => {
const flags =

View file

@ -79,7 +79,11 @@ export const FloatingLabel = memo(function FloatingLabel({
<group ref={groupRef}>
{isVisible ? (
<Html position={position} center>
<div ref={labelRef} className={styles.Label} style={{ color }}>
<div
ref={labelRef}
className={styles.Label}
style={{ color, opacity: fadeWithDistance ? 0 : undefined }}
>
{children}
</div>
</Html>

View file

@ -301,7 +301,7 @@ export function FogProvider({
// Initial update
useMemo(() => {
updateFogUniforms(uniformsRef.current, fogState, 0);
updateFogUniforms(uniformsRef.current, fogState, 0); // eslint-disable-line react-hooks/refs
}, [fogState]);
return (

View file

@ -4,7 +4,7 @@ import { useDataSource } from "../state/gameEntityStore";
import { useRecording } from "./RecordingProvider";
import { AudioProvider } from "./AudioContext";
import { CamerasProvider } from "./CamerasProvider";
import { InputProducers } from "./InputHandlers";
import { InputProducer } from "./InputProducer";
import { SceneLighting } from "./SceneLighting";
import { ThreeCanvas } from "./ThreeCanvas";
import { TickProvider } from "./TickProvider";
@ -14,6 +14,7 @@ import { AudioEnabled } from "./AudioEnabled";
import { DebugEnabled } from "./DebugEnabled";
import { InputConsumer } from "./InputConsumer";
import { CameraTourConsumer } from "./CameraTourConsumer";
import { ActiveInputBindings } from "./ActiveInputBindings";
function createLazy(
name: string,
@ -59,7 +60,8 @@ export const GameView = memo(function GameView({
<ThreeCanvas dpr={dpr} onCreated={onCreated}>
<TickProvider>
<CamerasProvider>
<InputProducers />
<ActiveInputBindings />
<InputProducer />
<AudioProvider>
<SceneLighting />
<Suspense>

View file

@ -0,0 +1,466 @@
import { useEffect, useMemo } from "react";
import { useThree } from "@react-three/fiber";
import {
inputControlsStore,
notifySubscribers,
keySetModifiersMatch,
eventModifiersMatch,
parseBinding,
defaultStateForBinding,
defaultDragState,
defaultTouchState,
type InputMapEntry,
type ActionState,
type DragState,
type KeyState,
type ScrollState,
type TouchState,
type ParsedAction,
type Modifier,
} from "./InputControls";
const DRAG_THRESHOLD = 3;
/**
* Parses the input map and attaches event listeners that write to the
* InputControls store. Place inside the r3f Canvas.
* Multiple InputBindings instances can coexist.
*
* Keyboard state is tracked centrally in InputControls (module-level
* keydown/keyup listeners). This component subscribes to key changes
* and derives its action state from them.
*/
export function InputBindings<T extends string = string>({
map,
}: {
map: readonly InputMapEntry<T>[] | InputMapEntry<T>[];
}) {
const store = inputControlsStore;
const canvas = useThree((state) => state.gl.domElement);
const bindings = useMemo(() => {
// Parse the map.
const actions: ParsedAction[] = map.map((entry) => {
const keys = Array.isArray(entry.keys) ? entry.keys : [entry.keys];
return { name: entry.name, bindings: keys.map(parseBinding) };
});
// Build default action state (applied in useEffect, not here).
const initialActions: Record<string, ActionState> = {};
for (const action of actions) {
initialActions[action.name] = defaultStateForBinding(action.bindings[0]);
}
// Build lookup indices.
const keyBindings = new Map<
string,
{
action: ParsedAction;
binding: { type: "key"; code: string; modifiers?: Modifier[] };
}[]
>();
const clickBindings: {
action: ParsedAction;
binding: {
type: "click";
button?: number;
modifiers?: Modifier[];
whenPointerLocked?: boolean;
};
}[] = [];
const dragBindings: {
action: ParsedAction;
binding: { type: "drag"; button?: number; whenPointerLocked?: boolean };
}[] = [];
const pointerLockMoveBindings: { action: ParsedAction }[] = [];
const scrollBindings: { action: ParsedAction }[] = [];
const touchBindings: { action: ParsedAction }[] = [];
for (const action of actions) {
for (const binding of action.bindings) {
switch (binding.type) {
case "key": {
let list = keyBindings.get(binding.code);
if (!list) {
list = [];
keyBindings.set(binding.code, list);
}
list.push({ action, binding });
break;
}
case "click":
clickBindings.push({ action, binding });
break;
case "drag":
dragBindings.push({ action, binding });
break;
case "pointerLockMove":
pointerLockMoveBindings.push({ action });
break;
case "scroll":
scrollBindings.push({ action });
break;
case "touch":
touchBindings.push({ action });
break;
}
}
}
/** Check if a binding's whenPointerLocked constraint is satisfied. */
function pointerLockMatches(whenPointerLocked?: boolean): boolean {
if (whenPointerLocked == null) return true;
return whenPointerLocked === !!document.pointerLockElement;
}
// ── Key action derivation ──
/**
* Derive key action state from the raw pressed-key set.
* Called whenever the global key set changes.
*/
function deriveKeyActions(keys: Set<string>) {
const { actions } = store.getState();
const updates: Record<string, ActionState> = {};
for (const [, entries] of keyBindings) {
for (const { action, binding } of entries) {
const shouldBePressed =
keys.has(binding.code) &&
keySetModifiersMatch(keys, binding.modifiers);
const prev = actions[action.name] as KeyState | undefined;
const wasPressed = prev?.pressed ?? false;
if (shouldBePressed && !wasPressed) {
updates[action.name] = { pressed: true };
notifySubscribers(action.name);
} else if (!shouldBePressed && wasPressed) {
updates[action.name] = { pressed: false };
}
}
}
if (Object.keys(updates).length > 0) {
store.setState((prev) => ({
...prev,
actions: { ...prev.actions, ...updates },
}));
}
}
// ── Mouse handlers ──
let mouseDownButton = -1;
let mouseDownX = 0;
let mouseDownY = 0;
let isDragging = false;
function setAction(name: string, state: ActionState) {
store.setState((prev) => ({
...prev,
actions: { ...prev.actions, [name]: state },
}));
}
function handleMouseDown(e: MouseEvent) {
const isLocked = !!document.pointerLockElement;
// Click bindings: set pressed on mousedown.
for (const { action, binding } of clickBindings) {
if (!pointerLockMatches(binding.whenPointerLocked)) continue;
const button = binding.button ?? 0;
if (e.button !== button) continue;
if (!eventModifiersMatch(e, binding.modifiers)) continue;
setAction(action.name, { pressed: true } satisfies KeyState);
}
// Drag tracking only when not pointer-locked (locked movement
// is handled by pointerLockMove bindings in handleMouseMove).
if (!isLocked) {
mouseDownButton = e.button;
mouseDownX = e.clientX;
mouseDownY = e.clientY;
isDragging = false;
}
}
function handleMouseMove(e: MouseEvent) {
// Pointer lock: accumulate deltas for pointerLockMove bindings.
// Mutate in place and batch into a single setState.
if (document.pointerLockElement) {
if (pointerLockMoveBindings.length > 0) {
const { actions } = store.getState();
const updates: Record<string, ActionState> = {};
for (const { action } of pointerLockMoveBindings) {
const prev = actions[action.name] as DragState;
updates[action.name] = {
...prev,
deltaX: prev.deltaX + e.movementX,
deltaY: prev.deltaY + e.movementY,
};
}
store.setState((prev) => ({
...prev,
actions: { ...prev.actions, ...updates },
}));
}
return;
}
// Non-locked: check drag threshold.
if (mouseDownButton < 0) return;
if (!isDragging) {
const dx = e.clientX - mouseDownX;
const dy = e.clientY - mouseDownY;
if (Math.abs(dx) < DRAG_THRESHOLD && Math.abs(dy) < DRAG_THRESHOLD) {
return;
}
isDragging = true;
// Crossed drag threshold — cancel matching click bindings.
for (const { action, binding } of clickBindings) {
if (!pointerLockMatches(binding.whenPointerLocked)) continue;
if ((binding.button ?? 0) !== mouseDownButton) continue;
const prev = store.getState().actions[action.name] as KeyState;
if (prev.pressed) {
setAction(action.name, { pressed: false } satisfies KeyState);
}
}
for (const { action, binding } of dragBindings) {
if (!pointerLockMatches(binding.whenPointerLocked)) continue;
if ((binding.button ?? 0) !== mouseDownButton) continue;
setAction(action.name, {
dragging: true,
deltaX: 0,
deltaY: 0,
startX: mouseDownX,
startY: mouseDownY,
} satisfies DragState);
}
}
// Accumulate drag deltas — batch into a single setState.
const { actions } = store.getState();
const dragUpdates: Record<string, ActionState> = {};
for (const { action, binding } of dragBindings) {
if (!pointerLockMatches(binding.whenPointerLocked)) continue;
if ((binding.button ?? 0) !== mouseDownButton) continue;
const prev = actions[action.name] as DragState;
dragUpdates[action.name] = {
...prev,
deltaX: prev.deltaX + e.movementX,
deltaY: prev.deltaY + e.movementY,
};
}
if (Object.keys(dragUpdates).length > 0) {
store.setState((prev) => ({
...prev,
actions: { ...prev.actions, ...dragUpdates },
}));
}
}
function handleMouseUp(e: MouseEvent) {
const isLocked = !!document.pointerLockElement;
// Click bindings: if still pressed (not cancelled by drag),
// this is a confirmed click — notify subscribers then release.
for (const { action, binding } of clickBindings) {
if (!pointerLockMatches(binding.whenPointerLocked)) continue;
const button = binding.button ?? 0;
if (e.button !== button) continue;
const prev = store.getState().actions[action.name] as KeyState;
if (prev.pressed) {
notifySubscribers(action.name);
setAction(action.name, { pressed: false } satisfies KeyState);
}
}
// Drag bindings: end dragging (only relevant when not locked).
if (!isLocked && e.button === mouseDownButton) {
for (const { action, binding } of dragBindings) {
if (!pointerLockMatches(binding.whenPointerLocked)) continue;
if ((binding.button ?? 0) !== mouseDownButton) continue;
const prev = store.getState().actions[action.name] as DragState;
if (prev.dragging) {
setAction(action.name, defaultDragState());
}
}
mouseDownButton = -1;
isDragging = false;
}
}
function handleWheel(e: WheelEvent) {
for (const { action } of scrollBindings) {
setAction(action.name, {
deltaX: e.deltaX,
deltaY: e.deltaY,
} satisfies ScrollState);
notifySubscribers(action.name);
}
}
// ── Touch handlers ──
let touchId: number | null = null;
let lastTouchX = 0;
let lastTouchY = 0;
function handleTouchStart(e: TouchEvent) {
if (touchId !== null) return;
if (touchBindings.length === 0) return;
const touch = e.changedTouches[0];
if (!touch) return;
touchId = touch.identifier;
lastTouchX = touch.clientX;
lastTouchY = touch.clientY;
for (const { action } of touchBindings) {
setAction(action.name, {
touching: true,
dragging: false,
deltaX: 0,
deltaY: 0,
} satisfies TouchState);
}
}
function handleTouchMove(e: TouchEvent) {
if (touchId === null) return;
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
if (touch.identifier !== touchId) continue;
const dx = touch.clientX - lastTouchX;
const dy = touch.clientY - lastTouchY;
lastTouchX = touch.clientX;
lastTouchY = touch.clientY;
for (const { action } of touchBindings) {
const prev = store.getState().actions[action.name] as TouchState;
setAction(action.name, {
touching: true,
dragging: true,
deltaX: prev.deltaX + dx,
deltaY: prev.deltaY + dy,
} satisfies TouchState);
}
break;
}
}
function handleTouchEnd(e: TouchEvent) {
if (touchId === null) return;
for (let i = 0; i < e.changedTouches.length; i++) {
if (e.changedTouches[i].identifier !== touchId) continue;
touchId = null;
for (const { action } of touchBindings) {
setAction(action.name, defaultTouchState());
}
break;
}
}
const actionNames = actions.map((a) => a.name);
const hasKeyBindings = keyBindings.size > 0;
return {
actionNames,
initialActions,
deriveKeyActions,
hasKeyBindings,
handleMouseDown,
handleMouseMove,
handleMouseUp,
handleWheel,
handleTouchStart,
handleTouchMove,
handleTouchEnd,
hasMouseBindings:
clickBindings.length > 0 ||
dragBindings.length > 0 ||
pointerLockMoveBindings.length > 0,
hasScrollBindings: scrollBindings.length > 0,
hasTouchBindings: touchBindings.length > 0,
};
}, [map, store]);
// Initialize action state, subscribe to key changes, and attach
// mouse/touch/scroll listeners.
useEffect(() => {
store.setState((prev) => ({
...prev,
actions: { ...prev.actions, ...bindings.initialActions },
}));
// Subscribe to global key set changes to derive key actions.
let unsubKeys: (() => void) | undefined;
if (bindings.hasKeyBindings) {
// Derive immediately from current key state.
bindings.deriveKeyActions(store.getState().keys);
unsubKeys = store.subscribe(
(state) => state.keys,
(keys) => bindings.deriveKeyActions(keys),
);
}
if (bindings.hasMouseBindings) {
canvas.addEventListener("mousedown", bindings.handleMouseDown);
document.addEventListener("mousemove", bindings.handleMouseMove);
document.addEventListener("mouseup", bindings.handleMouseUp);
}
if (bindings.hasScrollBindings) {
canvas.addEventListener("wheel", bindings.handleWheel, {
passive: true,
});
}
if (bindings.hasTouchBindings) {
canvas.addEventListener("touchstart", bindings.handleTouchStart, {
passive: true,
});
document.addEventListener("touchmove", bindings.handleTouchMove, {
passive: true,
});
document.addEventListener("touchend", bindings.handleTouchEnd, {
passive: true,
});
document.addEventListener("touchcancel", bindings.handleTouchEnd, {
passive: true,
});
}
return () => {
unsubKeys?.();
if (bindings.hasMouseBindings) {
canvas.removeEventListener("mousedown", bindings.handleMouseDown);
document.removeEventListener("mousemove", bindings.handleMouseMove);
document.removeEventListener("mouseup", bindings.handleMouseUp);
}
if (bindings.hasScrollBindings) {
canvas.removeEventListener("wheel", bindings.handleWheel);
}
if (bindings.hasTouchBindings) {
canvas.removeEventListener("touchstart", bindings.handleTouchStart);
document.removeEventListener("touchmove", bindings.handleTouchMove);
document.removeEventListener("touchend", bindings.handleTouchEnd);
document.removeEventListener("touchcancel", bindings.handleTouchEnd);
}
// Remove this instance's actions from the store.
store.setState((prev) => {
const nextActions = { ...prev.actions };
for (const name of bindings.actionNames) {
delete nextActions[name];
}
return { ...prev, actions: nextActions };
});
};
}, [bindings, store, canvas]);
return null;
}

View file

@ -17,7 +17,7 @@ import type { ClientMove } from "../../relay/types";
const log = createLogger("InputConsumer");
const MAX_SPEED = 300;
const MAX_SPEED = 270;
const LOCAL_MAX_PITCH = Math.PI / 2 - 0.01; // ~89°
/**
@ -261,6 +261,15 @@ export function InputConsumer() {
}
}, [liveReady, setMode]);
// Set input mode to "follow" during orbit override so
// MouseAndKeyboardHandler flips drag direction correctly.
useEffect(() => {
if (isLive) return;
return streamPlaybackStore.subscribe((state) => {
setMode(state.cameraMode === "orbitOverride" ? "follow" : "local");
});
}, [isLive, setMode]);
// ── processTick: send moves at the Torque tick rate (32Hz). ──
useTick(() => {
if (!activeAdapterRef.current || gameStatus !== "connected" || !liveReady)
@ -453,7 +462,19 @@ export function InputConsumer() {
} else {
// Local mode: apply input directly to camera.
const spState = streamPlaybackStore.getState();
if (spState.playback && !spState.freeFlyCamera) return;
if (spState.playback) {
if (spState.cameraMode === "freeFly") {
applyLocalCamera(camera, dYaw, dPitch, x, y, z, frameDelta);
} else if (spState.cameraMode === "orbitOverride") {
// Accumulate orbit yaw/pitch for StreamingController to read.
spState.orbitOverrideYaw += dYaw;
spState.orbitOverridePitch = Math.max(
-MAX_PITCH,
Math.min(MAX_PITCH, spState.orbitOverridePitch + dPitch),
);
}
return;
}
applyLocalCamera(camera, dYaw, dPitch, x, y, z, frameDelta);
return;

View file

@ -0,0 +1,368 @@
import { useEffect, useEffectEvent, useMemo } from "react";
import { createStore } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
import { useStoreWithEqualityFn } from "zustand/traditional";
// ── Types ──
export type Modifier = "Ctrl" | "Shift" | "Alt";
export type InputBinding =
| { type: "key"; code: string; modifiers?: Modifier[] }
| {
type: "click";
button?: number;
modifiers?: Modifier[];
whenPointerLocked?: boolean;
}
| { type: "drag"; button?: number; whenPointerLocked?: boolean }
| { type: "pointerLockMove" }
| { type: "scroll" }
| { type: "touch" };
/** String shorthand: `"KeyW"`, `"Shift-KeyA"`, or an InputBinding object. */
export type BindingShorthand = string | InputBinding;
export type InputMapEntry<T extends string = string> = {
name: T;
keys: BindingShorthand | BindingShorthand[];
};
export interface KeyState {
pressed: boolean;
}
export interface DragState {
dragging: boolean;
deltaX: number;
deltaY: number;
startX: number;
startY: number;
}
export interface ScrollState {
deltaX: number;
deltaY: number;
}
export interface TouchState {
touching: boolean;
dragging: boolean;
deltaX: number;
deltaY: number;
}
export type ActionState = KeyState | DragState | ScrollState | TouchState;
/** The full store state: raw keys + derived action state. */
export interface InputStoreState {
/** Physical key codes currently held down. */
keys: Set<string>;
/** Derived action state, keyed by action name. */
actions: Record<string, ActionState>;
}
// ── Modifier parsing ──
const MODIFIER_NAMES = new Set<string>(["Ctrl", "Shift", "Alt"]);
/** All physical key codes that are modifier keys (including Meta for
* key-tracking purposes, even though Meta bindings are not supported). */
const MODIFIER_CODE_SET = new Set([
"MetaLeft",
"MetaRight",
"ControlLeft",
"ControlRight",
"ShiftLeft",
"ShiftRight",
"AltLeft",
"AltRight",
]);
export function parseBinding(shorthand: BindingShorthand): InputBinding {
if (typeof shorthand !== "string") return shorthand;
const parts = shorthand.split("-");
const code = parts.pop()!;
const modifiers: Modifier[] = [];
for (const part of parts) {
if (MODIFIER_NAMES.has(part)) {
modifiers.push(part as Modifier);
}
}
return {
type: "key",
code,
modifiers: modifiers.length > 0 ? modifiers : undefined,
};
}
/** Check if the required modifiers match the pressed key set. */
export function keySetModifiersMatch(
keys: Set<string>,
required?: Modifier[],
): boolean {
const hasCtrl = keys.has("ControlLeft") || keys.has("ControlRight");
const hasShift = keys.has("ShiftLeft") || keys.has("ShiftRight");
const hasAlt = keys.has("AltLeft") || keys.has("AltRight");
return (
hasCtrl === (required?.includes("Ctrl") ?? false) &&
hasShift === (required?.includes("Shift") ?? false) &&
hasAlt === (required?.includes("Alt") ?? false)
);
}
/** Check if the required modifiers match a DOM event's modifier flags. */
export function eventModifiersMatch(
e: KeyboardEvent | MouseEvent,
required?: Modifier[],
): boolean {
const wantCtrl = required?.includes("Ctrl") ?? false;
const wantShift = required?.includes("Shift") ?? false;
const wantAlt = required?.includes("Alt") ?? false;
return (
e.ctrlKey === wantCtrl &&
e.shiftKey === wantShift &&
e.altKey === wantAlt
);
}
// ── Default state factories ──
export function defaultKeyState(): KeyState {
return { pressed: false };
}
export function defaultDragState(): DragState {
return { dragging: false, deltaX: 0, deltaY: 0, startX: 0, startY: 0 };
}
export function defaultScrollState(): ScrollState {
return { deltaX: 0, deltaY: 0 };
}
export function defaultTouchState(): TouchState {
return { touching: false, dragging: false, deltaX: 0, deltaY: 0 };
}
export function defaultStateForBinding(binding: InputBinding): ActionState {
switch (binding.type) {
case "key":
case "click":
return defaultKeyState();
case "drag":
case "pointerLockMove":
return defaultDragState();
case "scroll":
return defaultScrollState();
case "touch":
return defaultTouchState();
}
}
// ── Internal types ──
type ActionCallback = () => void;
export interface ParsedAction {
name: string;
bindings: InputBinding[];
}
// ── Module-level store and subscriber registry ──
export const inputControlsStore = createStore<InputStoreState>()(
subscribeWithSelector(() => ({
keys: new Set<string>(),
actions: {} as Record<string, ActionState>,
})),
);
const actionSubscribers = new Map<string, Set<ActionCallback>>();
export function subscribeAction(
action: string,
callback: ActionCallback,
): () => void {
let set = actionSubscribers.get(action);
if (!set) {
set = new Set();
actionSubscribers.set(action, set);
}
set.add(callback);
return () => {
set!.delete(callback);
if (set!.size === 0) actionSubscribers.delete(action);
};
}
export function notifySubscribers(action: string) {
const set = actionSubscribers.get(action);
if (set) {
for (const cb of set) cb();
}
}
// ── Centralized key tracking ──
// A single pair of keydown/keyup listeners manages the `keys` Set.
// InputBindings instances subscribe to key changes to derive actions,
// instead of each attaching their own keyboard listeners.
// Input types that accept text entry — all keys should be ignored.
const TEXT_INPUT_TYPES = new Set([
"text",
"search",
"url",
"tel",
"email",
"password",
"number",
"date",
"datetime-local",
"month",
"week",
"time",
]);
// Keys that interactive (non-text) elements use natively.
const INTERACTIVE_KEYS = new Set([
"Space",
"Enter",
"NumpadEnter",
"ArrowUp",
"ArrowDown",
"ArrowLeft",
"ArrowRight",
]);
function shouldIgnoreForFocus(e: KeyboardEvent): boolean {
// Tab: capture when pointer is locked (prevent focus shift), otherwise
// let the browser handle focus navigation.
if (e.code === "Tab") {
if (document.pointerLockElement) {
e.preventDefault();
return false;
}
return true;
}
const el = document.activeElement;
if (!el || el === document.body) return false;
const tag = el.tagName;
if ((el as HTMLElement).isContentEditable) return true;
if (tag === "TEXTAREA") return true;
if (tag === "INPUT") {
const type = (el as HTMLInputElement).type.toLowerCase();
if (TEXT_INPUT_TYPES.has(type)) return true;
return INTERACTIVE_KEYS.has(e.code);
}
if (
tag === "BUTTON" ||
tag === "SELECT" ||
tag === "A" ||
tag === "SUMMARY"
) {
return INTERACTIVE_KEYS.has(e.code);
}
return false;
}
function handleGlobalKeyDown(e: KeyboardEvent) {
// Meta (Cmd) is not supported as a modifier — macOS doesn't fire
// reliable keyup events while it's held, causing stale input state.
if (e.metaKey) return;
if (shouldIgnoreForFocus(e)) return;
const { keys: prevKeys } = inputControlsStore.getState();
if (prevKeys.has(e.code)) return; // Already tracked (repeat).
const nextKeys = new Set(prevKeys);
nextKeys.add(e.code);
inputControlsStore.setState((prev) => ({ ...prev, keys: nextKeys }));
}
function handleGlobalKeyUp(e: KeyboardEvent) {
const { keys: prevKeys } = inputControlsStore.getState();
if (!prevKeys.has(e.code)) return;
const nextKeys = new Set(prevKeys);
nextKeys.delete(e.code);
// macOS Cocoa quirk: keyup not fired for keys released while Cmd held.
// Clear non-modifier keys since we can't trust their state.
if (e.code === "MetaLeft" || e.code === "MetaRight") {
for (const code of nextKeys) {
if (!MODIFIER_CODE_SET.has(code)) {
nextKeys.delete(code);
}
}
}
inputControlsStore.setState((prev) => ({ ...prev, keys: nextKeys }));
}
function handleGlobalBlur() {
const { keys } = inputControlsStore.getState();
if (keys.size === 0) return;
inputControlsStore.setState((prev) => ({
...prev,
keys: new Set<string>(),
}));
}
// Attach once at module load.
window.addEventListener("keydown", handleGlobalKeyDown);
window.addEventListener("keyup", handleGlobalKeyUp);
window.addEventListener("blur", handleGlobalBlur);
// ── Public hooks ──
/** Reactive selector for input action state. */
export function useInputControls<T>(
selector: (state: Record<string, ActionState>) => T,
): T {
return useStoreWithEqualityFn(inputControlsStore, (s) => selector(s.actions));
}
/** Imperative access to input state for useFrame callbacks. */
export function useInputState() {
return useMemo(
() =>
[
inputControlsStore.subscribe,
() => inputControlsStore.getState().actions,
] as const,
[],
);
}
/** Zero accumulated deltas for drag, scroll, and touch actions.
* Does NOT reset `dragging` that is managed by binding lifecycle.
* Call after reading deltas in your useFrame. */
export function clearInputDeltas() {
const { actions } = inputControlsStore.getState();
const updates: Record<string, ActionState> = {};
for (const [name, s] of Object.entries(actions)) {
if ("deltaX" in s && (s.deltaX !== 0 || s.deltaY !== 0)) {
updates[name] = { ...s, deltaX: 0, deltaY: 0 };
}
}
if (Object.keys(updates).length > 0) {
inputControlsStore.setState((prev) => ({
...prev,
actions: { ...prev.actions, ...updates },
}));
}
}
/**
* Register a callback for a one-shot action (key press, click, etc.).
* The callback always sees current closure state via useEffectEvent.
*/
export function useInputAction(action: string, callback: () => void) {
const stableCallback = useEffectEvent(callback);
useEffect(() => {
return subscribeAction(action, stableCallback);
}, [action]);
}

View file

@ -6,13 +6,9 @@ import {
useRef,
useState,
} from "react";
import { KeyboardControls } from "@react-three/drei";
import { JoystickProvider } from "./JoystickContext";
import { useTouchDevice } from "./useTouchDevice";
import {
MouseAndKeyboardHandler,
KEYBOARD_CONTROLS,
} from "./MouseAndKeyboardHandler";
import { MouseAndKeyboardHandler } from "./MouseAndKeyboardHandler";
import {
InputContext,
type InputFrame,
@ -36,14 +32,12 @@ export function InputProvider({ children }: { children: ReactNode }) {
return (
<InputContext.Provider value={{ moveQueue, onInput, mode, setMode }}>
<KeyboardControls map={KEYBOARD_CONTROLS}>
<JoystickProvider>{children}</JoystickProvider>
</KeyboardControls>
<JoystickProvider>{children}</JoystickProvider>
</InputContext.Provider>
);
}
export function InputProducers() {
export function InputProducer() {
const isTouch = useTouchDevice();
return (
@ -57,6 +51,3 @@ export function InputProducers() {
</>
);
}
/** @deprecated Use `InputProducers` instead. */
export const InputHandlers = InputProducers;

View file

@ -74,6 +74,8 @@ export const InspectorControls = memo(function InspectorControls({
setAnimationEnabled,
fpsLimit,
setFpsLimit,
showInputOverlay,
setShowInputOverlay,
} = useSettings();
const {
speedMultiplier,
@ -329,6 +331,22 @@ export const InspectorControls = memo(function InspectorControls({
/>
</div>
</div>
<div className={styles.CheckboxField}>
<input
id="showInputOverlayInput"
type="checkbox"
checked={showInputOverlay}
onChange={(event) => {
setShowInputOverlay(event.target.checked);
}}
/>
<label
className={styles.Label}
htmlFor="showInputOverlayInput"
>
Show input overlay
</label>
</div>
</Accordion>
<Accordion value="audio" label="Audio">
<div className={styles.CheckboxField}>

View file

@ -14,9 +14,14 @@
display: flex;
gap: 4px;
flex-direction: column;
justify-content: center;
align-items: stretch;
justify-content: flex-end;
}
/* .Column[data-height="compact"] {
gap: 2px;
} */
.Row {
display: flex;
gap: 4px;
@ -27,29 +32,153 @@
width: 32px;
}
.Sep {
opacity: 0.5;
}
.Key {
min-width: 32px;
height: 32px;
display: flex;
flex: 1 0 0;
align-items: center;
justify-content: center;
padding: 0 8px;
align-items: stretch;
margin: 0 auto;
border-radius: 4px;
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.5);
font-size: 11px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
}
.Key[data-pressed="true"] {
background: rgba(52, 187, 171, 0.6);
border-color: rgba(35, 253, 220, 0.5);
/* .Column[data-height="compact"] .Key {
font-size: 10px;
font-weight: 500;
height: 26px;
} */
.Key[data-size="auto"] {
flex: 0 0 auto;
}
.Key[data-size="fill"] {
flex: 1 0 auto;
}
.Key[data-disabled="true"] {
opacity: 0.6;
}
.Key[data-pressed="true"]:not([data-disabled="true"]) {
background: rgba(35, 145, 132, 0.6);
border-color: rgba(24, 197, 171, 0.4);
}
.Label {
min-width: 30px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 6px;
background: rgba(200, 200, 200, 0.1);
}
.Key[data-pressed="true"]:not([data-disabled="true"]) .Label {
color: rgba(255, 255, 255, 0.7);
}
.Label[data-size="auto"] {
flex: 0 0 auto;
}
.Label[data-size="fill"] {
flex: 1 0 auto;
}
.Label:first-child {
border-right: 1px solid rgba(255, 255, 255, 0.2);
}
.Label:last-child {
border-left: 1px solid rgba(255, 255, 255, 0.2);
}
/* .Column[data-height="compact"] .Label:first-child {
padding-right: 5px;
} */
/* .Column[data-height="compact"] .Label:last-child {
padding-left: 5px;
} */
.Key[data-pressed="true"]:not([data-disabled="true"]) .Label {
border-color: rgba(24, 197, 171, 0.4);
color: rgba(162, 255, 222, 0.8);
}
.MultiInput {
font-family: var(--monospace-font);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.Input {
min-width: 30px;
display: flex;
align-items: center;
justify-content: center;
line-height: 0.8;
padding: 0 8px;
}
/* .Column[data-height="compact"] .Key .Input {
min-width: 26px;
} */
.Input[data-size="auto"],
.MultiInput[data-size="auto"] {
flex: 0 0 auto;
}
.Input[data-size="fill"],
.MultiInput[data-size="fill"] {
flex: 1 0 auto;
}
.Key[data-pressed="true"]:not([data-disabled="true"]) .Input {
color: #fff;
}
.Arrow {
margin-right: 3px;
.MultiInput .Input {
flex: 0 0 auto;
border: 0;
background: transparent;
}
.ColumnLabel {
font-size: 9px;
color: rgb(255, 255, 255, 0.5);
text-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
text-transform: uppercase;
padding: 2px 0;
text-align: center;
}
.PlayPauseIcon {
font-size: 9px;
}
.MouseIcon {
font-size: 19px;
}
/* .Column[data-height="compact"] .Key .MouseIcon {
font-size: 16px;
} */
.Input:has(.MouseIcon) {
padding: 0 4px;
}

View file

@ -1,82 +1,463 @@
import { useKeyboardControls } from "@react-three/drei";
import { type ControlName } from "./MouseAndKeyboardHandler";
import { useRecording } from "./RecordingProvider";
import { useLiveSelector } from "../state/liveConnectionStore";
import {
ScrollState,
useInputControls,
type ActionState,
type DragState,
type KeyState,
} from "./InputControls";
import {
SPEED_OPTIONS,
useIsPlaying,
useRecording,
useSpeed,
} from "./RecordingProvider";
import { useInputMode } from "./InputContext";
import { useCameraTour } from "../state/cameraTourStore";
import {
useDataSource,
useGameEntitiesByRenderType,
} from "../state/gameEntityStore";
import { FaAngleDoubleDown, FaAngleDoubleUp } from "react-icons/fa";
import { PiMouseLeftClickFill, PiMouseScroll } from "react-icons/pi";
import { ReactNode, useEffect, useRef, useState } from "react";
import {
FaArrowDown,
FaArrowLeft,
FaArrowRight,
FaArrowUp,
} from "react-icons/fa6";
import { GrPauseFill, GrPlayFill } from "react-icons/gr";
import { usePointerLocked } from "./usePointerLocked";
import styles from "./KeyboardOverlay.module.css";
import { useControls } from "./SettingsProvider";
import { MdSwipe } from "react-icons/md";
export function KeyboardOverlay() {
const recording = useRecording();
const liveReady = useLiveSelector((s) => s.liveReady);
const forward = useKeyboardControls<ControlName>((s) => s.forward);
const backward = useKeyboardControls<ControlName>((s) => s.backward);
const left = useKeyboardControls<ControlName>((s) => s.left);
const right = useKeyboardControls<ControlName>((s) => s.right);
const up = useKeyboardControls<ControlName>((s) => s.up);
const down = useKeyboardControls<ControlName>((s) => s.down);
const lookUp = useKeyboardControls<ControlName>((s) => s.lookUp);
const lookDown = useKeyboardControls<ControlName>((s) => s.lookDown);
const lookLeft = useKeyboardControls<ControlName>((s) => s.lookLeft);
const lookRight = useKeyboardControls<ControlName>((s) => s.lookRight);
type InputState = Record<string, ActionState>;
type ActionSelector = (state: InputState) => boolean;
// Show when no recording (map browsing) or during live mode once ready.
// Hidden during demo playback and during live map transitions.
if (recording && recording.source !== "live") return null;
if (recording?.source === "live" && !liveReady) return null;
function actionPressed(state: InputState, name: string): boolean {
const s = state[name];
return s != null && "pressed" in s && (s as KeyState).pressed;
}
function Key({
action,
input,
label,
labelPosition = "hidden",
labelSize = "fill",
inputSize = "fill",
size = "fill",
disabled = false,
debounce,
}: {
action: string | ActionSelector;
input: ReactNode;
label: ReactNode;
labelPosition?: "left" | "right" | "hidden";
labelSize?: "auto" | "fill";
inputSize?: "auto" | "fill";
size?: "auto" | "fill";
debounce?: number;
disabled?: boolean;
}) {
// Debounce state: when the raw value goes false within the debounce
// window, the selector keeps returning true (no re-render). A timer
// triggers one final re-render after the window expires.
const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const [held, setHeld] = useState(false);
const baseSelector =
typeof action === "function"
? action
: (s: InputState) => actionPressed(s, action);
const rawIsPressed = useInputControls(baseSelector);
useEffect(() => {
if (!debounce) return;
if (rawIsPressed) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
setHeld(true);
} else {
timerRef.current = setTimeout(() => {
timerRef.current = undefined;
setHeld(false);
}, debounce);
return () => clearTimeout(timerRef.current);
}
}, [rawIsPressed, debounce]);
const isPressed = debounce ? held : rawIsPressed;
return (
<div className={styles.Root}>
<div
className={styles.Key}
data-pressed={isPressed}
data-size={size}
data-disabled={disabled}
>
{labelPosition === "left" ? (
<span className={styles.Label} data-size={labelSize}>
{label}
</span>
) : null}
{Array.isArray(input) ? (
<div className={styles.MultiInput} data-size={inputSize}>
{input.map((input, i) => (
<span className={styles.Input} key={i}>
{input}
</span>
))}
</div>
) : (
<span className={styles.Input} data-size={inputSize}>
{input}
</span>
)}
{labelPosition === "right" ? (
<span className={styles.Label} data-size={labelSize}>
{label}
</span>
) : null}
</div>
);
}
function PointerLockKey() {
const isPointerLocked = usePointerLocked();
// When pointer lock exits, briefly keep showing the "Unlock mouse" UI
// so the Esc key appears highlighted (the browser consumes the keydown
// so we can't detect it directly).
const [justUnlocked, setJustUnlocked] = useState(false);
const wasLockedRef = useRef(false);
useEffect(() => {
if (wasLockedRef.current && !isPointerLocked) {
setJustUnlocked(true);
const id = setTimeout(() => setJustUnlocked(false), 150);
return () => clearTimeout(id);
}
wasLockedRef.current = isPointerLocked;
}, [isPointerLocked]);
const showLockedUI = isPointerLocked || justUnlocked;
return (
<Key
action={showLockedUI ? () => justUnlocked : "canvasClick"}
label={showLockedUI ? "Unlock mouse" : "Capture mouse"}
input={
showLockedUI ? (
"Esc"
) : (
<PiMouseLeftClickFill className={styles.MouseIcon} />
)
}
labelPosition="right"
inputSize="auto"
/>
);
}
function MoveKeys() {
return (
<>
<div className={styles.Column}>
<div className={styles.Row}>
<div className={styles.Spacer} />
<div className={styles.Key} data-pressed={forward}>
W
</div>
<Key action="moveForward" input="W" label="Forward" />
<div className={styles.Spacer} />
</div>
<div className={styles.Row}>
<div className={styles.Key} data-pressed={left}>
A
</div>
<div className={styles.Key} data-pressed={backward}>
S
</div>
<div className={styles.Key} data-pressed={right}>
D
</div>
<Key action="moveLeft" input="A" label="Strafe left" />
<Key action="moveBackward" input="S" label="Backward" />
<Key action="moveRight" input="D" label="Strafe right" />
</div>
</div>
<div className={styles.Column}>
<div className={styles.Row}>
<div className={styles.Key} data-pressed={up}>
<span className={styles.Arrow}>&uarr;</span> Space
</div>
<Key
action="moveUp"
input="E"
label={<FaAngleDoubleUp />}
labelPosition="left"
labelSize="auto"
/>
</div>
<div className={styles.Row}>
<div className={styles.Key} data-pressed={down}>
<span className={styles.Arrow}>&darr;</span> Shift
</div>
<Key
action="moveDown"
input="Q"
label={<FaAngleDoubleDown />}
labelPosition="left"
labelSize="auto"
/>
</div>
</div>
<div className={styles.Column}>
<div className={styles.Row}>
<div className={styles.Spacer} />
<div className={styles.Key} data-pressed={lookUp}>
&uarr;
</div>
<div className={styles.Spacer} />
</div>
<div className={styles.Row}>
<div className={styles.Key} data-pressed={lookLeft}>
&larr;
</div>
<div className={styles.Key} data-pressed={lookDown}>
&darr;
</div>
<div className={styles.Key} data-pressed={lookRight}>
&rarr;
</div>
</div>
</>
);
}
function LookKeys() {
return (
<div className={styles.Column}>
<div className={styles.Row}>
<div className={styles.Spacer} />
<Key action="lookUp" input={<FaArrowUp />} label="Look up" />
<div className={styles.Spacer} />
</div>
<div className={styles.Row}>
<Key action="lookLeft" input={<FaArrowLeft />} label="Look left" />
<Key action="lookDown" input={<FaArrowDown />} label="Look down" />
<Key action="lookRight" input={<FaArrowRight />} label="Look right" />
</div>
</div>
);
}
function FlySpeedKey() {
const { speedMultiplier } = useControls();
const [speedMultiplierChanged, setSpeedMultiplierChanged] = useState<
boolean | null
>(null);
useEffect(() => {
setSpeedMultiplierChanged((value) => (value == null ? false : true));
const timeoutId = setTimeout(() => {
setSpeedMultiplierChanged(false);
}, 100);
return () => clearTimeout(timeoutId);
}, [speedMultiplier]);
return (
<Key
action={(s) =>
((s.adjustSpeed as ScrollState)?.deltaY ?? 0) !== 0 &&
(speedMultiplierChanged ?? false)
}
debounce={50}
label="Adjust speed"
input={<PiMouseScroll className={styles.MouseIcon} />}
labelPosition="right"
inputSize="auto"
/>
);
}
function RotateCameraKey() {
return (
<Key
action={(s) => (s.dragLook as DragState | undefined)?.dragging ?? false}
input={<MdSwipe className={styles.MouseIcon} />}
label="Rotate camera"
labelPosition="right"
inputSize="auto"
/>
);
}
function SelectCameraKey() {
const dataSource = useDataSource();
const isMapMode = dataSource === "map";
const cameraEntities = useGameEntitiesByRenderType("Camera");
const cameraCount = isMapMode ? cameraEntities.length : 0;
return (
<Key
action={(s) =>
Array.from({ length: cameraCount }, (_, i) =>
actionPressed(s, `camera${i + 1}`),
).some((pressed) => pressed)
}
input={
cameraCount === 1 ? "1" : <>1&thinsp;&ndash;&thinsp;{cameraCount}</>
}
label="Select camera"
labelPosition="right"
/>
);
}
function FreeFlyOverlay() {
const isPointerLocked = usePointerLocked();
const dataSource = useDataSource();
const isMapMode = dataSource === "map";
const cameraEntities = useGameEntitiesByRenderType("Camera");
const cameraCount = isMapMode ? cameraEntities.length : 0;
return (
<>
<MoveKeys />
<LookKeys />
<div className={styles.Column} data-height="compact">
<div className={styles.Row}>
<FlySpeedKey />
</div>
<div className={styles.Row}>
<PointerLockKey />
</div>
</div>
<div className={styles.Column} data-height="compact">
{!isPointerLocked ? (
<div className={styles.Row}>
<RotateCameraKey />
</div>
) : null}
{cameraCount > 0 && (
<div className={styles.Row}>
<SelectCameraKey />
</div>
)}
</div>
</>
);
}
function DemoOverlay() {
const isPlaying = useIsPlaying();
const speed = useSpeed();
const nextSpeedIndex = SPEED_OPTIONS.indexOf(speed) + 1;
const prevSpeedIndex = SPEED_OPTIONS.indexOf(speed) - 1;
const atMaxSpeed = nextSpeedIndex >= SPEED_OPTIONS.length;
const atMinSpeed = prevSpeedIndex < 0;
return (
<>
<div className={styles.Column}>
<div className={styles.Row}>
<Key
action="decreasePlaybackSpeed"
label="Slow down"
input={["<", ","]}
labelPosition="right"
disabled={atMinSpeed}
/>
<Key
action="playPause"
label={
isPlaying ? (
<GrPauseFill className={styles.PlayPauseIcon} />
) : (
<GrPlayFill className={styles.PlayPauseIcon} />
)
}
input="Space"
labelPosition="left"
size="auto"
/>
<Key
action="increasePlaybackSpeed"
input={[">", "."]}
label="Speed up"
labelPosition="left"
disabled={atMaxSpeed}
/>
</div>
</div>
<div className={styles.Column}>
<div className={styles.Row}>
<PointerLockKey />
</div>
</div>
</>
);
}
function TourOverlay() {
return (
<>
<div className={styles.Column}>
<div className={styles.Row}>
<Key
action="nextStop"
label="Skip to next stop"
input={<PiMouseLeftClickFill className={styles.MouseIcon} />}
labelPosition="right"
/>
<Key
action="exitTour"
label="Exit tour"
input="Esc"
labelPosition="right"
/>
</div>
</div>
</>
);
}
function ObserverOverlay() {
const inputMode = useInputMode();
const isPointerLocked = usePointerLocked();
return (
<>
{inputMode === "fly" ? <MoveKeys /> : null}
<LookKeys />
<div className={styles.Column} data-height="compact">
{inputMode === "fly" ? (
<div className={styles.Row}>
<FlySpeedKey />
</div>
) : null}
<div className={styles.Row}>
<PointerLockKey />
</div>
</div>
<div className={styles.Column} data-height="compact">
{!isPointerLocked ? (
<div className={styles.Row}>
<RotateCameraKey />
</div>
) : null}
{inputMode === "follow" && isPointerLocked ? (
<div className={styles.Row}>
<Key
action="nextPlayer"
label="Next player"
input={<PiMouseLeftClickFill className={styles.MouseIcon} />}
labelPosition="right"
inputSize="auto"
/>
</div>
) : null}
<div className={styles.Row}>
<Key
action="toggleObserverMode"
label={inputMode === "follow" ? "Fly mode" : "Follow mode"}
input="Space"
labelPosition="right"
inputSize="auto"
/>
</div>
</div>
</>
);
}
export function KeyboardOverlay() {
const recording = useRecording();
const inputMode = useInputMode();
const isTourActive = useCameraTour((s) => s.animation !== null);
const isDemo = recording?.source === "demo";
const isLive = recording?.source === "live";
const isMap = !recording;
const isLiveObserver =
isLive && (inputMode === "fly" || inputMode === "follow");
const showFreeFly = isMap && !isTourActive;
return (
<div className={styles.Root}>
{showFreeFly && <FreeFlyOverlay />}
{isLiveObserver && <ObserverOverlay />}
{isDemo && <DemoOverlay />}
{isTourActive && <TourOverlay />}
</div>
);
}

View file

@ -104,8 +104,10 @@
opacity: 0.6;
}
.ToggleSidebarButton:active {
.ToggleSidebarButton:active,
.ToggleSidebarButton[data-active="true"] {
opacity: 1;
transform: translateY(1px);
}
.ToggleSidebarButton[data-orientation="top"] {

View file

@ -31,7 +31,7 @@ import {
CurrentMission,
useMissionQueryState,
} from "@/src/components/useQueryParams";
import { InputProvider } from "./InputHandlers";
import { InputProvider } from "./InputProducer";
import { VisualInput } from "./VisualInput";
import { LoadingIndicator } from "./LoadingIndicator";
import { engineStore } from "../state/engineStore";
@ -180,6 +180,18 @@ export function MapInspector() {
}
}, [isTouch, isTourActive, setSidebarOpen]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.code === "Backslash" && (e.metaKey || e.ctrlKey)) {
e.stopPropagation();
e.preventDefault();
setSidebarOpen((open) => !open);
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [setSidebarOpen]);
const loadingProgress = missionLoadingProgress;
const isLoading = loadingProgress < 1;
@ -220,12 +232,7 @@ export function MapInspector() {
return (
<main className={styles.Frame}>
<RecordingProvider>
<header
className={styles.Toolbar}
onKeyDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
<header className={styles.Toolbar}>
<button
type="button"
className={styles.ToggleSidebarButton}
@ -286,13 +293,7 @@ export function MapInspector() {
</header>
{sidebarOpen ? <div className={styles.Backdrop} /> : null}
<Activity mode={sidebarOpen ? "visible" : "hidden"}>
<div
className={styles.Sidebar}
onKeyDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
data-open={sidebarOpen}
>
<div className={styles.Sidebar} data-open={sidebarOpen}>
<InspectorControls
missionName={missionName}
missionType={missionType}
@ -342,12 +343,7 @@ export function MapInspector() {
)}
</div>
</InputProvider>
<footer
className={styles.PlayerBar}
onKeyDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
<footer className={styles.PlayerBar}>
{recording?.source === "demo" ? (
<Suspense>
<DemoPlaybackControls />

View file

@ -10,6 +10,33 @@
text-align: center;
}
.TourAllButton {
align-self: flex-start;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 6px;
padding: 10px 14px;
font-family: inherit;
font-size: 14px;
font-weight: 500;
border: 0;
background: transparent;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
}
@media (hover: hover) {
.TourAllButton:hover {
/* background: rgba(2, 93, 153, 0.4); */
color: #fff;
}
}
.TourAllButton:active {
/* background: rgba(0, 85, 177, 0.5); */
}
.CategoryHeader {
display: flex;
align-items: baseline;

View file

@ -12,6 +12,8 @@ import styles from "./MapTourPanel.module.css";
import { BsPlayFill } from "react-icons/bs";
import { HiMiniArrowLeftEndOnRectangle } from "react-icons/hi2";
const ALL_FEATURES_TOUR = "__all__";
function selectTourState(state: {
animation: {
targets: TourTarget[];
@ -51,6 +53,28 @@ export function MapTourPanel() {
);
const tourState = useCameraTour(selectTourState, tourStateEqual);
const allTargets = useMemo(() => {
// Build a lookup from target → category index for sorting by type.
const categoryIndex = new Map<TourTarget, number>();
for (let i = 0; i < categories.length; i++) {
for (const target of categories[i].targets) {
categoryIndex.set(target, i);
}
}
// Sort by [team, type, name] with "no team" (undefined/0) last.
return categories
.flatMap((c) => c.targets)
.sort((a, b) => {
const aTeam = a.teamId != null && a.teamId > 0 ? a.teamId : Infinity;
const bTeam = b.teamId != null && b.teamId > 0 ? b.teamId : Infinity;
if (aTeam !== bTeam) return aTeam - bTeam;
const aCat = categoryIndex.get(a) ?? 0;
const bCat = categoryIndex.get(b) ?? 0;
if (aCat !== bCat) return aCat - bCat;
return a.label.localeCompare(b.label);
});
}, [categories]);
if (categories.length === 0) {
return (
<div className={styles.Root}>
@ -59,8 +83,37 @@ export function MapTourPanel() {
);
}
const isTouringAll =
tourState !== null && tourState.categoryName === ALL_FEATURES_TOUR;
const handleTourAllClick = () => {
if (isTouringAll) {
cameraTourStore.getState().cancel();
} else {
cameraTourStore.getState().startTour(allTargets, ALL_FEATURES_TOUR);
}
};
return (
<div className={styles.Root}>
<button
type="button"
className={styles.TourAllButton}
data-active={isTouringAll}
onClick={handleTourAllClick}
>
{isTouringAll ? (
<>
<HiMiniArrowLeftEndOnRectangle className={styles.ExitIcon} /> Exit
tour
</>
) : (
<>
<BsPlayFill className={styles.PlayIcon} />{" "}
<span className={styles.ButtonLabel}>Tour all features</span>
</>
)}
</button>
{categories.map((category) => (
<CategoryGroup
key={category.name}
@ -120,8 +173,8 @@ function CategoryGroup({
const isActive =
(isTouringCategory && tourState!.currentIndex === index) ||
(tourState !== null &&
tourState.targets.length === 1 &&
tourState.targets[0].entityId === target.entityId);
tourState.targets[tourState.currentIndex]?.entityId ===
target.entityId);
return (
<button
key={target.entityId}

View file

@ -213,7 +213,8 @@ export function MissionSelect({
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
if (e.code === "KeyK" && (e.metaKey || e.ctrlKey)) {
e.stopPropagation();
e.preventDefault();
inputRef.current?.focus();
combobox.show();
@ -283,7 +284,14 @@ export function MissionSelect({
<Activity mode={isOpen ? "visible" : "hidden"}>
<div className={styles.Backdrop} />
</Activity>
<div className={styles.InputWrapper}>
<div
className={styles.InputWrapper}
onKeyDown={(event) => {
if (!event.metaKey) {
event.stopPropagation();
}
}}
>
<Combobox
ref={inputRef}
autoSelect
@ -322,6 +330,11 @@ export function MissionSelect({
fitViewport
autoFocusOnHide={false}
className={styles.Popover}
onKeyDown={(event) => {
if (!event.metaKey) {
event.stopPropagation();
}
}}
>
<ComboboxList className={styles.List}>
{filteredResults.type === "flat"

View file

@ -1,7 +1,5 @@
import { useEffect, useEffectEvent, useRef } from "react";
import { useEffect, useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useKeyboardControls } from "@react-three/drei";
import { PointerLockControls } from "three-stdlib";
import {
MAX_SPEED_MULTIPLIER,
MIN_SPEED_MULTIPLIER,
@ -11,99 +9,39 @@ import { useCameras } from "./CamerasProvider";
import { useInputContext } from "./InputContext";
import { useTouchDevice } from "./useTouchDevice";
import { cameraTourStore } from "../state/cameraTourStore";
import {
useInputAction,
useInputState,
clearInputDeltas,
type ActionState,
type DragState,
type KeyState,
type ScrollState,
} from "./InputControls";
export const Controls = {
forward: "forward",
backward: "backward",
left: "left",
right: "right",
up: "up",
down: "down",
lookUp: "lookUp",
lookDown: "lookDown",
lookLeft: "lookLeft",
lookRight: "lookRight",
camera1: "camera1",
camera2: "camera2",
camera3: "camera3",
camera4: "camera4",
camera5: "camera5",
camera6: "camera6",
camera7: "camera7",
camera8: "camera8",
camera9: "camera9",
} as const;
export type ControlName = (typeof Controls)[keyof typeof Controls];
export const KEYBOARD_CONTROLS = [
{ name: Controls.forward, keys: ["KeyW"] },
{ name: Controls.backward, keys: ["KeyS"] },
{ name: Controls.left, keys: ["KeyA"] },
{ name: Controls.right, keys: ["KeyD"] },
{ name: Controls.up, keys: ["Space"] },
{ name: Controls.down, keys: ["ShiftLeft", "ShiftRight"] },
{ name: Controls.lookUp, keys: ["ArrowUp"] },
{ name: Controls.lookDown, keys: ["ArrowDown"] },
{ name: Controls.lookLeft, keys: ["ArrowLeft"] },
{ name: Controls.lookRight, keys: ["ArrowRight"] },
{ name: Controls.camera1, keys: ["Digit1"] },
{ name: Controls.camera2, keys: ["Digit2"] },
{ name: Controls.camera3, keys: ["Digit3"] },
{ name: Controls.camera4, keys: ["Digit4"] },
{ name: Controls.camera5, keys: ["Digit5"] },
{ name: Controls.camera6, keys: ["Digit6"] },
{ name: Controls.camera7, keys: ["Digit7"] },
{ name: Controls.camera8, keys: ["Digit8"] },
{ name: Controls.camera9, keys: ["Digit9"] },
];
const TOUR_CANCEL_KEYS = new Set([
"KeyW", "KeyA", "KeyS", "KeyD",
"Space", "ShiftLeft", "ShiftRight",
"ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight",
]);
const MIN_SPEED_ADJUSTMENT = 2;
const MAX_SPEED_ADJUSTMENT = 11;
const DRAG_THRESHOLD = 3; // px of movement before it counts as a drag
/** Hardcoded drag sensitivity (not affected by user setting). */
const DRAG_SENSITIVITY = 0.002;
export const ARROW_LOOK_SPEED = 1; // radians/sec
const MIN_SPEED_ADJUSTMENT = 1;
const MAX_SPEED_ADJUSTMENT = 11;
/** Hardcoded drag sensitivity for non-locked drag (not affected by user setting). */
const DRAG_SENSITIVITY = 0.002;
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)
const steps = Math.round(t * 15);
return (steps + 1) / 16;
}
function isPressed(state: Record<string, ActionState>, name: string): boolean {
const s = state[name];
return s != null && "pressed" in s && (s as KeyState).pressed;
}
export function MouseAndKeyboardHandler() {
const isTouch = useTouchDevice();
// Don't let KeyboardControls handle stuff when metaKey is held.
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
// Let Cmd/Ctrl+K pass through for search focus.
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
return;
}
if (e.metaKey) {
e.stopImmediatePropagation();
}
};
window.addEventListener("keydown", handleKey, { capture: true });
window.addEventListener("keyup", handleKey, { capture: true });
return () => {
window.removeEventListener("keydown", handleKey, { capture: true });
window.removeEventListener("keyup", handleKey, { capture: true });
};
}, []);
const {
speedMultiplier,
setSpeedMultiplier,
@ -112,269 +50,136 @@ export function MouseAndKeyboardHandler() {
invertDrag,
} = useControls();
const { onInput, mode } = useInputContext();
const [subscribe, getKeys] = useKeyboardControls<ControlName>();
const camera = useThree((state) => state.camera);
const [, getInputState] = useInputState();
const gl = useThree((state) => state.gl);
const { nextCamera, setCameraIndex, cameraCount } = useCameras();
const controlsRef = useRef<PointerLockControls | null>(null);
const getInvertScroll = useEffectEvent(() => invertScroll);
const getInvertDrag = useEffectEvent(() => invertDrag);
const getMode = useEffectEvent(() => mode);
const getMouseSensitivity = useEffectEvent(() => mouseSensitivity);
const getIsTouch = useEffectEvent(() => isTouch);
// Accumulated mouse deltas between frames.
const mouseDeltaYaw = useRef(0);
const mouseDeltaPitch = useRef(0);
const { setCameraIndex, cameraCount } = useCameras();
// Trigger flags set by event handlers, consumed in useFrame.
const triggerFire = useRef(false);
const triggerObserve = useRef(false);
// Setup pointer lock controls
// Exit pointer lock when switching to touch mode.
useEffect(() => {
const controls = new PointerLockControls(camera, gl.domElement);
controlsRef.current = controls;
return () => {
controls.dispose();
};
}, [camera, gl.domElement]);
// Exit pointer lock when switching to touch mode or when a tour starts.
useEffect(() => {
if (isTouch && controlsRef.current?.isLocked) {
controlsRef.current.unlock();
if (isTouch && document.pointerLockElement) {
document.exitPointerLock();
}
}, [isTouch]);
// Exit pointer lock when a tour starts.
useEffect(() => {
return cameraTourStore.subscribe((state) => {
if (state.animation && controlsRef.current?.isLocked) {
controlsRef.current.unlock();
if (state.animation && document.pointerLockElement) {
document.exitPointerLock();
}
});
}, []);
// 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;
let dragging = false;
let didDrag = false;
let startX = 0;
let startY = 0;
// Canvas click: lock pointer (only fires when not already locked).
useInputAction("canvasClick", () => {
if (!isTouch && !cameraTourStore.getState().animation) {
gl.domElement.requestPointerLock();
}
});
const handleMouseDown = (e: MouseEvent) => {
if (controlsRef.current?.isLocked) return;
if (e.target !== canvas) return;
dragging = true;
didDrag = false;
startX = e.clientX;
startY = e.clientY;
};
// Next player (live observer follow mode): fire trigger 0.
useInputAction("nextPlayer", () => {
triggerFire.current = true;
});
const handleMouseMove = (e: MouseEvent) => {
if (controlsRef.current?.isLocked) {
// Pointer is locked: accumulate raw deltas.
const sens = getMouseSensitivity();
mouseDeltaYaw.current += e.movementX * sens;
mouseDeltaPitch.current += e.movementY * sens;
return;
}
// Handle mousewheel for speed adjustment.
useInputAction("adjustSpeed", () => {
const scroll = getInputState().adjustSpeed as ScrollState | undefined;
if (!scroll || scroll.deltaY === 0) return;
if (!dragging) return;
if (
!didDrag &&
Math.abs(e.clientX - startX) < DRAG_THRESHOLD &&
Math.abs(e.clientY - startY) < DRAG_THRESHOLD
) {
return;
}
didDrag = true;
const scrollSign = invertScroll ? -1 : 1;
const direction = (scroll.deltaY > 0 ? -1 : 1) * scrollSign;
const scaledDeltaY = Math.ceil(Math.log2(Math.abs(scroll.deltaY) + 1));
const speedDelta =
Math.max(
MIN_SPEED_ADJUSTMENT,
Math.min(MAX_SPEED_ADJUSTMENT, scaledDeltaY),
) * direction;
// In follow/orbit mode, drag direction is reversed because the camera
// orbits around a target — dragging right should move the camera right
// (decreasing yaw), opposite of fly mode.
const orbitFlip = getMode() === "follow" ? -1 : 1;
const dragSign = (getInvertDrag() ? 1 : -1) * orbitFlip;
mouseDeltaYaw.current += dragSign * e.movementX * DRAG_SENSITIVITY;
mouseDeltaPitch.current += dragSign * e.movementY * DRAG_SENSITIVITY;
};
const handleMouseUp = () => {
dragging = false;
};
const handleClick = (e: MouseEvent) => {
const controls = controlsRef.current;
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 && !getIsTouch() && !cameraTourStore.getState().animation) {
controls?.lock();
}
};
canvas.addEventListener("mousedown", handleMouseDown);
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("click", handleClick);
return () => {
canvas.removeEventListener("mousedown", handleMouseDown);
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("click", handleClick);
};
}, [camera, gl.domElement, nextCamera, mode]);
// Handle number keys 1-9 for camera selection (local-only UI action).
useEffect(() => {
const cameraControls = [
Controls.camera1,
Controls.camera2,
Controls.camera3,
Controls.camera4,
Controls.camera5,
Controls.camera6,
Controls.camera7,
Controls.camera8,
Controls.camera9,
];
return subscribe((state) => {
for (let i = 0; i < cameraControls.length; i++) {
if (state[cameraControls[i]] && i < cameraCount) {
setCameraIndex(i);
break;
}
}
setSpeedMultiplier((prev) => {
const newSpeed = Math.round(prev * 100) + speedDelta;
return Math.max(
MIN_SPEED_MULTIPLIER,
Math.min(MAX_SPEED_MULTIPLIER, newSpeed / 100),
);
});
}, [subscribe, setCameraIndex, cameraCount]);
});
// Handle mousewheel for speed adjustment (local setting, stays in KMH).
useEffect(() => {
const handleWheel = (e: WheelEvent) => {
e.preventDefault();
const scrollSign = getInvertScroll() ? -1 : 1;
const direction = (e.deltaY > 0 ? -1 : 1) * scrollSign;
// scale deltaY in a way that feels natural for both trackpads (often just
// a deltaY of 1 at a time!) and scroll wheels (can be 100s or more).
const scaledDeltaY = Math.ceil(Math.log2(Math.abs(e.deltaY) + 1));
const delta =
Math.max(
MIN_SPEED_ADJUSTMENT,
Math.min(MAX_SPEED_ADJUSTMENT, scaledDeltaY),
) * direction;
setSpeedMultiplier((prev) => {
const newSpeed = Math.round(prev * 100) + delta;
return Math.max(
MIN_SPEED_MULTIPLIER,
Math.min(MAX_SPEED_MULTIPLIER, newSpeed / 100),
);
});
};
const canvas = gl.domElement;
canvas.addEventListener("wheel", handleWheel, { passive: false });
return () => {
canvas.removeEventListener("wheel", handleWheel);
};
}, [gl.domElement, setSpeedMultiplier]);
// Escape or movement keys: cancel active camera tour.
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (!cameraTourStore.getState().animation) return;
if (e.code === "Escape" || TOUR_CANCEL_KEYS.has(e.code)) {
cameraTourStore.getState().cancel();
}
};
window.addEventListener("keydown", handleKey);
return () => window.removeEventListener("keydown", handleKey);
}, []);
// Handle number keys 1-9 for camera selection.
const selectCamera = (i: number) => {
if (i < cameraCount) setCameraIndex(i);
};
useInputAction("camera1", () => selectCamera(0));
useInputAction("camera2", () => selectCamera(1));
useInputAction("camera3", () => selectCamera(2));
useInputAction("camera4", () => selectCamera(3));
useInputAction("camera5", () => selectCamera(4));
useInputAction("camera6", () => selectCamera(5));
useInputAction("camera7", () => selectCamera(6));
useInputAction("camera8", () => selectCamera(7));
useInputAction("camera9", () => selectCamera(8));
// '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]);
useInputAction("toggleObserverMode", () => {
triggerObserve.current = true;
});
// Build and emit InputFrame each render frame.
useFrame((_state, delta) => {
// Suppress all input while a camera tour is active.
if (cameraTourStore.getState().animation) return;
const {
forward,
backward,
left,
right,
up,
down,
lookUp,
lookDown,
lookLeft,
lookRight,
} = getKeys();
const inputState = getInputState();
// ── Look deltas ──
let deltaYaw = 0;
let deltaPitch = 0;
// Pointer-locked mouse movement (raw deltas, user sensitivity).
const locked = inputState.lockedLook as DragState | undefined;
if (locked && (locked.deltaX !== 0 || locked.deltaY !== 0)) {
deltaYaw = locked.deltaX * mouseSensitivity;
deltaPitch = locked.deltaY * mouseSensitivity;
}
// Drag-to-look (unlocked, fixed sensitivity, orbit flip in follow mode).
const drag = inputState.dragLook as DragState | undefined;
if (drag?.dragging && (drag.deltaX !== 0 || drag.deltaY !== 0)) {
const orbitFlip = mode === "follow" ? -1 : 1;
const dragSign = (invertDrag ? 1 : -1) * orbitFlip;
deltaYaw += dragSign * drag.deltaX * DRAG_SENSITIVITY;
deltaPitch += dragSign * drag.deltaY * DRAG_SENSITIVITY;
}
// Arrow keys contribute to look deltas.
let deltaYaw = mouseDeltaYaw.current;
let deltaPitch = mouseDeltaPitch.current;
mouseDeltaYaw.current = 0;
mouseDeltaPitch.current = 0;
if (isPressed(inputState, "lookLeft")) deltaYaw -= ARROW_LOOK_SPEED * delta;
if (isPressed(inputState, "lookRight"))
deltaYaw += ARROW_LOOK_SPEED * delta;
if (isPressed(inputState, "lookUp")) deltaPitch -= ARROW_LOOK_SPEED * delta;
if (isPressed(inputState, "lookDown"))
deltaPitch += ARROW_LOOK_SPEED * delta;
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]).
// ── Movement axes ──
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;
if (isPressed(inputState, "moveLeft")) x -= 1;
if (isPressed(inputState, "moveRight")) x += 1;
if (isPressed(inputState, "moveForward")) y += 1;
if (isPressed(inputState, "moveBackward")) y -= 1;
if (isPressed(inputState, "moveUp")) z += 1;
if (isPressed(inputState, "moveDown")) 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.
// ── Triggers ──
const triggers = [false, false, false, false, false, false];
if (triggerFire.current) {
triggers[0] = true;
@ -385,6 +190,9 @@ export function MouseAndKeyboardHandler() {
triggerObserve.current = false;
}
// Always clear deltas so stale values don't linger in the store.
clearInputDeltas();
// Only emit if there's actual input.
const hasLook = deltaYaw !== 0 || deltaPitch !== 0;
const hasMove = x !== 0 || y !== 0 || z !== 0;

View file

@ -1,4 +1,6 @@
import { useStore } from "zustand";
import { useEngineSelector } from "../state/engineStore";
import { streamPlaybackStore } from "../state/streamPlaybackStore";
import { DEFAULT_TEAM_NAMES } from "../stringUtils";
import { textureToUrl } from "../loaders";
import type { StreamEntity, TeamScore, WeaponsHudSlot } from "../stream/types";
@ -410,18 +412,24 @@ export function PlayerHUD() {
const hasControlPlayer = useEngineSelector(
(state) => !!state.playback.streamSnapshot?.controlPlayerGhostId,
);
const cameraMode = useStore(streamPlaybackStore, (s) => s.cameraMode);
// In free-fly mode the camera is disconnected from the player, so
// player-specific HUD elements (health, energy, weapons, etc.) are hidden.
const showPlayerElements =
hasControlPlayer && cameraMode !== "freeFly";
return (
<div className={styles.PlayerHUD}>
<ChatWindow />
{hasControlPlayer && (
{showPlayerElements && (
<div className={styles.Bars}>
<HealthBar />
<EnergyBar />
</div>
)}
<Compass />
{hasControlPlayer && (
{showPlayerElements && (
<>
<WeaponHUD />
<PackAndInventoryHUD />

View file

@ -10,7 +10,6 @@ import {
LoopOnce,
LoopRepeat,
Object3D,
Audio as ThreeAudio,
PositionalAudio,
Vector3,
} from "three";
@ -43,10 +42,7 @@ import {
} from "./AudioEmitter";
import type { ResolvedAudioProfile } from "./AudioEmitter";
import { audioToUrl } from "../loaders";
import { createLogger } from "../logger";
import { useSettings } from "./SettingsProvider";
const log = createLogger("PlayerModel");
import { useEngineStoreApi, useEngineSelector } from "../state/engineStore";
import { streamPlaybackStore } from "../state/streamPlaybackStore";
import type { PlayerEntity } from "../state/gameEntityTypes";
@ -495,7 +491,7 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
} catch {
// File not in manifest.
}
}, [audioLoader, entity.dataBlockId]);
}, [audioLoader, engineStore, entity.dataBlockId]);
// Cleanup jet sound on unmount.
useEffect(() => {
@ -740,12 +736,7 @@ export function PlayerModel({ entity }: { entity: PlayerEntity }) {
const jetSound = jetSoundRef.current;
const soundActuallyPlaying = jetSound?.isPlaying ?? false;
if (isJetting && !soundActuallyPlaying) {
if (
audioEnabled &&
audioListener &&
jetBufferRef.current &&
jetProfile
) {
if (audioEnabled && audioListener && jetBufferRef.current && jetProfile) {
let sound = jetSound;
if (!sound) {
sound = new PositionalAudio(audioListener);

View file

@ -2,6 +2,8 @@ import { useCallback, type ReactNode } from "react";
import type { StreamRecording } from "../stream/types";
import { useEngineSelector } from "../state/engineStore";
export const SPEED_OPTIONS = [0.25, 0.5, 1, 2, 4];
export function RecordingProvider({ children }: { children: ReactNode }) {
return <>{children}</>;
}

Some files were not shown because too many files have changed in this diff Show more