@adatechnology/game-floorplan-structure 1.2.1 → 1.2.3

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.
@@ -2755,6 +2755,62 @@ var GameFloorplanStructure = (() => {
2755
2755
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
2756
2756
  };
2757
2757
  var clamp3 = (v, a, b) => Math.max(a, Math.min(b, v));
2758
+ var splinterBox = (inp, box2, limit = inp.maxFragments) => {
2759
+ const { rnd } = inp;
2760
+ const dims = box2.s;
2761
+ const [fmin, fmax] = inp.fragmentSize;
2762
+ const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
2763
+ const [a, b] = [0, 1, 2].filter((i) => i !== g);
2764
+ const w = Math.max(fmin * 1.5, 0.01);
2765
+ const c = clamp3(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
2766
+ const maxLen = fmax * 4;
2767
+ const cells = bsp(box2, c, [a, b], rnd);
2768
+ const pieces = [];
2769
+ for (const cell of cells) {
2770
+ const extra = rnd() < 0.4 ? 1 : 0;
2771
+ const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
2772
+ for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
2773
+ }
2774
+ 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] }));
2775
+ };
2776
+ var severAt = (inp, root, at2, gap) => {
2777
+ const dims = root.s;
2778
+ const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
2779
+ const half = dims[L] / 2;
2780
+ const lo = clamp3(at2 - gap / 2, -half, half), hi = clamp3(at2 + gap / 2, -half, half);
2781
+ const out = [];
2782
+ const stub = (from, to) => {
2783
+ if (to - from < 0.02) return;
2784
+ const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
2785
+ c[L] = (from + to) / 2;
2786
+ sz[L] = to - from;
2787
+ out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
2788
+ };
2789
+ stub(-half, lo);
2790
+ stub(hi, half);
2791
+ const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
2792
+ band.c[L] = (lo + hi) / 2;
2793
+ band.s[L] = Math.max(hi - lo, 0.01);
2794
+ for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
2795
+ return out;
2796
+ };
2797
+ var tearStack = (inp, root, t, u, v, n, sheet) => {
2798
+ const { dims, rnd, maxFragments } = inp;
2799
+ const layers = clamp3(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
2800
+ const per = clamp3(n, 3, Math.max(3, Math.floor(maxFragments / layers)));
2801
+ const out = [];
2802
+ for (let k = 0; k < layers; k++) {
2803
+ const off = -dims[t] / 2 + (k + 0.5) * dims[t] / layers;
2804
+ for (const b of bsp(root, per, [u, v], rnd)) {
2805
+ if (out.length >= maxFragments) return out;
2806
+ const c = [b.c[0], b.c[1], b.c[2]];
2807
+ c[t] = off;
2808
+ 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 });
2809
+ }
2810
+ }
2811
+ return out;
2812
+ };
2813
+ var STACK_LAYER = 0.012;
2758
2814
  var planFragments = (inp) => {
2759
2815
  const { style, dims, rnd, maxFragments } = inp;
2760
2816
  const [fmin, fmax] = inp.fragmentSize;
@@ -2776,27 +2832,14 @@ var GameFloorplanStructure = (() => {
2776
2832
  return out;
2777
2833
  }
2778
2834
  if (style === "splinter") {
2779
- const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
2780
- const [a, b] = [0, 1, 2].filter((i) => i !== g);
2781
- const w = Math.max(fmin * 1.5, 0.01);
2782
- const c = clamp3(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
2783
- const maxLen = fmax * 4;
2784
- const cells = bsp(root, c, [a, b], rnd);
2785
- const pieces = [];
2786
- for (const cell of cells) {
2787
- let list = [cell];
2788
- const extra = rnd() < 0.4 ? 1 : 0;
2789
- const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
2790
- list = bsp(cell, k, [g], rnd);
2791
- for (const p of list) pieces.push(p);
2792
- }
2793
- for (const p of pieces.slice(0, maxFragments)) {
2794
- 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] });
2795
- }
2835
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
2836
+ for (const p of splinterBox(inp, root)) out.push(p);
2796
2837
  return out;
2797
2838
  }
2798
2839
  if (style === "tear") {
2799
2840
  const n2 = clamp3(Math.round(dims[u] * dims[v] / (mean * mean)), 3, maxFragments);
2841
+ const sheet = inp.sheet ?? 0;
2842
+ if (sheet > 0 && dims[t] > sheet * 4) return tearStack(inp, root, t, u, v, n2, sheet);
2800
2843
  const th2 = Math.min(inp.thickness, dims[t]);
2801
2844
  for (const b of bsp(root, n2, [u, v], rnd)) {
2802
2845
  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] });
@@ -4379,6 +4422,7 @@ var GameFloorplanStructure = (() => {
4379
4422
  0.03,
4380
4423
  0.15
4381
4424
  ],
4425
+ sheet: 1e-3,
4382
4426
  damageScale: {
4383
4427
  slash: 3
4384
4428
  },
@@ -4394,7 +4438,8 @@ var GameFloorplanStructure = (() => {
4394
4438
  smoke: "grey"
4395
4439
  },
4396
4440
  visual: {
4397
- charColor: 1709072
4441
+ charColor: 1709072,
4442
+ innerColor: 15524048
4398
4443
  },
4399
4444
  tags: [
4400
4445
  "fabric",
@@ -4776,9 +4821,14 @@ var GameFloorplanStructure = (() => {
4776
4821
  var BREACH_AREA = 0.25;
4777
4822
  var BREACH_BLAST_REACH = 0.12;
4778
4823
  var RESIDUAL_CAPACITY = 0.15;
4824
+ var SEVER_BAND = 0.1;
4825
+ var SEVER_ASPECT = 3;
4826
+ var SEVER_BALLISTIC = 2;
4779
4827
  var SUPPORT_TOLERANCE = 0.05;
4780
4828
  var UNIT_FRAGMENT_FACTOR = 4;
4781
4829
  var BREACH_GRID_CAP = 4096;
4830
+ var CHIP_ENERGY = 200;
4831
+ var CHIP_SPLASH = 0.5;
4782
4832
  var CUT_SLICE = 1;
4783
4833
  var cutFactorOf = (shape) => clamp22(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
4784
4834
  var clamp22 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -4822,6 +4872,7 @@ var GameFloorplanStructure = (() => {
4822
4872
  let windCfg = windOpts ? resolveWind(windOpts) : null;
4823
4873
  const ignitionRules = resolveIgnitionRules(options.ignition);
4824
4874
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
4875
+ const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
4825
4876
  const rng = createRng(options.seed ?? 1);
4826
4877
  const rnd = () => rng.next();
4827
4878
  let time = 0;
@@ -5195,41 +5246,68 @@ var GameFloorplanStructure = (() => {
5195
5246
  markDamaged(p);
5196
5247
  if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
5197
5248
  };
5249
+ const freeSpan = (p, supports) => {
5250
+ if (supports.length === 0) return Infinity;
5251
+ const d = p.dims;
5252
+ const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
5253
+ const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
5254
+ let far = 0;
5255
+ for (let i = 0; i <= 16; i++) {
5256
+ const x = (i / 16 - 0.5) * d[L];
5257
+ let near = Infinity;
5258
+ for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
5259
+ far = Math.max(far, near);
5260
+ }
5261
+ return 2 * far;
5262
+ };
5198
5263
  const solveStructure = (o) => {
5199
5264
  o.maxStress = 0;
5200
5265
  if (!o.hasStructure) return;
5201
5266
  const parts = o.parts;
5202
5267
  const n = parts.length;
5203
5268
  const dist = new Array(n).fill(-1);
5204
- const queue = [];
5205
5269
  const ok = (i) => parts[i].structural && !isLost(parts[i]);
5206
5270
  const isPartition = (i) => parts[i].spec.structure?.role === "partition";
5207
- for (let i = 0; i < n; i++) if (ok(i) && parts[i].anchored) {
5208
- dist[i] = 0;
5209
- queue.push(i);
5210
- }
5211
- for (let h = 0; h < queue.length; h++) {
5212
- const s = queue[h];
5213
- if (isPartition(s)) continue;
5214
- for (const j of o.byPart.get(parts[s].spec.id)) {
5215
- if (j.st.open || !j.bears) continue;
5216
- const r = j.ia === s ? j.ib : j.ia;
5217
- if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
5218
- dist[r] = dist[s] + 1;
5219
- queue.push(r);
5271
+ const out = /* @__PURE__ */ new Set();
5272
+ let queue = [];
5273
+ let lowerOf = /* @__PURE__ */ new Map();
5274
+ for (; ; ) {
5275
+ dist.fill(-1);
5276
+ queue = [];
5277
+ for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
5278
+ dist[i] = 0;
5279
+ queue.push(i);
5220
5280
  }
5221
- }
5222
- const incoming = new Array(n).fill(0);
5223
- const lowerOf = /* @__PURE__ */ new Map();
5224
- for (const i of queue) {
5225
- const lower = [];
5226
- for (const j of o.byPart.get(parts[i].spec.id)) {
5227
- if (j.st.open || !j.bears) continue;
5228
- const k = restsOn(o, j, i);
5229
- if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
5281
+ for (let h = 0; h < queue.length; h++) {
5282
+ const s = queue[h];
5283
+ if (isPartition(s)) continue;
5284
+ for (const j of o.byPart.get(parts[s].spec.id)) {
5285
+ if (j.st.open || !j.bears) continue;
5286
+ const r = j.ia === s ? j.ib : j.ia;
5287
+ if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
5288
+ dist[r] = dist[s] + 1;
5289
+ queue.push(r);
5290
+ }
5291
+ }
5292
+ lowerOf = /* @__PURE__ */ new Map();
5293
+ let dropped = false;
5294
+ for (const i of queue) {
5295
+ const lower = [];
5296
+ for (const j of o.byPart.get(parts[i].spec.id)) {
5297
+ if (j.st.open || !j.bears) continue;
5298
+ const k = restsOn(o, j, i);
5299
+ if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
5300
+ }
5301
+ lowerOf.set(i, lower);
5302
+ const max = parts[i].spec.structure?.maxSpan;
5303
+ if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
5304
+ out.add(i);
5305
+ dropped = true;
5306
+ }
5230
5307
  }
5231
- lowerOf.set(i, lower);
5308
+ if (!dropped) break;
5232
5309
  }
5310
+ const incoming = new Array(n).fill(0);
5233
5311
  const bending = new Array(n).fill(0);
5234
5312
  const through = parts.map(() => []);
5235
5313
  const pass = (k, at2, mass) => {
@@ -5741,6 +5819,7 @@ var GameFloorplanStructure = (() => {
5741
5819
  const r = rayPart(o, p, origin, d);
5742
5820
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
5743
5821
  if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
5822
+ if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
5744
5823
  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 };
5745
5824
  }
5746
5825
  }
@@ -6004,6 +6083,62 @@ var GameFloorplanStructure = (() => {
6004
6083
  if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
6005
6084
  openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
6006
6085
  };
6086
+ const unitGrids = /* @__PURE__ */ new WeakMap();
6087
+ const gridOf = (p) => {
6088
+ let grid = unitGrids.get(p);
6089
+ if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP));
6090
+ return grid;
6091
+ };
6092
+ const insideChip = (p, objectPoint) => {
6093
+ const cells = p.st.chipped;
6094
+ if (!cells?.length) return false;
6095
+ const [u, v] = planeAxes(p.thin);
6096
+ const lp = toLocal(p.pos, p.q, objectPoint);
6097
+ 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);
6098
+ };
6099
+ const chipUnits = (o, p, wp, energy, dir, radius, src) => {
6100
+ var _a, _b;
6101
+ if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
6102
+ const [u, v] = planeAxes(p.thin);
6103
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
6104
+ const grid = gridOf(p);
6105
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
6106
+ const out = [];
6107
+ for (let i = 0; i < grid.length; i++) {
6108
+ const cell = grid[i];
6109
+ if (worn[i] === -1) continue;
6110
+ 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));
6111
+ if (gap > radius) continue;
6112
+ const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
6113
+ const total = (worn[i] ?? 0) + energy * share;
6114
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
6115
+ if (total < chipEnergy * mass) {
6116
+ worn[i] = total;
6117
+ continue;
6118
+ }
6119
+ worn[i] = -1;
6120
+ out.push(cell);
6121
+ }
6122
+ if (out.length === 0) return;
6123
+ const { c, q } = partXf(o, p);
6124
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
6125
+ const holes = localHolesOf(p);
6126
+ for (const cell of out) {
6127
+ const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
6128
+ const center = toWorld(c, q, cell.center);
6129
+ const speed = clamp22(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
6130
+ const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
6131
+ p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
6132
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
6133
+ p.st.mass = Math.max(0, p.st.mass - mass);
6134
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
6135
+ markDamaged(p);
6136
+ setStage(o, p, 1, {}, src);
6137
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
6138
+ debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
6139
+ }
6140
+ for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
6141
+ };
6007
6142
  const openBreach = (o, p, center, total, energy, dir, src) => {
6008
6143
  const [u, v] = planeAxes(p.thin);
6009
6144
  const radius = clamp22(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
@@ -6015,7 +6150,8 @@ var GameFloorplanStructure = (() => {
6015
6150
  failPart(o, p, worldCenter, dir, energy, 12, src);
6016
6151
  return;
6017
6152
  }
6018
- const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP), p.thin, lc, radius);
6153
+ const worn = p.rt.chips;
6154
+ const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
6019
6155
  const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
6020
6156
  const speed = clamp22(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
6021
6157
  const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
@@ -6099,6 +6235,8 @@ var GameFloorplanStructure = (() => {
6099
6235
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
6100
6236
  const hitLocal = toLocal(c, q, hitW);
6101
6237
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
6238
+ const axis = style === "splinter" ? severAxis(p) : -1;
6239
+ const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
6102
6240
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
6103
6241
  style,
6104
6242
  dims: p.dims,
@@ -6107,7 +6245,9 @@ var GameFloorplanStructure = (() => {
6107
6245
  hit: hitLocal,
6108
6246
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
6109
6247
  maxFragments,
6110
- rnd
6248
+ rnd,
6249
+ sheet: p.mat.sheet,
6250
+ ...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
6111
6251
  });
6112
6252
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
6113
6253
  const mass = p.st.mass;
@@ -6131,7 +6271,8 @@ var GameFloorplanStructure = (() => {
6131
6271
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
6132
6272
  angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
6133
6273
  mass: mass * f.weight / totW,
6134
- ...marks.length > 0 ? { marks } : {}
6274
+ ...marks.length > 0 ? { marks } : {},
6275
+ ...f.inner ? { inner: true } : {}
6135
6276
  };
6136
6277
  });
6137
6278
  const reinforcement = p.spec.reinforcement;
@@ -6165,10 +6306,33 @@ var GameFloorplanStructure = (() => {
6165
6306
  emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
6166
6307
  };
6167
6308
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
6309
+ const severAxis = (p) => {
6310
+ if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
6311
+ const d = p.dims;
6312
+ const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
6313
+ return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
6314
+ };
6315
+ const bandOf = (o, p, axis, origin) => {
6316
+ const { c, q } = partXf(o, p);
6317
+ const along = toLocal(c, q, origin)[axis];
6318
+ const L = p.dims[axis];
6319
+ return clamp22(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
6320
+ };
6168
6321
  const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
6322
+ var _a;
6169
6323
  if (isLost(p) || amount <= 0) return 0;
6170
6324
  const prev = p.st.hp;
6171
6325
  p.st.hp -= amount;
6326
+ const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
6327
+ if (axis >= 0 && p.st.hp > 0) {
6328
+ const band = bandOf(o, p, axis, origin);
6329
+ const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
6330
+ cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
6331
+ if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
6332
+ p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
6333
+ p.st.hp = 0;
6334
+ }
6335
+ }
6172
6336
  markDamaged(p);
6173
6337
  const trigger = CHAIN_TRIGGER_OF[type];
6174
6338
  const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
@@ -6207,7 +6371,7 @@ var GameFloorplanStructure = (() => {
6207
6371
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
6208
6372
  const mat = p.mat;
6209
6373
  const piercing = round3.piercing ?? 1;
6210
- if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2))) {
6374
+ if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2)) || insideChip(p, objLocal(o, entry2))) {
6211
6375
  return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry2, d, Math.min(pathLen, p.thickness)), damage: 0 };
6212
6376
  }
6213
6377
  const sin = Math.min(1, Math.abs(vec.dot(d, n)));
@@ -6266,6 +6430,7 @@ var GameFloorplanStructure = (() => {
6266
6430
  debris(o, p, mat.hitDebris, entry2, n, clamp22(Math.round(E / 60) + 2, 2, 14), clamp22(Math.sqrt(E) / 6, 1, 8), src);
6267
6431
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
6268
6432
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry2, d, E * 0.1, 12, src);
6433
+ chipUnits(o, p, entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
6269
6434
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
6270
6435
  };
6271
6436
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -6293,6 +6458,7 @@ var GameFloorplanStructure = (() => {
6293
6458
  const scale = mat.damageScale?.[kind] ?? 1;
6294
6459
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
6295
6460
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
6461
+ if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
6296
6462
  return done;
6297
6463
  };
6298
6464
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {