@adatechnology/game-test-room 0.15.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 +1 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.global.js +267 -52
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +48 -6
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.global.js
CHANGED
|
@@ -482,6 +482,13 @@ var GameTestRoom = (() => {
|
|
|
482
482
|
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
483
483
|
return object;
|
|
484
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
|
+
}
|
|
485
492
|
|
|
486
493
|
// ../game-destruction/dist/index.js
|
|
487
494
|
var FIRE_PRIME_C = 300;
|
|
@@ -812,6 +819,45 @@ var GameTestRoom = (() => {
|
|
|
812
819
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
813
820
|
};
|
|
814
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
|
+
};
|
|
815
861
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
816
862
|
const { dims, rnd: rnd2, maxFragments } = inp;
|
|
817
863
|
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
@@ -850,23 +896,8 @@ var GameTestRoom = (() => {
|
|
|
850
896
|
return out;
|
|
851
897
|
}
|
|
852
898
|
if (style === "splinter") {
|
|
853
|
-
|
|
854
|
-
const
|
|
855
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
856
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
857
|
-
const maxLen = fmax * 4;
|
|
858
|
-
const cells = bsp(root, c, [a, b], rnd2);
|
|
859
|
-
const pieces = [];
|
|
860
|
-
for (const cell of cells) {
|
|
861
|
-
let list = [cell];
|
|
862
|
-
const extra = rnd2() < 0.4 ? 1 : 0;
|
|
863
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
864
|
-
list = bsp(cell, k, [g], rnd2);
|
|
865
|
-
for (const p of list) pieces.push(p);
|
|
866
|
-
}
|
|
867
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
868
|
-
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] });
|
|
869
|
-
}
|
|
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);
|
|
870
901
|
return out;
|
|
871
902
|
}
|
|
872
903
|
if (style === "tear") {
|
|
@@ -2849,9 +2880,14 @@ var GameTestRoom = (() => {
|
|
|
2849
2880
|
var BREACH_AREA = 0.25;
|
|
2850
2881
|
var BREACH_BLAST_REACH = 0.12;
|
|
2851
2882
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2883
|
+
var SEVER_BAND = 0.1;
|
|
2884
|
+
var SEVER_ASPECT = 3;
|
|
2885
|
+
var SEVER_BALLISTIC = 2;
|
|
2852
2886
|
var SUPPORT_TOLERANCE = 0.05;
|
|
2853
2887
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
2854
2888
|
var BREACH_GRID_CAP = 4096;
|
|
2889
|
+
var CHIP_ENERGY = 200;
|
|
2890
|
+
var CHIP_SPLASH = 0.5;
|
|
2855
2891
|
var CUT_SLICE = 1;
|
|
2856
2892
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2857
2893
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2895,6 +2931,7 @@ var GameTestRoom = (() => {
|
|
|
2895
2931
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2896
2932
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
2897
2933
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
2934
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
2898
2935
|
const rng = createRng(options.seed ?? 1);
|
|
2899
2936
|
const rnd2 = () => rng.next();
|
|
2900
2937
|
let time = 0;
|
|
@@ -3268,41 +3305,68 @@ var GameTestRoom = (() => {
|
|
|
3268
3305
|
markDamaged(p);
|
|
3269
3306
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
3270
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
|
+
};
|
|
3271
3322
|
const solveStructure = (o) => {
|
|
3272
3323
|
o.maxStress = 0;
|
|
3273
3324
|
if (!o.hasStructure) return;
|
|
3274
3325
|
const parts = o.parts;
|
|
3275
3326
|
const n = parts.length;
|
|
3276
3327
|
const dist = new Array(n).fill(-1);
|
|
3277
|
-
const queue = [];
|
|
3278
3328
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
3279
3329
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
if (
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
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
|
+
}
|
|
3293
3350
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
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
|
+
}
|
|
3303
3366
|
}
|
|
3304
|
-
|
|
3367
|
+
if (!dropped) break;
|
|
3305
3368
|
}
|
|
3369
|
+
const incoming = new Array(n).fill(0);
|
|
3306
3370
|
const bending = new Array(n).fill(0);
|
|
3307
3371
|
const through = parts.map(() => []);
|
|
3308
3372
|
const pass = (k, at3, mass) => {
|
|
@@ -3814,6 +3878,7 @@ var GameTestRoom = (() => {
|
|
|
3814
3878
|
const r = rayPart(o, p, origin, d);
|
|
3815
3879
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
3816
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;
|
|
3817
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 };
|
|
3818
3883
|
}
|
|
3819
3884
|
}
|
|
@@ -4077,6 +4142,62 @@ var GameTestRoom = (() => {
|
|
|
4077
4142
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
4078
4143
|
openBreach(o, p, [sum2[0] / total, sum2[1] / total, sum2[2] / total], total, energy, dir, src);
|
|
4079
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
|
+
};
|
|
4080
4201
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
4081
4202
|
const [u, v] = planeAxes(p.thin);
|
|
4082
4203
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -4088,7 +4209,8 @@ var GameTestRoom = (() => {
|
|
|
4088
4209
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
4089
4210
|
return;
|
|
4090
4211
|
}
|
|
4091
|
-
const
|
|
4212
|
+
const worn = p.rt.chips;
|
|
4213
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
4092
4214
|
const removed = p.st.mass * cells.reduce((sum2, cell) => sum2 + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
4093
4215
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
4094
4216
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -4172,6 +4294,8 @@ var GameTestRoom = (() => {
|
|
|
4172
4294
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
4173
4295
|
const hitLocal = toLocal(c, q, hitW);
|
|
4174
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);
|
|
4175
4299
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
4176
4300
|
style,
|
|
4177
4301
|
dims: p.dims,
|
|
@@ -4181,7 +4305,8 @@ var GameTestRoom = (() => {
|
|
|
4181
4305
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
4182
4306
|
maxFragments,
|
|
4183
4307
|
rnd: rnd2,
|
|
4184
|
-
sheet: p.mat.sheet
|
|
4308
|
+
sheet: p.mat.sheet,
|
|
4309
|
+
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
4185
4310
|
});
|
|
4186
4311
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
4187
4312
|
const mass = p.st.mass;
|
|
@@ -4240,10 +4365,33 @@ var GameTestRoom = (() => {
|
|
|
4240
4365
|
emit2({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
4241
4366
|
};
|
|
4242
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
|
+
};
|
|
4243
4380
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
4381
|
+
var _a;
|
|
4244
4382
|
if (isLost(p) || amount <= 0) return 0;
|
|
4245
4383
|
const prev = p.st.hp;
|
|
4246
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
|
+
}
|
|
4247
4395
|
markDamaged(p);
|
|
4248
4396
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
4249
4397
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -4282,7 +4430,7 @@ var GameTestRoom = (() => {
|
|
|
4282
4430
|
const ballistic2 = (o, p, entry, n, d, E, caliber, pathLen, src, round2 = {}) => {
|
|
4283
4431
|
const mat = p.mat;
|
|
4284
4432
|
const piercing = round2.piercing ?? 1;
|
|
4285
|
-
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))) {
|
|
4286
4434
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
4287
4435
|
}
|
|
4288
4436
|
const sin2 = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -4341,6 +4489,7 @@ var GameTestRoom = (() => {
|
|
|
4341
4489
|
debris2(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4342
4490
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
4343
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);
|
|
4344
4493
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
4345
4494
|
};
|
|
4346
4495
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -4368,6 +4517,7 @@ var GameTestRoom = (() => {
|
|
|
4368
4517
|
const scale2 = mat.damageScale?.[kind] ?? 1;
|
|
4369
4518
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale2 * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
4370
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);
|
|
4371
4521
|
return done;
|
|
4372
4522
|
};
|
|
4373
4523
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -5517,6 +5667,7 @@ var GameTestRoom = (() => {
|
|
|
5517
5667
|
sway: "sway",
|
|
5518
5668
|
spall: "plaster came off",
|
|
5519
5669
|
stage: "damage stage",
|
|
5670
|
+
chip: "brick knocked out",
|
|
5520
5671
|
expose: "rebar bare",
|
|
5521
5672
|
body: "body",
|
|
5522
5673
|
blast: "blast",
|
|
@@ -5607,6 +5758,7 @@ var GameTestRoom = (() => {
|
|
|
5607
5758
|
sway: "balan\xE7o",
|
|
5608
5759
|
spall: "reboco soltou",
|
|
5609
5760
|
stage: "est\xE1gio de dano",
|
|
5761
|
+
chip: "tijolo arrancado",
|
|
5610
5762
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
5611
5763
|
body: "corpo",
|
|
5612
5764
|
blast: "explos\xE3o",
|
|
@@ -17648,6 +17800,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
17648
17800
|
}
|
|
17649
17801
|
};
|
|
17650
17802
|
var rendererCount = 0;
|
|
17803
|
+
var COLLAPSE_CLOUD_KG = 1e3;
|
|
17651
17804
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
17652
17805
|
var GLOW_FROM_C = 430;
|
|
17653
17806
|
var QUEUE_CAP = 4e3;
|
|
@@ -18107,10 +18260,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18107
18260
|
case "settle":
|
|
18108
18261
|
this.onSettle(ev.object);
|
|
18109
18262
|
break;
|
|
18110
|
-
case "collapse":
|
|
18263
|
+
case "collapse": {
|
|
18264
|
+
const root = this.bounds.get(ev.object)?.root;
|
|
18265
|
+
const furniture = !!root && kitOriginOf(root)?.kind === "furniture";
|
|
18111
18266
|
this.splitLoose(ev.object);
|
|
18112
|
-
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);
|
|
18113
18269
|
break;
|
|
18270
|
+
}
|
|
18114
18271
|
case "disassemble_progress": {
|
|
18115
18272
|
const k = `unscrew:${ev.joint}`;
|
|
18116
18273
|
if (this.time - (this.throttles.get(k) ?? -1) > 0.25) {
|
|
@@ -18140,6 +18297,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18140
18297
|
case "stage":
|
|
18141
18298
|
this.onStage(ev);
|
|
18142
18299
|
break;
|
|
18300
|
+
case "chip":
|
|
18301
|
+
this.onChip(ev);
|
|
18302
|
+
break;
|
|
18143
18303
|
case "expose":
|
|
18144
18304
|
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
18145
18305
|
if (ev.bars.length > 0) {
|
|
@@ -18396,6 +18556,19 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
18396
18556
|
if (!played) this.vfx.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
18397
18557
|
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
18398
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
|
+
}
|
|
18399
18572
|
onFracture(ev) {
|
|
18400
18573
|
const b = this.bounds.get(ev.object);
|
|
18401
18574
|
const pv = b?.parts.get(ev.part);
|
|
@@ -38191,7 +38364,7 @@ void main(){
|
|
|
38191
38364
|
if (!position) throw new Error(`game-test-room: scenery "${id}" is new, so it needs a position`);
|
|
38192
38365
|
merged.set(id, { ...obj, position });
|
|
38193
38366
|
}
|
|
38194
|
-
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 }));
|
|
38195
38368
|
}
|
|
38196
38369
|
function createArena(THREE, size, scene2, scenery, tapOption) {
|
|
38197
38370
|
const group = markKitOrigin(new THREE.Group(), { owner: "game-test-room", kind: "room" });
|
|
@@ -40424,6 +40597,9 @@ void main(){
|
|
|
40424
40597
|
"act.strike.leg": "{weapon}: {clip} with the {leg} leg begins, impact in {seconds} s",
|
|
40425
40598
|
"leg.left": "left",
|
|
40426
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",
|
|
40427
40603
|
"act.flame.hit": "{weapon}: flame at {temp} \xB0C on {where}",
|
|
40428
40604
|
"act.flame.air": "{weapon}: flame, nothing in range",
|
|
40429
40605
|
"act.heat.hit": "{weapon}: heating {where} at {temp} \xB0C",
|
|
@@ -40455,6 +40631,7 @@ void main(){
|
|
|
40455
40631
|
"loading.open_area": "Opening {area}\u2026",
|
|
40456
40632
|
"loading.spawn": "Building {name}\u2026",
|
|
40457
40633
|
"act.loosened": "{name} was heavy but free: the blast moved it",
|
|
40634
|
+
"act.shoved": "{name} stands free: the shots moved it",
|
|
40458
40635
|
"act.impact": "{name} hit {where} at {speed} m/s: {energy} J of blunt damage",
|
|
40459
40636
|
"act.use_item": "{name}: {use}",
|
|
40460
40637
|
"act.tap.on": "Tap opened",
|
|
@@ -40731,6 +40908,9 @@ void main(){
|
|
|
40731
40908
|
"act.strike.leg": "{weapon}: {clip} com a perna {leg} come\xE7a, impacto em {seconds} s",
|
|
40732
40909
|
"leg.left": "esquerda",
|
|
40733
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",
|
|
40734
40914
|
"act.flame.hit": "{weapon}: chama a {temp} \xB0C em {where}",
|
|
40735
40915
|
"act.flame.air": "{weapon}: chama, nada ao alcance",
|
|
40736
40916
|
"act.heat.hit": "{weapon}: esquentando {where} a {temp} \xB0C",
|
|
@@ -40762,6 +40942,7 @@ void main(){
|
|
|
40762
40942
|
"loading.open_area": "Abrindo {area}\u2026",
|
|
40763
40943
|
"loading.spawn": "Montando {name}\u2026",
|
|
40764
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",
|
|
40765
40946
|
"act.impact": "{name} bateu em {where} a {speed} m/s: {energy} J de dano de impacto",
|
|
40766
40947
|
"act.use_item": "{name}: {use}",
|
|
40767
40948
|
"act.tap.on": "Abriu a torneira",
|
|
@@ -41225,7 +41406,7 @@ void main(){
|
|
|
41225
41406
|
relay.type = "button";
|
|
41226
41407
|
relay.onclick = () => table.relay();
|
|
41227
41408
|
trials.appendChild(relay);
|
|
41228
|
-
element.append(head, search, found, sub4,
|
|
41409
|
+
element.append(head, search, found, sub4, trials, list);
|
|
41229
41410
|
const groups = () => {
|
|
41230
41411
|
const keep = select.value;
|
|
41231
41412
|
select.textContent = "";
|
|
@@ -41872,6 +42053,7 @@ void main(){
|
|
|
41872
42053
|
|
|
41873
42054
|
// src/room.ts
|
|
41874
42055
|
var FIXED_DT = 1 / 60;
|
|
42056
|
+
var SHOVE_SPEED = 0.04;
|
|
41875
42057
|
var METRICS_INTERVAL_S = 0.25;
|
|
41876
42058
|
var CARRY_NEAR = 0.62;
|
|
41877
42059
|
var CARRY_PALM = 0.012;
|
|
@@ -42234,7 +42416,9 @@ void main(){
|
|
|
42234
42416
|
scene2.add(root);
|
|
42235
42417
|
root.updateMatrixWorld(true);
|
|
42236
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)] } : {} });
|
|
42237
|
-
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;
|
|
42238
42422
|
}
|
|
42239
42423
|
};
|
|
42240
42424
|
addArena();
|
|
@@ -42329,6 +42513,7 @@ void main(){
|
|
|
42329
42513
|
renderer.unbind(id);
|
|
42330
42514
|
} catch {
|
|
42331
42515
|
}
|
|
42516
|
+
e.follower?.dispose?.();
|
|
42332
42517
|
if (world.has(id)) world.remove(id);
|
|
42333
42518
|
e.so.root.removeFromParent();
|
|
42334
42519
|
letGo(e.so.root);
|
|
@@ -42363,6 +42548,7 @@ void main(){
|
|
|
42363
42548
|
for (const e of entries.values()) {
|
|
42364
42549
|
world.reset(e.so.objectId);
|
|
42365
42550
|
renderer.restore(e.so.objectId);
|
|
42551
|
+
e.follower?.restore?.();
|
|
42366
42552
|
}
|
|
42367
42553
|
renderer.clear();
|
|
42368
42554
|
vfx.world.clear();
|
|
@@ -42384,6 +42570,7 @@ void main(){
|
|
|
42384
42570
|
if (heldEntry === e) continue;
|
|
42385
42571
|
world.reset(e.so.objectId);
|
|
42386
42572
|
renderer.restore(e.so.objectId);
|
|
42573
|
+
e.follower?.restore?.();
|
|
42387
42574
|
if (e.containerId) {
|
|
42388
42575
|
const c = fluids.container(e.containerId);
|
|
42389
42576
|
const f = e.item?.fluid?.container;
|
|
@@ -42687,6 +42874,19 @@ void main(){
|
|
|
42687
42874
|
if (!rendererPieces.has(objectId)) return;
|
|
42688
42875
|
renderer.impulse(objectId, [dir[0] * momentum, dir[1] * momentum, dir[2] * momentum], point);
|
|
42689
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
|
+
};
|
|
42690
42890
|
const fireShots = (shots, recoil) => {
|
|
42691
42891
|
if (shots.length === 0) return;
|
|
42692
42892
|
const w = toolName();
|
|
@@ -42696,6 +42896,7 @@ void main(){
|
|
|
42696
42896
|
let hits = 0;
|
|
42697
42897
|
let first = null;
|
|
42698
42898
|
let energy = 0;
|
|
42899
|
+
const pushes = /* @__PURE__ */ new Map();
|
|
42699
42900
|
withoutHeld(() => {
|
|
42700
42901
|
for (const s of shots) {
|
|
42701
42902
|
energy = Math.max(energy, s.energy);
|
|
@@ -42707,11 +42908,16 @@ void main(){
|
|
|
42707
42908
|
end3 = r0.point;
|
|
42708
42909
|
if (!first) first = { object: r0.object, part: r0.part, point: r0.point, normal: [0, 1, 0], distance: dist3(s.origin, r0.point) };
|
|
42709
42910
|
const speed = Math.max(80, s.speed);
|
|
42710
|
-
|
|
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);
|
|
42711
42916
|
}
|
|
42712
42917
|
if (shots.length <= 3 || rnd2() < 0.34) tracer(m, end3);
|
|
42713
42918
|
}
|
|
42714
42919
|
});
|
|
42920
|
+
shoveFree(pushes);
|
|
42715
42921
|
player.kick(recoil[0], recoil[1]);
|
|
42716
42922
|
const f = first;
|
|
42717
42923
|
const d = f ? num(f.distance, 1) : "";
|
|
@@ -42803,16 +43009,25 @@ void main(){
|
|
|
42803
43009
|
let strikes = [];
|
|
42804
43010
|
const kickLeg = options.kickLeg ?? "alternate";
|
|
42805
43011
|
let nextLeg = "right";
|
|
43012
|
+
const punchHand = options.punchHand ?? "alternate";
|
|
43013
|
+
const punchTools = new Set(options.punchTools ?? ["hand", "fists"]);
|
|
43014
|
+
let nextHand = "right";
|
|
42806
43015
|
const strikeUse = (u, id = toolId) => {
|
|
42807
43016
|
const clip = strikeClips[id];
|
|
42808
43017
|
const leg = id === "kick" ? kickLeg === "alternate" ? nextLeg : kickLeg : void 0;
|
|
42809
|
-
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;
|
|
42810
43021
|
if (leg && seconds !== null && kickLeg === "alternate") nextLeg = leg === "right" ? "left" : "right";
|
|
43022
|
+
if (hand && seconds !== null && punchHand === "alternate") nextHand = hand === "right" ? "left" : "right";
|
|
42811
43023
|
if (clip === void 0 || seconds === null) {
|
|
42812
43024
|
meleeUse(u, id);
|
|
42813
43025
|
return;
|
|
42814
43026
|
}
|
|
42815
|
-
|
|
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
|
+
});
|
|
42816
43031
|
strikes.push({
|
|
42817
43032
|
key: id,
|
|
42818
43033
|
remaining: seconds,
|