@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.js CHANGED
@@ -340,6 +340,62 @@ var frame = (ax, ay, az, angle = 0) => {
340
340
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
341
341
  };
342
342
  var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
343
+ var splinterBox = (inp, box, limit = inp.maxFragments) => {
344
+ const { rnd } = inp;
345
+ const dims = box.s;
346
+ const [fmin, fmax] = inp.fragmentSize;
347
+ const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
348
+ const [a, b] = [0, 1, 2].filter((i) => i !== g);
349
+ const w = Math.max(fmin * 1.5, 0.01);
350
+ const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
351
+ const maxLen = fmax * 4;
352
+ const cells = bsp(box, c, [a, b], rnd);
353
+ const pieces = [];
354
+ for (const cell of cells) {
355
+ const extra = rnd() < 0.4 ? 1 : 0;
356
+ const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
357
+ for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
358
+ }
359
+ 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] }));
360
+ };
361
+ var severAt = (inp, root, at, gap) => {
362
+ const dims = root.s;
363
+ const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
364
+ const half = dims[L] / 2;
365
+ const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
366
+ const out = [];
367
+ const stub = (from, to) => {
368
+ if (to - from < 0.02) return;
369
+ const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
370
+ c[L] = (from + to) / 2;
371
+ sz[L] = to - from;
372
+ out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
373
+ };
374
+ stub(-half, lo);
375
+ stub(hi, half);
376
+ const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
377
+ band.c[L] = (lo + hi) / 2;
378
+ band.s[L] = Math.max(hi - lo, 0.01);
379
+ for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
380
+ return out;
381
+ };
382
+ var tearStack = (inp, root, t, u, v, n, sheet) => {
383
+ const { dims, rnd, maxFragments } = inp;
384
+ const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
385
+ const per = clamp(n, 3, Math.max(3, Math.floor(maxFragments / layers)));
386
+ const out = [];
387
+ for (let k = 0; k < layers; k++) {
388
+ const off = -dims[t] / 2 + (k + 0.5) * dims[t] / layers;
389
+ for (const b of bsp(root, per, [u, v], rnd)) {
390
+ if (out.length >= maxFragments) return out;
391
+ const c = [b.c[0], b.c[1], b.c[2]];
392
+ c[t] = off;
393
+ 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 });
394
+ }
395
+ }
396
+ return out;
397
+ };
398
+ var STACK_LAYER = 0.012;
343
399
  var planFragments = (inp) => {
344
400
  const { style, dims, rnd, maxFragments } = inp;
345
401
  const [fmin, fmax] = inp.fragmentSize;
@@ -361,27 +417,14 @@ var planFragments = (inp) => {
361
417
  return out;
362
418
  }
363
419
  if (style === "splinter") {
364
- const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
365
- const [a, b] = [0, 1, 2].filter((i) => i !== g);
366
- const w = Math.max(fmin * 1.5, 0.01);
367
- const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
368
- const maxLen = fmax * 4;
369
- const cells = bsp(root, c, [a, b], rnd);
370
- const pieces = [];
371
- for (const cell of cells) {
372
- let list = [cell];
373
- const extra = rnd() < 0.4 ? 1 : 0;
374
- const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
375
- list = bsp(cell, k, [g], rnd);
376
- for (const p of list) pieces.push(p);
377
- }
378
- for (const p of pieces.slice(0, maxFragments)) {
379
- 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] });
380
- }
420
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
421
+ for (const p of splinterBox(inp, root)) out.push(p);
381
422
  return out;
382
423
  }
383
424
  if (style === "tear") {
384
425
  const n2 = clamp(Math.round(dims[u] * dims[v] / (mean * mean)), 3, maxFragments);
426
+ const sheet = inp.sheet ?? 0;
427
+ if (sheet > 0 && dims[t] > sheet * 4) return tearStack(inp, root, t, u, v, n2, sheet);
385
428
  const th2 = Math.min(inp.thickness, dims[t]);
386
429
  for (const b of bsp(root, n2, [u, v], rnd)) {
387
430
  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] });
@@ -2027,6 +2070,7 @@ var materials_default = [
2027
2070
  0.03,
2028
2071
  0.15
2029
2072
  ],
2073
+ sheet: 1e-3,
2030
2074
  damageScale: {
2031
2075
  slash: 3
2032
2076
  },
@@ -2042,7 +2086,8 @@ var materials_default = [
2042
2086
  smoke: "grey"
2043
2087
  },
2044
2088
  visual: {
2045
- charColor: 1709072
2089
+ charColor: 1709072,
2090
+ innerColor: 15524048
2046
2091
  },
2047
2092
  tags: [
2048
2093
  "fabric",
@@ -2436,9 +2481,14 @@ var BREACH_BLAST_FACTOR = 6;
2436
2481
  var BREACH_AREA = 0.25;
2437
2482
  var BREACH_BLAST_REACH = 0.12;
2438
2483
  var RESIDUAL_CAPACITY = 0.15;
2484
+ var SEVER_BAND = 0.1;
2485
+ var SEVER_ASPECT = 3;
2486
+ var SEVER_BALLISTIC = 2;
2439
2487
  var SUPPORT_TOLERANCE = 0.05;
2440
2488
  var UNIT_FRAGMENT_FACTOR = 4;
2441
2489
  var BREACH_GRID_CAP = 4096;
2490
+ var CHIP_ENERGY = 200;
2491
+ var CHIP_SPLASH = 0.5;
2442
2492
  var CUT_SLICE = 1;
2443
2493
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
2444
2494
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -2482,6 +2532,7 @@ var createDestruction = (options = {}) => {
2482
2532
  let windCfg = windOpts ? resolveWind(windOpts) : null;
2483
2533
  const ignitionRules = resolveIgnitionRules(options.ignition);
2484
2534
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
2535
+ const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
2485
2536
  const rng = createRng(options.seed ?? 1);
2486
2537
  const rnd = () => rng.next();
2487
2538
  let time = 0;
@@ -2855,41 +2906,68 @@ var createDestruction = (options = {}) => {
2855
2906
  markDamaged(p);
2856
2907
  if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
2857
2908
  };
2909
+ const freeSpan = (p, supports) => {
2910
+ if (supports.length === 0) return Infinity;
2911
+ const d = p.dims;
2912
+ const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
2913
+ const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
2914
+ let far = 0;
2915
+ for (let i = 0; i <= 16; i++) {
2916
+ const x = (i / 16 - 0.5) * d[L];
2917
+ let near = Infinity;
2918
+ for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
2919
+ far = Math.max(far, near);
2920
+ }
2921
+ return 2 * far;
2922
+ };
2858
2923
  const solveStructure = (o) => {
2859
2924
  o.maxStress = 0;
2860
2925
  if (!o.hasStructure) return;
2861
2926
  const parts = o.parts;
2862
2927
  const n = parts.length;
2863
2928
  const dist = new Array(n).fill(-1);
2864
- const queue = [];
2865
2929
  const ok = (i) => parts[i].structural && !isLost(parts[i]);
2866
2930
  const isPartition = (i) => parts[i].spec.structure?.role === "partition";
2867
- for (let i = 0; i < n; i++) if (ok(i) && parts[i].anchored) {
2868
- dist[i] = 0;
2869
- queue.push(i);
2870
- }
2871
- for (let h = 0; h < queue.length; h++) {
2872
- const s = queue[h];
2873
- if (isPartition(s)) continue;
2874
- for (const j of o.byPart.get(parts[s].spec.id)) {
2875
- if (j.st.open || !j.bears) continue;
2876
- const r = j.ia === s ? j.ib : j.ia;
2877
- if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
2878
- dist[r] = dist[s] + 1;
2879
- queue.push(r);
2931
+ const out = /* @__PURE__ */ new Set();
2932
+ let queue = [];
2933
+ let lowerOf = /* @__PURE__ */ new Map();
2934
+ for (; ; ) {
2935
+ dist.fill(-1);
2936
+ queue = [];
2937
+ for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
2938
+ dist[i] = 0;
2939
+ queue.push(i);
2880
2940
  }
2881
- }
2882
- const incoming = new Array(n).fill(0);
2883
- const lowerOf = /* @__PURE__ */ new Map();
2884
- for (const i of queue) {
2885
- const lower = [];
2886
- for (const j of o.byPart.get(parts[i].spec.id)) {
2887
- if (j.st.open || !j.bears) continue;
2888
- const k = restsOn(o, j, i);
2889
- if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
2941
+ for (let h = 0; h < queue.length; h++) {
2942
+ const s = queue[h];
2943
+ if (isPartition(s)) continue;
2944
+ for (const j of o.byPart.get(parts[s].spec.id)) {
2945
+ if (j.st.open || !j.bears) continue;
2946
+ const r = j.ia === s ? j.ib : j.ia;
2947
+ if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
2948
+ dist[r] = dist[s] + 1;
2949
+ queue.push(r);
2950
+ }
2951
+ }
2952
+ lowerOf = /* @__PURE__ */ new Map();
2953
+ let dropped = false;
2954
+ for (const i of queue) {
2955
+ const lower = [];
2956
+ for (const j of o.byPart.get(parts[i].spec.id)) {
2957
+ if (j.st.open || !j.bears) continue;
2958
+ const k = restsOn(o, j, i);
2959
+ if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
2960
+ }
2961
+ lowerOf.set(i, lower);
2962
+ const max = parts[i].spec.structure?.maxSpan;
2963
+ if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
2964
+ out.add(i);
2965
+ dropped = true;
2966
+ }
2890
2967
  }
2891
- lowerOf.set(i, lower);
2968
+ if (!dropped) break;
2892
2969
  }
2970
+ const incoming = new Array(n).fill(0);
2893
2971
  const bending = new Array(n).fill(0);
2894
2972
  const through = parts.map(() => []);
2895
2973
  const pass = (k, at, mass) => {
@@ -3401,6 +3479,7 @@ var createDestruction = (options = {}) => {
3401
3479
  const r = rayPart(o, p, origin, d);
3402
3480
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
3403
3481
  if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
3482
+ if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
3404
3483
  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 };
3405
3484
  }
3406
3485
  }
@@ -3664,6 +3743,62 @@ var createDestruction = (options = {}) => {
3664
3743
  if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
3665
3744
  openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
3666
3745
  };
3746
+ const unitGrids = /* @__PURE__ */ new WeakMap();
3747
+ const gridOf = (p) => {
3748
+ let grid = unitGrids.get(p);
3749
+ if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP));
3750
+ return grid;
3751
+ };
3752
+ const insideChip = (p, objectPoint) => {
3753
+ const cells = p.st.chipped;
3754
+ if (!cells?.length) return false;
3755
+ const [u, v] = planeAxes(p.thin);
3756
+ const lp = toLocal(p.pos, p.q, objectPoint);
3757
+ 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);
3758
+ };
3759
+ const chipUnits = (o, p, wp, energy, dir, radius, src) => {
3760
+ var _a, _b;
3761
+ if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
3762
+ const [u, v] = planeAxes(p.thin);
3763
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
3764
+ const grid = gridOf(p);
3765
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
3766
+ const out = [];
3767
+ for (let i = 0; i < grid.length; i++) {
3768
+ const cell = grid[i];
3769
+ if (worn[i] === -1) continue;
3770
+ 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));
3771
+ if (gap > radius) continue;
3772
+ const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
3773
+ const total = (worn[i] ?? 0) + energy * share;
3774
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
3775
+ if (total < chipEnergy * mass) {
3776
+ worn[i] = total;
3777
+ continue;
3778
+ }
3779
+ worn[i] = -1;
3780
+ out.push(cell);
3781
+ }
3782
+ if (out.length === 0) return;
3783
+ const { c, q } = partXf(o, p);
3784
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
3785
+ const holes = localHolesOf(p);
3786
+ for (const cell of out) {
3787
+ const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
3788
+ const center = toWorld(c, q, cell.center);
3789
+ const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
3790
+ const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
3791
+ p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
3792
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
3793
+ p.st.mass = Math.max(0, p.st.mass - mass);
3794
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
3795
+ markDamaged(p);
3796
+ setStage(o, p, 1, {}, src);
3797
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
3798
+ debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
3799
+ }
3800
+ for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
3801
+ };
3667
3802
  const openBreach = (o, p, center, total, energy, dir, src) => {
3668
3803
  const [u, v] = planeAxes(p.thin);
3669
3804
  const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
@@ -3675,7 +3810,8 @@ var createDestruction = (options = {}) => {
3675
3810
  failPart(o, p, worldCenter, dir, energy, 12, src);
3676
3811
  return;
3677
3812
  }
3678
- const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
3813
+ const worn = p.rt.chips;
3814
+ const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
3679
3815
  const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
3680
3816
  const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
3681
3817
  const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
@@ -3759,6 +3895,8 @@ var createDestruction = (options = {}) => {
3759
3895
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
3760
3896
  const hitLocal = toLocal(c, q, hitW);
3761
3897
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
3898
+ const axis = style === "splinter" ? severAxis(p) : -1;
3899
+ const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
3762
3900
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
3763
3901
  style,
3764
3902
  dims: p.dims,
@@ -3767,7 +3905,9 @@ var createDestruction = (options = {}) => {
3767
3905
  hit: hitLocal,
3768
3906
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
3769
3907
  maxFragments,
3770
- rnd
3908
+ rnd,
3909
+ sheet: p.mat.sheet,
3910
+ ...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
3771
3911
  });
3772
3912
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
3773
3913
  const mass = p.st.mass;
@@ -3791,7 +3931,8 @@ var createDestruction = (options = {}) => {
3791
3931
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
3792
3932
  angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
3793
3933
  mass: mass * f.weight / totW,
3794
- ...marks.length > 0 ? { marks } : {}
3934
+ ...marks.length > 0 ? { marks } : {},
3935
+ ...f.inner ? { inner: true } : {}
3795
3936
  };
3796
3937
  });
3797
3938
  const reinforcement = p.spec.reinforcement;
@@ -3825,10 +3966,33 @@ var createDestruction = (options = {}) => {
3825
3966
  emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
3826
3967
  };
3827
3968
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
3969
+ const severAxis = (p) => {
3970
+ if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
3971
+ const d = p.dims;
3972
+ const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
3973
+ return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
3974
+ };
3975
+ const bandOf = (o, p, axis, origin) => {
3976
+ const { c, q } = partXf(o, p);
3977
+ const along = toLocal(c, q, origin)[axis];
3978
+ const L = p.dims[axis];
3979
+ return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
3980
+ };
3828
3981
  const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
3982
+ var _a;
3829
3983
  if (isLost(p) || amount <= 0) return 0;
3830
3984
  const prev = p.st.hp;
3831
3985
  p.st.hp -= amount;
3986
+ const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
3987
+ if (axis >= 0 && p.st.hp > 0) {
3988
+ const band = bandOf(o, p, axis, origin);
3989
+ const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
3990
+ cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
3991
+ if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
3992
+ p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
3993
+ p.st.hp = 0;
3994
+ }
3995
+ }
3832
3996
  markDamaged(p);
3833
3997
  const trigger = CHAIN_TRIGGER_OF[type];
3834
3998
  const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
@@ -3867,7 +4031,7 @@ var createDestruction = (options = {}) => {
3867
4031
  const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
3868
4032
  const mat = p.mat;
3869
4033
  const piercing = round.piercing ?? 1;
3870
- if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
4034
+ if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry)) || insideChip(p, objLocal(o, entry))) {
3871
4035
  return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
3872
4036
  }
3873
4037
  const sin = Math.min(1, Math.abs(vec.dot(d, n)));
@@ -3926,6 +4090,7 @@ var createDestruction = (options = {}) => {
3926
4090
  debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
3927
4091
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
3928
4092
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
4093
+ chipUnits(o, p, entry, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
3929
4094
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
3930
4095
  };
3931
4096
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -3953,6 +4118,7 @@ var createDestruction = (options = {}) => {
3953
4118
  const scale = mat.damageScale?.[kind] ?? 1;
3954
4119
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
3955
4120
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
4121
+ if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
3956
4122
  return done;
3957
4123
  };
3958
4124
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
@@ -5444,6 +5610,7 @@ var locales = {
5444
5610
  sway: "sway",
5445
5611
  spall: "plaster came off",
5446
5612
  stage: "damage stage",
5613
+ chip: "brick knocked out",
5447
5614
  expose: "rebar bare",
5448
5615
  body: "body",
5449
5616
  blast: "blast",
@@ -5534,6 +5701,7 @@ var locales = {
5534
5701
  sway: "balan\xE7o",
5535
5702
  spall: "reboco soltou",
5536
5703
  stage: "est\xE1gio de dano",
5704
+ chip: "tijolo arrancado",
5537
5705
  expose: "vergalh\xE3o \xE0 mostra",
5538
5706
  body: "corpo",
5539
5707
  blast: "explos\xE3o",