@adatechnology/game-destruction 0.9.0 → 0.10.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/dist/index.d.ts +12 -0
- package/dist/index.global.js +189 -44
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +189 -44
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -336,6 +336,12 @@ interface DestructionOptions {
|
|
|
336
336
|
radius?: number;
|
|
337
337
|
/** Multiplier of the breach area a blast makes over a blow of the same damage: a blast loads the whole crater (default 6). */
|
|
338
338
|
blastFactor?: number;
|
|
339
|
+
/**
|
|
340
|
+
* Joules per kilogram of a unit (`PartSpec.units`: a brick, a block) that knock it out of its wall: shots and blows wear the unit they land on and
|
|
341
|
+
* a little the ones around it, and a unit that took this much comes out (`chip` event). Default 200 (a 4 kg brick: two 9 mm rounds, one
|
|
342
|
+
* rifle round); 0 turns it off.
|
|
343
|
+
*/
|
|
344
|
+
chipEnergy?: number;
|
|
339
345
|
};
|
|
340
346
|
/**
|
|
341
347
|
* Speed of the blast front in m/s. A glass part at distance `d` from an explosion breaks `d / blastFrontSpeed` seconds later, in `update`
|
|
@@ -504,8 +510,14 @@ interface PartRuntime {
|
|
|
504
510
|
spalls?: Array<[number, number, number, number, number, number]>;
|
|
505
511
|
/** Through damage so far: `[x, y, z, area]` in object space, newest last. */
|
|
506
512
|
breach?: Array<[number, number, number, number]>;
|
|
513
|
+
/** Energy each unit of the part took so far (`breach.chipEnergy`), by its index in the unit grid; -1 when the unit came out. */
|
|
514
|
+
chips?: Record<number, number>;
|
|
507
515
|
/** Mass in kg that left the part in a breach. */
|
|
508
516
|
removed?: number;
|
|
517
|
+
/** Damage taken by each band across a long wooden part (`splinter`), by band index from its negative end: the band that runs out breaks it there. */
|
|
518
|
+
cuts?: Record<number, number>;
|
|
519
|
+
/** Where along its long axis (part space) the part broke across (`cuts`). */
|
|
520
|
+
cutAt?: number;
|
|
509
521
|
/** Plastic rotation in radians a reinforced beam or slab took in bending (blasts add it up; `flexure.failRotationDeg` breaks it). */
|
|
510
522
|
hinge?: number;
|
|
511
523
|
}
|
package/dist/index.global.js
CHANGED
|
@@ -450,6 +450,45 @@ var GameDestruction = (() => {
|
|
|
450
450
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
451
451
|
};
|
|
452
452
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
453
|
+
var splinterBox = (inp, box, limit = inp.maxFragments) => {
|
|
454
|
+
const { rnd } = inp;
|
|
455
|
+
const dims = box.s;
|
|
456
|
+
const [fmin, fmax] = inp.fragmentSize;
|
|
457
|
+
const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
|
|
458
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== g);
|
|
459
|
+
const w = Math.max(fmin * 1.5, 0.01);
|
|
460
|
+
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
461
|
+
const maxLen = fmax * 4;
|
|
462
|
+
const cells = bsp(box, c, [a, b], rnd);
|
|
463
|
+
const pieces = [];
|
|
464
|
+
for (const cell of cells) {
|
|
465
|
+
const extra = rnd() < 0.4 ? 1 : 0;
|
|
466
|
+
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
467
|
+
for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
|
|
468
|
+
}
|
|
469
|
+
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] }));
|
|
470
|
+
};
|
|
471
|
+
var severAt = (inp, root, at, gap) => {
|
|
472
|
+
const dims = root.s;
|
|
473
|
+
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
474
|
+
const half = dims[L] / 2;
|
|
475
|
+
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
476
|
+
const out = [];
|
|
477
|
+
const stub = (from, to) => {
|
|
478
|
+
if (to - from < 0.02) return;
|
|
479
|
+
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
480
|
+
c[L] = (from + to) / 2;
|
|
481
|
+
sz[L] = to - from;
|
|
482
|
+
out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
|
|
483
|
+
};
|
|
484
|
+
stub(-half, lo);
|
|
485
|
+
stub(hi, half);
|
|
486
|
+
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
487
|
+
band.c[L] = (lo + hi) / 2;
|
|
488
|
+
band.s[L] = Math.max(hi - lo, 0.01);
|
|
489
|
+
for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
490
|
+
return out;
|
|
491
|
+
};
|
|
453
492
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
454
493
|
const { dims, rnd, maxFragments } = inp;
|
|
455
494
|
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
@@ -488,23 +527,8 @@ var GameDestruction = (() => {
|
|
|
488
527
|
return out;
|
|
489
528
|
}
|
|
490
529
|
if (style === "splinter") {
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
494
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
495
|
-
const maxLen = fmax * 4;
|
|
496
|
-
const cells = bsp(root, c, [a, b], rnd);
|
|
497
|
-
const pieces = [];
|
|
498
|
-
for (const cell of cells) {
|
|
499
|
-
let list = [cell];
|
|
500
|
-
const extra = rnd() < 0.4 ? 1 : 0;
|
|
501
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
502
|
-
list = bsp(cell, k, [g], rnd);
|
|
503
|
-
for (const p of list) pieces.push(p);
|
|
504
|
-
}
|
|
505
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
506
|
-
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] });
|
|
507
|
-
}
|
|
530
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
531
|
+
for (const p of splinterBox(inp, root)) out.push(p);
|
|
508
532
|
return out;
|
|
509
533
|
}
|
|
510
534
|
if (style === "tear") {
|
|
@@ -2718,9 +2742,14 @@ var GameDestruction = (() => {
|
|
|
2718
2742
|
var BREACH_AREA = 0.25;
|
|
2719
2743
|
var BREACH_BLAST_REACH = 0.12;
|
|
2720
2744
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2745
|
+
var SEVER_BAND = 0.1;
|
|
2746
|
+
var SEVER_ASPECT = 3;
|
|
2747
|
+
var SEVER_BALLISTIC = 2;
|
|
2721
2748
|
var SUPPORT_TOLERANCE = 0.05;
|
|
2722
2749
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
2723
2750
|
var BREACH_GRID_CAP = 4096;
|
|
2751
|
+
var CHIP_ENERGY = 200;
|
|
2752
|
+
var CHIP_SPLASH = 0.5;
|
|
2724
2753
|
var CUT_SLICE = 1;
|
|
2725
2754
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2726
2755
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2764,6 +2793,7 @@ var GameDestruction = (() => {
|
|
|
2764
2793
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2765
2794
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
2766
2795
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
2796
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
2767
2797
|
const rng = createRng(options.seed ?? 1);
|
|
2768
2798
|
const rnd = () => rng.next();
|
|
2769
2799
|
let time = 0;
|
|
@@ -3137,41 +3167,68 @@ var GameDestruction = (() => {
|
|
|
3137
3167
|
markDamaged(p);
|
|
3138
3168
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
3139
3169
|
};
|
|
3170
|
+
const freeSpan = (p, supports) => {
|
|
3171
|
+
if (supports.length === 0) return Infinity;
|
|
3172
|
+
const d = p.dims;
|
|
3173
|
+
const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
|
|
3174
|
+
const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
|
|
3175
|
+
let far = 0;
|
|
3176
|
+
for (let i = 0; i <= 16; i++) {
|
|
3177
|
+
const x = (i / 16 - 0.5) * d[L];
|
|
3178
|
+
let near = Infinity;
|
|
3179
|
+
for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
|
|
3180
|
+
far = Math.max(far, near);
|
|
3181
|
+
}
|
|
3182
|
+
return 2 * far;
|
|
3183
|
+
};
|
|
3140
3184
|
const solveStructure = (o) => {
|
|
3141
3185
|
o.maxStress = 0;
|
|
3142
3186
|
if (!o.hasStructure) return;
|
|
3143
3187
|
const parts = o.parts;
|
|
3144
3188
|
const n = parts.length;
|
|
3145
3189
|
const dist = new Array(n).fill(-1);
|
|
3146
|
-
const queue = [];
|
|
3147
3190
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
3148
3191
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
if (
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
const r = j.ia === s ? j.ib : j.ia;
|
|
3159
|
-
if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
|
|
3160
|
-
dist[r] = dist[s] + 1;
|
|
3161
|
-
queue.push(r);
|
|
3192
|
+
const out = /* @__PURE__ */ new Set();
|
|
3193
|
+
let queue = [];
|
|
3194
|
+
let lowerOf = /* @__PURE__ */ new Map();
|
|
3195
|
+
for (; ; ) {
|
|
3196
|
+
dist.fill(-1);
|
|
3197
|
+
queue = [];
|
|
3198
|
+
for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
|
|
3199
|
+
dist[i] = 0;
|
|
3200
|
+
queue.push(i);
|
|
3162
3201
|
}
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3202
|
+
for (let h = 0; h < queue.length; h++) {
|
|
3203
|
+
const s = queue[h];
|
|
3204
|
+
if (isPartition(s)) continue;
|
|
3205
|
+
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
3206
|
+
if (j.st.open || !j.bears) continue;
|
|
3207
|
+
const r = j.ia === s ? j.ib : j.ia;
|
|
3208
|
+
if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
|
|
3209
|
+
dist[r] = dist[s] + 1;
|
|
3210
|
+
queue.push(r);
|
|
3211
|
+
}
|
|
3172
3212
|
}
|
|
3173
|
-
lowerOf
|
|
3213
|
+
lowerOf = /* @__PURE__ */ new Map();
|
|
3214
|
+
let dropped = false;
|
|
3215
|
+
for (const i of queue) {
|
|
3216
|
+
const lower = [];
|
|
3217
|
+
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
3218
|
+
if (j.st.open || !j.bears) continue;
|
|
3219
|
+
const k = restsOn(o, j, i);
|
|
3220
|
+
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
3221
|
+
}
|
|
3222
|
+
lowerOf.set(i, lower);
|
|
3223
|
+
const max = parts[i].spec.structure?.maxSpan;
|
|
3224
|
+
if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
|
|
3225
|
+
out.add(i);
|
|
3226
|
+
dropped = true;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
if (!dropped) break;
|
|
3174
3230
|
}
|
|
3231
|
+
const incoming = new Array(n).fill(0);
|
|
3175
3232
|
const bending = new Array(n).fill(0);
|
|
3176
3233
|
const through = parts.map(() => []);
|
|
3177
3234
|
const pass = (k, at, mass) => {
|
|
@@ -3683,6 +3740,7 @@ var GameDestruction = (() => {
|
|
|
3683
3740
|
const r = rayPart(o, p, origin, d);
|
|
3684
3741
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
3685
3742
|
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
3743
|
+
if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
|
|
3686
3744
|
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 };
|
|
3687
3745
|
}
|
|
3688
3746
|
}
|
|
@@ -3946,6 +4004,62 @@ var GameDestruction = (() => {
|
|
|
3946
4004
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
3947
4005
|
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
3948
4006
|
};
|
|
4007
|
+
const unitGrids = /* @__PURE__ */ new WeakMap();
|
|
4008
|
+
const gridOf = (p) => {
|
|
4009
|
+
let grid = unitGrids.get(p);
|
|
4010
|
+
if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP));
|
|
4011
|
+
return grid;
|
|
4012
|
+
};
|
|
4013
|
+
const insideChip = (p, objectPoint) => {
|
|
4014
|
+
const cells = p.st.chipped;
|
|
4015
|
+
if (!cells?.length) return false;
|
|
4016
|
+
const [u, v] = planeAxes(p.thin);
|
|
4017
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
4018
|
+
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);
|
|
4019
|
+
};
|
|
4020
|
+
const chipUnits = (o, p, wp, energy, dir, radius, src) => {
|
|
4021
|
+
var _a, _b;
|
|
4022
|
+
if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
4023
|
+
const [u, v] = planeAxes(p.thin);
|
|
4024
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4025
|
+
const grid = gridOf(p);
|
|
4026
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4027
|
+
const out = [];
|
|
4028
|
+
for (let i = 0; i < grid.length; i++) {
|
|
4029
|
+
const cell = grid[i];
|
|
4030
|
+
if (worn[i] === -1) continue;
|
|
4031
|
+
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));
|
|
4032
|
+
if (gap > radius) continue;
|
|
4033
|
+
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
4034
|
+
const total = (worn[i] ?? 0) + energy * share;
|
|
4035
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
4036
|
+
if (total < chipEnergy * mass) {
|
|
4037
|
+
worn[i] = total;
|
|
4038
|
+
continue;
|
|
4039
|
+
}
|
|
4040
|
+
worn[i] = -1;
|
|
4041
|
+
out.push(cell);
|
|
4042
|
+
}
|
|
4043
|
+
if (out.length === 0) return;
|
|
4044
|
+
const { c, q } = partXf(o, p);
|
|
4045
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
4046
|
+
const holes = localHolesOf(p);
|
|
4047
|
+
for (const cell of out) {
|
|
4048
|
+
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
4049
|
+
const center = toWorld(c, q, cell.center);
|
|
4050
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
4051
|
+
const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
|
|
4052
|
+
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
4053
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
4054
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
4055
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
4056
|
+
markDamaged(p);
|
|
4057
|
+
setStage(o, p, 1, {}, src);
|
|
4058
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
4059
|
+
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
4060
|
+
}
|
|
4061
|
+
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
4062
|
+
};
|
|
3949
4063
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
3950
4064
|
const [u, v] = planeAxes(p.thin);
|
|
3951
4065
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -3957,7 +4071,8 @@ var GameDestruction = (() => {
|
|
|
3957
4071
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
3958
4072
|
return;
|
|
3959
4073
|
}
|
|
3960
|
-
const
|
|
4074
|
+
const worn = p.rt.chips;
|
|
4075
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
3961
4076
|
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
3962
4077
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
3963
4078
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -4041,6 +4156,8 @@ var GameDestruction = (() => {
|
|
|
4041
4156
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
4042
4157
|
const hitLocal = toLocal(c, q, hitW);
|
|
4043
4158
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
4159
|
+
const axis = style === "splinter" ? severAxis(p) : -1;
|
|
4160
|
+
const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
4044
4161
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
4045
4162
|
style,
|
|
4046
4163
|
dims: p.dims,
|
|
@@ -4050,7 +4167,8 @@ var GameDestruction = (() => {
|
|
|
4050
4167
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
4051
4168
|
maxFragments,
|
|
4052
4169
|
rnd,
|
|
4053
|
-
sheet: p.mat.sheet
|
|
4170
|
+
sheet: p.mat.sheet,
|
|
4171
|
+
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
4054
4172
|
});
|
|
4055
4173
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
4056
4174
|
const mass = p.st.mass;
|
|
@@ -4109,10 +4227,33 @@ var GameDestruction = (() => {
|
|
|
4109
4227
|
emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
4110
4228
|
};
|
|
4111
4229
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
4230
|
+
const severAxis = (p) => {
|
|
4231
|
+
if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
|
|
4232
|
+
const d = p.dims;
|
|
4233
|
+
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
4234
|
+
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
4235
|
+
};
|
|
4236
|
+
const bandOf = (o, p, axis, origin) => {
|
|
4237
|
+
const { c, q } = partXf(o, p);
|
|
4238
|
+
const along = toLocal(c, q, origin)[axis];
|
|
4239
|
+
const L = p.dims[axis];
|
|
4240
|
+
return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
|
|
4241
|
+
};
|
|
4112
4242
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
4243
|
+
var _a;
|
|
4113
4244
|
if (isLost(p) || amount <= 0) return 0;
|
|
4114
4245
|
const prev = p.st.hp;
|
|
4115
4246
|
p.st.hp -= amount;
|
|
4247
|
+
const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
|
|
4248
|
+
if (axis >= 0 && p.st.hp > 0) {
|
|
4249
|
+
const band = bandOf(o, p, axis, origin);
|
|
4250
|
+
const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
|
|
4251
|
+
cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
|
|
4252
|
+
if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
|
|
4253
|
+
p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
|
|
4254
|
+
p.st.hp = 0;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4116
4257
|
markDamaged(p);
|
|
4117
4258
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
4118
4259
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -4151,7 +4292,7 @@ var GameDestruction = (() => {
|
|
|
4151
4292
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
4152
4293
|
const mat = p.mat;
|
|
4153
4294
|
const piercing = round.piercing ?? 1;
|
|
4154
|
-
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
4295
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry)) || insideChip(p, objLocal(o, entry))) {
|
|
4155
4296
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
4156
4297
|
}
|
|
4157
4298
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -4210,6 +4351,7 @@ var GameDestruction = (() => {
|
|
|
4210
4351
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4211
4352
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
4212
4353
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
|
|
4354
|
+
chipUnits(o, p, entry, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
|
|
4213
4355
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
4214
4356
|
};
|
|
4215
4357
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -4237,6 +4379,7 @@ var GameDestruction = (() => {
|
|
|
4237
4379
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
4238
4380
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
4239
4381
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
4382
|
+
if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
|
|
4240
4383
|
return done;
|
|
4241
4384
|
};
|
|
4242
4385
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -5728,6 +5871,7 @@ var GameDestruction = (() => {
|
|
|
5728
5871
|
sway: "sway",
|
|
5729
5872
|
spall: "plaster came off",
|
|
5730
5873
|
stage: "damage stage",
|
|
5874
|
+
chip: "brick knocked out",
|
|
5731
5875
|
expose: "rebar bare",
|
|
5732
5876
|
body: "body",
|
|
5733
5877
|
blast: "blast",
|
|
@@ -5818,6 +5962,7 @@ var GameDestruction = (() => {
|
|
|
5818
5962
|
sway: "balan\xE7o",
|
|
5819
5963
|
spall: "reboco soltou",
|
|
5820
5964
|
stage: "est\xE1gio de dano",
|
|
5965
|
+
chip: "tijolo arrancado",
|
|
5821
5966
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
5822
5967
|
body: "corpo",
|
|
5823
5968
|
blast: "explos\xE3o",
|