@oddessentials/agent-guild 1.28.0 → 1.30.0
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/bin/native/win32-arm64/agent-guild-report.exe +0 -0
- package/bin/native/win32-x64/agent-guild-report.exe +0 -0
- package/bin/windows/arm64/agent-guild-start.exe +0 -0
- package/bin/windows/x64/agent-guild-start.exe +0 -0
- package/docs/api.md +10 -2
- package/node_modules/@jsquash/jpeg/CHANGELOG.md +54 -0
- package/node_modules/@jsquash/jpeg/LICENSE +201 -0
- package/node_modules/@jsquash/jpeg/README.md +105 -0
- package/node_modules/@jsquash/jpeg/codec/LICENSE.codec.md +132 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.d.ts +7 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.js +15 -0
- package/node_modules/@jsquash/jpeg/codec/dec/mozjpeg_dec.wasm +0 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.d.ts +37 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.js +15 -0
- package/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.wasm +0 -0
- package/node_modules/@jsquash/jpeg/codec/pre.js +24 -0
- package/node_modules/@jsquash/jpeg/decode.d.ts +16 -0
- package/node_modules/@jsquash/jpeg/decode.js +36 -0
- package/node_modules/@jsquash/jpeg/emscripten-types.d.ts +137 -0
- package/node_modules/@jsquash/jpeg/encode.d.ts +21 -0
- package/node_modules/@jsquash/jpeg/encode.js +35 -0
- package/node_modules/@jsquash/jpeg/index.d.ts +3 -0
- package/node_modules/@jsquash/jpeg/index.js +2 -0
- package/node_modules/@jsquash/jpeg/meta.d.ts +24 -0
- package/node_modules/@jsquash/jpeg/meta.js +25 -0
- package/node_modules/@jsquash/jpeg/package.json +32 -0
- package/node_modules/@jsquash/jpeg/utils.d.ts +17 -0
- package/node_modules/@jsquash/jpeg/utils.js +31 -0
- package/node_modules/@jsquash/png/CHANGELOG.md +69 -0
- package/node_modules/@jsquash/png/LICENSE +201 -0
- package/node_modules/@jsquash/png/README.md +114 -0
- package/node_modules/@jsquash/png/codec/LICENSE.codec.md +29 -0
- package/node_modules/@jsquash/png/codec/README.md +5 -0
- package/node_modules/@jsquash/png/codec/pkg/README.md +5 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png.d.ts +71 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png.js +314 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png_bg.wasm +0 -0
- package/node_modules/@jsquash/png/codec/pkg/squoosh_png_bg.wasm.d.ts +13 -0
- package/node_modules/@jsquash/png/codec/pre.js +24 -0
- package/node_modules/@jsquash/png/decode.d.ts +29 -0
- package/node_modules/@jsquash/png/decode.js +35 -0
- package/node_modules/@jsquash/png/encode.d.ts +31 -0
- package/node_modules/@jsquash/png/encode.js +40 -0
- package/node_modules/@jsquash/png/index.d.ts +3 -0
- package/node_modules/@jsquash/png/index.js +2 -0
- package/node_modules/@jsquash/png/meta.d.ts +16 -0
- package/node_modules/@jsquash/png/meta.js +15 -0
- package/node_modules/@jsquash/png/package.json +34 -0
- package/node_modules/@jsquash/webp/CHANGELOG.md +48 -0
- package/node_modules/@jsquash/webp/LICENSE +201 -0
- package/node_modules/@jsquash/webp/README.md +100 -0
- package/node_modules/@jsquash/webp/codec/LICENSE.codec.md +29 -0
- package/node_modules/@jsquash/webp/codec/Makefile.animated.backup +88 -0
- package/node_modules/@jsquash/webp/codec/README.md +5 -0
- package/node_modules/@jsquash/webp/codec/dec/README.md +18 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.d.ts +7 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.js +16 -0
- package/node_modules/@jsquash/webp/codec/dec/webp_dec.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/enc/README.md +22 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.d.ts +42 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.js +16 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.d.ts +1 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.js +16 -0
- package/node_modules/@jsquash/webp/codec/enc/webp_enc_simd.wasm +0 -0
- package/node_modules/@jsquash/webp/codec/pre.js +24 -0
- package/node_modules/@jsquash/webp/decode.d.ts +15 -0
- package/node_modules/@jsquash/webp/decode.js +34 -0
- package/node_modules/@jsquash/webp/emscripten-types.d.ts +137 -0
- package/node_modules/@jsquash/webp/encode.d.ts +23 -0
- package/node_modules/@jsquash/webp/encode.js +43 -0
- package/node_modules/@jsquash/webp/index.d.ts +3 -0
- package/node_modules/@jsquash/webp/index.js +2 -0
- package/node_modules/@jsquash/webp/meta.d.ts +19 -0
- package/node_modules/@jsquash/webp/meta.js +33 -0
- package/node_modules/@jsquash/webp/package.json +35 -0
- package/node_modules/@jsquash/webp/utils.d.ts +17 -0
- package/node_modules/@jsquash/webp/utils.js +31 -0
- package/node_modules/argparse/lib/argparse.js +1 -1
- package/node_modules/argparse/lib/textwrap.js +13 -2
- package/node_modules/argparse/package.json +1 -1
- package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs +737 -686
- package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs.map +1 -1
- package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js +8 -8
- package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js.map +1 -1
- package/node_modules/markdown-it/dist/markdown-it.cjs.js +660 -518
- package/node_modules/markdown-it/dist/markdown-it.cjs.js.map +1 -1
- package/node_modules/markdown-it/dist/markdown-it.d.mts +1 -0
- package/node_modules/markdown-it/dist/markdown-it.mjs +179 -124
- package/node_modules/markdown-it/dist/markdown-it.mjs.map +1 -1
- package/node_modules/markdown-it/package.json +11 -11
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/node_modules/wasm-feature-detect/LICENSE +202 -0
- package/node_modules/wasm-feature-detect/README.md +109 -0
- package/node_modules/wasm-feature-detect/README.md.ejs +89 -0
- package/node_modules/wasm-feature-detect/dist/cjs/index.cjs +1 -0
- package/node_modules/wasm-feature-detect/dist/esm/index.js +1 -0
- package/node_modules/wasm-feature-detect/dist/index.d.cts +24 -0
- package/node_modules/wasm-feature-detect/dist/index.d.ts +24 -0
- package/node_modules/wasm-feature-detect/dist/umd/index.js +1 -0
- package/node_modules/wasm-feature-detect/package.json +53 -0
- package/node_modules/ws/README.md +21 -29
- package/node_modules/ws/lib/buffer-util.js +13 -2
- package/node_modules/ws/lib/sender.js +4 -1
- package/node_modules/ws/lib/websocket.js +8 -3
- package/node_modules/ws/package.json +1 -1
- package/package.json +7 -4
- package/src/manager/config.mjs +2 -0
- package/src/manager/folder-browser.mjs +39 -3
- package/src/manager/main.mjs +4 -0
- package/src/manager/server.mjs +32 -5
- package/src/manager/steam-workshop.mjs +64 -0
- package/src/manager/yard-art.mjs +1 -1
- package/src/manager/yard-piece-check.mjs +424 -0
- package/src/manager/yard-pieces.mjs +188 -0
- package/src/manager/yard-worlds.mjs +214 -0
- package/web/app.js +175 -11
- package/web/index.html +20 -2
- package/web/skins/grove/ui/companion-bird.webp +0 -0
- package/web/skins/guild/skin.css +2 -13
- package/web/styles.css +15 -1
- package/web/yard/art.json +1 -1
- package/web/yard/model.mjs +44 -2
- package/web/yard/photo.js +3 -4
- package/web/yard/renderer.js +150 -38
- package/web/yard/vendor/engine.js +500 -495
- package/web/yard/view.js +160 -14
- package/web/yard/yard.css +55 -12
- package/web/skins/grove/ui/install-now-dark.svg +0 -91
- package/web/skins/grove/ui/install-now-light.svg +0 -91
package/web/yard/renderer.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import * as T from './vendor/engine.js';
|
|
2
2
|
import { WORLDS, PROVIDER_ORDER, characterIndex, GILDED, CAMERA, SUN, minZoom, overviewFor, clampPan, viewBasis, providerPositions, sessionPose, familiarPose, hash,
|
|
3
|
-
stage, nextStagingChange, doorFor, arrives, route, clipFor, helperOffset, stationProp, hallActivity, lookTarget, lookLimits, STAGING, photoSize, marksFor, SHOWN_HELPERS } from './model.mjs';
|
|
3
|
+
stage, nextStagingChange, doorFor, arrives, route, clipFor, helperOffset, stationProp, hallActivity, lookTarget, lookLimits, yardExtras, playableClip, STAGING, photoSize, marksFor, SHOWN_HELPERS } from './model.mjs';
|
|
4
4
|
|
|
5
5
|
const ASSETS = new URL('./assets/', import.meta.url);
|
|
6
6
|
const vector = new T.Vector3();
|
|
7
7
|
const lookModel = new T.Matrix4(), lookParent = new T.Matrix4(), lookFrame = new T.Quaternion(), lookTurn = new T.Quaternion(), lookTilt = new T.Quaternion();
|
|
8
8
|
const axisX = new T.Vector3(1, 0, 0), axisY = new T.Vector3(0, 1, 0);
|
|
9
9
|
const noop = () => {};
|
|
10
|
+
const cancelled = new WeakSet();
|
|
11
|
+
addEventListener('unhandledrejection', event => { if (cancelled.has(event.reason)) event.preventDefault(); });
|
|
12
|
+
const cancel = loader => { const reason = new DOMException('This piece is no longer shown.', 'AbortError'); cancelled.add(reason); loader.abortController.abort(reason); };
|
|
13
|
+
const worldFile = (skin, custom, file) => `../worlds/${skin}/${file.split('/').pop()}?v=${custom.revision}`;
|
|
14
|
+
const pieceFiles = (skin, slot, piece) => (slot === 'companion' ? [0, 1, 2, 3].map(n => `companion_${n}.glb`) : ['character.glb'])
|
|
15
|
+
.map(file => new URL(`../pieces/${skin}/${slot}/${file}?v=${piece.revision}`, ASSETS).href);
|
|
10
16
|
const vector2 = new T.Vector2();
|
|
11
17
|
// How fast characters turn to a new heading, in radians a second, and how
|
|
12
18
|
// long a clip change blends.
|
|
@@ -81,6 +87,21 @@ function disposeSkeletons(root) {
|
|
|
81
87
|
root?.traverse(node=>{if(node.skeleton)skeletons.add(node.skeleton);});
|
|
82
88
|
for(const skeleton of skeletons)skeleton.dispose();
|
|
83
89
|
}
|
|
90
|
+
function checkPiece({scene,animations}) {
|
|
91
|
+
if(!animations.every(playableClip))throw new Error('An animation in this piece has values the Yard cannot play.');
|
|
92
|
+
const probe=T.cloneSkeleton(scene);
|
|
93
|
+
try {
|
|
94
|
+
probe.updateMatrixWorld(true);
|
|
95
|
+
probe.traverse(node=>{
|
|
96
|
+
if(!node.isMesh)return;
|
|
97
|
+
const vertices=node.geometry.attributes.position?.count,triangles=node.geometry.index?.array;
|
|
98
|
+
if(!vertices||(triangles&&!triangles.every(at=>at<vertices)))throw new Error('A mesh in this piece refers to vertices it does not have.');
|
|
99
|
+
if(!node.isSkinnedMesh)return;
|
|
100
|
+
node.skeleton.update();node.computeBoundingBox();node.computeBoundingSphere();
|
|
101
|
+
if(!Number.isFinite(node.boundingSphere?.radius))throw new Error('A rigged mesh in this piece cannot be drawn.');
|
|
102
|
+
});
|
|
103
|
+
} finally {disposeSkeletons(probe);}
|
|
104
|
+
}
|
|
84
105
|
function disposeTree(root) {
|
|
85
106
|
disposeSkeletons(root);
|
|
86
107
|
const geometries=new Set(),materials=new Set();
|
|
@@ -118,8 +139,9 @@ function disposeWorld(root) {
|
|
|
118
139
|
}
|
|
119
140
|
export class YardRenderer {
|
|
120
141
|
// clock gives the time staging is judged at; tests pass a fixed one.
|
|
121
|
-
constructor(host,labels,{select,open,error=noop,clock=Date.now}) {
|
|
122
|
-
this.host=host;this.labels=labels;this.onSelect=select;this.onOpen=open;this.onError=error;this.clock=clock;
|
|
142
|
+
constructor(host,labels,{select,open,error=noop,piece=noop,clock=Date.now}) {
|
|
143
|
+
this.host=host;this.labels=labels;this.onSelect=select;this.onOpen=open;this.onError=error;this.onPiece=piece;this.clock=clock;
|
|
144
|
+
this.pieceSets={};this.shownPieces={skin:null,pieces:{}};this.pieceLoaders=new Map();this.preloads=new Map();
|
|
123
145
|
this.scene=new T.Scene();this.units=new Map();this.halls=new Map();this.slots=new Map();this.cache=new Map();
|
|
124
146
|
this.loadingManager=new T.LoadingManager();
|
|
125
147
|
this.providers=[];this.sessions=[];this.selected=null;this.world=null;this.skin=null;this.request=0;this.worldRequest=0;this.pending=null;
|
|
@@ -176,20 +198,29 @@ export class YardRenderer {
|
|
|
176
198
|
this.resizeObserver=new ResizeObserver(()=>this.resize());this.resizeObserver.observe(host);
|
|
177
199
|
this.resize();this.overview();
|
|
178
200
|
}
|
|
179
|
-
async asset(name) {
|
|
201
|
+
async asset(name,url) {
|
|
180
202
|
if(!this.cache.has(name)) {
|
|
181
|
-
const
|
|
203
|
+
const failed=[],manager=url?new T.LoadingManager():this.loadingManager;
|
|
204
|
+
if(url){manager.onError=item=>failed.push(item);this.pieceLoaders.set(name,manager);}
|
|
205
|
+
const pending=new T.GLTFLoader(manager).loadAsync(url||new URL(name+'.glb',ASSETS).href).then(asset=>{
|
|
206
|
+
try {
|
|
207
|
+
if(failed.length)throw new Error('A texture in this piece could not be drawn.');
|
|
208
|
+
if(url)checkPiece(asset);
|
|
209
|
+
return asset;
|
|
210
|
+
} catch(err){disposeTree(asset.scene);throw err;}
|
|
211
|
+
}).catch(err=>{
|
|
182
212
|
if(this.cache.get(name)===pending)this.cache.delete(name);
|
|
183
213
|
throw err;
|
|
184
214
|
});
|
|
185
215
|
this.cache.set(name,pending);
|
|
216
|
+
if(url)pending.catch(noop).finally(()=>{if(this.pieceLoaders.get(name)===manager)this.pieceLoaders.delete(name);});
|
|
186
217
|
}
|
|
187
218
|
return this.cache.get(name);
|
|
188
219
|
}
|
|
189
|
-
goldAsset(name) {
|
|
220
|
+
goldAsset(name,url) {
|
|
190
221
|
const key='gold:'+name;
|
|
191
222
|
if(!this.cache.has(key)) {
|
|
192
|
-
const pending=this.asset(name).then(asset=>{const scene=T.cloneSkeleton(asset.scene);gild(scene);return {...asset,scene};}).catch(err=>{
|
|
223
|
+
const pending=this.asset(name,url).then(asset=>{const scene=T.cloneSkeleton(asset.scene);gild(scene);return {...asset,scene};}).catch(err=>{
|
|
193
224
|
if(this.cache.get(key)===pending)this.cache.delete(key);
|
|
194
225
|
throw err;
|
|
195
226
|
});
|
|
@@ -197,10 +228,63 @@ export class YardRenderer {
|
|
|
197
228
|
}
|
|
198
229
|
return this.cache.get(key);
|
|
199
230
|
}
|
|
231
|
+
activePieces() { return this.pieceSets[this.skin]||{}; }
|
|
232
|
+
pieceSource(slot) {
|
|
233
|
+
const piece=this.activePieces()[slot];
|
|
234
|
+
return piece?{slot,revision:piece.revision,urls:pieceFiles(this.skin,slot,piece)}:null;
|
|
235
|
+
}
|
|
236
|
+
// skin's pieces by slot, each { revision }; a slot without one shows the original.
|
|
237
|
+
setPieces(skin,pieces) {
|
|
238
|
+
this.pieceSets[skin]=pieces;
|
|
239
|
+
if(skin===this.skin)this.applyPieces();
|
|
240
|
+
}
|
|
241
|
+
applyPieces() {
|
|
242
|
+
if(this.disposed||!this.world)return;
|
|
243
|
+
const before=this.shownPieces,after={skin:this.skin,pieces:{...this.activePieces()}};
|
|
244
|
+
const changed=[...new Set([...Object.keys(before.pieces),...Object.keys(after.pieces)])]
|
|
245
|
+
.filter(slot=>before.skin!==after.skin||before.pieces[slot]?.revision!==after.pieces[slot]?.revision);
|
|
246
|
+
if(!changed.length&&before.skin===after.skin)return;
|
|
247
|
+
this.shownPieces=after;
|
|
248
|
+
if(before.skin===after.skin)for(const unit of this.units.values()){
|
|
249
|
+
if(changed.includes('companion'))this.clearHelpers(unit);
|
|
250
|
+
if(changed.includes(PROVIDER_ORDER[characterIndex(unit.session.provider.id)])){this.dropModel(unit);this.loadUnit(unit,unit.session);}
|
|
251
|
+
if(changed.includes('companion')&&unit.loaded)this.helpers(unit,unit.session);
|
|
252
|
+
}
|
|
253
|
+
for(const slot of changed){
|
|
254
|
+
if(before.pieces[slot]){this.preloads.delete(slot);for(const url of pieceFiles(before.skin,slot,before.pieces[slot]))this.release(url);}
|
|
255
|
+
if(after.pieces[slot])this.preload(slot,after.pieces[slot]);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
preload(slot,piece) {
|
|
259
|
+
const skin=this.skin,attempt={};
|
|
260
|
+
this.preloads.set(slot,attempt);
|
|
261
|
+
Promise.all(pieceFiles(skin,slot,piece).map(url=>this.asset(url,url))).then(()=>true,()=>false).then(ok=>{
|
|
262
|
+
if(this.preloads.get(slot)!==attempt)return;
|
|
263
|
+
this.preloads.delete(slot);
|
|
264
|
+
if(!this.disposed&&this.skin===skin&&this.activePieces()[slot]?.revision===piece.revision)this.onPiece({skin,slot,revision:piece.revision,ok});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
pieceFailed(source) {
|
|
268
|
+
if(this.activePieces()[source.slot]?.revision===source.revision)this.onPiece({skin:this.skin,slot:source.slot,revision:source.revision,ok:false});
|
|
269
|
+
}
|
|
270
|
+
release(name) {
|
|
271
|
+
const loader=this.pieceLoaders.get(name);if(loader){cancel(loader);this.pieceLoaders.delete(name);}
|
|
272
|
+
for(const key of [name,'gold:'+name]){
|
|
273
|
+
const pending=this.cache.get(key);
|
|
274
|
+
if(!pending)continue;
|
|
275
|
+
this.cache.delete(key);
|
|
276
|
+
pending.then(asset=>disposeTree(asset.scene)).catch(noop);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
dropModel(unit) {
|
|
280
|
+
for(const mixer of unit.mixers){mixer.stopAllAction();mixer.uncacheRoot(mixer.getRoot());}
|
|
281
|
+
if(unit.model){unit.root.remove(unit.model);disposeSkeletons(unit.model);}
|
|
282
|
+
Object.assign(unit,{mixers:[],model:null,loaded:false,loading:false,animations:null,action:null,clip:null,lookBones:null,looking:false,load:(unit.load||0)+1});
|
|
283
|
+
}
|
|
200
284
|
// Shows skin's world in theme, or only theme's plates when that world is
|
|
201
285
|
// already up. What is on screen stays until the new world or theme is
|
|
202
286
|
// ready; progress receives the bytes downloaded so far.
|
|
203
|
-
async setWorld(skin,theme,{progress=noop}={}) {
|
|
287
|
+
async setWorld(skin,theme,{progress=noop,custom=null}={}) {
|
|
204
288
|
if(this.disposed)return;
|
|
205
289
|
if(!WORLDS[skin])throw new Error('This skin has no yard.');
|
|
206
290
|
const request=++this.worldRequest;
|
|
@@ -208,7 +292,7 @@ export class YardRenderer {
|
|
|
208
292
|
const pending=this.pending=tracked(progress);
|
|
209
293
|
let loaded;
|
|
210
294
|
try {
|
|
211
|
-
if(this.skin===skin&&this.world){
|
|
295
|
+
if(this.skin===skin&&this.world&&(this.custom?.revision??null)===(custom?.revision??null)){
|
|
212
296
|
const world=this.world;
|
|
213
297
|
await this.plateTheme(world,themeKey(theme),pending.manager);
|
|
214
298
|
if(!this.disposed&&request===this.worldRequest&&this.world===world){this.light(skin,theme);this.drawOnce();}
|
|
@@ -217,7 +301,7 @@ export class YardRenderer {
|
|
|
217
301
|
const url=new URL(skin+'.glb',ASSETS).href;
|
|
218
302
|
loaded=await new T.GLTFLoader(pending.manager).loadAsync(url,pending.stream(url));
|
|
219
303
|
if(this.disposed||request!==this.worldRequest){disposeWorld(loaded.scene);return;}
|
|
220
|
-
await Promise.all([this.surfaceWorld(loaded.scene,skin,pending.manager),this.plateWorld(loaded.scene,skin,theme,pending.manager)]);
|
|
304
|
+
await Promise.all([this.surfaceWorld(loaded.scene,skin,pending.manager,custom),this.plateWorld(loaded.scene,skin,theme,pending.manager,custom)]);
|
|
221
305
|
} catch(err) {
|
|
222
306
|
disposeWorld(loaded?.scene);
|
|
223
307
|
if(!this.disposed&&request===this.worldRequest)throw err;
|
|
@@ -233,7 +317,8 @@ export class YardRenderer {
|
|
|
233
317
|
for(const item of this.halls.values()){item.label.remove();item.chip.remove();}
|
|
234
318
|
this.halls.clear();
|
|
235
319
|
if(this.world){this.scene.remove(this.world);disposeWorld(this.world);}
|
|
236
|
-
this.world=loaded.scene;this.skin=skin;this.scene.add(this.world);
|
|
320
|
+
this.world=loaded.scene;this.skin=skin;this.custom=custom;this.scene.add(this.world);
|
|
321
|
+
this.applyPieces();
|
|
237
322
|
this.makeEffects(WORLDS[skin].effect);
|
|
238
323
|
this.light(skin,theme);
|
|
239
324
|
this.host.dataset.world=skin;
|
|
@@ -250,10 +335,12 @@ export class YardRenderer {
|
|
|
250
335
|
// direction as the camera, so they line up at any pan or zoom. Each theme
|
|
251
336
|
// has its own set: the current theme's base layer gates the load, and its
|
|
252
337
|
// sharper layers and the other theme follow without blocking it.
|
|
253
|
-
async plateWorld(world,skin,theme,manager) {
|
|
338
|
+
async plateWorld(world,skin,theme,manager,custom=null) {
|
|
254
339
|
const response=await fetch(new URL(skin+'/plates.json',ASSETS));
|
|
255
340
|
if(!response.ok)throw new Error('The Yard plates could not be loaded.');
|
|
256
|
-
|
|
341
|
+
const {themes}=await response.json();
|
|
342
|
+
if(custom)for(const layers of Object.values(themes))for(const layer of layers)for(const tile of layer.tiles)tile.file=worldFile(skin,custom,tile.file);
|
|
343
|
+
world.userData.plates={themes,groups:{}};
|
|
257
344
|
await this.plateTheme(world,themeKey(theme),manager);
|
|
258
345
|
// Live halls and characters cast onto the plate's ground.
|
|
259
346
|
const catcher=new T.Mesh(new T.PlaneGeometry(400,400),new T.ShadowMaterial({opacity:.3,depthWrite:false}));
|
|
@@ -314,7 +401,7 @@ export class YardRenderer {
|
|
|
314
401
|
}
|
|
315
402
|
// Live halls take scanned material sets by material name,
|
|
316
403
|
// and light from a small copy of the plates' sky.
|
|
317
|
-
async surfaceWorld(world,skin,manager) {
|
|
404
|
+
async surfaceWorld(world,skin,manager,custom=null) {
|
|
318
405
|
const response=await fetch(new URL(skin+'/surfaces.json',ASSETS));
|
|
319
406
|
if(!response.ok)throw new Error('The Yard surfaces could not be loaded.');
|
|
320
407
|
const {sky,surfaces}=await response.json();
|
|
@@ -337,7 +424,7 @@ export class YardRenderer {
|
|
|
337
424
|
const pmrem=new T.PMREMGenerator(this.renderer);
|
|
338
425
|
try {
|
|
339
426
|
for(const [theme,file] of Object.entries(sky)) {
|
|
340
|
-
const equirect=await new T.HDRLoader(manager).loadAsync(new URL(file,ASSETS).href);
|
|
427
|
+
const equirect=await new T.HDRLoader(manager).loadAsync(new URL(custom?worldFile(skin,custom,file):file,ASSETS).href);
|
|
341
428
|
environments[theme]=pmrem.fromEquirectangular(equirect).texture;equirect.dispose();
|
|
342
429
|
}
|
|
343
430
|
} finally { pmrem.dispose(); }
|
|
@@ -374,11 +461,11 @@ export class YardRenderer {
|
|
|
374
461
|
this.scene.environment=this.world?.userData.environments?.[key]||null;
|
|
375
462
|
if(this.world&&this.skin===skin)this.showPlates(key);
|
|
376
463
|
this.renderer.toneMappingExposure=light?1.65:1.12;
|
|
377
|
-
this.hemi.color.set(WORLDS[skin].hemi);
|
|
464
|
+
this.hemi.color.set(this.custom?.lights?.[key].hemi??WORLDS[skin].hemi);
|
|
378
465
|
this.hemi.intensity=(light?3.2:1.8)*.35;
|
|
379
466
|
// Blender and three.js place an equirectangular sky half a turn apart.
|
|
380
467
|
this.scene.environmentRotation.y=Math.PI;this.scene.environmentIntensity=light?1.25:.7;
|
|
381
|
-
this.sun.color.set(WORLDS[skin].sun[key]);
|
|
468
|
+
this.sun.color.set(this.custom?.lights?.[key].sun??WORLDS[skin].sun[key]);
|
|
382
469
|
this.sun.intensity=light?4.0:3.2;
|
|
383
470
|
this.dirty=true;
|
|
384
471
|
}
|
|
@@ -590,22 +677,24 @@ export class YardRenderer {
|
|
|
590
677
|
}
|
|
591
678
|
async loadUnit(unit,session) {
|
|
592
679
|
if(unit.loaded||unit.loading)return;
|
|
593
|
-
const generation=this.request;
|
|
594
|
-
const
|
|
595
|
-
const
|
|
680
|
+
const generation=this.request,token=unit.load=(unit.load||0)+1;
|
|
681
|
+
const index=characterIndex(session.provider.id),source=this.pieceSource(PROVIDER_ORDER[index]);
|
|
682
|
+
const name=source?source.urls[0]:WORLDS[this.skin].characters+index;
|
|
683
|
+
const stale=()=>this.disposed||generation!==this.request||token!==unit.load||!unit.root.parent;
|
|
596
684
|
unit.loading=true;
|
|
597
685
|
try {
|
|
598
|
-
const asset=await (GILDED.includes(session.provider.id)?this.goldAsset(
|
|
599
|
-
if(
|
|
686
|
+
const asset=await (GILDED.includes(session.provider.id)?this.goldAsset(name,source?.urls[0]):this.asset(name,source?.urls[0]));
|
|
687
|
+
if(stale())return;
|
|
600
688
|
const model=T.cloneSkeleton(asset.scene);
|
|
601
689
|
model.rotation.y=unit.heading;
|
|
602
|
-
model.traverse(n=>{if(n.isMesh){n.castShadow=true;n.receiveShadow=true;}});
|
|
690
|
+
model.traverse(n=>{if(n.isMesh){n.castShadow=true;n.receiveShadow=true;}if(n.isSkinnedMesh)n.frustumCulled=false;});
|
|
603
691
|
unit.root.add(model);unit.model=model;unit.loaded=true;
|
|
604
692
|
unit.label.title='';
|
|
605
693
|
// The walk's stride and cycle, and where blows land, as rig.py wrote them.
|
|
606
694
|
let yard=null;asset.scene.traverse(n=>{if(n.userData?.yard)yard=n.userData.yard;});
|
|
607
|
-
|
|
608
|
-
unit.
|
|
695
|
+
yard=yardExtras(yard,'character');
|
|
696
|
+
unit.speed=yard.stride/yard.cycle;
|
|
697
|
+
unit.events=yard.events;unit.height=yard.height;unit.surface=yard.surface;unit.look=lookLimits(yard.look);
|
|
609
698
|
const neck=model.getObjectByName('Neck'),head=model.getObjectByName('Head');
|
|
610
699
|
unit.lookBones=neck&&head&&model.getObjectByName('LeftUpperLeg')?{neck,head}:null;
|
|
611
700
|
this.scaleProp(unit.session?.id||session.id);
|
|
@@ -615,8 +704,10 @@ export class YardRenderer {
|
|
|
615
704
|
unit.clip=null;this.play(unit);
|
|
616
705
|
this.helpers(unit,unit.session||session);this.dirty=true;this.drawOnce();
|
|
617
706
|
} catch(err){
|
|
618
|
-
if(
|
|
619
|
-
|
|
707
|
+
if(stale())return;
|
|
708
|
+
if(source){this.pieceFailed(source);return;}
|
|
709
|
+
console.warn('Yard character unavailable:',err.message);unit.label.title='Character art unavailable. Session controls remain available.';
|
|
710
|
+
} finally {if(token===unit.load)unit.loading=false;}
|
|
620
711
|
}
|
|
621
712
|
// A hall's own copies of the materials that glow with its crew, so one
|
|
622
713
|
// hall lighting up does not light the others that share them.
|
|
@@ -1052,28 +1143,47 @@ export class YardRenderer {
|
|
|
1052
1143
|
if(helper.action){if(this.reduced)helper.action.stop();else helper.action.crossFadeTo(action,FADE,false);}
|
|
1053
1144
|
helper.action=action;
|
|
1054
1145
|
}
|
|
1146
|
+
clearHelpers(unit) {
|
|
1147
|
+
unit.revision++;unit.helperSignature=null;
|
|
1148
|
+
for(const helper of unit.helpers){unit.crew.remove(helper.model);helper.mixer?.stopAllAction();helper.mixer?.uncacheRoot(helper.model);disposeSkeletons(helper.model);}
|
|
1149
|
+
unit.helpers=[];
|
|
1150
|
+
this.dirty=true;this.drawOnce();
|
|
1151
|
+
}
|
|
1055
1152
|
async helpers(unit,session) {
|
|
1056
1153
|
const agents=helpersFor(session);
|
|
1057
|
-
const
|
|
1154
|
+
const kinds=WORLDS[this.skin].helpers,source=this.pieceSource('companion');
|
|
1155
|
+
const signature=JSON.stringify([source?.revision??null,agents]);
|
|
1058
1156
|
if(unit.helperSignature===signature)return;
|
|
1157
|
+
this.clearHelpers(unit);
|
|
1059
1158
|
unit.helperSignature=signature;
|
|
1060
|
-
const rev
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1159
|
+
const rev=unit.revision,generation=this.request;
|
|
1160
|
+
const stale=()=>this.disposed||generation!==this.request||rev!==unit.revision||!unit.root.parent;
|
|
1161
|
+
let pieces=null;
|
|
1162
|
+
if(source&&agents.length){
|
|
1163
|
+
try {pieces=await Promise.all(source.urls.map(url=>this.asset(url,url)));}
|
|
1164
|
+
catch {
|
|
1165
|
+
if(stale())return;
|
|
1166
|
+
unit.helperSignature=null;
|
|
1167
|
+
this.pieceFailed(source);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if(stale())return;
|
|
1171
|
+
}
|
|
1064
1172
|
// The full helper count and all helper details remain in the canonical inspector.
|
|
1065
1173
|
// Small squads preserve readable character silhouettes at the overview scale.
|
|
1066
1174
|
await Promise.all(agents.slice(0,SHOWN_HELPERS).map(async(agent,i)=>{
|
|
1067
1175
|
try {
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1176
|
+
const n=hash(agent.name||agent.id)%4;
|
|
1177
|
+
const asset=pieces?pieces[n]:await this.asset((agent.shell?kinds.shell:kinds.agent)+n);
|
|
1178
|
+
if(stale())return;
|
|
1070
1179
|
const model=T.cloneSkeleton(asset.scene);
|
|
1180
|
+
model.traverse(n=>{if(n.isSkinnedMesh)n.frustumCulled=false;});
|
|
1071
1181
|
const offset=helperOffset(i,agents.length);
|
|
1072
1182
|
model.scale.setScalar(.45);model.position.set(unit.root.position.x+offset.x,.27,unit.root.position.z+offset.z);
|
|
1073
1183
|
unit.crew.add(model);
|
|
1074
1184
|
const mixer=new T.AnimationMixer(model);
|
|
1075
1185
|
let yard=null;asset.scene.traverse(n=>{if(n.userData?.yard)yard=n.userData.yard;});
|
|
1076
|
-
const helper={id:agent.id,top:(yard
|
|
1186
|
+
const helper={id:agent.id,top:yardExtras(yard,'helper').height*model.scale.y,model,mixer,offset,pose:agent.pose,clips:asset.animations,moving:false,action:null};
|
|
1077
1187
|
this.helperClip(helper,agent.pose);
|
|
1078
1188
|
mixer.update(.3+(hash(agent.id)%40)/10);
|
|
1079
1189
|
unit.helpers.push(helper);
|
|
@@ -1081,7 +1191,7 @@ export class YardRenderer {
|
|
|
1081
1191
|
} catch {
|
|
1082
1192
|
// The next state transition can rebuild this incomplete squad. Routine
|
|
1083
1193
|
// unchanged updates do not call helpers(), so failure cannot loop.
|
|
1084
|
-
if(!
|
|
1194
|
+
if(!stale())unit.helperSignature=null;
|
|
1085
1195
|
}
|
|
1086
1196
|
}));
|
|
1087
1197
|
}
|
|
@@ -1135,7 +1245,7 @@ export class YardRenderer {
|
|
|
1135
1245
|
this.select(this.selected);
|
|
1136
1246
|
this.renderer.render(this.scene,this.camera);
|
|
1137
1247
|
}
|
|
1138
|
-
return {canvas,scale:size.scale,skin:this.skin,theme:this.theme,tags};
|
|
1248
|
+
return {canvas,scale:size.scale,skin:this.skin,title:this.custom?.title,theme:this.theme,tags};
|
|
1139
1249
|
}
|
|
1140
1250
|
tags() {
|
|
1141
1251
|
const origin=this.host.getBoundingClientRect();
|
|
@@ -1151,6 +1261,8 @@ export class YardRenderer {
|
|
|
1151
1261
|
const rect=this.canvas.getBoundingClientRect();
|
|
1152
1262
|
this.pointer.set((event.clientX-rect.left)/rect.width*2-1,-(event.clientY-rect.top)/rect.height*2+1);
|
|
1153
1263
|
this.ray.setFromCamera(this.pointer,this.camera);
|
|
1264
|
+
// A skinned mesh keeps the bounds of the pose it first had; picking needs the pose on screen.
|
|
1265
|
+
for(const unit of this.units.values())for(const root of [unit.root,unit.crew])root?.traverse(n=>{if(!n.isSkinnedMesh)return;n.computeBoundingSphere();if(n.boundingBox)n.computeBoundingBox();});
|
|
1154
1266
|
const hits=this.ray.intersectObjects([...this.halls.values(),...this.units.values()].flatMap(i=>i.crew?[i.root,i.crew]:[i.root]),true);
|
|
1155
1267
|
for(const hit of hits) { for(let node=hit.object;node;node=node.parent) if(node.userData.entity)return node.userData.entity; }
|
|
1156
1268
|
return null;
|
|
@@ -1338,7 +1450,7 @@ export class YardRenderer {
|
|
|
1338
1450
|
this.clearFireworks();this.fireworkTexture?.dispose();
|
|
1339
1451
|
for(const spark of this.sparks){spark.points.geometry.dispose();spark.points.material.dispose();}this.sparks=[];
|
|
1340
1452
|
this.releaseEffects();this.clearProps();
|
|
1341
|
-
this.loadingManager.abort();this.pending?.close();this.pending=null;
|
|
1453
|
+
this.loadingManager.abort();for(const loader of this.pieceLoaders.values())cancel(loader);this.pieceLoaders.clear();this.pending?.close();this.pending=null;
|
|
1342
1454
|
this.resizeObserver.disconnect();this.controls.dispose();this.clearUnits();
|
|
1343
1455
|
for(const item of this.halls.values()){item.label.remove();item.chip.remove();}this.halls.clear();this.list.remove();
|
|
1344
1456
|
disposeWorld(this.world);
|