@adatechnology/game-test-room 0.14.0 → 0.16.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/README.md +17 -1
- package/dist/index.d.ts +179 -17
- package/dist/index.global.js +813 -146
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +552 -96
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.global.js
CHANGED
|
@@ -23,20 +23,26 @@ var GameTestRoom = (() => {
|
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
DEFAULT_HAND_STANCES: () => DEFAULT_HAND_STANCES,
|
|
25
25
|
DEFAULT_STRIKE_CLIPS: () => DEFAULT_STRIKE_CLIPS,
|
|
26
|
+
DEFAULT_TRIALS: () => DEFAULT_TRIALS,
|
|
26
27
|
LOADING_CLASS: () => LOADING_CLASS,
|
|
27
28
|
LOADING_CSS: () => LOADING_CSS,
|
|
28
29
|
MATERIAL_COLORS: () => MATERIAL_COLORS,
|
|
30
|
+
TRIAL_KINDS: () => TRIAL_KINDS,
|
|
29
31
|
brickWallSpec: () => brickWallSpec,
|
|
30
32
|
buildFromSpec: () => buildFromSpec,
|
|
31
33
|
createLoading: () => createLoading,
|
|
32
34
|
createLog: () => createLog,
|
|
33
35
|
createTestRoom: () => createTestRoom,
|
|
36
|
+
createTestTable: () => createTestTable,
|
|
34
37
|
decodeBenchState: () => decodeBenchState,
|
|
35
38
|
encodeBenchState: () => encodeBenchState,
|
|
39
|
+
fitRow: () => fitRow,
|
|
36
40
|
loadingMarkup: () => loadingMarkup,
|
|
37
41
|
locales: () => locales4,
|
|
38
42
|
nextPaint: () => nextPaint,
|
|
39
|
-
resolveStrings: () => resolveStrings5
|
|
43
|
+
resolveStrings: () => resolveStrings5,
|
|
44
|
+
rowCentres: () => rowCentres,
|
|
45
|
+
trialView: () => trialView
|
|
40
46
|
});
|
|
41
47
|
|
|
42
48
|
// ../game-contracts/dist/index.js
|
|
@@ -476,6 +482,13 @@ var GameTestRoom = (() => {
|
|
|
476
482
|
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
477
483
|
return object;
|
|
478
484
|
}
|
|
485
|
+
function kitOriginOf(object) {
|
|
486
|
+
for (let node = object; node; node = node.parent) {
|
|
487
|
+
const origin = node.userData?.[KIT_ORIGIN_KEY];
|
|
488
|
+
if (origin && typeof origin.owner === "string") return origin;
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
479
492
|
|
|
480
493
|
// ../game-destruction/dist/index.js
|
|
481
494
|
var FIRE_PRIME_C = 300;
|
|
@@ -806,6 +819,62 @@ var GameTestRoom = (() => {
|
|
|
806
819
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
807
820
|
};
|
|
808
821
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
822
|
+
var splinterBox = (inp, box2, limit = inp.maxFragments) => {
|
|
823
|
+
const { rnd: rnd2 } = inp;
|
|
824
|
+
const dims = box2.s;
|
|
825
|
+
const [fmin, fmax] = inp.fragmentSize;
|
|
826
|
+
const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
|
|
827
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== g);
|
|
828
|
+
const w = Math.max(fmin * 1.5, 0.01);
|
|
829
|
+
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
830
|
+
const maxLen = fmax * 4;
|
|
831
|
+
const cells = bsp(box2, c, [a, b], rnd2);
|
|
832
|
+
const pieces = [];
|
|
833
|
+
for (const cell of cells) {
|
|
834
|
+
const extra = rnd2() < 0.4 ? 1 : 0;
|
|
835
|
+
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
836
|
+
for (const p of bsp(cell, k, [g], rnd2)) pieces.push(p);
|
|
837
|
+
}
|
|
838
|
+
return pieces.slice(0, limit).map((p) => ({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] }));
|
|
839
|
+
};
|
|
840
|
+
var severAt = (inp, root, at3, gap) => {
|
|
841
|
+
const dims = root.s;
|
|
842
|
+
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
843
|
+
const half = dims[L] / 2;
|
|
844
|
+
const lo = clamp(at3 - gap / 2, -half, half), hi = clamp(at3 + gap / 2, -half, half);
|
|
845
|
+
const out = [];
|
|
846
|
+
const stub = (from, to) => {
|
|
847
|
+
if (to - from < 0.02) return;
|
|
848
|
+
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
849
|
+
c[L] = (from + to) / 2;
|
|
850
|
+
sz[L] = to - from;
|
|
851
|
+
out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
|
|
852
|
+
};
|
|
853
|
+
stub(-half, lo);
|
|
854
|
+
stub(hi, half);
|
|
855
|
+
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
856
|
+
band.c[L] = (lo + hi) / 2;
|
|
857
|
+
band.s[L] = Math.max(hi - lo, 0.01);
|
|
858
|
+
for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
859
|
+
return out;
|
|
860
|
+
};
|
|
861
|
+
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
862
|
+
const { dims, rnd: rnd2, maxFragments } = inp;
|
|
863
|
+
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
864
|
+
const per = clamp(n, 3, Math.max(3, Math.floor(maxFragments / layers)));
|
|
865
|
+
const out = [];
|
|
866
|
+
for (let k = 0; k < layers; k++) {
|
|
867
|
+
const off = -dims[t] / 2 + (k + 0.5) * dims[t] / layers;
|
|
868
|
+
for (const b of bsp(root, per, [u, v], rnd2)) {
|
|
869
|
+
if (out.length >= maxFragments) return out;
|
|
870
|
+
const c = [b.c[0], b.c[1], b.c[2]];
|
|
871
|
+
c[t] = off;
|
|
872
|
+
out.push({ center: c, size: [b.s[u], b.s[v], sheet], q: frame(u, v, t, (rnd2() - 0.5) * 0.8), shape: "scrap", weight: b.s[u] * b.s[v], inner: out.length % 4 !== 0 });
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return out;
|
|
876
|
+
};
|
|
877
|
+
var STACK_LAYER = 0.012;
|
|
809
878
|
var planFragments = (inp) => {
|
|
810
879
|
const { style, dims, rnd: rnd2, maxFragments } = inp;
|
|
811
880
|
const [fmin, fmax] = inp.fragmentSize;
|
|
@@ -827,27 +896,14 @@ var GameTestRoom = (() => {
|
|
|
827
896
|
return out;
|
|
828
897
|
}
|
|
829
898
|
if (style === "splinter") {
|
|
830
|
-
|
|
831
|
-
const
|
|
832
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
833
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
834
|
-
const maxLen = fmax * 4;
|
|
835
|
-
const cells = bsp(root, c, [a, b], rnd2);
|
|
836
|
-
const pieces = [];
|
|
837
|
-
for (const cell of cells) {
|
|
838
|
-
let list = [cell];
|
|
839
|
-
const extra = rnd2() < 0.4 ? 1 : 0;
|
|
840
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
841
|
-
list = bsp(cell, k, [g], rnd2);
|
|
842
|
-
for (const p of list) pieces.push(p);
|
|
843
|
-
}
|
|
844
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
845
|
-
out.push({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] });
|
|
846
|
-
}
|
|
899
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
900
|
+
for (const p of splinterBox(inp, root)) out.push(p);
|
|
847
901
|
return out;
|
|
848
902
|
}
|
|
849
903
|
if (style === "tear") {
|
|
850
904
|
const n2 = clamp(Math.round(dims[u] * dims[v] / (mean * mean)), 3, maxFragments);
|
|
905
|
+
const sheet = inp.sheet ?? 0;
|
|
906
|
+
if (sheet > 0 && dims[t] > sheet * 4) return tearStack(inp, root, t, u, v, n2, sheet);
|
|
851
907
|
const th2 = Math.min(inp.thickness, dims[t]);
|
|
852
908
|
for (const b of bsp(root, n2, [u, v], rnd2)) {
|
|
853
909
|
out.push({ center: [b.c[0], b.c[1], b.c[2]], size: [b.s[u], b.s[v], th2], q: frame(u, v, t), shape: "scrap", weight: b.s[u] * b.s[v] });
|
|
@@ -2420,6 +2476,7 @@ var GameTestRoom = (() => {
|
|
|
2420
2476
|
0.03,
|
|
2421
2477
|
0.15
|
|
2422
2478
|
],
|
|
2479
|
+
sheet: 1e-3,
|
|
2423
2480
|
damageScale: {
|
|
2424
2481
|
slash: 3
|
|
2425
2482
|
},
|
|
@@ -2435,7 +2492,8 @@ var GameTestRoom = (() => {
|
|
|
2435
2492
|
smoke: "grey"
|
|
2436
2493
|
},
|
|
2437
2494
|
visual: {
|
|
2438
|
-
charColor: 1709072
|
|
2495
|
+
charColor: 1709072,
|
|
2496
|
+
innerColor: 15524048
|
|
2439
2497
|
},
|
|
2440
2498
|
tags: [
|
|
2441
2499
|
"fabric",
|
|
@@ -2822,9 +2880,14 @@ var GameTestRoom = (() => {
|
|
|
2822
2880
|
var BREACH_AREA = 0.25;
|
|
2823
2881
|
var BREACH_BLAST_REACH = 0.12;
|
|
2824
2882
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2883
|
+
var SEVER_BAND = 0.1;
|
|
2884
|
+
var SEVER_ASPECT = 3;
|
|
2885
|
+
var SEVER_BALLISTIC = 2;
|
|
2825
2886
|
var SUPPORT_TOLERANCE = 0.05;
|
|
2826
2887
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
2827
2888
|
var BREACH_GRID_CAP = 4096;
|
|
2889
|
+
var CHIP_ENERGY = 200;
|
|
2890
|
+
var CHIP_SPLASH = 0.5;
|
|
2828
2891
|
var CUT_SLICE = 1;
|
|
2829
2892
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2830
2893
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2868,6 +2931,7 @@ var GameTestRoom = (() => {
|
|
|
2868
2931
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2869
2932
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
2870
2933
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
2934
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
2871
2935
|
const rng = createRng(options.seed ?? 1);
|
|
2872
2936
|
const rnd2 = () => rng.next();
|
|
2873
2937
|
let time = 0;
|
|
@@ -3241,41 +3305,68 @@ var GameTestRoom = (() => {
|
|
|
3241
3305
|
markDamaged(p);
|
|
3242
3306
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
3243
3307
|
};
|
|
3308
|
+
const freeSpan = (p, supports) => {
|
|
3309
|
+
if (supports.length === 0) return Infinity;
|
|
3310
|
+
const d = p.dims;
|
|
3311
|
+
const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
|
|
3312
|
+
const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
|
|
3313
|
+
let far = 0;
|
|
3314
|
+
for (let i = 0; i <= 16; i++) {
|
|
3315
|
+
const x = (i / 16 - 0.5) * d[L];
|
|
3316
|
+
let near = Infinity;
|
|
3317
|
+
for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
|
|
3318
|
+
far = Math.max(far, near);
|
|
3319
|
+
}
|
|
3320
|
+
return 2 * far;
|
|
3321
|
+
};
|
|
3244
3322
|
const solveStructure = (o) => {
|
|
3245
3323
|
o.maxStress = 0;
|
|
3246
3324
|
if (!o.hasStructure) return;
|
|
3247
3325
|
const parts = o.parts;
|
|
3248
3326
|
const n = parts.length;
|
|
3249
3327
|
const dist = new Array(n).fill(-1);
|
|
3250
|
-
const queue = [];
|
|
3251
3328
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
3252
3329
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
if (
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3330
|
+
const out = /* @__PURE__ */ new Set();
|
|
3331
|
+
let queue = [];
|
|
3332
|
+
let lowerOf = /* @__PURE__ */ new Map();
|
|
3333
|
+
for (; ; ) {
|
|
3334
|
+
dist.fill(-1);
|
|
3335
|
+
queue = [];
|
|
3336
|
+
for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
|
|
3337
|
+
dist[i] = 0;
|
|
3338
|
+
queue.push(i);
|
|
3339
|
+
}
|
|
3340
|
+
for (let h = 0; h < queue.length; h++) {
|
|
3341
|
+
const s = queue[h];
|
|
3342
|
+
if (isPartition(s)) continue;
|
|
3343
|
+
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
3344
|
+
if (j.st.open || !j.bears) continue;
|
|
3345
|
+
const r = j.ia === s ? j.ib : j.ia;
|
|
3346
|
+
if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
|
|
3347
|
+
dist[r] = dist[s] + 1;
|
|
3348
|
+
queue.push(r);
|
|
3349
|
+
}
|
|
3266
3350
|
}
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3351
|
+
lowerOf = /* @__PURE__ */ new Map();
|
|
3352
|
+
let dropped = false;
|
|
3353
|
+
for (const i of queue) {
|
|
3354
|
+
const lower = [];
|
|
3355
|
+
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
3356
|
+
if (j.st.open || !j.bears) continue;
|
|
3357
|
+
const k = restsOn(o, j, i);
|
|
3358
|
+
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
3359
|
+
}
|
|
3360
|
+
lowerOf.set(i, lower);
|
|
3361
|
+
const max2 = parts[i].spec.structure?.maxSpan;
|
|
3362
|
+
if (max2 !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max2) {
|
|
3363
|
+
out.add(i);
|
|
3364
|
+
dropped = true;
|
|
3365
|
+
}
|
|
3276
3366
|
}
|
|
3277
|
-
|
|
3367
|
+
if (!dropped) break;
|
|
3278
3368
|
}
|
|
3369
|
+
const incoming = new Array(n).fill(0);
|
|
3279
3370
|
const bending = new Array(n).fill(0);
|
|
3280
3371
|
const through = parts.map(() => []);
|
|
3281
3372
|
const pass = (k, at3, mass) => {
|
|
@@ -3787,6 +3878,7 @@ var GameTestRoom = (() => {
|
|
|
3787
3878
|
const r = rayPart(o, p, origin, d);
|
|
3788
3879
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
3789
3880
|
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
3881
|
+
if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
|
|
3790
3882
|
if (!best || r.t0 < best.distance) best = { object: o.id, part: p.spec.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
|
|
3791
3883
|
}
|
|
3792
3884
|
}
|
|
@@ -4050,6 +4142,62 @@ var GameTestRoom = (() => {
|
|
|
4050
4142
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
4051
4143
|
openBreach(o, p, [sum2[0] / total, sum2[1] / total, sum2[2] / total], total, energy, dir, src);
|
|
4052
4144
|
};
|
|
4145
|
+
const unitGrids = /* @__PURE__ */ new WeakMap();
|
|
4146
|
+
const gridOf = (p) => {
|
|
4147
|
+
let grid = unitGrids.get(p);
|
|
4148
|
+
if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP));
|
|
4149
|
+
return grid;
|
|
4150
|
+
};
|
|
4151
|
+
const insideChip = (p, objectPoint) => {
|
|
4152
|
+
const cells = p.st.chipped;
|
|
4153
|
+
if (!cells?.length) return false;
|
|
4154
|
+
const [u, v] = planeAxes(p.thin);
|
|
4155
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
4156
|
+
return cells.some((c) => Math.abs(lp[u] - c.center[u]) <= c.size[u] / 2 && Math.abs(lp[v] - c.center[v]) <= c.size[v] / 2);
|
|
4157
|
+
};
|
|
4158
|
+
const chipUnits = (o, p, wp, energy, dir, radius, src) => {
|
|
4159
|
+
var _a, _b;
|
|
4160
|
+
if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
4161
|
+
const [u, v] = planeAxes(p.thin);
|
|
4162
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4163
|
+
const grid = gridOf(p);
|
|
4164
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4165
|
+
const out = [];
|
|
4166
|
+
for (let i = 0; i < grid.length; i++) {
|
|
4167
|
+
const cell = grid[i];
|
|
4168
|
+
if (worn[i] === -1) continue;
|
|
4169
|
+
const gap = Math.hypot(Math.max(0, Math.abs(lp[u] - cell.center[u]) - cell.size[u] / 2), Math.max(0, Math.abs(lp[v] - cell.center[v]) - cell.size[v] / 2));
|
|
4170
|
+
if (gap > radius) continue;
|
|
4171
|
+
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
4172
|
+
const total = (worn[i] ?? 0) + energy * share;
|
|
4173
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
4174
|
+
if (total < chipEnergy * mass) {
|
|
4175
|
+
worn[i] = total;
|
|
4176
|
+
continue;
|
|
4177
|
+
}
|
|
4178
|
+
worn[i] = -1;
|
|
4179
|
+
out.push(cell);
|
|
4180
|
+
}
|
|
4181
|
+
if (out.length === 0) return;
|
|
4182
|
+
const { c, q } = partXf(o, p);
|
|
4183
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
4184
|
+
const holes = localHolesOf(p);
|
|
4185
|
+
for (const cell of out) {
|
|
4186
|
+
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
4187
|
+
const center = toWorld(c, q, cell.center);
|
|
4188
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
4189
|
+
const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
|
|
4190
|
+
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
4191
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
4192
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
4193
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
4194
|
+
markDamaged(p);
|
|
4195
|
+
setStage(o, p, 1, {}, src);
|
|
4196
|
+
emit2({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
4197
|
+
debris2(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
4198
|
+
}
|
|
4199
|
+
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
4200
|
+
};
|
|
4053
4201
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
4054
4202
|
const [u, v] = planeAxes(p.thin);
|
|
4055
4203
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -4061,7 +4209,8 @@ var GameTestRoom = (() => {
|
|
|
4061
4209
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
4062
4210
|
return;
|
|
4063
4211
|
}
|
|
4064
|
-
const
|
|
4212
|
+
const worn = p.rt.chips;
|
|
4213
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
4065
4214
|
const removed = p.st.mass * cells.reduce((sum2, cell) => sum2 + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
4066
4215
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
4067
4216
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -4145,6 +4294,8 @@ var GameTestRoom = (() => {
|
|
|
4145
4294
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
4146
4295
|
const hitLocal = toLocal(c, q, hitW);
|
|
4147
4296
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
4297
|
+
const axis2 = style === "splinter" ? severAxis(p) : -1;
|
|
4298
|
+
const cutAt = axis2 < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis2] : void 0);
|
|
4148
4299
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
4149
4300
|
style,
|
|
4150
4301
|
dims: p.dims,
|
|
@@ -4153,7 +4304,9 @@ var GameTestRoom = (() => {
|
|
|
4153
4304
|
hit: hitLocal,
|
|
4154
4305
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
4155
4306
|
maxFragments,
|
|
4156
|
-
rnd: rnd2
|
|
4307
|
+
rnd: rnd2,
|
|
4308
|
+
sheet: p.mat.sheet,
|
|
4309
|
+
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
4157
4310
|
});
|
|
4158
4311
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
4159
4312
|
const mass = p.st.mass;
|
|
@@ -4177,7 +4330,8 @@ var GameTestRoom = (() => {
|
|
|
4177
4330
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd2(), dv[2] * k],
|
|
4178
4331
|
angularVelocity: [(rnd2() - 0.5) * 12, (rnd2() - 0.5) * 12, (rnd2() - 0.5) * 12],
|
|
4179
4332
|
mass: mass * f.weight / totW,
|
|
4180
|
-
...marks.length > 0 ? { marks } : {}
|
|
4333
|
+
...marks.length > 0 ? { marks } : {},
|
|
4334
|
+
...f.inner ? { inner: true } : {}
|
|
4181
4335
|
};
|
|
4182
4336
|
});
|
|
4183
4337
|
const reinforcement = p.spec.reinforcement;
|
|
@@ -4211,10 +4365,33 @@ var GameTestRoom = (() => {
|
|
|
4211
4365
|
emit2({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
4212
4366
|
};
|
|
4213
4367
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
4368
|
+
const severAxis = (p) => {
|
|
4369
|
+
if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
|
|
4370
|
+
const d = p.dims;
|
|
4371
|
+
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
4372
|
+
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
4373
|
+
};
|
|
4374
|
+
const bandOf = (o, p, axis2, origin) => {
|
|
4375
|
+
const { c, q } = partXf(o, p);
|
|
4376
|
+
const along = toLocal(c, q, origin)[axis2];
|
|
4377
|
+
const L = p.dims[axis2];
|
|
4378
|
+
return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
|
|
4379
|
+
};
|
|
4214
4380
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
4381
|
+
var _a;
|
|
4215
4382
|
if (isLost(p) || amount <= 0) return 0;
|
|
4216
4383
|
const prev = p.st.hp;
|
|
4217
4384
|
p.st.hp -= amount;
|
|
4385
|
+
const axis2 = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
|
|
4386
|
+
if (axis2 >= 0 && p.st.hp > 0) {
|
|
4387
|
+
const band = bandOf(o, p, axis2, origin);
|
|
4388
|
+
const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
|
|
4389
|
+
cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
|
|
4390
|
+
if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis2]) {
|
|
4391
|
+
p.rt.cutAt = -p.dims[axis2] / 2 + (band + 0.5) * SEVER_BAND;
|
|
4392
|
+
p.st.hp = 0;
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4218
4395
|
markDamaged(p);
|
|
4219
4396
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
4220
4397
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -4253,7 +4430,7 @@ var GameTestRoom = (() => {
|
|
|
4253
4430
|
const ballistic2 = (o, p, entry, n, d, E, caliber, pathLen, src, round2 = {}) => {
|
|
4254
4431
|
const mat = p.mat;
|
|
4255
4432
|
const piercing = round2.piercing ?? 1;
|
|
4256
|
-
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
4433
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry)) || insideChip(p, objLocal(o, entry))) {
|
|
4257
4434
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
4258
4435
|
}
|
|
4259
4436
|
const sin2 = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -4312,6 +4489,7 @@ var GameTestRoom = (() => {
|
|
|
4312
4489
|
debris2(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4313
4490
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
4314
4491
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
4492
|
+
chipUnits(o, p, entry, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
|
|
4315
4493
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
4316
4494
|
};
|
|
4317
4495
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -4339,6 +4517,7 @@ var GameTestRoom = (() => {
|
|
|
4339
4517
|
const scale2 = mat.damageScale?.[kind] ?? 1;
|
|
4340
4518
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale2 * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
4341
4519
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
4520
|
+
if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
|
|
4342
4521
|
return done;
|
|
4343
4522
|
};
|
|
4344
4523
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -5488,6 +5667,7 @@ var GameTestRoom = (() => {
|
|
|
5488
5667
|
sway: "sway",
|
|
5489
5668
|
spall: "plaster came off",
|
|
5490
5669
|
stage: "damage stage",
|
|
5670
|
+
chip: "brick knocked out",
|
|
5491
5671
|
expose: "rebar bare",
|
|
5492
5672
|
body: "body",
|
|
5493
5673
|
blast: "blast",
|
|
@@ -5578,6 +5758,7 @@ var GameTestRoom = (() => {
|
|
|
5578
5758
|
sway: "balan\xE7o",
|
|
5579
5759
|
spall: "reboco soltou",
|
|
5580
5760
|
stage: "est\xE1gio de dano",
|
|
5761
|
+
chip: "tijolo arrancado",
|
|
5581
5762
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
5582
5763
|
body: "corpo",
|
|
5583
5764
|
blast: "explos\xE3o",
|
|
@@ -15742,6 +15923,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15742
15923
|
const bl = Math.round(ab + ((b & 255) - ab) * t);
|
|
15743
15924
|
return r << 16 | g << 8 | bl;
|
|
15744
15925
|
}
|
|
15926
|
+
var SHEET_MAX = 4e-3;
|
|
15927
|
+
var SHEET_AIR = 2.5;
|
|
15928
|
+
var SHEET_SWAY = 3;
|
|
15745
15929
|
var FragmentSystem = class {
|
|
15746
15930
|
constructor(THREE, o) {
|
|
15747
15931
|
__publicField2(this, "THREE", THREE);
|
|
@@ -15847,7 +16031,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15847
16031
|
asleep: false,
|
|
15848
16032
|
rest: 0,
|
|
15849
16033
|
fade: 1,
|
|
15850
|
-
dying: false
|
|
16034
|
+
dying: false,
|
|
16035
|
+
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0
|
|
15851
16036
|
};
|
|
15852
16037
|
this.list.push(live);
|
|
15853
16038
|
let alive = this.list.reduce((n, l) => n + (l.dying ? 0 : 1), 0);
|
|
@@ -15931,6 +16116,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15931
16116
|
}
|
|
15932
16117
|
} else {
|
|
15933
16118
|
f.ang.multiplyScalar(Math.exp(-0.15 * dt));
|
|
16119
|
+
if (f.air > 0) this.flutter(f, dt);
|
|
15934
16120
|
}
|
|
15935
16121
|
if (contact && f.vel.lengthSq() < 4) this.settle(f, dt);
|
|
15936
16122
|
const slow = f.vel.lengthSq() < 0.012 && f.ang.lengthSq() < 0.2;
|
|
@@ -15945,6 +16131,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15945
16131
|
f.still = 0;
|
|
15946
16132
|
}
|
|
15947
16133
|
}
|
|
16134
|
+
/** A sheet in the air: drag slows it to a slow fall, and it sways and turns over as it goes. */
|
|
16135
|
+
flutter(f, dt) {
|
|
16136
|
+
f.vel.multiplyScalar(Math.exp(-f.air * dt));
|
|
16137
|
+
f.vel.x += (this.rng() - 0.5) * SHEET_SWAY * dt;
|
|
16138
|
+
f.vel.z += (this.rng() - 0.5) * SHEET_SWAY * dt;
|
|
16139
|
+
f.ang.x += (this.rng() - 0.5) * SHEET_SWAY * 4 * dt;
|
|
16140
|
+
f.ang.z += (this.rng() - 0.5) * SHEET_SWAY * 4 * dt;
|
|
16141
|
+
}
|
|
15948
16142
|
/** Tip the fragment towards lying on its flattest face. */
|
|
15949
16143
|
settle(f, dt) {
|
|
15950
16144
|
const mesh2 = f.mesh;
|
|
@@ -17606,6 +17800,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
17606
17800
|
}
|
|
17607
17801
|
};
|
|
17608
17802
|
var rendererCount = 0;
|
|
17803
|
+
var COLLAPSE_CLOUD_KG = 1e3;
|
|
17609
17804
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
17610
17805
|
var GLOW_FROM_C = 430;
|
|
17611
17806
|
var QUEUE_CAP = 4e3;
|
|
@@ -18065,10 +18260,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18065
18260
|
case "settle":
|
|
18066
18261
|
this.onSettle(ev.object);
|
|
18067
18262
|
break;
|
|
18068
|
-
case "collapse":
|
|
18263
|
+
case "collapse": {
|
|
18264
|
+
const root = this.bounds.get(ev.object)?.root;
|
|
18265
|
+
const furniture = !!root && kitOriginOf(root)?.kind === "furniture";
|
|
18069
18266
|
this.splitLoose(ev.object);
|
|
18070
|
-
this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
18267
|
+
if (!furniture || ev.mass >= COLLAPSE_CLOUD_KG) this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
18268
|
+
else this.vfx.burst("dust", this.rootPoint(ev.object), [0, 1, 0], clamp6(Math.round(ev.mass / 4), 4, 24), 0.8);
|
|
18071
18269
|
break;
|
|
18270
|
+
}
|
|
18072
18271
|
case "disassemble_progress": {
|
|
18073
18272
|
const k = `unscrew:${ev.joint}`;
|
|
18074
18273
|
if (this.time - (this.throttles.get(k) ?? -1) > 0.25) {
|
|
@@ -18098,6 +18297,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18098
18297
|
case "stage":
|
|
18099
18298
|
this.onStage(ev);
|
|
18100
18299
|
break;
|
|
18300
|
+
case "chip":
|
|
18301
|
+
this.onChip(ev);
|
|
18302
|
+
break;
|
|
18101
18303
|
case "expose":
|
|
18102
18304
|
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
18103
18305
|
if (ev.bars.length > 0) {
|
|
@@ -18225,6 +18427,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18225
18427
|
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
18226
18428
|
if (kind === "smoke") return { color: SMOKE_TONES[def?.thermal.smoke ?? "grey"] };
|
|
18227
18429
|
if (kind === "fibers" || kind === "leaves") {
|
|
18430
|
+
if (def?.visual?.innerColor !== void 0) return { color: def.visual.innerColor };
|
|
18228
18431
|
const c = pv ? this.visuals.baseColorHex(pv.meshes[0] ?? {}) : -1;
|
|
18229
18432
|
return c >= 0 ? { color: c } : void 0;
|
|
18230
18433
|
}
|
|
@@ -18353,6 +18556,19 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18353
18556
|
if (!played) this.vfx.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
18354
18557
|
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
18355
18558
|
}
|
|
18559
|
+
/**
|
|
18560
|
+
* A unit came out of a wall of units: the unit flies as a fragment with a puff of its dust. The gap itself is the drawing's (the house renderer redraws
|
|
18561
|
+
* the wall as its courses without that unit); a part drawn as one box keeps its box.
|
|
18562
|
+
*/
|
|
18563
|
+
onChip(ev) {
|
|
18564
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
18565
|
+
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
18566
|
+
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
18567
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
18568
|
+
for (const fr of ev.fragments) this.fragments.spawn(fr, this.fragments.materialFor(fr.material, fallbackColor(fr.material, def), false), ev.object, maxAlive);
|
|
18569
|
+
if (!this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.center)) this.vfx.burst("dust", ev.center, [0, 1, 0], 6, 0.8, { color });
|
|
18570
|
+
this.sound(impactKey(def), ev.center, 0.7);
|
|
18571
|
+
}
|
|
18356
18572
|
onFracture(ev) {
|
|
18357
18573
|
const b = this.bounds.get(ev.object);
|
|
18358
18574
|
const pv = b?.parts.get(ev.part);
|
|
@@ -18373,7 +18589,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18373
18589
|
for (let i = 0; i < ev.fragments.length; i += step) {
|
|
18374
18590
|
const fr = ev.fragments[i];
|
|
18375
18591
|
const fdef = this.world.materials.get(fr.material);
|
|
18376
|
-
const
|
|
18592
|
+
const tone2 = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def || !fdef ? color : fallbackColor(fr.material, fdef);
|
|
18593
|
+
const mat = this.fragments.materialFor(fr.material, tone2, transparent || fdef?.visual?.transparent === true, opacity);
|
|
18377
18594
|
this.fragments.spawn(fr, mat, ev.object, maxAlive);
|
|
18378
18595
|
}
|
|
18379
18596
|
const up = [0, 1, 0];
|
|
@@ -18391,7 +18608,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18391
18608
|
this.vfx.burst("dust", ev.point, up, 5, 0.8, { color: 11901556 });
|
|
18392
18609
|
break;
|
|
18393
18610
|
case "tear":
|
|
18394
|
-
this.vfx.burst("fibers", ev.point, up, 12 * sizeK, 1.6, { color });
|
|
18611
|
+
this.vfx.burst("fibers", ev.point, up, 12 * sizeK, 1.6, { color: def?.visual?.innerColor ?? color });
|
|
18395
18612
|
break;
|
|
18396
18613
|
case "snap":
|
|
18397
18614
|
this.vfx.burst("chips", ev.point, up, 8 * sizeK, 3);
|
|
@@ -38147,7 +38364,7 @@ void main(){
|
|
|
38147
38364
|
if (!position) throw new Error(`game-test-room: scenery "${id}" is new, so it needs a position`);
|
|
38148
38365
|
merged.set(id, { ...obj, position });
|
|
38149
38366
|
}
|
|
38150
|
-
return [...merged.entries()].map(([id, o]) => ({ id, spec: o.spec, position: o.position, rotationY: o.rotationY ?? 0, colors: o.colors, build: o.build, label: o.label, top: o.top, spout: o.spout }));
|
|
38367
|
+
return [...merged.entries()].map(([id, o]) => ({ id, spec: o.spec, position: o.position, rotationY: o.rotationY ?? 0, colors: o.colors, build: o.build, follow: o.follow, label: o.label, top: o.top, spout: o.spout, free: o.free }));
|
|
38151
38368
|
}
|
|
38152
38369
|
function createArena(THREE, size, scene2, scenery, tapOption) {
|
|
38153
38370
|
const group = markKitOrigin(new THREE.Group(), { owner: "game-test-room", kind: "room" });
|
|
@@ -38748,6 +38965,14 @@ void main(){
|
|
|
38748
38965
|
...projectile.fire ? { fire: projectile.fire } : {}
|
|
38749
38966
|
});
|
|
38750
38967
|
};
|
|
38968
|
+
const blastAt = (weapon, explosive, point) => {
|
|
38969
|
+
const def = arsenal.explosives.get(explosive);
|
|
38970
|
+
if (!def) return false;
|
|
38971
|
+
const event = new ExplosiveBody({ explosive: def, origin: point, triggers: ["remote"], rules: arsenal.rules.explosives }).trigger("remote");
|
|
38972
|
+
if (event?.type !== "detonate") return false;
|
|
38973
|
+
detonate({ weapon }, event.detonation);
|
|
38974
|
+
return true;
|
|
38975
|
+
};
|
|
38751
38976
|
const detonateCharges = () => {
|
|
38752
38977
|
let count = 0;
|
|
38753
38978
|
for (const projectile of [...projectiles]) {
|
|
@@ -38839,7 +39064,172 @@ void main(){
|
|
|
38839
39064
|
}
|
|
38840
39065
|
}
|
|
38841
39066
|
};
|
|
38842
|
-
return { throwUse, placeUse, backblastUse, detonateCharges, update, clear };
|
|
39067
|
+
return { throwUse, placeUse, backblastUse, detonateCharges, blastAt, update, clear };
|
|
39068
|
+
}
|
|
39069
|
+
|
|
39070
|
+
// src/test-table.ts
|
|
39071
|
+
var TRIAL_KINDS = ["shot", "punch", "stab", "fire", "blast"];
|
|
39072
|
+
var DEFAULT_TRIALS = {
|
|
39073
|
+
shot: { weapon: "pistol_9mm", distance: 2.5, lift: 0.5 },
|
|
39074
|
+
punch: { weapon: "fists", distance: 0.3, lift: 0.15 },
|
|
39075
|
+
stab: { weapon: "knife", distance: 0.22, lift: 0.1 },
|
|
39076
|
+
fire: { weapon: "flamethrower", distance: 1.6, lift: 0.4, hold: 0.8 },
|
|
39077
|
+
blast: { weapon: "frag_grenade", distance: 0.3, lift: 1, watch: 6 }
|
|
39078
|
+
};
|
|
39079
|
+
var resolveTrials = (overrides = {}) => Object.fromEntries(TRIAL_KINDS.map((kind) => [kind, { ...DEFAULT_TRIALS[kind], ...overrides[kind] }]));
|
|
39080
|
+
function fitRow(width, start, total, length3, gap) {
|
|
39081
|
+
let used = 0;
|
|
39082
|
+
let count = 0;
|
|
39083
|
+
for (let i = start; i < total; i++) {
|
|
39084
|
+
const w = width(i);
|
|
39085
|
+
const next = count === 0 ? w : used + gap + w;
|
|
39086
|
+
if (count > 0 && next > length3) break;
|
|
39087
|
+
used = next;
|
|
39088
|
+
count++;
|
|
39089
|
+
}
|
|
39090
|
+
return count;
|
|
39091
|
+
}
|
|
39092
|
+
function rowCentres(widths, centreX, gap) {
|
|
39093
|
+
const length3 = widths.reduce((sum2, w) => sum2 + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
39094
|
+
let x = centreX - length3 / 2;
|
|
39095
|
+
return widths.map((w) => {
|
|
39096
|
+
const centre = x + w / 2;
|
|
39097
|
+
x += w + gap;
|
|
39098
|
+
return centre;
|
|
39099
|
+
});
|
|
39100
|
+
}
|
|
39101
|
+
function trialView(tool, kind, min2, max2) {
|
|
39102
|
+
const target = [(min2[0] + max2[0]) / 2, (min2[1] + max2[1]) / 2, (min2[2] + max2[2]) / 2];
|
|
39103
|
+
const away = kind === "blast" ? tool.watch ?? 6 : tool.distance;
|
|
39104
|
+
return { eye: [target[0], target[1] + tool.lift, max2[2] + away], target };
|
|
39105
|
+
}
|
|
39106
|
+
function createTestTable(context, options = {}) {
|
|
39107
|
+
const gap = options.gap ?? 0.12;
|
|
39108
|
+
const margin = options.margin ?? 0.1;
|
|
39109
|
+
const trials = resolveTrials(options.trials);
|
|
39110
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
39111
|
+
let current = null;
|
|
39112
|
+
const itemsOf = (group) => context.groups().get(group) ?? [];
|
|
39113
|
+
const emit2 = () => {
|
|
39114
|
+
const page = state();
|
|
39115
|
+
for (const listener of [...listeners]) listener(page);
|
|
39116
|
+
return page;
|
|
39117
|
+
};
|
|
39118
|
+
const state = () => {
|
|
39119
|
+
if (!current) return null;
|
|
39120
|
+
const total = itemsOf(current.group).length;
|
|
39121
|
+
const live = current.objects.filter((o) => context.box(o.objectId));
|
|
39122
|
+
return {
|
|
39123
|
+
group: current.group,
|
|
39124
|
+
page: current.page,
|
|
39125
|
+
first: current.first,
|
|
39126
|
+
total,
|
|
39127
|
+
objects: live,
|
|
39128
|
+
selected: current.selected,
|
|
39129
|
+
hasPrev: current.page > 0,
|
|
39130
|
+
hasNext: current.first + current.objects.length < total
|
|
39131
|
+
};
|
|
39132
|
+
};
|
|
39133
|
+
const bounds = /* @__PURE__ */ new Map();
|
|
39134
|
+
const widthOf = (itemId) => context.measure(itemId).w;
|
|
39135
|
+
const boundsUntil = (group, length3, until) => {
|
|
39136
|
+
const items = itemsOf(group);
|
|
39137
|
+
const known = bounds.get(group) ?? [0];
|
|
39138
|
+
bounds.set(group, known);
|
|
39139
|
+
while (known[known.length - 1] < items.length && !until(known)) {
|
|
39140
|
+
const first = known[known.length - 1];
|
|
39141
|
+
known.push(first + fitRow((i) => widthOf(items[i].id), first, items.length, length3, gap));
|
|
39142
|
+
}
|
|
39143
|
+
return known;
|
|
39144
|
+
};
|
|
39145
|
+
const lengthOf2 = (top) => top.maxX - top.minX - 2 * margin;
|
|
39146
|
+
const lay = (group, page, top) => {
|
|
39147
|
+
const items = itemsOf(group);
|
|
39148
|
+
const known = boundsUntil(group, lengthOf2(top), (b) => b.length > page + 1);
|
|
39149
|
+
const index = Math.max(0, Math.min(page, known.length - 2));
|
|
39150
|
+
const first = known[index];
|
|
39151
|
+
const laid = items.slice(first, known[index + 1]);
|
|
39152
|
+
context.clear();
|
|
39153
|
+
const sizes = laid.map((item) => context.measure(item.id));
|
|
39154
|
+
const centres = rowCentres(sizes.map((size) => size.w), (top.minX + top.maxX) / 2, gap);
|
|
39155
|
+
const objects = laid.map((item, i) => {
|
|
39156
|
+
const d = Math.min(sizes[i].d, top.maxZ - top.minZ);
|
|
39157
|
+
const { objectId } = context.spawn(item.id, [centres[i], top.top, top.maxZ - margin / 2 - d / 2]);
|
|
39158
|
+
return { objectId, itemId: item.id, name: item.name };
|
|
39159
|
+
});
|
|
39160
|
+
current = { group, page: index, first, objects, selected: objects[0]?.objectId ?? null };
|
|
39161
|
+
context.log("test_table", context.translate("act.test_table", { group, from: first + 1, to: first + laid.length, total: items.length }), { group, page: index, items: laid.map((i) => i.id) });
|
|
39162
|
+
return emit2();
|
|
39163
|
+
};
|
|
39164
|
+
const open = (group, page = 0) => {
|
|
39165
|
+
const top = context.top();
|
|
39166
|
+
if (!itemsOf(group).length || !top) return null;
|
|
39167
|
+
return lay(group, page, top);
|
|
39168
|
+
};
|
|
39169
|
+
const find = (itemId) => {
|
|
39170
|
+
const top = context.top();
|
|
39171
|
+
const group = [...context.groups()].find(([, items]) => items.some((i) => i.id === itemId))?.[0];
|
|
39172
|
+
if (group === void 0 || !top) return null;
|
|
39173
|
+
const index = itemsOf(group).findIndex((i) => i.id === itemId);
|
|
39174
|
+
const known = boundsUntil(group, lengthOf2(top), (b) => b[b.length - 1] > index);
|
|
39175
|
+
lay(group, known.findIndex((bound, i) => bound <= index && index < known[i + 1]), top);
|
|
39176
|
+
const target = current?.objects.find((o) => o.itemId === itemId);
|
|
39177
|
+
if (current && target) current.selected = target.objectId;
|
|
39178
|
+
return emit2();
|
|
39179
|
+
};
|
|
39180
|
+
const selected = (objectId) => {
|
|
39181
|
+
const id = objectId ?? current?.selected ?? null;
|
|
39182
|
+
return id && context.box(id) ? id : null;
|
|
39183
|
+
};
|
|
39184
|
+
const trial = (kind, objectId) => {
|
|
39185
|
+
const id = selected(objectId);
|
|
39186
|
+
const box2 = id ? context.box(id) : null;
|
|
39187
|
+
if (!id || !box2) return false;
|
|
39188
|
+
const tool = trials[kind];
|
|
39189
|
+
if (!context.equip(tool.weapon)) return false;
|
|
39190
|
+
const view = trialView(tool, kind, box2.min, box2.max);
|
|
39191
|
+
context.look(view.eye, view.target);
|
|
39192
|
+
const name = current?.objects.find((o) => o.objectId === id)?.name ?? id;
|
|
39193
|
+
context.log("trial", context.translate(`act.trial.${kind}`, { name }), { kind, objectId: id, weapon: tool.weapon });
|
|
39194
|
+
if (kind === "blast") {
|
|
39195
|
+
const point = [view.target[0], box2.min[1] + 0.05, box2.min[2] - tool.distance];
|
|
39196
|
+
return context.blastAt(tool.weapon, point);
|
|
39197
|
+
}
|
|
39198
|
+
context.use(tool.hold ?? 0);
|
|
39199
|
+
emit2();
|
|
39200
|
+
return true;
|
|
39201
|
+
};
|
|
39202
|
+
return {
|
|
39203
|
+
groups: () => [...context.groups()].filter(([, items]) => items.length > 0).map(([group]) => group),
|
|
39204
|
+
items: () => [...context.groups()].flatMap(([group, items]) => items.map((item) => ({ id: item.id, name: item.name, group }))),
|
|
39205
|
+
open,
|
|
39206
|
+
find,
|
|
39207
|
+
next: () => {
|
|
39208
|
+
const page = state();
|
|
39209
|
+
return page?.hasNext ? open(page.group, page.page + 1) : page;
|
|
39210
|
+
},
|
|
39211
|
+
prev: () => {
|
|
39212
|
+
const page = state();
|
|
39213
|
+
return page?.hasPrev ? open(page.group, page.page - 1) : page;
|
|
39214
|
+
},
|
|
39215
|
+
relay: () => {
|
|
39216
|
+
if (!current) return null;
|
|
39217
|
+
context.resetAll();
|
|
39218
|
+
return open(current.group, current.page);
|
|
39219
|
+
},
|
|
39220
|
+
state,
|
|
39221
|
+
select: (objectId) => {
|
|
39222
|
+
if (!current || !current.objects.some((o) => o.objectId === objectId) || !context.box(objectId)) return false;
|
|
39223
|
+
current.selected = objectId;
|
|
39224
|
+
emit2();
|
|
39225
|
+
return true;
|
|
39226
|
+
},
|
|
39227
|
+
trial,
|
|
39228
|
+
on: (listener) => {
|
|
39229
|
+
listeners.add(listener);
|
|
39230
|
+
return () => listeners.delete(listener);
|
|
39231
|
+
}
|
|
39232
|
+
};
|
|
38843
39233
|
}
|
|
38844
39234
|
|
|
38845
39235
|
// src/host-events.ts
|
|
@@ -40207,6 +40597,9 @@ void main(){
|
|
|
40207
40597
|
"act.strike.leg": "{weapon}: {clip} with the {leg} leg begins, impact in {seconds} s",
|
|
40208
40598
|
"leg.left": "left",
|
|
40209
40599
|
"leg.right": "right",
|
|
40600
|
+
"act.strike.hand": "{weapon}: {clip} with the {hand} hand begins, impact in {seconds} s",
|
|
40601
|
+
"hand.left": "left",
|
|
40602
|
+
"hand.right": "right",
|
|
40210
40603
|
"act.flame.hit": "{weapon}: flame at {temp} \xB0C on {where}",
|
|
40211
40604
|
"act.flame.air": "{weapon}: flame, nothing in range",
|
|
40212
40605
|
"act.heat.hit": "{weapon}: heating {where} at {temp} \xB0C",
|
|
@@ -40238,6 +40631,7 @@ void main(){
|
|
|
40238
40631
|
"loading.open_area": "Opening {area}\u2026",
|
|
40239
40632
|
"loading.spawn": "Building {name}\u2026",
|
|
40240
40633
|
"act.loosened": "{name} was heavy but free: the blast moved it",
|
|
40634
|
+
"act.shoved": "{name} stands free: the shots moved it",
|
|
40241
40635
|
"act.impact": "{name} hit {where} at {speed} m/s: {energy} J of blunt damage",
|
|
40242
40636
|
"act.use_item": "{name}: {use}",
|
|
40243
40637
|
"act.tap.on": "Tap opened",
|
|
@@ -40271,7 +40665,7 @@ void main(){
|
|
|
40271
40665
|
"hud.throw": "throw",
|
|
40272
40666
|
"hud.tilt": "tilt (hold)",
|
|
40273
40667
|
"hud.equip": "equip {weapon}",
|
|
40274
|
-
"ui.help": "Click: look \xB7 WASD: walk \xB7 Shift: run \xB7 C: crouch \xB7 Space: jump \xB7 1-0 / wheel: tool \xB7 LMB: use \xB7 E: grab/drop \xB7 G or RMB: throw \xB7 R: reload \xB7 Q: fire mode \xB7 X: clear a jam \xB7 B: safety \xB7 Y: detonate charges set down \xB7 Tab: panels \xB7 Esc: free the mouse",
|
|
40668
|
+
"ui.help": "Click: look \xB7 WASD: walk \xB7 Shift: run \xB7 C: crouch \xB7 Space: jump \xB7 1-0 / wheel: tool \xB7 LMB: use (bare hands: punch) \xB7 E: grab/drop \xB7 G or RMB: throw \xB7 R: reload \xB7 Q: fire mode \xB7 X: clear a jam \xB7 B: safety \xB7 Y: detonate charges set down \xB7 Tab: panels \xB7 Esc: free the mouse",
|
|
40275
40669
|
"ui.help.camera": "V: 1st/3rd person (Shift+V: kick)",
|
|
40276
40670
|
"camera.first": "1st person",
|
|
40277
40671
|
"camera.third": "3rd person",
|
|
@@ -40285,6 +40679,32 @@ void main(){
|
|
|
40285
40679
|
"ui.copy_json": "Copy JSON",
|
|
40286
40680
|
"ui.download_ndjson": "Download NDJSON",
|
|
40287
40681
|
"ui.download_txt": "Download TXT",
|
|
40682
|
+
"ui.test_table": "Test table",
|
|
40683
|
+
"ui.test_table.group": "Group to lay on the table",
|
|
40684
|
+
"ui.test_table.lay": "Lay",
|
|
40685
|
+
"ui.test_table.prev": "Previous items",
|
|
40686
|
+
"ui.test_table.next": "Next items",
|
|
40687
|
+
"ui.test_table.relay": "Put back",
|
|
40688
|
+
"ui.test_table.search": "Find an item to put on the table",
|
|
40689
|
+
"ui.test_table.hint": "Pick a group and lay it on the table, then pick an item and try it.",
|
|
40690
|
+
"ui.test_table.page": "Items {from}\u2013{to} of {total}. Pick one, then a trial:",
|
|
40691
|
+
"ui.trial.shot": "Shot",
|
|
40692
|
+
"ui.trial.punch": "Punch",
|
|
40693
|
+
"ui.trial.stab": "Knife",
|
|
40694
|
+
"ui.trial.fire": "Fire",
|
|
40695
|
+
"ui.trial.blast": "Blast",
|
|
40696
|
+
"ui.trial.shot.hint": "A 9 mm round from 2.5 m",
|
|
40697
|
+
"ui.trial.punch.hint": "A punch, up close",
|
|
40698
|
+
"ui.trial.stab.hint": "A knife blow, up close",
|
|
40699
|
+
"ui.trial.fire.hint": "A flamethrower burst from 1.6 m",
|
|
40700
|
+
"ui.trial.blast.hint": "A fragmentation grenade goes off just behind it (it reaches the neighbours too)",
|
|
40701
|
+
"act.test_table": "Test table: {group}, items {from}\u2013{to} of {total}",
|
|
40702
|
+
"act.trial.shot": "Trial: shooting {name}",
|
|
40703
|
+
"act.trial.punch": "Trial: punching {name}",
|
|
40704
|
+
"act.trial.stab": "Trial: stabbing {name}",
|
|
40705
|
+
"act.trial.fire": "Trial: setting fire to {name}",
|
|
40706
|
+
"act.trial.blast": "Trial: a blast beside {name}",
|
|
40707
|
+
"arena.test_table": "Test table",
|
|
40288
40708
|
"ui.clear_log": "Clear",
|
|
40289
40709
|
"ui.catalog": "Catalogue",
|
|
40290
40710
|
"ui.clear": "Clear",
|
|
@@ -40488,6 +40908,9 @@ void main(){
|
|
|
40488
40908
|
"act.strike.leg": "{weapon}: {clip} com a perna {leg} come\xE7a, impacto em {seconds} s",
|
|
40489
40909
|
"leg.left": "esquerda",
|
|
40490
40910
|
"leg.right": "direita",
|
|
40911
|
+
"act.strike.hand": "{weapon}: {clip} com a m\xE3o {hand} come\xE7a, impacto em {seconds} s",
|
|
40912
|
+
"hand.left": "esquerda",
|
|
40913
|
+
"hand.right": "direita",
|
|
40491
40914
|
"act.flame.hit": "{weapon}: chama a {temp} \xB0C em {where}",
|
|
40492
40915
|
"act.flame.air": "{weapon}: chama, nada ao alcance",
|
|
40493
40916
|
"act.heat.hit": "{weapon}: esquentando {where} a {temp} \xB0C",
|
|
@@ -40519,6 +40942,7 @@ void main(){
|
|
|
40519
40942
|
"loading.open_area": "Abrindo {area}\u2026",
|
|
40520
40943
|
"loading.spawn": "Montando {name}\u2026",
|
|
40521
40944
|
"act.loosened": "{name} \xE9 pesado mas estava solto: a explos\xE3o o moveu",
|
|
40945
|
+
"act.shoved": "{name} estava solto no ch\xE3o: os tiros o moveram",
|
|
40522
40946
|
"act.impact": "{name} bateu em {where} a {speed} m/s: {energy} J de dano de impacto",
|
|
40523
40947
|
"act.use_item": "{name}: {use}",
|
|
40524
40948
|
"act.tap.on": "Abriu a torneira",
|
|
@@ -40552,7 +40976,7 @@ void main(){
|
|
|
40552
40976
|
"hud.throw": "arremessar",
|
|
40553
40977
|
"hud.tilt": "inclinar (segurar)",
|
|
40554
40978
|
"hud.equip": "equipar {weapon}",
|
|
40555
|
-
"ui.help": "Clique: olhar \xB7 WASD: andar \xB7 Shift: correr \xB7 C: agachar \xB7 Espa\xE7o: pular \xB7 1-0 / roda: ferramenta \xB7 bot\xE3o esquerdo: usar \xB7 E: pegar/soltar \xB7 G ou bot\xE3o direito: arremessar \xB7 R: recarregar \xB7 Q: modo de tiro \xB7 X: destravar \xB7 B: trava \xB7 Y: detonar cargas postas \xB7 Tab: pain\xE9is \xB7 Esc: soltar o mouse",
|
|
40979
|
+
"ui.help": "Clique: olhar \xB7 WASD: andar \xB7 Shift: correr \xB7 C: agachar \xB7 Espa\xE7o: pular \xB7 1-0 / roda: ferramenta \xB7 bot\xE3o esquerdo: usar (m\xE3o livre: soco) \xB7 E: pegar/soltar \xB7 G ou bot\xE3o direito: arremessar \xB7 R: recarregar \xB7 Q: modo de tiro \xB7 X: destravar \xB7 B: trava \xB7 Y: detonar cargas postas \xB7 Tab: pain\xE9is \xB7 Esc: soltar o mouse",
|
|
40556
40980
|
"ui.help.camera": "V: 1\xAA/3\xAA pessoa (Shift+V: chute)",
|
|
40557
40981
|
"camera.first": "1\xAA pessoa",
|
|
40558
40982
|
"camera.third": "3\xAA pessoa",
|
|
@@ -40566,6 +40990,32 @@ void main(){
|
|
|
40566
40990
|
"ui.copy_json": "Copiar JSON",
|
|
40567
40991
|
"ui.download_ndjson": "Baixar NDJSON",
|
|
40568
40992
|
"ui.download_txt": "Baixar TXT",
|
|
40993
|
+
"ui.test_table": "Mesa de testes",
|
|
40994
|
+
"ui.test_table.group": "Grupo para p\xF4r na mesa",
|
|
40995
|
+
"ui.test_table.lay": "P\xF4r na mesa",
|
|
40996
|
+
"ui.test_table.prev": "Itens anteriores",
|
|
40997
|
+
"ui.test_table.next": "Pr\xF3ximos itens",
|
|
40998
|
+
"ui.test_table.relay": "Repor",
|
|
40999
|
+
"ui.test_table.search": "Buscar item para p\xF4r na mesa",
|
|
41000
|
+
"ui.test_table.hint": "Escolha um grupo e ponha na mesa; depois escolha um item e teste.",
|
|
41001
|
+
"ui.test_table.page": "Itens {from}\u2013{to} de {total}. Escolha um e um teste:",
|
|
41002
|
+
"ui.trial.shot": "Tiro",
|
|
41003
|
+
"ui.trial.punch": "Soco",
|
|
41004
|
+
"ui.trial.stab": "Faca",
|
|
41005
|
+
"ui.trial.fire": "Fogo",
|
|
41006
|
+
"ui.trial.blast": "Explos\xE3o",
|
|
41007
|
+
"ui.trial.shot.hint": "Um tiro de 9 mm a 2,5 m",
|
|
41008
|
+
"ui.trial.punch.hint": "Um soco, de perto",
|
|
41009
|
+
"ui.trial.stab.hint": "Um golpe de faca, de perto",
|
|
41010
|
+
"ui.trial.fire.hint": "Uma rajada de lan\xE7a-chamas a 1,6 m",
|
|
41011
|
+
"ui.trial.blast.hint": "Uma granada de fragmenta\xE7\xE3o explode logo atr\xE1s dele (pega os vizinhos tamb\xE9m)",
|
|
41012
|
+
"act.test_table": "Mesa de testes: {group}, itens {from}\u2013{to} de {total}",
|
|
41013
|
+
"act.trial.shot": "Teste: tiro em {name}",
|
|
41014
|
+
"act.trial.punch": "Teste: soco em {name}",
|
|
41015
|
+
"act.trial.stab": "Teste: faca em {name}",
|
|
41016
|
+
"act.trial.fire": "Teste: fogo em {name}",
|
|
41017
|
+
"act.trial.blast": "Teste: explos\xE3o ao lado de {name}",
|
|
41018
|
+
"arena.test_table": "Mesa de testes",
|
|
40569
41019
|
"ui.clear_log": "Limpar",
|
|
40570
41020
|
"ui.catalog": "Cat\xE1logo",
|
|
40571
41021
|
"ui.clear": "Limpar",
|
|
@@ -40907,6 +41357,122 @@ void main(){
|
|
|
40907
41357
|
};
|
|
40908
41358
|
}
|
|
40909
41359
|
|
|
41360
|
+
// src/test-table-panel.ts
|
|
41361
|
+
var TEST_TABLE_CSS = `
|
|
41362
|
+
.tr-table{flex:0 0 auto;max-height:42%}
|
|
41363
|
+
.tr-table .sub{color:#9aa3b2;margin:4px 0}
|
|
41364
|
+
.tr-table .list{display:flex;flex-wrap:wrap;gap:3px}
|
|
41365
|
+
.tr-table .list .tr-btn{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
41366
|
+
.tr-table select{flex:1;min-width:0;max-width:55%}
|
|
41367
|
+
.tr-table .tr-search{margin:6px 0 0}
|
|
41368
|
+
.tr-root .tr-table button.tr-btn.on{background:#3b3a14;border-color:#ffd23f;color:#fff}
|
|
41369
|
+
`;
|
|
41370
|
+
var mk2 = (tag, cls, text) => {
|
|
41371
|
+
const e = document.createElement(tag);
|
|
41372
|
+
if (cls) e.className = cls;
|
|
41373
|
+
if (text !== void 0) e.textContent = text;
|
|
41374
|
+
return e;
|
|
41375
|
+
};
|
|
41376
|
+
function createTestTablePanel(table, t) {
|
|
41377
|
+
const element = mk2("div", "tr-panel tr-table");
|
|
41378
|
+
element.appendChild(mk2("h3", void 0, t("ui.test_table")));
|
|
41379
|
+
const head = mk2("div", "tr-row");
|
|
41380
|
+
const select = mk2("select");
|
|
41381
|
+
select.setAttribute("aria-label", t("ui.test_table.group"));
|
|
41382
|
+
const lay = mk2("button", "tr-btn", t("ui.test_table.lay"));
|
|
41383
|
+
const prev = mk2("button", "tr-btn", "\u25C0");
|
|
41384
|
+
const next = mk2("button", "tr-btn", "\u25B6");
|
|
41385
|
+
prev.title = t("ui.test_table.prev");
|
|
41386
|
+
next.title = t("ui.test_table.next");
|
|
41387
|
+
for (const b of [lay, prev, next]) b.type = "button";
|
|
41388
|
+
head.append(select, lay, prev, next);
|
|
41389
|
+
const search = mk2("input", "tr-search");
|
|
41390
|
+
search.type = "search";
|
|
41391
|
+
search.placeholder = t("ui.test_table.search");
|
|
41392
|
+
const found = mk2("div", "list");
|
|
41393
|
+
const sub4 = mk2("div", "sub", t("ui.test_table.hint"));
|
|
41394
|
+
const list = mk2("div", "list");
|
|
41395
|
+
const trials = mk2("div", "tr-row");
|
|
41396
|
+
const trialButtons = TRIAL_KINDS.map((kind) => {
|
|
41397
|
+
const b = mk2("button", "tr-btn", t(`ui.trial.${kind}`));
|
|
41398
|
+
b.type = "button";
|
|
41399
|
+
b.dataset.trial = kind;
|
|
41400
|
+
b.title = t(`ui.trial.${kind}.hint`);
|
|
41401
|
+
b.onclick = () => table.trial(kind);
|
|
41402
|
+
trials.appendChild(b);
|
|
41403
|
+
return b;
|
|
41404
|
+
});
|
|
41405
|
+
const relay = mk2("button", "tr-btn", t("ui.test_table.relay"));
|
|
41406
|
+
relay.type = "button";
|
|
41407
|
+
relay.onclick = () => table.relay();
|
|
41408
|
+
trials.appendChild(relay);
|
|
41409
|
+
element.append(head, search, found, sub4, trials, list);
|
|
41410
|
+
const groups = () => {
|
|
41411
|
+
const keep = select.value;
|
|
41412
|
+
select.textContent = "";
|
|
41413
|
+
for (const group of table.groups()) {
|
|
41414
|
+
const op = mk2("option", void 0, group);
|
|
41415
|
+
op.value = group;
|
|
41416
|
+
select.appendChild(op);
|
|
41417
|
+
}
|
|
41418
|
+
if ([...select.options].some((o) => o.value === keep)) select.value = keep;
|
|
41419
|
+
};
|
|
41420
|
+
const render = (page) => {
|
|
41421
|
+
list.textContent = "";
|
|
41422
|
+
prev.disabled = !page?.hasPrev;
|
|
41423
|
+
next.disabled = !page?.hasNext;
|
|
41424
|
+
for (const b of [...trialButtons, relay]) b.disabled = !page?.selected;
|
|
41425
|
+
if (!page) {
|
|
41426
|
+
sub4.textContent = t("ui.test_table.hint");
|
|
41427
|
+
return;
|
|
41428
|
+
}
|
|
41429
|
+
if (select.value !== page.group) select.value = page.group;
|
|
41430
|
+
sub4.textContent = t("ui.test_table.page", { from: page.first + 1, to: page.first + page.objects.length, total: page.total });
|
|
41431
|
+
for (const o of page.objects) {
|
|
41432
|
+
const b = mk2("button", o.objectId === page.selected ? "tr-btn on" : "tr-btn", o.name);
|
|
41433
|
+
b.type = "button";
|
|
41434
|
+
b.title = o.itemId;
|
|
41435
|
+
b.onclick = () => table.select(o.objectId);
|
|
41436
|
+
list.appendChild(b);
|
|
41437
|
+
}
|
|
41438
|
+
};
|
|
41439
|
+
const fold2 = (text) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
41440
|
+
search.oninput = () => {
|
|
41441
|
+
found.textContent = "";
|
|
41442
|
+
const term = fold2(search.value.trim());
|
|
41443
|
+
if (!term) return;
|
|
41444
|
+
const hits = table.items().filter((item) => fold2(`${item.name} ${item.id}`).includes(term)).slice(0, 8);
|
|
41445
|
+
for (const item of hits) {
|
|
41446
|
+
const b = mk2("button", "tr-btn", item.name);
|
|
41447
|
+
b.type = "button";
|
|
41448
|
+
b.title = `${item.group} \xB7 ${item.id}`;
|
|
41449
|
+
b.onclick = () => {
|
|
41450
|
+
search.value = "";
|
|
41451
|
+
found.textContent = "";
|
|
41452
|
+
table.find(item.id);
|
|
41453
|
+
};
|
|
41454
|
+
found.appendChild(b);
|
|
41455
|
+
}
|
|
41456
|
+
if (!hits.length) found.appendChild(mk2("div", "sub", t("ui.no_results")));
|
|
41457
|
+
};
|
|
41458
|
+
lay.onclick = () => table.open(select.value);
|
|
41459
|
+
prev.onclick = () => table.prev();
|
|
41460
|
+
next.onclick = () => table.next();
|
|
41461
|
+
select.onchange = () => table.open(select.value);
|
|
41462
|
+
for (const box2 of [select, search]) box2.addEventListener("keydown", (e) => e.stopPropagation());
|
|
41463
|
+
const off = table.on(render);
|
|
41464
|
+
groups();
|
|
41465
|
+
render(table.state());
|
|
41466
|
+
return {
|
|
41467
|
+
element,
|
|
41468
|
+
refresh: () => {
|
|
41469
|
+
groups();
|
|
41470
|
+
render(table.state());
|
|
41471
|
+
},
|
|
41472
|
+
dispose: off
|
|
41473
|
+
};
|
|
41474
|
+
}
|
|
41475
|
+
|
|
40910
41476
|
// src/ui.ts
|
|
40911
41477
|
var CSS = `
|
|
40912
41478
|
.tr-root{position:absolute;inset:0;pointer-events:none;font:12px/1.35 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:#e8eaee;overflow:hidden}
|
|
@@ -40985,7 +41551,7 @@ void main(){
|
|
|
40985
41551
|
@media (max-width:980px){.tr-left,.tr-right{display:none}.tr-root.open .tr-left,.tr-root.open .tr-right{display:flex}.tr-toggle{display:block;pointer-events:auto}.tr-root.open .tr-toggle{right:min(310px,34vw)}.tr-left{width:min(400px,46vw)}.tr-help{display:none}}
|
|
40986
41552
|
`;
|
|
40987
41553
|
var fold = (text) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
40988
|
-
var
|
|
41554
|
+
var mk3 = (tag, cls, text) => {
|
|
40989
41555
|
const e = document.createElement(tag);
|
|
40990
41556
|
if (cls) e.className = cls;
|
|
40991
41557
|
if (text !== void 0) e.textContent = text;
|
|
@@ -40996,7 +41562,7 @@ void main(){
|
|
|
40996
41562
|
return source.replace(/["\\\n\r]/g, (char) => encodeURIComponent(char));
|
|
40997
41563
|
};
|
|
40998
41564
|
var thumb = (thumbnail, credit) => {
|
|
40999
|
-
const e =
|
|
41565
|
+
const e = mk3("span", "tr-thumb");
|
|
41000
41566
|
if (credit) e.title = credit;
|
|
41001
41567
|
else e.setAttribute("aria-hidden", "true");
|
|
41002
41568
|
e.style.setProperty("--tr-thumb", `url("${thumbSource(thumbnail)}")`);
|
|
@@ -41017,42 +41583,43 @@ void main(){
|
|
|
41017
41583
|
}
|
|
41018
41584
|
function createUi(o) {
|
|
41019
41585
|
const { t } = o;
|
|
41020
|
-
const style =
|
|
41021
|
-
style.textContent = CSS;
|
|
41022
|
-
const root =
|
|
41586
|
+
const style = mk3("style");
|
|
41587
|
+
style.textContent = CSS + (o.testTable ? TEST_TABLE_CSS : "");
|
|
41588
|
+
const root = mk3("div", "tr-root");
|
|
41023
41589
|
const pos = getComputedStyle?.(o.container)?.position;
|
|
41024
41590
|
if (!pos || pos === "static") o.container.style.position = "relative";
|
|
41025
41591
|
root.appendChild(style);
|
|
41026
41592
|
o.container.appendChild(root);
|
|
41027
|
-
const cross5 =
|
|
41028
|
-
cross5.appendChild(
|
|
41029
|
-
const top =
|
|
41030
|
-
const toolBox =
|
|
41031
|
-
const status =
|
|
41032
|
-
const prog =
|
|
41033
|
-
const progLabel =
|
|
41034
|
-
const progBar =
|
|
41035
|
-
const progFill =
|
|
41593
|
+
const cross5 = mk3("div", "tr-cross");
|
|
41594
|
+
cross5.appendChild(mk3("i"));
|
|
41595
|
+
const top = mk3("div", "tr-top");
|
|
41596
|
+
const toolBox = mk3("div", "tr-tool");
|
|
41597
|
+
const status = mk3("div", "tr-status");
|
|
41598
|
+
const prog = mk3("div", "tr-prog");
|
|
41599
|
+
const progLabel = mk3("b");
|
|
41600
|
+
const progBar = mk3("div", "tr-bar");
|
|
41601
|
+
const progFill = mk3("i");
|
|
41036
41602
|
progBar.appendChild(progFill);
|
|
41037
41603
|
prog.append(progLabel, progBar);
|
|
41038
41604
|
top.append(toolBox, status, prog);
|
|
41039
|
-
const bottom =
|
|
41040
|
-
const held =
|
|
41041
|
-
const hotbar =
|
|
41605
|
+
const bottom = mk3("div", "tr-bottom");
|
|
41606
|
+
const held = mk3("div", "tr-held");
|
|
41607
|
+
const hotbar = mk3("div", "tr-hotbar");
|
|
41042
41608
|
bottom.append(held, hotbar);
|
|
41043
|
-
const help =
|
|
41609
|
+
const help = mk3("div", "tr-help");
|
|
41044
41610
|
help.textContent = t("ui.help");
|
|
41045
|
-
const left =
|
|
41046
|
-
const insp =
|
|
41047
|
-
const logPanel =
|
|
41048
|
-
|
|
41049
|
-
|
|
41050
|
-
const
|
|
41051
|
-
const
|
|
41611
|
+
const left = mk3("div", "tr-left");
|
|
41612
|
+
const insp = mk3("div", "tr-panel tr-insp");
|
|
41613
|
+
const logPanel = mk3("div", "tr-panel tr-log");
|
|
41614
|
+
const tablePanel = o.testTable ? createTestTablePanel(o.testTable, t) : null;
|
|
41615
|
+
left.append(insp, ...tablePanel ? [tablePanel.element] : [], logPanel);
|
|
41616
|
+
const logTitle = mk3("h3", void 0, t("ui.log"));
|
|
41617
|
+
const logBar = mk3("div", "tr-logbar");
|
|
41618
|
+
const logBody = mk3("div", "tr-logbody");
|
|
41052
41619
|
const filters = { action: true, event: true, check: true };
|
|
41053
41620
|
const filterBtns = [];
|
|
41054
41621
|
for (const ch of ["action", "event", "check"]) {
|
|
41055
|
-
const b =
|
|
41622
|
+
const b = mk3("button", "tr-btn on", t(`ui.filter.${ch}`));
|
|
41056
41623
|
b.type = "button";
|
|
41057
41624
|
b.onclick = () => {
|
|
41058
41625
|
filters[ch] = !filters[ch];
|
|
@@ -41063,8 +41630,8 @@ void main(){
|
|
|
41063
41630
|
logBar.appendChild(b);
|
|
41064
41631
|
}
|
|
41065
41632
|
let autoscroll = true;
|
|
41066
|
-
const asLabel =
|
|
41067
|
-
const asBox =
|
|
41633
|
+
const asLabel = mk3("label");
|
|
41634
|
+
const asBox = mk3("input");
|
|
41068
41635
|
asBox.type = "checkbox";
|
|
41069
41636
|
asBox.checked = true;
|
|
41070
41637
|
asBox.onchange = () => {
|
|
@@ -41073,7 +41640,7 @@ void main(){
|
|
|
41073
41640
|
asLabel.append(asBox, document.createTextNode(" " + t("ui.autoscroll")));
|
|
41074
41641
|
logBar.appendChild(asLabel);
|
|
41075
41642
|
const logBtn = (label, fn) => {
|
|
41076
|
-
const b =
|
|
41643
|
+
const b = mk3("button", "tr-btn", label);
|
|
41077
41644
|
b.type = "button";
|
|
41078
41645
|
b.onclick = fn;
|
|
41079
41646
|
logBar.appendChild(b);
|
|
@@ -41081,7 +41648,7 @@ void main(){
|
|
|
41081
41648
|
logBtn(t("ui.copy_json"), () => {
|
|
41082
41649
|
const text = o.log.toJSON();
|
|
41083
41650
|
const select = () => {
|
|
41084
|
-
const ta =
|
|
41651
|
+
const ta = mk3("textarea");
|
|
41085
41652
|
ta.value = text;
|
|
41086
41653
|
ta.setAttribute("readonly", "");
|
|
41087
41654
|
ta.style.cssText = "position:fixed;left:8px;bottom:8px;width:min(480px,80vw);height:120px;z-index:99999";
|
|
@@ -41115,12 +41682,12 @@ void main(){
|
|
|
41115
41682
|
const fill = (n, e) => {
|
|
41116
41683
|
n.className = lineClass(e);
|
|
41117
41684
|
n.textContent = "";
|
|
41118
|
-
const tm =
|
|
41685
|
+
const tm = mk3("time", void 0, fmtTime(e.t));
|
|
41119
41686
|
n.append(tm, document.createTextNode(e.text));
|
|
41120
41687
|
};
|
|
41121
41688
|
const addNode = (e) => {
|
|
41122
41689
|
if (!filters[e.channel]) return;
|
|
41123
|
-
const n =
|
|
41690
|
+
const n = mk3("div");
|
|
41124
41691
|
fill(n, e);
|
|
41125
41692
|
nodes.set(e.seq, n);
|
|
41126
41693
|
logBody.appendChild(n);
|
|
@@ -41142,18 +41709,18 @@ void main(){
|
|
|
41142
41709
|
if (n) fill(n, e);
|
|
41143
41710
|
} else addNode(e);
|
|
41144
41711
|
});
|
|
41145
|
-
const right =
|
|
41146
|
-
const cat =
|
|
41147
|
-
const anim =
|
|
41148
|
-
const tests =
|
|
41712
|
+
const right = mk3("div", "tr-right");
|
|
41713
|
+
const cat = mk3("div", "tr-panel tr-cat");
|
|
41714
|
+
const anim = mk3("div", "tr-panel tr-anim");
|
|
41715
|
+
const tests = mk3("div", "tr-panel tr-tests");
|
|
41149
41716
|
const benchPanel = createBenchPanel({ t, num: (n, d) => num(n, d), onControl: o.onControl, onLayer: o.onLayer, onAction: o.onAction });
|
|
41150
41717
|
right.append(cat, anim, benchPanel.element, tests);
|
|
41151
|
-
cat.appendChild(
|
|
41152
|
-
const itemSearch =
|
|
41718
|
+
cat.appendChild(mk3("h3", void 0, t("ui.catalog")));
|
|
41719
|
+
const itemSearch = mk3("input", "tr-search");
|
|
41153
41720
|
itemSearch.type = "search";
|
|
41154
41721
|
itemSearch.placeholder = t("ui.search_items");
|
|
41155
|
-
const tabs =
|
|
41156
|
-
const items =
|
|
41722
|
+
const tabs = mk3("div", "tr-tabs");
|
|
41723
|
+
const items = mk3("div", "tr-items");
|
|
41157
41724
|
cat.append(itemSearch, tabs, items);
|
|
41158
41725
|
let activeGroup = "";
|
|
41159
41726
|
const visibleItems = () => {
|
|
@@ -41165,18 +41732,18 @@ void main(){
|
|
|
41165
41732
|
items.textContent = "";
|
|
41166
41733
|
const searching = itemSearch.value.trim() !== "";
|
|
41167
41734
|
const found = visibleItems();
|
|
41168
|
-
if (!found.length) items.appendChild(
|
|
41735
|
+
if (!found.length) items.appendChild(mk3("div", "tr-empty", t("ui.no_results")));
|
|
41169
41736
|
for (const { item: it, group } of found) {
|
|
41170
|
-
const wrap =
|
|
41171
|
-
const b =
|
|
41172
|
-
if (it.thumbnail) b.append(thumb(it.thumbnail),
|
|
41173
|
-
if (searching) b.appendChild(
|
|
41737
|
+
const wrap = mk3("div", "tr-item");
|
|
41738
|
+
const b = mk3("button", it.thumbnail ? "tr-btn has-thumb" : "tr-btn", it.thumbnail ? void 0 : it.name);
|
|
41739
|
+
if (it.thumbnail) b.append(thumb(it.thumbnail), mk3("span", "tr-name", it.name));
|
|
41740
|
+
if (searching) b.appendChild(mk3("small", void 0, group));
|
|
41174
41741
|
b.type = "button";
|
|
41175
41742
|
b.title = it.note ?? it.name;
|
|
41176
41743
|
b.onclick = () => o.onSpawn(it.id);
|
|
41177
41744
|
wrap.appendChild(b);
|
|
41178
41745
|
if (it.weapon) {
|
|
41179
|
-
const w =
|
|
41746
|
+
const w = mk3("button", "tr-btn", "\u2694");
|
|
41180
41747
|
w.type = "button";
|
|
41181
41748
|
w.title = t("ui.equip");
|
|
41182
41749
|
w.onclick = () => o.onEquip(it.id);
|
|
@@ -41190,7 +41757,7 @@ void main(){
|
|
|
41190
41757
|
const groupNames = [...o.groups().keys()];
|
|
41191
41758
|
if (!groupNames.includes(activeGroup)) activeGroup = groupNames[0] ?? "";
|
|
41192
41759
|
for (const g of groupNames) {
|
|
41193
|
-
const b =
|
|
41760
|
+
const b = mk3("button", g === activeGroup ? "tr-btn on" : "tr-btn", g);
|
|
41194
41761
|
b.type = "button";
|
|
41195
41762
|
b.onclick = () => {
|
|
41196
41763
|
activeGroup = g;
|
|
@@ -41203,9 +41770,9 @@ void main(){
|
|
|
41203
41770
|
renderTabs();
|
|
41204
41771
|
itemSearch.oninput = renderItems;
|
|
41205
41772
|
for (const box2 of [itemSearch]) box2.addEventListener("keydown", (e) => e.stopPropagation());
|
|
41206
|
-
const controls =
|
|
41773
|
+
const controls = mk3("div", "tr-row");
|
|
41207
41774
|
const ctl = (label, fn) => {
|
|
41208
|
-
const b =
|
|
41775
|
+
const b = mk3("button", "tr-btn", label);
|
|
41209
41776
|
b.type = "button";
|
|
41210
41777
|
b.onclick = () => fn(b);
|
|
41211
41778
|
controls.appendChild(b);
|
|
@@ -41230,29 +41797,29 @@ void main(){
|
|
|
41230
41797
|
fullscreenBtn.title = t("ui.fullscreen.hint");
|
|
41231
41798
|
const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key) => ({ key, b: ctl(t(`ui.blast.${key}`), () => o.onBlast(key)) }));
|
|
41232
41799
|
for (const { key, b } of blastBtns) b.title = t(`ui.blast.${key}.hint`);
|
|
41233
|
-
const qSel =
|
|
41800
|
+
const qSel = mk3("select");
|
|
41234
41801
|
for (const q of ["low", "medium", "high"]) {
|
|
41235
|
-
const op =
|
|
41802
|
+
const op = mk3("option", void 0, t(`ui.quality.${q}`));
|
|
41236
41803
|
op.value = q;
|
|
41237
41804
|
qSel.appendChild(op);
|
|
41238
41805
|
}
|
|
41239
41806
|
qSel.value = o.quality;
|
|
41240
41807
|
qSel.onchange = () => o.onQuality(qSel.value);
|
|
41241
|
-
const qLabel =
|
|
41808
|
+
const qLabel = mk3("label", void 0, t("ui.quality") + " ");
|
|
41242
41809
|
qLabel.appendChild(qSel);
|
|
41243
41810
|
controls.appendChild(qLabel);
|
|
41244
|
-
const count =
|
|
41811
|
+
const count = mk3("span");
|
|
41245
41812
|
controls.appendChild(count);
|
|
41246
41813
|
cat.appendChild(controls);
|
|
41247
|
-
tests.appendChild(
|
|
41248
|
-
const runAll =
|
|
41814
|
+
tests.appendChild(mk3("h3", void 0, t("ui.tests")));
|
|
41815
|
+
const runAll = mk3("button", "tr-btn", t("ui.run_all"));
|
|
41249
41816
|
runAll.type = "button";
|
|
41250
|
-
const summary =
|
|
41251
|
-
const testSearch =
|
|
41817
|
+
const summary = mk3("div", "tr-summary");
|
|
41818
|
+
const testSearch = mk3("input", "tr-search");
|
|
41252
41819
|
testSearch.type = "search";
|
|
41253
41820
|
testSearch.placeholder = t("ui.search_tests");
|
|
41254
41821
|
testSearch.addEventListener("keydown", (e) => e.stopPropagation());
|
|
41255
|
-
const list =
|
|
41822
|
+
const list = mk3("div", "list");
|
|
41256
41823
|
tests.append(runAll, summary, testSearch, list);
|
|
41257
41824
|
testSearch.oninput = () => {
|
|
41258
41825
|
const term = fold(testSearch.value.trim());
|
|
@@ -41275,7 +41842,7 @@ void main(){
|
|
|
41275
41842
|
list.textContent = "";
|
|
41276
41843
|
scBtns.clear();
|
|
41277
41844
|
for (const s of o.scenarios()) {
|
|
41278
|
-
const b =
|
|
41845
|
+
const b = mk3("button", "tr-btn", s.name);
|
|
41279
41846
|
b.type = "button";
|
|
41280
41847
|
b.onclick = async () => {
|
|
41281
41848
|
if (busy) return;
|
|
@@ -41301,7 +41868,7 @@ void main(){
|
|
|
41301
41868
|
busy = false;
|
|
41302
41869
|
}
|
|
41303
41870
|
};
|
|
41304
|
-
const toggle =
|
|
41871
|
+
const toggle = mk3("button", "tr-btn tr-toggle", t("ui.panel"));
|
|
41305
41872
|
toggle.type = "button";
|
|
41306
41873
|
const togglePanels = () => {
|
|
41307
41874
|
root.classList.toggle("open");
|
|
@@ -41310,15 +41877,15 @@ void main(){
|
|
|
41310
41877
|
toggle.onclick = togglePanels;
|
|
41311
41878
|
root.append(cross5, top, bottom, help, left, right, toggle);
|
|
41312
41879
|
const bar2 = (value, max2, cls) => {
|
|
41313
|
-
const b =
|
|
41314
|
-
const i =
|
|
41880
|
+
const b = mk3("div", `tr-bar ${cls}`);
|
|
41881
|
+
const i = mk3("i");
|
|
41315
41882
|
i.style.width = `${Math.max(0, Math.min(100, value / Math.max(1e-9, max2) * 100))}%`;
|
|
41316
41883
|
b.appendChild(i);
|
|
41317
41884
|
return b;
|
|
41318
41885
|
};
|
|
41319
41886
|
const row = (k, v) => {
|
|
41320
|
-
const r =
|
|
41321
|
-
r.append(
|
|
41887
|
+
const r = mk3("div", "r");
|
|
41888
|
+
r.append(mk3("span", void 0, k), mk3("span", void 0, v));
|
|
41322
41889
|
return r;
|
|
41323
41890
|
};
|
|
41324
41891
|
const num = (n, d = 1) => {
|
|
@@ -41327,13 +41894,13 @@ void main(){
|
|
|
41327
41894
|
};
|
|
41328
41895
|
const renderInspector = (i) => {
|
|
41329
41896
|
insp.textContent = "";
|
|
41330
|
-
insp.appendChild(
|
|
41897
|
+
insp.appendChild(mk3("h3", void 0, t("ui.inspector")));
|
|
41331
41898
|
if (!i) {
|
|
41332
|
-
insp.appendChild(
|
|
41899
|
+
insp.appendChild(mk3("div", void 0, t("ui.aim_at_something")));
|
|
41333
41900
|
return;
|
|
41334
41901
|
}
|
|
41335
41902
|
if (i.thumbnail) insp.appendChild(thumb(i.thumbnail, i.thumbnailCredit));
|
|
41336
|
-
insp.appendChild(
|
|
41903
|
+
insp.appendChild(mk3("div", "t", `${i.object} \u203A ${i.part}`));
|
|
41337
41904
|
insp.appendChild(row(t("ins.material"), i.material));
|
|
41338
41905
|
insp.appendChild(row(t("ins.status"), i.status + (i.burning ? ` \xB7 ${t("ins.burning")}` : "")));
|
|
41339
41906
|
insp.appendChild(row(t("ins.hp"), `${num(i.hp, 0)} / ${num(i.maxHp, 0)}`));
|
|
@@ -41352,30 +41919,30 @@ void main(){
|
|
|
41352
41919
|
insp.appendChild(bar2(Math.min(i.stress, 1.2), 1.2, i.stress > 1 ? "bad" : i.stress > 0.7 ? "warn" : "hp"));
|
|
41353
41920
|
}
|
|
41354
41921
|
if (i.joints.length) {
|
|
41355
|
-
insp.appendChild(
|
|
41922
|
+
insp.appendChild(mk3("div", void 0, t("ins.joints") + ":"));
|
|
41356
41923
|
for (const j of i.joints) {
|
|
41357
41924
|
const tools = j.tools.length ? j.tools.join(", ") : t("tool.none");
|
|
41358
|
-
insp.appendChild(
|
|
41925
|
+
insp.appendChild(mk3("div", void 0, `\u2022 ${j.kind} (${j.id})${j.open ? ` \xB7 ${t("ins.open")}` : ` \u2192 ${tools}`}`));
|
|
41359
41926
|
}
|
|
41360
41927
|
}
|
|
41361
|
-
if (i.note) insp.appendChild(
|
|
41928
|
+
if (i.note) insp.appendChild(mk3("div", void 0, i.note));
|
|
41362
41929
|
};
|
|
41363
41930
|
let lastAnim = "";
|
|
41364
41931
|
const renderAnimations = (a) => {
|
|
41365
41932
|
anim.textContent = "";
|
|
41366
|
-
anim.appendChild(
|
|
41933
|
+
anim.appendChild(mk3("h3", void 0, t("ui.animations")));
|
|
41367
41934
|
if (!a) {
|
|
41368
|
-
anim.appendChild(
|
|
41935
|
+
anim.appendChild(mk3("div", "sub", t("ui.animations_aim")));
|
|
41369
41936
|
return;
|
|
41370
41937
|
}
|
|
41371
|
-
anim.appendChild(
|
|
41938
|
+
anim.appendChild(mk3("div", "sub", a.name));
|
|
41372
41939
|
if (a.clips.length === 0) {
|
|
41373
|
-
anim.appendChild(
|
|
41940
|
+
anim.appendChild(mk3("div", "sub", t("ui.animations_none")));
|
|
41374
41941
|
return;
|
|
41375
41942
|
}
|
|
41376
|
-
const l =
|
|
41943
|
+
const l = mk3("div", "list");
|
|
41377
41944
|
for (const c of a.clips) {
|
|
41378
|
-
const b =
|
|
41945
|
+
const b = mk3("button", a.playing === c.id ? "tr-btn on" : "tr-btn", `\u25B6 ${t(`anim.${c.id}`) === `anim.${c.id}` ? c.id : t(`anim.${c.id}`)} (${num(c.duration, 1)} s)`);
|
|
41379
41946
|
b.type = "button";
|
|
41380
41947
|
b.onclick = () => o.onPlay(a.objectId, c.id);
|
|
41381
41948
|
l.appendChild(b);
|
|
@@ -41392,8 +41959,8 @@ void main(){
|
|
|
41392
41959
|
hud(s) {
|
|
41393
41960
|
toolBox.textContent = "";
|
|
41394
41961
|
toolBox.append(document.createTextNode(s.toolName));
|
|
41395
|
-
if (s.ammo) toolBox.append(
|
|
41396
|
-
if (s.camera) toolBox.append(
|
|
41962
|
+
if (s.ammo) toolBox.append(mk3("small", void 0, s.ammo));
|
|
41963
|
+
if (s.camera) toolBox.append(mk3("small", void 0, t(`camera.${s.camera}`)));
|
|
41397
41964
|
const helpText = s.camera ? `${t("ui.help")} \xB7 ${t("ui.help.camera")}` : t("ui.help");
|
|
41398
41965
|
if (help.textContent !== helpText) help.textContent = helpText;
|
|
41399
41966
|
if (s.status) status.textContent = s.status;
|
|
@@ -41416,7 +41983,7 @@ void main(){
|
|
|
41416
41983
|
];
|
|
41417
41984
|
if (s.held.weapon) keys.push(["H", t("hud.equip", { weapon: s.held.weapon })]);
|
|
41418
41985
|
for (const [k, label] of keys) {
|
|
41419
|
-
held.append(
|
|
41986
|
+
held.append(mk3("kbd", void 0, k), document.createTextNode(` ${label} `));
|
|
41420
41987
|
}
|
|
41421
41988
|
} else held.style.display = "none";
|
|
41422
41989
|
const key = s.slots.join("|") + s.page;
|
|
@@ -41426,9 +41993,9 @@ void main(){
|
|
|
41426
41993
|
slotsEl.length = 0;
|
|
41427
41994
|
hudPage = s.page - 1;
|
|
41428
41995
|
s.slots.forEach((name, i) => {
|
|
41429
|
-
const b =
|
|
41996
|
+
const b = mk3("button", "tr-slot");
|
|
41430
41997
|
b.type = "button";
|
|
41431
|
-
b.append(
|
|
41998
|
+
b.append(mk3("b", void 0, String((i + 1) % 10)), document.createTextNode(name));
|
|
41432
41999
|
b.title = name;
|
|
41433
42000
|
b.onclick = () => o.onTool(hudPage * 10 + i);
|
|
41434
42001
|
hotbar.appendChild(b);
|
|
@@ -41460,6 +42027,7 @@ void main(){
|
|
|
41460
42027
|
refresh() {
|
|
41461
42028
|
renderTabs();
|
|
41462
42029
|
renderScenarios();
|
|
42030
|
+
tablePanel?.refresh();
|
|
41463
42031
|
},
|
|
41464
42032
|
showGroup(group) {
|
|
41465
42033
|
activeGroup = group;
|
|
@@ -41474,6 +42042,7 @@ void main(){
|
|
|
41474
42042
|
},
|
|
41475
42043
|
dispose() {
|
|
41476
42044
|
offLog();
|
|
42045
|
+
tablePanel?.dispose();
|
|
41477
42046
|
if (toastTimer) clearTimeout(toastTimer);
|
|
41478
42047
|
root.remove();
|
|
41479
42048
|
}
|
|
@@ -41484,6 +42053,7 @@ void main(){
|
|
|
41484
42053
|
|
|
41485
42054
|
// src/room.ts
|
|
41486
42055
|
var FIXED_DT = 1 / 60;
|
|
42056
|
+
var SHOVE_SPEED = 0.04;
|
|
41487
42057
|
var METRICS_INTERVAL_S = 0.25;
|
|
41488
42058
|
var CARRY_NEAR = 0.62;
|
|
41489
42059
|
var CARRY_PALM = 0.012;
|
|
@@ -41846,7 +42416,9 @@ void main(){
|
|
|
41846
42416
|
scene2.add(root);
|
|
41847
42417
|
root.updateMatrixWorld(true);
|
|
41848
42418
|
world.add(structuredCloneSpec(o.spec), { id: o.id, position: o.position, ...o.rotationY ? { rotation: [0, Math.sin(o.rotationY / 2), 0, Math.cos(o.rotationY / 2)] } : {} });
|
|
41849
|
-
registerEntry(o.id, o.id, root, o.spec, o.label ?? S.text[`arena.${o.id}`] ?? o.id, void 0, void 0, true, false);
|
|
42419
|
+
const e = registerEntry(o.id, o.id, root, o.spec, o.label ?? S.text[`arena.${o.id}`] ?? o.id, void 0, void 0, true, false);
|
|
42420
|
+
if (o.free) e.free = true;
|
|
42421
|
+
e.follower = o.follow?.(root, world, o.id) || null;
|
|
41850
42422
|
}
|
|
41851
42423
|
};
|
|
41852
42424
|
addArena();
|
|
@@ -41941,6 +42513,7 @@ void main(){
|
|
|
41941
42513
|
renderer.unbind(id);
|
|
41942
42514
|
} catch {
|
|
41943
42515
|
}
|
|
42516
|
+
e.follower?.dispose?.();
|
|
41944
42517
|
if (world.has(id)) world.remove(id);
|
|
41945
42518
|
e.so.root.removeFromParent();
|
|
41946
42519
|
letGo(e.so.root);
|
|
@@ -41975,6 +42548,7 @@ void main(){
|
|
|
41975
42548
|
for (const e of entries.values()) {
|
|
41976
42549
|
world.reset(e.so.objectId);
|
|
41977
42550
|
renderer.restore(e.so.objectId);
|
|
42551
|
+
e.follower?.restore?.();
|
|
41978
42552
|
}
|
|
41979
42553
|
renderer.clear();
|
|
41980
42554
|
vfx.world.clear();
|
|
@@ -41996,6 +42570,7 @@ void main(){
|
|
|
41996
42570
|
if (heldEntry === e) continue;
|
|
41997
42571
|
world.reset(e.so.objectId);
|
|
41998
42572
|
renderer.restore(e.so.objectId);
|
|
42573
|
+
e.follower?.restore?.();
|
|
41999
42574
|
if (e.containerId) {
|
|
42000
42575
|
const c = fluids.container(e.containerId);
|
|
42001
42576
|
const f = e.item?.fluid?.container;
|
|
@@ -42299,6 +42874,19 @@ void main(){
|
|
|
42299
42874
|
if (!rendererPieces.has(objectId)) return;
|
|
42300
42875
|
renderer.impulse(objectId, [dir[0] * momentum, dir[1] * momentum, dir[2] * momentum], point);
|
|
42301
42876
|
};
|
|
42877
|
+
const shoveFree = (pushes) => {
|
|
42878
|
+
for (const [objectId, push] of pushes) {
|
|
42879
|
+
const e = entries.get(objectId);
|
|
42880
|
+
if (!e || e.piece || !e.free) continue;
|
|
42881
|
+
const mass = Math.max(0.2, massOfObject(objectId));
|
|
42882
|
+
if (Math.hypot(...push.impulse) / mass < SHOVE_SPEED) continue;
|
|
42883
|
+
const piece = renderer.makePiece(objectId, { mass });
|
|
42884
|
+
e.piece = piece;
|
|
42885
|
+
rendererPieces.set(objectId, piece);
|
|
42886
|
+
action("loosened", t("act.shoved", { name: e.name }), { objectId, itemId: e.so.itemId });
|
|
42887
|
+
renderer.impulse(objectId, push.impulse, push.point);
|
|
42888
|
+
}
|
|
42889
|
+
};
|
|
42302
42890
|
const fireShots = (shots, recoil) => {
|
|
42303
42891
|
if (shots.length === 0) return;
|
|
42304
42892
|
const w = toolName();
|
|
@@ -42308,6 +42896,7 @@ void main(){
|
|
|
42308
42896
|
let hits = 0;
|
|
42309
42897
|
let first = null;
|
|
42310
42898
|
let energy = 0;
|
|
42899
|
+
const pushes = /* @__PURE__ */ new Map();
|
|
42311
42900
|
withoutHeld(() => {
|
|
42312
42901
|
for (const s of shots) {
|
|
42313
42902
|
energy = Math.max(energy, s.energy);
|
|
@@ -42319,11 +42908,16 @@ void main(){
|
|
|
42319
42908
|
end3 = r0.point;
|
|
42320
42909
|
if (!first) first = { object: r0.object, part: r0.part, point: r0.point, normal: [0, 1, 0], distance: dist3(s.origin, r0.point) };
|
|
42321
42910
|
const speed = Math.max(80, s.speed);
|
|
42322
|
-
|
|
42911
|
+
const momentum = 2 * s.energy / speed;
|
|
42912
|
+
pushPiece(r0.object, s.direction, momentum, r0.point);
|
|
42913
|
+
const push = pushes.get(r0.object) ?? { impulse: [0, 0, 0], point: r0.point };
|
|
42914
|
+
for (let k = 0; k < 3; k++) push.impulse[k] += s.direction[k] * momentum;
|
|
42915
|
+
pushes.set(r0.object, push);
|
|
42323
42916
|
}
|
|
42324
42917
|
if (shots.length <= 3 || rnd2() < 0.34) tracer(m, end3);
|
|
42325
42918
|
}
|
|
42326
42919
|
});
|
|
42920
|
+
shoveFree(pushes);
|
|
42327
42921
|
player.kick(recoil[0], recoil[1]);
|
|
42328
42922
|
const f = first;
|
|
42329
42923
|
const d = f ? num(f.distance, 1) : "";
|
|
@@ -42415,16 +43009,25 @@ void main(){
|
|
|
42415
43009
|
let strikes = [];
|
|
42416
43010
|
const kickLeg = options.kickLeg ?? "alternate";
|
|
42417
43011
|
let nextLeg = "right";
|
|
43012
|
+
const punchHand = options.punchHand ?? "alternate";
|
|
43013
|
+
const punchTools = new Set(options.punchTools ?? ["hand", "fists"]);
|
|
43014
|
+
let nextHand = "right";
|
|
42418
43015
|
const strikeUse = (u, id = toolId) => {
|
|
42419
43016
|
const clip = strikeClips[id];
|
|
42420
43017
|
const leg = id === "kick" ? kickLeg === "alternate" ? nextLeg : kickLeg : void 0;
|
|
42421
|
-
const
|
|
43018
|
+
const hand = punchTools.has(id) ? punchHand === "alternate" ? nextHand : punchHand : void 0;
|
|
43019
|
+
const side = leg ? { leg } : hand ? { hand } : void 0;
|
|
43020
|
+
const seconds = clip === void 0 ? null : avatar?.strike?.(clip, side) ?? null;
|
|
42422
43021
|
if (leg && seconds !== null && kickLeg === "alternate") nextLeg = leg === "right" ? "left" : "right";
|
|
43022
|
+
if (hand && seconds !== null && punchHand === "alternate") nextHand = hand === "right" ? "left" : "right";
|
|
42423
43023
|
if (clip === void 0 || seconds === null) {
|
|
42424
43024
|
meleeUse(u, id);
|
|
42425
43025
|
return;
|
|
42426
43026
|
}
|
|
42427
|
-
|
|
43027
|
+
const text = leg ? "act.strike.leg" : hand ? "act.strike.hand" : "act.strike";
|
|
43028
|
+
action("strike", t(text, { weapon: toolName(id), clip, seconds: num(seconds, 2), leg: leg ? t(`leg.${leg}`) : "", hand: hand ? t(`hand.${hand}`) : "" }), {
|
|
43029
|
+
data: { weapon: id, clip, impactIn: seconds, ...leg ? { leg } : {}, ...hand ? { hand } : {} }
|
|
43030
|
+
});
|
|
42428
43031
|
strikes.push({
|
|
42429
43032
|
key: id,
|
|
42430
43033
|
remaining: seconds,
|
|
@@ -42663,6 +43266,10 @@ void main(){
|
|
|
42663
43266
|
heldPiece = null;
|
|
42664
43267
|
heldTilt = 0;
|
|
42665
43268
|
};
|
|
43269
|
+
const handUse = () => {
|
|
43270
|
+
if (heldPiece) grab();
|
|
43271
|
+
else useTool(FIXED_DT, false);
|
|
43272
|
+
};
|
|
42666
43273
|
const grab = () => {
|
|
42667
43274
|
if (heldPiece) {
|
|
42668
43275
|
const name = heldName;
|
|
@@ -43131,10 +43738,10 @@ void main(){
|
|
|
43131
43738
|
if (tiltKey || heldTilt > 0) {
|
|
43132
43739
|
heldTilt = Math.max(0, Math.min(2.4, heldTilt + (tiltKey ? 2.2 : -3) * dt));
|
|
43133
43740
|
}
|
|
43134
|
-
if (useHeld && !paused) {
|
|
43741
|
+
if ((useHeld || useFirst) && !paused) {
|
|
43135
43742
|
if (tool.def.category === "hand") {
|
|
43136
|
-
if (useFirst)
|
|
43137
|
-
} else useTool(dt, !useFirst);
|
|
43743
|
+
if (useFirst) handUse();
|
|
43744
|
+
} else if (useHeld) useTool(dt, !useFirst);
|
|
43138
43745
|
useFirst = false;
|
|
43139
43746
|
}
|
|
43140
43747
|
if (!paused) followHeld(dt);
|
|
@@ -43689,6 +44296,63 @@ void main(){
|
|
|
43689
44296
|
const row = (list, gap, z) => list.map((item, index) => spawn2(item.id, [(index - (list.length - 1) / 2) * gap, 0, z], 0));
|
|
43690
44297
|
return [...row(front, 1.6, 1.5), ...row(back, 4.5, -4)];
|
|
43691
44298
|
};
|
|
44299
|
+
const catalogGroups = () => {
|
|
44300
|
+
const m = /* @__PURE__ */ new Map();
|
|
44301
|
+
for (const i of catalog) {
|
|
44302
|
+
const g = m.get(i.group) ?? [];
|
|
44303
|
+
g.push(i);
|
|
44304
|
+
m.set(i.group, g);
|
|
44305
|
+
}
|
|
44306
|
+
return m;
|
|
44307
|
+
};
|
|
44308
|
+
const tableOptions = options.testTable === false ? {} : options.testTable ?? {};
|
|
44309
|
+
const tableScenery = tableOptions.scenery ?? "test_table";
|
|
44310
|
+
const measured = /* @__PURE__ */ new Map();
|
|
44311
|
+
const testTable = createTestTable(
|
|
44312
|
+
{
|
|
44313
|
+
groups: catalogGroups,
|
|
44314
|
+
top: () => {
|
|
44315
|
+
const top = arena.tops.find((x) => x.id === tableScenery);
|
|
44316
|
+
return top && topOk(top) ? top : null;
|
|
44317
|
+
},
|
|
44318
|
+
measure: (itemId) => {
|
|
44319
|
+
let size = measured.get(itemId);
|
|
44320
|
+
if (!size) {
|
|
44321
|
+
const item = catalogMap.get(itemId);
|
|
44322
|
+
const box2 = item ? new THREE.Box3().setFromObject(buildRoot(item)) : null;
|
|
44323
|
+
size = box2 && !box2.isEmpty() ? { w: box2.max.x - box2.min.x, d: box2.max.z - box2.min.z } : { w: 0.3, d: 0.3 };
|
|
44324
|
+
measured.set(itemId, size);
|
|
44325
|
+
}
|
|
44326
|
+
return size;
|
|
44327
|
+
},
|
|
44328
|
+
spawn: (itemId, position) => spawn2(itemId, position, 0),
|
|
44329
|
+
clear,
|
|
44330
|
+
resetAll,
|
|
44331
|
+
box: (objectId) => {
|
|
44332
|
+
const e = entries.get(objectId);
|
|
44333
|
+
if (!e || !world.has(objectId)) return null;
|
|
44334
|
+
const box2 = new THREE.Box3().setFromObject(e.so.root);
|
|
44335
|
+
return box2.isEmpty() ? null : { min: [box2.min.x, box2.min.y, box2.min.z], max: [box2.max.x, box2.max.y, box2.max.z] };
|
|
44336
|
+
},
|
|
44337
|
+
equip: (weapon) => {
|
|
44338
|
+
if (!arsenal.weapons.has(weapon)) return false;
|
|
44339
|
+
if (heldEntry) releaseHeld();
|
|
44340
|
+
equip(weapon);
|
|
44341
|
+
room.waitUntilReady();
|
|
44342
|
+
return true;
|
|
44343
|
+
},
|
|
44344
|
+
look: (eye, target) => player.look(eye, target),
|
|
44345
|
+
use: (hold) => room.use(hold),
|
|
44346
|
+
blastAt: (weapon, point) => {
|
|
44347
|
+
const def = arsenal.weapons.get(weapon);
|
|
44348
|
+
const explosive = def?.action.kind === "throwable" ? def.action.explosive : def?.action.kind === "launcher" ? def.action.projectile : void 0;
|
|
44349
|
+
return explosive !== void 0 && explosives.blastAt(weapon, explosive, point);
|
|
44350
|
+
},
|
|
44351
|
+
log: (type, text, data) => action(type, text, { data }),
|
|
44352
|
+
translate: t
|
|
44353
|
+
},
|
|
44354
|
+
tableOptions
|
|
44355
|
+
);
|
|
43692
44356
|
const room = {
|
|
43693
44357
|
world,
|
|
43694
44358
|
renderer,
|
|
@@ -43708,6 +44372,7 @@ void main(){
|
|
|
43708
44372
|
get scenarios() {
|
|
43709
44373
|
return allScenarios();
|
|
43710
44374
|
},
|
|
44375
|
+
testTable,
|
|
43711
44376
|
spawn: spawn2,
|
|
43712
44377
|
clear,
|
|
43713
44378
|
resetAll,
|
|
@@ -43715,7 +44380,8 @@ void main(){
|
|
|
43715
44380
|
useItem,
|
|
43716
44381
|
use(hold = 0) {
|
|
43717
44382
|
if (tool.def.category === "hand") {
|
|
43718
|
-
|
|
44383
|
+
handUse();
|
|
44384
|
+
flush();
|
|
43719
44385
|
return;
|
|
43720
44386
|
}
|
|
43721
44387
|
useTool(FIXED_DT, false);
|
|
@@ -43948,7 +44614,8 @@ void main(){
|
|
|
43948
44614
|
},
|
|
43949
44615
|
onAction: (id) => {
|
|
43950
44616
|
if (activeBench) slowly(activeBench, id, () => benches.get(activeBench)?.act(id));
|
|
43951
|
-
}
|
|
44617
|
+
},
|
|
44618
|
+
...options.testTable !== false && arena.tops.some((x) => x.id === tableScenery) ? { testTable } : {}
|
|
43952
44619
|
});
|
|
43953
44620
|
ui.hud(hudState());
|
|
43954
44621
|
showBench();
|