@adatechnology/game-destruction 0.8.2 → 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 +19 -0
- package/dist/index.global.js +214 -46
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +214 -46
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,11 @@ interface MaterialDef {
|
|
|
62
62
|
fracture: FractureStyle;
|
|
63
63
|
/** Fragment size range in metres (default `[0.04, 0.2]`). */
|
|
64
64
|
fragmentSize?: [number, number];
|
|
65
|
+
/**
|
|
66
|
+
* Thickness of one sheet, in metres, for a material that comes in sheets (paper). A `tear` part much thicker than a sheet is a stack of them (a book,
|
|
67
|
+
* a ream, a magazine) and comes apart as thin loose scraps (`Fragment.inner` for the pages) instead of slabs as thick as the part.
|
|
68
|
+
*/
|
|
69
|
+
sheet?: number;
|
|
65
70
|
/** Damage multipliers per damage type (default 1); `shelter` multiplies the resistance the material adds between a blast and a target (`blast.shelter`). */
|
|
66
71
|
damageScale?: Partial<Record<DamageType | 'shelter', number>>;
|
|
67
72
|
/** Debris emitted on a hit that does not break the part. */
|
|
@@ -79,6 +84,8 @@ interface MaterialDef {
|
|
|
79
84
|
glows?: boolean;
|
|
80
85
|
/** Transparent (glass): cracks are drawn as a star on the surface. */
|
|
81
86
|
transparent?: boolean;
|
|
87
|
+
/** Colour of the inside, for the inner fragments and the hit debris (the pages of a book under its cover); default the colour of the part. */
|
|
88
|
+
innerColor?: number;
|
|
82
89
|
};
|
|
83
90
|
/** Free tags, e.g. `metal`, `organic`, `conductive`. */
|
|
84
91
|
tags?: string[];
|
|
@@ -329,6 +336,12 @@ interface DestructionOptions {
|
|
|
329
336
|
radius?: number;
|
|
330
337
|
/** Multiplier of the breach area a blast makes over a blow of the same damage: a blast loads the whole crater (default 6). */
|
|
331
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;
|
|
332
345
|
};
|
|
333
346
|
/**
|
|
334
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`
|
|
@@ -497,8 +510,14 @@ interface PartRuntime {
|
|
|
497
510
|
spalls?: Array<[number, number, number, number, number, number]>;
|
|
498
511
|
/** Through damage so far: `[x, y, z, area]` in object space, newest last. */
|
|
499
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>;
|
|
500
515
|
/** Mass in kg that left the part in a breach. */
|
|
501
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;
|
|
502
521
|
/** Plastic rotation in radians a reinforced beam or slab took in bending (blasts add it up; `flexure.failRotationDeg` breaks it). */
|
|
503
522
|
hinge?: number;
|
|
504
523
|
}
|
package/dist/index.global.js
CHANGED
|
@@ -450,6 +450,62 @@ 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
|
+
};
|
|
492
|
+
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
493
|
+
const { dims, rnd, maxFragments } = inp;
|
|
494
|
+
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
495
|
+
const per = clamp(n, 3, Math.max(3, Math.floor(maxFragments / layers)));
|
|
496
|
+
const out = [];
|
|
497
|
+
for (let k = 0; k < layers; k++) {
|
|
498
|
+
const off = -dims[t] / 2 + (k + 0.5) * dims[t] / layers;
|
|
499
|
+
for (const b of bsp(root, per, [u, v], rnd)) {
|
|
500
|
+
if (out.length >= maxFragments) return out;
|
|
501
|
+
const c = [b.c[0], b.c[1], b.c[2]];
|
|
502
|
+
c[t] = off;
|
|
503
|
+
out.push({ center: c, size: [b.s[u], b.s[v], sheet], q: frame(u, v, t, (rnd() - 0.5) * 0.8), shape: "scrap", weight: b.s[u] * b.s[v], inner: out.length % 4 !== 0 });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return out;
|
|
507
|
+
};
|
|
508
|
+
var STACK_LAYER = 0.012;
|
|
453
509
|
var planFragments = (inp) => {
|
|
454
510
|
const { style, dims, rnd, maxFragments } = inp;
|
|
455
511
|
const [fmin, fmax] = inp.fragmentSize;
|
|
@@ -471,27 +527,14 @@ var GameDestruction = (() => {
|
|
|
471
527
|
return out;
|
|
472
528
|
}
|
|
473
529
|
if (style === "splinter") {
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
477
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
478
|
-
const maxLen = fmax * 4;
|
|
479
|
-
const cells = bsp(root, c, [a, b], rnd);
|
|
480
|
-
const pieces = [];
|
|
481
|
-
for (const cell of cells) {
|
|
482
|
-
let list = [cell];
|
|
483
|
-
const extra = rnd() < 0.4 ? 1 : 0;
|
|
484
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
485
|
-
list = bsp(cell, k, [g], rnd);
|
|
486
|
-
for (const p of list) pieces.push(p);
|
|
487
|
-
}
|
|
488
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
489
|
-
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] });
|
|
490
|
-
}
|
|
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);
|
|
491
532
|
return out;
|
|
492
533
|
}
|
|
493
534
|
if (style === "tear") {
|
|
494
535
|
const n2 = clamp(Math.round(dims[u] * dims[v] / (mean * mean)), 3, maxFragments);
|
|
536
|
+
const sheet = inp.sheet ?? 0;
|
|
537
|
+
if (sheet > 0 && dims[t] > sheet * 4) return tearStack(inp, root, t, u, v, n2, sheet);
|
|
495
538
|
const th2 = Math.min(inp.thickness, dims[t]);
|
|
496
539
|
for (const b of bsp(root, n2, [u, v], rnd)) {
|
|
497
540
|
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] });
|
|
@@ -2137,6 +2180,7 @@ var GameDestruction = (() => {
|
|
|
2137
2180
|
0.03,
|
|
2138
2181
|
0.15
|
|
2139
2182
|
],
|
|
2183
|
+
sheet: 1e-3,
|
|
2140
2184
|
damageScale: {
|
|
2141
2185
|
slash: 3
|
|
2142
2186
|
},
|
|
@@ -2152,7 +2196,8 @@ var GameDestruction = (() => {
|
|
|
2152
2196
|
smoke: "grey"
|
|
2153
2197
|
},
|
|
2154
2198
|
visual: {
|
|
2155
|
-
charColor: 1709072
|
|
2199
|
+
charColor: 1709072,
|
|
2200
|
+
innerColor: 15524048
|
|
2156
2201
|
},
|
|
2157
2202
|
tags: [
|
|
2158
2203
|
"fabric",
|
|
@@ -2697,9 +2742,14 @@ var GameDestruction = (() => {
|
|
|
2697
2742
|
var BREACH_AREA = 0.25;
|
|
2698
2743
|
var BREACH_BLAST_REACH = 0.12;
|
|
2699
2744
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2745
|
+
var SEVER_BAND = 0.1;
|
|
2746
|
+
var SEVER_ASPECT = 3;
|
|
2747
|
+
var SEVER_BALLISTIC = 2;
|
|
2700
2748
|
var SUPPORT_TOLERANCE = 0.05;
|
|
2701
2749
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
2702
2750
|
var BREACH_GRID_CAP = 4096;
|
|
2751
|
+
var CHIP_ENERGY = 200;
|
|
2752
|
+
var CHIP_SPLASH = 0.5;
|
|
2703
2753
|
var CUT_SLICE = 1;
|
|
2704
2754
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2705
2755
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2743,6 +2793,7 @@ var GameDestruction = (() => {
|
|
|
2743
2793
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2744
2794
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
2745
2795
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
2796
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
2746
2797
|
const rng = createRng(options.seed ?? 1);
|
|
2747
2798
|
const rnd = () => rng.next();
|
|
2748
2799
|
let time = 0;
|
|
@@ -3116,41 +3167,68 @@ var GameDestruction = (() => {
|
|
|
3116
3167
|
markDamaged(p);
|
|
3117
3168
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
3118
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
|
+
};
|
|
3119
3184
|
const solveStructure = (o) => {
|
|
3120
3185
|
o.maxStress = 0;
|
|
3121
3186
|
if (!o.hasStructure) return;
|
|
3122
3187
|
const parts = o.parts;
|
|
3123
3188
|
const n = parts.length;
|
|
3124
3189
|
const dist = new Array(n).fill(-1);
|
|
3125
|
-
const queue = [];
|
|
3126
3190
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
3127
3191
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
if (
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
const r = j.ia === s ? j.ib : j.ia;
|
|
3138
|
-
if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
|
|
3139
|
-
dist[r] = dist[s] + 1;
|
|
3140
|
-
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);
|
|
3141
3201
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
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
|
+
}
|
|
3212
|
+
}
|
|
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
|
+
}
|
|
3151
3228
|
}
|
|
3152
|
-
|
|
3229
|
+
if (!dropped) break;
|
|
3153
3230
|
}
|
|
3231
|
+
const incoming = new Array(n).fill(0);
|
|
3154
3232
|
const bending = new Array(n).fill(0);
|
|
3155
3233
|
const through = parts.map(() => []);
|
|
3156
3234
|
const pass = (k, at, mass) => {
|
|
@@ -3662,6 +3740,7 @@ var GameDestruction = (() => {
|
|
|
3662
3740
|
const r = rayPart(o, p, origin, d);
|
|
3663
3741
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
3664
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;
|
|
3665
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 };
|
|
3666
3745
|
}
|
|
3667
3746
|
}
|
|
@@ -3925,6 +4004,62 @@ var GameDestruction = (() => {
|
|
|
3925
4004
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
3926
4005
|
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
3927
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
|
+
};
|
|
3928
4063
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
3929
4064
|
const [u, v] = planeAxes(p.thin);
|
|
3930
4065
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -3936,7 +4071,8 @@ var GameDestruction = (() => {
|
|
|
3936
4071
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
3937
4072
|
return;
|
|
3938
4073
|
}
|
|
3939
|
-
const
|
|
4074
|
+
const worn = p.rt.chips;
|
|
4075
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
3940
4076
|
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
3941
4077
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
3942
4078
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -4020,6 +4156,8 @@ var GameDestruction = (() => {
|
|
|
4020
4156
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
4021
4157
|
const hitLocal = toLocal(c, q, hitW);
|
|
4022
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);
|
|
4023
4161
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
4024
4162
|
style,
|
|
4025
4163
|
dims: p.dims,
|
|
@@ -4028,7 +4166,9 @@ var GameDestruction = (() => {
|
|
|
4028
4166
|
hit: hitLocal,
|
|
4029
4167
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
4030
4168
|
maxFragments,
|
|
4031
|
-
rnd
|
|
4169
|
+
rnd,
|
|
4170
|
+
sheet: p.mat.sheet,
|
|
4171
|
+
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
4032
4172
|
});
|
|
4033
4173
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
4034
4174
|
const mass = p.st.mass;
|
|
@@ -4052,7 +4192,8 @@ var GameDestruction = (() => {
|
|
|
4052
4192
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
4053
4193
|
angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
|
|
4054
4194
|
mass: mass * f.weight / totW,
|
|
4055
|
-
...marks.length > 0 ? { marks } : {}
|
|
4195
|
+
...marks.length > 0 ? { marks } : {},
|
|
4196
|
+
...f.inner ? { inner: true } : {}
|
|
4056
4197
|
};
|
|
4057
4198
|
});
|
|
4058
4199
|
const reinforcement = p.spec.reinforcement;
|
|
@@ -4086,10 +4227,33 @@ var GameDestruction = (() => {
|
|
|
4086
4227
|
emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
4087
4228
|
};
|
|
4088
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
|
+
};
|
|
4089
4242
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
4243
|
+
var _a;
|
|
4090
4244
|
if (isLost(p) || amount <= 0) return 0;
|
|
4091
4245
|
const prev = p.st.hp;
|
|
4092
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
|
+
}
|
|
4093
4257
|
markDamaged(p);
|
|
4094
4258
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
4095
4259
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -4128,7 +4292,7 @@ var GameDestruction = (() => {
|
|
|
4128
4292
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
4129
4293
|
const mat = p.mat;
|
|
4130
4294
|
const piercing = round.piercing ?? 1;
|
|
4131
|
-
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))) {
|
|
4132
4296
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
4133
4297
|
}
|
|
4134
4298
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -4187,6 +4351,7 @@ var GameDestruction = (() => {
|
|
|
4187
4351
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4188
4352
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
4189
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);
|
|
4190
4355
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
4191
4356
|
};
|
|
4192
4357
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -4214,6 +4379,7 @@ var GameDestruction = (() => {
|
|
|
4214
4379
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
4215
4380
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
4216
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);
|
|
4217
4383
|
return done;
|
|
4218
4384
|
};
|
|
4219
4385
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -5705,6 +5871,7 @@ var GameDestruction = (() => {
|
|
|
5705
5871
|
sway: "sway",
|
|
5706
5872
|
spall: "plaster came off",
|
|
5707
5873
|
stage: "damage stage",
|
|
5874
|
+
chip: "brick knocked out",
|
|
5708
5875
|
expose: "rebar bare",
|
|
5709
5876
|
body: "body",
|
|
5710
5877
|
blast: "blast",
|
|
@@ -5795,6 +5962,7 @@ var GameDestruction = (() => {
|
|
|
5795
5962
|
sway: "balan\xE7o",
|
|
5796
5963
|
spall: "reboco soltou",
|
|
5797
5964
|
stage: "est\xE1gio de dano",
|
|
5965
|
+
chip: "tijolo arrancado",
|
|
5798
5966
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
5799
5967
|
body: "corpo",
|
|
5800
5968
|
blast: "explos\xE3o",
|