xxscreeps-mod-client 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ Environment variables:
23
23
  | Mount path | `SCREEPS_MOD_CLIENT_MOUNT_PATH` | `/` |
24
24
  | Redirect `/` → mount path | `SCREEPS_MOD_CLIENT_ROOT_REDIRECT` | `true` if `mountPath !== '/'`, else `false` |
25
25
 
26
- When mounted at `/`, the mod only serves paths that map to an existing file in `dist/` or that look like an SPA route (no file extension). Requests to xxscreeps' own endpoints (`/api/`, `/socket/`, …) are passed through to subsequent middleware.
26
+ When mounted at `/`, the mod only serves paths that map to an existing file in the client bundle or that look like an SPA route (no file extension). Requests to xxscreeps' own endpoints (`/api/`, `/socket/`, …) are passed through to subsequent middleware.
27
27
 
28
28
  ### Example
29
29
 
@@ -31,18 +31,9 @@ When mounted at `/`, the mod only serves paths that map to an existing file in `
31
31
  SCREEPS_MOD_CLIENT_MOUNT_PATH=/play npx xxscreeps start
32
32
  ```
33
33
 
34
- ## Build
34
+ ## How it works
35
35
 
36
- ```sh
37
- pnpm --filter xxscreeps-mod-client build
38
- ```
39
-
40
- This builds `screeps-client` with `base=/` (absolute asset URLs at the server root) and copies the artifacts into `dist/`. The shipped bundle is tied to its build-time base path — overriding `SCREEPS_MOD_CLIENT_MOUNT_PATH` at runtime requires a rebuild with a matching base:
41
-
42
- ```sh
43
- SCREEPS_MOD_CLIENT_BUILD_BASE=/play/ pnpm --filter xxscreeps-mod-client build
44
- SCREEPS_MOD_CLIENT_MOUNT_PATH=/play npx xxscreeps start
45
- ```
36
+ The mod resolves the client bundle from its [`screeps-client`](../screeps-client) dependency at runtime — no separate build step is needed. The shipped bundle is built with `base=/` (absolute asset URLs at the server root), which means non-default mount paths require a custom build of `screeps-client` with a matching base.
46
37
 
47
38
  ## xxscreeps mode
48
39
 
package/backend.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import path from 'node:path'
2
- import { fileURLToPath } from 'node:url'
2
+ import { createRequire } from 'node:module'
3
3
  import { createReadStream, existsSync, statSync } from 'node:fs'
4
4
  import { hooks } from 'xxscreeps/backend/index.js'
5
5
 
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
- const distDir = path.join(__dirname, 'dist')
6
+ const require = createRequire(import.meta.url)
7
+ const clientPkgPath = require.resolve('screeps-client/package.json')
8
+ const distDir = path.join(path.dirname(clientPkgPath), 'dist', 'xxscreeps-mod')
8
9
  const indexFile = path.join(distDir, 'index.html')
9
10
 
10
11
  const CONTENT_TYPES = {
@@ -64,7 +65,7 @@ function sendFile(ctx, filePath, stat) {
64
65
 
65
66
  hooks.register('middleware', koa => {
66
67
  if (!existsSync(indexFile)) {
67
- console.error(`[xxscreeps-mod-client] dist/index.html not found at ${indexFile}. Run "pnpm --filter xxscreeps-mod-client build" first.`)
68
+ console.error(`[xxscreeps-mod-client] client bundle not found at ${indexFile}. Run "pnpm --filter screeps-client build:embedded:xxscreeps" first.`)
68
69
  return
69
70
  }
70
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xxscreeps-mod-client",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "xxscreeps mod that serves the screeps-client and connects it to the same server.",
5
5
  "type": "module",
6
6
  "xxscreeps": true,
@@ -10,8 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "index.js",
13
- "backend.js",
14
- "dist/"
13
+ "backend.js"
15
14
  ],
16
15
  "repository": {
17
16
  "type": "git",
@@ -23,6 +22,9 @@
23
22
  "engines": {
24
23
  "node": ">=20"
25
24
  },
25
+ "dependencies": {
26
+ "screeps-client": "^0.3.0"
27
+ },
26
28
  "peerDependencies": {
27
29
  "xxscreeps": "*"
28
30
  },
@@ -30,9 +32,5 @@
30
32
  "xxscreeps": {
31
33
  "optional": true
32
34
  }
33
- },
34
- "scripts": {
35
- "build": "node ./scripts/build-client.js && node ./scripts/copy-dist.js",
36
- "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\""
37
35
  }
38
36
  }
@@ -1 +0,0 @@
1
- import{f as E,e as w,j as r,s as c,u as ee,g as te,t as u,h as oe}from"./index-BKZ4Y5Yn.js";import{l as f,e as ie,g as ne,c as re,r as le,h as P,f as b,j as $,F as R,S as j,n as se,E as de}from"./vendor-codemirror-BbK0dVBs.js";import"./vendor-pixi-CqiKBxHw.js";var ae=u("<span style=font-size:11px;color:#e3b341>Unsaved changes"),ce=u('<div style="padding:12px 10px;font-size:12px;color:#484f58;font-style:italic">Loading…'),fe=u("<div style=flex:1;display:flex;align-items:center;justify-content:center;color:#484f58;font-size:13px;font-style:italic>"),ue=u('<div style=position:absolute;inset:0px;background:#0d1117;z-index:100;display:flex;flex-direction:column;overflow:hidden><div style="display:flex;align-items:center;gap:12px;padding:10px 16px;border-bottom:1px solid #30363d;flex-shrink:0"><span style=font-size:15px;font-weight:600;color:#c9d1d9>Code</span><select style="background:#010409;color:#c9d1d9;border:1px solid #30363d;border-radius:4px;padding:4px 8px;font-size:12px;cursor:pointer"></select><div style=flex:1></div><button style="padding:5px 14px;border-radius:4px;border:1px solid #238636;font-size:12px;font-weight:600"></button><button style="background:transparent;border:none;color:#8b949e;font-size:18px;cursor:pointer;line-height:1;padding:2px 6px">✕</button></div><div style=flex:1;display:flex;overflow:hidden><div style="width:180px;flex-shrink:0;border-right:1px solid #21262d;display:flex;flex-direction:column;overflow:hidden"><div style="padding:6px 10px;font-size:10px;font-weight:700;color:#8b949e;text-transform:uppercase;letter-spacing:0.06em;border-bottom:1px solid #21262d;flex-shrink:0">Modules</div><div style=flex:1;overflow:auto></div></div><div style=flex:1;display:flex;flex-direction:column;overflow:hidden><div style="padding:5px 14px;font-size:12px;color:#8b949e;border-bottom:1px solid #21262d;flex-shrink:0;font-family:monospace;background:#0d1117">.js</div><div style=flex:1;overflow:hidden;flex-direction:column>'),pe=u("<option>"),xe=u('<div style="padding:7px 12px;font-size:12px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">');const{error:L}=te("code"),ve=de.theme({"&":{height:"100%"},".cm-scroller":{"font-family":"'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace","font-size":"13px","line-height":"1.6",overflow:"auto"},".cm-gutters":{background:"#0d1117","border-right":"1px solid #21262d"},".cm-lineNumbers .cm-gutterElement":{color:"#484f58","min-width":"3em"},".cm-activeLineGutter":{background:"#161b22"},".cm-activeLine":{background:"#161b22"}}),he=[re,se(),le,ve];function ye(W){const[A,G]=f([]),[k,F]=f(""),[m,S]=f({}),[s,C]=f(""),[_,M]=f(!1),[p,B]=f(!1),[x,z]=f(!1),J=()=>Object.keys(m()),{editorView:U,ref:q,createExtension:H}=ie({onValueChange:o=>{const t=s();!t||m()[t]===o||(S(n=>({...n,[t]:o})),z(!0))}});ne(U,()=>m()[s()]??""),H(he),P(()=>{const o=E();o&&o.http.user.branches().then(t=>{G(t.list);const n=t.list.find(d=>d.activeWorld)??t.list[0];n&&F(n.branch)}).catch(t=>{L("branches failed:",t),w("Failed to load branches","error")})}),P(()=>{const o=k(),t=E();!o||!t||(M(!0),S({}),C(""),z(!1),t.http.user.code.get(o).then(n=>{const d=n.modules??{};S(d),C(Object.keys(d)[0]??"")}).catch(n=>{L("get failed:",n),w("Failed to load code","error")}).finally(()=>M(!1)))});const I=()=>{const o=E(),t=k();!o||!t||(B(!0),o.http.user.code.set(t,m()).then(()=>{w("Code saved","success"),z(!1)}).catch(n=>{L("set failed:",n),w("Failed to save code","error")}).finally(()=>B(!1)))};return(()=>{var o=ue(),t=o.firstChild,n=t.firstChild,d=n.nextSibling,K=d.nextSibling,a=K.nextSibling,Q=a.nextSibling,X=t.nextSibling,V=X.firstChild,Y=V.firstChild,T=Y.nextSibling,D=V.nextSibling,y=D.firstChild,Z=y.firstChild,N=y.nextSibling;return d.addEventListener("change",e=>F(e.currentTarget.value)),r(d,b(R,{get each(){return A()},children:e=>(()=>{var i=pe();return r(i,()=>e.branch,null),r(i,()=>e.activeWorld?" ★":"",null),$(()=>i.value=e.branch),i})()})),r(t,b(j,{get when(){return x()},get children(){return ae()}}),a),a.$$click=I,r(a,()=>p()?"Saving…":"Save"),Q.$$click=()=>W.onClose(),r(T,b(j,{get when(){return _()},get children(){return ce()}}),null),r(T,b(R,{get each(){return J()},children:e=>(()=>{var i=xe();return i.$$click=()=>C(e),r(i,e),$(l=>{var v=s()===e?"#1f3158":"transparent",h=s()===e?"#58a6ff":"#c9d1d9",g=`2px solid ${s()===e?"#388bfd":"transparent"}`;return v!==l.e&&c(i,"background",l.e=v),h!==l.t&&c(i,"color",l.t=h),g!==l.a&&c(i,"border-left",l.a=g),l},{e:void 0,t:void 0,a:void 0}),i})()}),null),r(y,s,Z),ee(q,N),r(D,b(j,{get when(){return!s()},get children(){var e=fe();return r(e,()=>_()?"Loading code…":"Select a module"),e}}),null),$(e=>{var i=p()||_()||!x(),l=p()||!x()?"#161b22":"#1a3a2a",v=p()||!x()?"#484f58":"#3fb950",h=p()||!x()?"default":"pointer",g=s()?"block":"none",O=s()?"flex":"none";return i!==e.e&&(a.disabled=e.e=i),l!==e.t&&c(a,"background",e.t=l),v!==e.a&&c(a,"color",e.a=v),h!==e.o&&c(a,"cursor",e.o=h),g!==e.i&&c(y,"display",e.i=g),O!==e.n&&c(N,"display",e.n=O),e},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0}),$(()=>d.value=k()),o})()}oe(["click"]);export{ye as CodePanel};
@@ -1 +0,0 @@
1
- var ce=Object.defineProperty;var he=(a,t,e)=>t in a?ce(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var w=(a,t,e)=>he(a,typeof t!="symbol"?t+"":t,e);import{B as de,d as me,c as fe,E as ue,O as we,a as ie,b as se,p as O,T as ge,i as U,g as oe,f as I,o as be,w as X,n as ye,m as ve,l as Re,q,u as xe,k as pe,t as Se}from"./index-BKZ4Y5Yn.js";import{l as V,h as D,q as Ce,p as H}from"./vendor-codemirror-BbK0dVBs.js";import{A as Me,a as F,G as Y,c as $,S as J,T as j}from"./vendor-pixi-CqiKBxHw.js";const ne="screeps-terrain-v1";async function Le(a,t,e){try{const o=await caches.open(ne),i=`/terrain/${a}_${t}_z${e}.webp`,s=new Request(i),n=await o.match(s);if(n)return await n.blob()}catch{}return null}async function Oe(a,t,e,o){try{const i=await caches.open(ne),s=`/terrain/${a}_${t}_z${e}.webp`,n=new Request(s),r=new Response(o);await i.put(n,r)}catch{}}async function Be(a){const t=new OffscreenCanvas(a.width,a.height),e=t.getContext("2d");if(!e)throw new Error("OffscreenCanvas 2D context not available");return e.drawImage(a,0,0),await t.convertToBlob({type:"image/webp"})}async function ke(a){return await createImageBitmap(a)}function Te(a){return new Worker("/assets/terrain.worker-D9zNeJfz.js",{name:a==null?void 0:a.name})}const{warn:De}=oe("MapRenderer"),ae=3,f=ae*50,Ee=[50,60,70,80,90,100,110],G=1,z=50,Ae=2600,_e=5,Q=.2,ee=5,Ie=ue,Ge=we,Ye=ie,Xe=se,He=4491519,$e=new Set(["w","r","pb","p","s","c","m","k"]),ze={H:13421772,O:13421772,U:5809919,L:4176208,K:10711543,Z:13801762,X:16273737},Ne=[16,24,32,40];class Ze{constructor(t){w(this,"app");w(this,"world");w(this,"boundsGraphics",null);w(this,"selectionGraphics",null);w(this,"safeModeGraphics",null);w(this,"safeModeRooms",new Set);w(this,"activeRooms",new Map);w(this,"roomPool",[]);w(this,"terrainBaked",new Set);w(this,"terrainData",new Map);w(this,"worker");w(this,"pendingBakes",new Map);w(this,"nextBakeId",0);w(this,"currentShard","shard0");w(this,"callbacks");w(this,"resizeObserver",null);w(this,"_destroyed",!1);w(this,"showRoomNames",!1);w(this,"worldBoundsSet",null);w(this,"lastVisibleBounds",null);w(this,"animTargetX",0);w(this,"animTargetY",0);w(this,"isAnimating",!1);w(this,"overlayMode","owner");w(this,"isDragging",!1);w(this,"hasDragged",!1);w(this,"dragStartX",0);w(this,"dragStartY",0);w(this,"dragWorldX",0);w(this,"dragWorldY",0);w(this,"lastVisibleKey","");w(this,"visibleDebounceTimer",null);w(this,"selectedRoom",null);w(this,"currentUserId",null);w(this,"badgeCache",new de);w(this,"lastCheckX",0);w(this,"lastCheckY",0);w(this,"lastCheckScale",0);this.app=new Me,this.callbacks=t,this.worker=new Te,this.worker.onmessage=e=>{const{id:o,bitmap:i}=e.data,s=this.pendingBakes.get(o);s&&(this.pendingBakes.delete(o),s.resolve(i))}}async init(t){const e=t.parentElement??t,{width:o,height:i}=e.getBoundingClientRect();await this.app.init({canvas:t,width:o,height:i,background:me,antialias:!1,preference:"webgl"}),this.resizeObserver=new ResizeObserver(s=>{const{width:n,height:r}=s[0].contentRect,d=this.app.screen.width,h=this.app.screen.height;this.app.renderer.resize(n,r),this.world.x+=(n-d)/2,this.world.y+=(r-h)/2}),this.resizeObserver.observe(e),this.world=new F,this.app.stage.addChild(this.world),this.boundsGraphics=new Y,this.world.addChild(this.boundsGraphics),this.selectionGraphics=new Y,this.world.addChild(this.selectionGraphics),this.safeModeGraphics=new Y,this.world.addChild(this.safeModeGraphics),this.setupInteraction(),this.app.ticker.add(()=>{if(this.isAnimating){const s=this.animTargetX-this.world.x,n=this.animTargetY-this.world.y;Math.abs(s)<.5&&Math.abs(n)<.5?(this.world.x=this.animTargetX,this.world.y=this.animTargetY,this.isAnimating=!1):(this.world.x+=s*.2,this.world.y+=n*.2)}this.checkVisibleRooms()})}get zoom(){var t;return((t=this.world)==null?void 0:t.scale.x)??1}setZoom(t){var h,y;if(!this.world)return;t=Math.max(Q,Math.min(ee,t));const e=this.world.scale.x;if(t===e)return;const o=this.app.screen.width/2,i=this.app.screen.height/2,s=(o-this.world.x)/e,n=(i-this.world.y)/e,r=this.getLOD(),d=this.zoom>=G;this.world.scale.set(t),this.world.x=o-s*t,this.world.y=i-n*t,this.getLOD()!==r&&this.applyLOD(),t>=G!==d&&this.updateAllNameLabels(),this.updateAllRoomScales(t),(y=(h=this.callbacks).onZoomChanged)==null||y.call(h,t),this.setSelectedRoom(this.selectedRoom),this.redrawSafeMode()}centerOn(t,e,o=!1){const i=t*f+f/2,s=e*f+f/2,n=this.world.scale.x,r=this.app.screen.width/2-i*n,d=this.app.screen.height/2-s*n;o?(this.animTargetX=r,this.animTargetY=d,this.isAnimating=!0):(this.isAnimating=!1,this.world.x=r,this.world.y=d)}async getTerrainBitmap(t,e,o){const i=this.currentShard;try{const s=await Le(i,t,e);if(s)return await ke(s);const n=this.nextBakeId++,r=new Promise((h,y)=>{this.pendingBakes.set(n,{roomName:t,lod:e,resolve:h,reject:y})});this.worker.postMessage({id:n,roomName:t,lod:e,raw:o});const d=await r;return Be(d).then(h=>{Oe(i,t,e,h)}).catch(h=>De("failed to save to terrainCache:",h)),d}catch{return null}}async setRoomTerrain(t,e){const o=e.raw;let i=!1;for(let h=0;h<o.length;h++)if(o[h]!==0){i=!0;break}if(!i){this.terrainBaked.add(t);return}const s=this.getOrCreate(t),n=this.getLOD();n===0&&this.terrainData.set(t,o);const r=await this.getTerrainBitmap(t,n,o);if(!r)return;if(!this.activeRooms.has(t)){r.close();return}const d=$.from(r);n===0?(s.texLo&&!s.texLo.destroyed&&s.texLo.destroy(!0),s.texLo=d,this.getLOD()===0&&s.texLo&&(s.terrainSprite.texture=s.texLo)):(s.texHi&&!s.texHi.destroyed&&s.texHi.destroy(!0),s.texHi=d,this.getLOD()===1&&s.texHi&&(s.terrainSprite.texture=s.texHi)),s.terrainSprite.width=f,s.terrainSprite.height=f,this.terrainBaked.add(t)}getLOD(){return this.zoom<G?0:1}async applyLOD(){const t=this.getLOD()===1,e=[];for(const[o,i]of this.activeRooms)if(this.terrainBaked.has(o))if(t&&!i.texHi){const s=this.terrainData.get(o);s&&(this.terrainData.delete(o),e.push(this.getTerrainBitmap(o,1,s).then(n=>{n&&this.activeRooms.has(o)?(i.texHi=$.from(n),this.getLOD()===1&&(i.terrainSprite.texture=i.texHi)):n&&n.close()})))}else{const s=t?i.texHi:i.texLo;s&&!s.destroyed&&(i.terrainSprite.texture=s)}await Promise.all(e)}setRoomMap2(t,e,o="live"){const s=this.getOrCreate(t).map2Graphics;s.alpha=o==="cache"?.6:1;const n=ae;s.clear();const r=e.r??[];for(const[R,v]of r)s.rect(R*n,v*n,n,n);r.length&&s.fill(fe);const d=e.w??[];for(const[R,v]of d)s.rect(R*n+.5,v*n+.5,n-1,n-1);d.length&&s.fill(4486980);const h=e.s??[];for(const[R,v]of h)s.circle((R+.5)*n,(v+.5)*n,2.5);h.length&&s.fill(Ie);const y=e.c??[];for(const[R,v]of y)s.circle((R+.5)*n,(v+.5)*n,2);y.length&&s.fill(Ge);const p=e.m??[];for(const[R,v]of p)s.circle((R+.5)*n,(v+.5)*n,2);p.length&&s.fill(Ye);const S=e.k??[];for(const[R,v]of S)s.circle((R+.5)*n,(v+.5)*n,2);S.length&&s.fill(Xe);const B=e.pb??[];for(const[R,v]of B)s.circle((R+.5)*n,(v+.5)*n,1.5);B.length&&s.fill(se);const k=e;for(const R in k){if($e.has(R))continue;const v=k[R];if(!Array.isArray(v)||v.length===0)continue;for(const[W,E]of v)s.circle((W+.5)*n,(E+.5)*n,1);const Z=R===this.currentUserId?He:16711680;s.fill(Z)}}clearRoomMap2(t){var e;(e=this.activeRooms.get(t))==null||e.map2Graphics.clear()}clearAllMap2(){for(const t of this.activeRooms.values())t.map2Graphics.clear()}setRoomOwned(t,e){const i=this.getOrCreate(t).ownerOverlay;i.clear(),e&&(i.rect(0,0,f,f),i.fill({color:10027008,alpha:.18}))}async setRoomBadge(t,e,o){const i=this.activeRooms.get(t);if(i){if(!e){i.badgeSprite&&(i.container.removeChild(i.badgeSprite),i.badgeSprite.destroy(),i.badgeSprite=void 0),i.badgeLevel=void 0;return}try{const s=await this.badgeCache.getOrCreate(e);if(!this.activeRooms.has(t))return;if(i.badgeSprite){if(i.badgeSprite.texture===s){i.badgeLevel=o,this.applyBadgeSize(i,this.zoom),this.applyOverlayMode(i);return}i.badgeSprite.texture=s}else{const n=new J(s);n.anchor.set(.5),n.x=f/2,n.y=f/2;const r=i.container.getChildIndex(i.nameLabel);i.container.addChildAt(n,r),i.badgeSprite=n}i.badgeLevel=o,this.applyBadgeSize(i,this.zoom),this.applyOverlayMode(i)}catch{}}}applyBadgeSize(t,e){if(!t.badgeSprite||t.badgeLevel===void 0)return;const i=(Ee[t.badgeLevel-1]??24)*Math.min(1,e);t.badgeSprite.width=i/e,t.badgeSprite.height=i/e}setOverlayMode(t){if(this.overlayMode!==t){this.overlayMode=t;for(const e of this.activeRooms.values())this.applyOverlayMode(e)}}setRoomMineral(t,e,o){const i=this.activeRooms.get(t);if(!i)return;if(!e||!o){i.mineralCircle&&(i.container.removeChild(i.mineralCircle),i.mineralCircle.destroy(),i.mineralCircle=void 0),i.mineralLabel&&(i.container.removeChild(i.mineralLabel),i.mineralLabel.destroy(),i.mineralLabel=void 0),i.mineralDensity=void 0;return}const s=ze[e]??ie;if(i.mineralDensity=o,i.mineralColor=s,!i.mineralCircle){const n=new Y;n.x=f/2,n.y=f/2;const r=i.container.getChildIndex(i.nameLabel);i.container.addChildAt(n,r),i.mineralCircle=n}if(i.mineralLabel)i.mineralLabel.text=e;else{const n=new j({text:e,style:{fontSize:36,fill:16777215,fontFamily:"ui-monospace, monospace",fontWeight:"bold"}});n.anchor.set(.5),n.x=f/2,n.y=f/2,i.container.addChild(n),i.mineralLabel=n}this.applyMineralSize(i,this.zoom),this.applyOverlayMode(i)}applyOverlayMode(t){t.badgeSprite&&(t.badgeSprite.visible=this.overlayMode==="owner"),t.mineralCircle&&(t.mineralCircle.visible=this.overlayMode==="mineral"),t.mineralLabel&&(t.mineralLabel.visible=this.overlayMode==="mineral")}applyMineralSize(t,e){if(!t.mineralCircle||!t.mineralLabel||t.mineralDensity===void 0||t.mineralColor===void 0)return;const o=Math.max(.5,Math.min(1.5,e)),i=(Ne[t.mineralDensity-1]??24)*o,s=i/2/e;t.mineralCircle.clear(),t.mineralCircle.circle(0,0,1),t.mineralCircle.fill(t.mineralColor);const r=2/e;t.mineralCircle.stroke({color:0,width:r/s}),t.mineralCircle.scale.set(s);const h=i*.55/36/e;t.mineralLabel.scale.set(h)}updateNameLabelScale(t,e){t.nameLabel.scale.set(.5/e)}updateAllRoomScales(t){for(const e of this.activeRooms.values())this.applyBadgeSize(e,t),this.updateNameLabelScale(e,t),this.applyMineralSize(e,t)}setCurrentUser(t){this.currentUserId=t}setSelectedRoom(t){if(this.selectedRoom=t,!this.selectionGraphics||(this.selectionGraphics.clear(),this.world.removeChild(this.selectionGraphics),this.world.addChild(this.selectionGraphics),!t))return;const e=O(t);if(!e)return;const o=e.x*f,i=e.y*f;this.selectionGraphics.rect(o,i,f,f);const s=Math.max(2,Math.ceil(2/this.zoom));this.selectionGraphics.stroke({color:16777215,width:s,alignment:0})}setRoomSafeMode(t,e){e?this.safeModeRooms.add(t):this.safeModeRooms.delete(t),this.redrawSafeMode()}redrawSafeMode(){if(!this.safeModeGraphics||(this.safeModeGraphics.clear(),this.safeModeRooms.size===0))return;const t=Math.max(1,Math.ceil(1/this.zoom));for(const e of this.safeModeRooms){const o=O(e);if(!o)continue;const i=o.x*f,s=o.y*f;this.safeModeGraphics.rect(i,s,f,f),this.safeModeGraphics.stroke({color:16776960,width:t,alignment:1,alpha:.5})}this.world.removeChild(this.safeModeGraphics),this.world.addChild(this.safeModeGraphics)}setBounds(t,e,o,i){if(!this.boundsGraphics)return;this.worldBoundsSet={minX:t,maxX:e,minY:o,maxY:i},this.boundsGraphics.clear();const s=t*f,n=o*f,r=(e-t+1)*f,d=(i-o+1)*f;this.boundsGraphics.rect(s,n,r,d),this.boundsGraphics.stroke({color:ge,width:6,alignment:0}),this.updateAllNameLabels()}clearBounds(){var t;this.worldBoundsSet=null,(t=this.boundsGraphics)==null||t.clear(),this.updateAllNameLabels()}setShowRoomNames(t){this.showRoomNames=t,this.updateAllNameLabels()}hasRoom(t){return this.terrainBaked.has(t)}clearRoom(t){const e=this.activeRooms.get(t);e&&(e.texLo&&!e.texLo.destroyed&&e.texLo.destroy(!0),e.texHi&&!e.texHi.destroyed&&e.texHi.destroy(!0),e.texLo=null,e.texHi=null,e.terrainSprite.texture=$.EMPTY,e.map2Graphics.clear(),e.ownerOverlay.clear(),e.badgeSprite&&(e.container.removeChild(e.badgeSprite),e.badgeSprite.destroy(),e.badgeSprite=void 0),e.mineralCircle&&(e.container.removeChild(e.mineralCircle),e.mineralCircle.destroy(),e.mineralCircle=void 0),e.mineralLabel&&(e.container.removeChild(e.mineralLabel),e.mineralLabel.destroy(),e.mineralLabel=void 0),e.mineralDensity=void 0,e.mineralColor=void 0,e.container.visible=!1,this.safeModeRooms.delete(t),this.terrainBaked.delete(t),this.terrainData.delete(t),this.activeRooms.delete(t),this.roomPool.length<Ae?this.roomPool.push(e):(this.world.removeChild(e.container),e.container.destroy({children:!0,context:!0})))}clearInvisibleRooms(t){const e=this.lastVisibleBounds;for(const o of[...this.activeRooms.keys()])if(!t.has(o)){if(e){const i=O(o);if(i&&i.x>=e.rxMin-z&&i.x<=e.rxMax+z&&i.y>=e.ryMin-z&&i.y<=e.ryMax+z)continue}this.clearRoom(o)}}destroy(){var t;if(!this._destroyed){this._destroyed=!0,(t=this.resizeObserver)==null||t.disconnect(),this.resizeObserver=null,this.visibleDebounceTimer!==null&&(clearTimeout(this.visibleDebounceTimer),this.visibleDebounceTimer=null);for(const[,e]of this.activeRooms)e.texLo&&!e.texLo.destroyed&&e.texLo.destroy(!0),e.texHi&&!e.texHi.destroyed&&e.texHi.destroy(!0);this.activeRooms.clear(),this.safeModeRooms.clear();for(const e of this.roomPool)e.texLo&&!e.texLo.destroyed&&e.texLo.destroy(!0),e.texHi&&!e.texHi.destroyed&&e.texHi.destroy(!0);this.roomPool.length=0,this.terrainBaked.clear(),this.terrainData.clear(),this.worker.terminate(),this.pendingBakes.clear(),this.badgeCache.destroy();try{this.app.destroy(!1,{children:!0,texture:!0,context:!0})}catch{}}}getOrCreate(t){const e=this.activeRooms.get(t);if(e)return e;const o=O(t);if(!o)throw new Error(`MapRenderer: invalid room "${t}"`);let i;if(this.roomPool.length>0)i=this.roomPool.pop();else{const s=new F;s.cullable=!0;const n=new J($.EMPTY),r=new Y,d=new Y;s.addChild(n),s.addChild(r),s.addChild(d);const h=new j({text:"",style:{fontSize:36,fill:9147550,fontFamily:"ui-monospace, monospace"}});h.scale.set(.25),h.x=2,h.y=2,s.addChild(h),this.world.addChild(s),i={container:s,terrainSprite:n,texLo:null,texHi:null,map2Graphics:r,ownerOverlay:d,nameLabel:h}}return i.container.x=o.x*f,i.container.y=o.y*f,i.container.visible=!0,i.nameLabel.text=t,i.nameLabel.visible=this.showRoomNames&&this.zoom>=G&&this.nameLabelShouldShow(o.x,o.y),this.updateNameLabelScale(i,this.zoom),i.badgeSprite&&(i.container.removeChild(i.badgeSprite),i.badgeSprite.destroy(),i.badgeSprite=void 0),i.mineralCircle&&(i.container.removeChild(i.mineralCircle),i.mineralCircle.destroy(),i.mineralCircle=void 0),i.mineralLabel&&(i.container.removeChild(i.mineralLabel),i.mineralLabel.destroy(),i.mineralLabel=void 0),i.mineralDensity=void 0,i.mineralColor=void 0,this.safeModeGraphics&&(this.world.removeChild(this.safeModeGraphics),this.world.addChild(this.safeModeGraphics)),this.selectionGraphics&&(this.world.removeChild(this.selectionGraphics),this.world.addChild(this.selectionGraphics)),this.activeRooms.set(t,i),i}nameLabelShouldShow(t,e){const o=this.worldBoundsSet;return!(o&&(t<o.minX||t>o.maxX||e<o.minY||e>o.maxY))}updateAllNameLabels(){const t=this.showRoomNames&&this.zoom>=G;for(const[e,o]of this.activeRooms){if(!t){o.nameLabel.visible=!1;continue}const i=O(e);i&&(o.nameLabel.visible=this.nameLabelShouldShow(i.x,i.y))}}setupInteraction(){const t=this.app.canvas;t.style.touchAction="none",t.style.userSelect="none",t.addEventListener("pointermove",e=>{if(this.isDragging)return;const o=t.getBoundingClientRect();this.emitHover(e.clientX-o.left,e.clientY-o.top)}),t.addEventListener("pointerleave",()=>{this.isDragging||this.callbacks.onRoomHover(null)}),t.addEventListener("pointerdown",e=>{e.preventDefault(),this.isAnimating=!1,this.isDragging=!0,this.hasDragged=!1,this.dragStartX=e.clientX,this.dragStartY=e.clientY,this.dragWorldX=this.world.x,this.dragWorldY=this.world.y;const o=n=>{const r=n.clientX-this.dragStartX,d=n.clientY-this.dragStartY;(Math.abs(r)>3||Math.abs(d)>3)&&(this.hasDragged=!0);const h=this.worldBoundsSet;if(h){const y=this.world.scale.x,p=50,S=p-(h.maxX+1)*f*y,B=this.app.screen.width-p-h.minX*f*y,k=p-(h.maxY+1)*f*y,R=this.app.screen.height-p-h.minY*f*y;this.world.x=this.rubberBand(this.dragWorldX+r,S,B),this.world.y=this.rubberBand(this.dragWorldY+d,k,R)}else this.world.x=this.dragWorldX+r,this.world.y=this.dragWorldY+d},i=n=>{if(!this.hasDragged){const r=t.getBoundingClientRect(),d=this.screenToRoom(n.clientX-r.left,n.clientY-r.top);d&&this.callbacks.onRoomClick(d)}s()},s=()=>{this.isDragging=!1,this.springBack(),window.removeEventListener("pointermove",o),window.removeEventListener("pointerup",i),window.removeEventListener("pointercancel",s)};window.addEventListener("pointermove",o),window.addEventListener("pointerup",i),window.addEventListener("pointercancel",s)}),t.addEventListener("wheel",e=>{var y,p;if(e.preventDefault(),this.isDragging)return;this.isAnimating=!1;const o=this.world.scale.x,i=e.deltaY<0?1.1:.9,s=Math.max(Q,Math.min(ee,o*i)),n=(e.offsetX-this.world.x)/o,r=(e.offsetY-this.world.y)/o,d=this.getLOD(),h=this.zoom>=G;this.world.scale.set(s),this.world.x=e.offsetX-n*s,this.world.y=e.offsetY-r*s,this.getLOD()!==d&&this.applyLOD(),s>=G!==h&&this.updateAllNameLabels(),this.updateAllRoomScales(s),(p=(y=this.callbacks).onZoomChanged)==null||p.call(y,s),this.setSelectedRoom(this.selectedRoom),this.redrawSafeMode()},{passive:!1})}rubberBand(t,e,o){const i=o-e;if(i<=0||t>=e&&t<=o)return t;const s=.55;if(t<e){const r=e-t;return e-(1-1/(r*s/i+1))*i*s}const n=t-o;return o+(1-1/(n*s/i+1))*i*s}springBack(){const t=this.worldBoundsSet;if(!t)return;const e=this.world.scale.x,o=this.app.screen.width,i=this.app.screen.height,s=50,n=this.world.x+t.minX*f*e,r=this.world.x+(t.maxX+1)*f*e,d=this.world.y+t.minY*f*e,h=this.world.y+(t.maxY+1)*f*e;let y=this.world.x,p=this.world.y;r<s?y+=s-r:n>o-s&&(y-=n-(o-s)),h<s?p+=s-h:d>i-s&&(p-=d-(i-s)),(y!==this.world.x||p!==this.world.y)&&(this.animTargetX=y,this.animTargetY=p,this.isAnimating=!0)}screenToRoom(t,e){const o=this.world.scale.x,i=(t-this.world.x)/o,s=(e-this.world.y)/o,n=Math.floor(i/f),r=Math.floor(s/f);return U(n,r)}emitHover(t,e){this.callbacks.onRoomHover(this.screenToRoom(t,e))}checkVisibleRooms(){const t=this.world.scale.x,e=this.world.x,o=this.world.y;if(this.lastCheckX===e&&this.lastCheckY===o&&this.lastCheckScale===t)return;this.lastCheckX=e,this.lastCheckY=o,this.lastCheckScale=t;const i=-e/t,s=-o/t,n=(this.app.screen.width-e)/t,r=(this.app.screen.height-o)/t,d=Math.floor(i/f)-1,h=Math.ceil(n/f),y=Math.floor(s/f)-1,p=Math.ceil(r/f);this.lastVisibleBounds={rxMin:d,rxMax:h,ryMin:y,ryMax:p};const S=[];for(let k=d;k<=h;k++)for(let R=y;R<=p;R++){const v=U(k,R);v&&S.push(v)}const B=`${d},${y},${h},${p}`;B!==this.lastVisibleKey&&(this.lastVisibleKey=B,this.visibleDebounceTimer!==null&&clearTimeout(this.visibleDebounceTimer),this.visibleDebounceTimer=setTimeout(()=>{this.visibleDebounceTimer=null,this.callbacks.onVisibleRoomsChanged(S)},_e))}}var We=Se("<div style=width:100%;height:100%;position:relative><canvas style=display:block>");const{log:N,error:te}=oe("map");function qe(a){let t,e=null;const[o,i]=V([]),[s,n]=V(1),r=()=>a.originRoom,[d,h]=V(r()??null);let y=null,p=null;const S=new Map,B=new Map,k=new Map,R=l=>{const c=X();if(!c)return!0;const g=O(l);return!!g&&pe(g.x,g.y,c)},v=l=>{var g;const c=B.get(l);return{room:l,owner:(c==null?void 0:c.username)??((g=c==null?void 0:c.own)!=null&&g.user?`user:${c.own.user}`:null),mineral:(c==null?void 0:c.mineral)??null,density:(c==null?void 0:c.density)??null}},Z=200,W=0;let E=[],_=null;const K=()=>{_=null;const l=I();if(!l||!e)return;const c=new Set(o());if(E=E.filter(m=>c.has(m)&&!e.hasRoom(m)),E.length===0)return;const g=E.splice(0,Z);l.stores.room.terrainBulk(g,a.shard).then(m=>{for(const[u,b]of m)e==null||e.setRoomTerrain(u,b)}).catch(m=>te("terrain fetch failed:",m)).finally(()=>{E.length>0&&(_=setTimeout(K,W))})};return D(()=>{I(),a.shard,B.clear()}),Ce(()=>{t&&(async()=>{var c,g;if(e=new Ze({onRoomHover:m=>{var u;(u=a.onHoveredRoomChanged)==null||u.call(a,m?v(m):null)},onRoomClick:m=>{var u;d()!==m?(h(m),e==null||e.setSelectedRoom(m),(u=a.onSelectedRoomChanged)==null||u.call(a,v(m))):R(m)&&setTimeout(()=>a.onNavigateToRoom(m),0)},onVisibleRoomsChanged:m=>{const u=m.length===0;p!==u&&(p=u,u||N(`zoom in — terrain loading active, ${m.length} rooms visible`)),i(m)},onZoomChanged:m=>{var u;n(m),(u=a.onZoomChanged)==null||u.call(a,m)}}),await e.init(t),!e)return;a.initialZoom!==void 0&&a.initialZoom>0&&e.setZoom(a.initialZoom),(c=a.onZoomChanged)==null||c.call(a,e.zoom);const l=X();if(l&&e.setBounds(l.minX,l.maxX,l.minY,l.maxY),a.originRoom){const m=O(a.originRoom);m&&e.centerOn(m.x,m.y),e.setSelectedRoom(a.originRoom),(g=a.onSelectedRoomChanged)==null||g.call(a,v(a.originRoom))}else{const m=I();if(m)try{const u=await m.http.user.worldStartRoom(a.shard??"shard0");if(!e)return;const b=Array.isArray(u==null?void 0:u.room)?u.room[0]:u==null?void 0:u.room;if(typeof b=="string"){const x=O(b);x&&e.centerOn(x.x,x.y)}}catch(u){te("worldStartRoom failed:",u)}}})()}),H(()=>{_!==null&&(clearTimeout(_),_=null),E=[];for(const l of S.values())l.dispose();S.clear(),e==null||e.destroy(),e=null}),D(()=>{be({currentRoom:d,worldBounds:X,onMove:(g,m)=>{var b,x;const u=U(g,m);h(u),e==null||e.setSelectedRoom(u),e==null||e.centerOn(g,m,!0),(b=a.onSelectedRoomChanged)==null||b.call(a,v(u)),(x=a.onHoveredRoomChanged)==null||x.call(a,v(u))}});const c=g=>{var b,x;const m=((b=g.target)==null?void 0:b.tagName)??"",u=((x=g.target)==null?void 0:x.isContentEditable)??!1;if(!(m==="INPUT"||m==="TEXTAREA"||u)&&g.key==="m"){const C=d();C&&R(C)&&a.onNavigateToRoom(C)}};window.addEventListener("keydown",c),H(()=>window.removeEventListener("keydown",c))}),D(()=>{var x;const l=I(),c=o(),g=a.shard;if(!l||c.length===0)return;const m=new Set(c),u=c.filter(C=>!(e!=null&&e.hasRoom(C)));if(u.length>0){const C=c.reduce((T,A)=>{var L;return T+(((L=O(A))==null?void 0:L.x)??0)},0)/c.length,M=c.reduce((T,A)=>{var L;return T+(((L=O(A))==null?void 0:L.y)??0)},0)/c.length;E=u.slice().sort((T,A)=>{const L=O(T),P=O(A),re=L?Math.abs(L.x-C)+Math.abs(L.y-M):999,le=P?Math.abs(P.x-C)+Math.abs(P.y-M):999;return re-le}),_===null&&(_=setTimeout(K,0))}l.stores.mapStats.request(c,"owner0",g??void 0);const b=s()>=.4;if(b!==y&&(y=b,(x=a.onSubscriptionStateChanged)==null||x.call(a,b),b||e==null||e.clearAllMap2()),b){const C=new Set(c.map(M=>`${M}/${g}`));for(const[M,T]of S)C.has(M)||(T.dispose(),S.delete(M),e==null||e.clearRoomMap2(M.split("/")[0]));for(const M of c){const T=`${M}/${g}`;S.has(T)||S.set(T,l.stores.map.subscribeMap2(M,g))}}else{for(const[,C]of S)C.dispose();S.clear()}e==null||e.clearInvisibleRooms(m)}),D(()=>{e==null||e.setShowRoomNames(ye()),e&&(e.currentShard=a.shard??"shard0")}),D(()=>{e==null||e.setOverlayMode(ve())}),D(()=>{const l=I(),c=a.shard;l&&l.stores.server.worldInfo(c??void 0).then(g=>{N(`worldInfo(shard=${c??"none"}) — x: [${g.minX}, ${g.maxX}] y: [${g.minY}, ${g.maxY}]`),Re(g)}).catch(g=>{})}),D(()=>{var l;e==null||e.setCurrentUser(((l=q())==null?void 0:l._id)??null)}),D(()=>{const l=X();l?(e==null||e.setBounds(l.minX,l.maxX,l.minY,l.maxY),N(`worldBounds applied — shard: ${a.shard??"none"} x: [${l.minX}, ${l.maxX}] y: [${l.minY}, ${l.maxY}] (fetched for shard: ${l.shard??"none"})`)):(e==null||e.clearBounds(),N(`worldBounds — none (shard: ${a.shard??"none"})`))}),D(()=>{const l=I();if(!l)return;const c=l.stores.map.on("room:map2update",({room:g,shard:m,data:u,source:b})=>{m===a.shard&&(e==null||e.setRoomMap2(g,u,b))});H(()=>c.dispose())}),D(()=>{var u;const l=I();if(!l)return;const c=(u=q())==null?void 0:u._id,g=new Set(o()),m=l.stores.mapStats.on("mapStats:room",({room:b,stat:x})=>{var A,L;B.set(b,x);const C=!!(x.own&&x.own.user!==c);g.has(b)&&(e==null||e.setRoomOwned(b,C),e==null||e.setRoomSafeMode(b,!!x.safeMode)),e==null||e.setRoomMineral(b,x.mineral,x.density);const M=x.badge?JSON.stringify(x.badge):"";k.get(b)!==M&&(k.set(b,M),e==null||e.setRoomBadge(b,x.badge,(A=x.own)==null?void 0:A.level)),d()===b&&((L=a.onSelectedRoomChanged)==null||L.call(a,v(b)))});H(()=>m.dispose())}),(()=>{var l=We(),c=l.firstChild;return xe(g=>t=g,c),l})()}export{qe as MapViewer};