@adatechnology/game-floorplan-structure 1.3.0 → 1.3.2

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.
@@ -4217,6 +4217,7 @@ var GameFloorplanStructure = (() => {
4217
4217
  },
4218
4218
  hitDebris: "chips",
4219
4219
  breakDebris: "dust",
4220
+ visual: { innerColor: 10197652 },
4220
4221
  thermal: {
4221
4222
  conductivity: 0.15,
4222
4223
  heatCapacity: 2.5
@@ -4247,6 +4248,7 @@ var GameFloorplanStructure = (() => {
4247
4248
  ],
4248
4249
  hitDebris: "chips",
4249
4250
  breakDebris: "dust",
4251
+ visual: { innerColor: 12085567 },
4250
4252
  thermal: {
4251
4253
  conductivity: 0.15,
4252
4254
  heatCapacity: 2.5
@@ -4277,6 +4279,7 @@ var GameFloorplanStructure = (() => {
4277
4279
  ],
4278
4280
  hitDebris: "dust",
4279
4281
  breakDebris: "dust",
4282
+ visual: { innerColor: 14999507 },
4280
4283
  thermal: {
4281
4284
  conductivity: 0.1,
4282
4285
  heatCapacity: 1.5
@@ -4302,6 +4305,7 @@ var GameFloorplanStructure = (() => {
4302
4305
  ],
4303
4306
  hitDebris: "chips",
4304
4307
  breakDebris: "dust",
4308
+ visual: { innerColor: 9407364 },
4305
4309
  thermal: {
4306
4310
  conductivity: 0.2,
4307
4311
  heatCapacity: 2.5
@@ -5043,6 +5047,7 @@ var GameFloorplanStructure = (() => {
5043
5047
  var FLAMMABLE_FLUIDS = ["gas", "fuel"];
5044
5048
  var VENT_DIAMETER = 8e-3;
5045
5049
  var FLAME_LIGHTS_LEAK_C = 470;
5050
+ var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
5046
5051
  var BREACH_RADIUS = 0.55;
5047
5052
  var BREACH_BLAST_FACTOR = 6;
5048
5053
  var BREACH_AREA = 0.25;
@@ -5058,7 +5063,9 @@ var GameFloorplanStructure = (() => {
5058
5063
  var BREACH_GRID_CAP = 4096;
5059
5064
  var CHIP_ENERGY = 200;
5060
5065
  var CHIP_SPLASH = 0.5;
5066
+ var CRATER_SPENT = 0.6;
5061
5067
  var BAR_BOW = 0.08;
5068
+ var BULLET_JOINT_SPAN = 0.3;
5062
5069
  var CUT_SLICE = 1;
5063
5070
  var cutFactorOf = (shape) => clamp22(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
5064
5071
  var clamp22 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -5077,12 +5084,22 @@ var GameFloorplanStructure = (() => {
5077
5084
  const cells = p.st.chipped;
5078
5085
  if (!cells?.length) return 0;
5079
5086
  const [u, v] = planeAxes(p.thin);
5080
- const along = p.dims[u] >= p.dims[v] ? u : v;
5087
+ const role = p.spec.structure?.role;
5088
+ const up = rotateVec(p.q, [0, 0, 0].map((_, k) => k === v ? 1 : 0));
5089
+ const upright = Math.abs(up[1]) > 0.7 ? v : Math.abs(rotateVec(p.q, [0, 0, 0].map((_, k) => k === u ? 1 : 0))[1]) > 0.7 ? u : -1;
5090
+ const along = (role === "wall" || role === "partition" || role === "column") && upright >= 0 ? upright : p.dims[u] >= p.dims[v] ? u : v;
5081
5091
  const across = along === u ? v : u;
5082
5092
  let worst = 0;
5083
5093
  for (const c of cells) {
5084
- let gone = 0;
5085
- for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
5094
+ const spans = [];
5095
+ for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) spans.push([d.center[across] - d.size[across] / 2, d.center[across] + d.size[across] / 2]);
5096
+ spans.sort((a, b) => a[0] - b[0]);
5097
+ let gone = 0, end = -Infinity;
5098
+ for (const [a, b] of spans) {
5099
+ if (b <= end) continue;
5100
+ gone += b - Math.max(a, end);
5101
+ end = b;
5102
+ }
5086
5103
  worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
5087
5104
  }
5088
5105
  return Math.min(1, worst);
@@ -5378,7 +5395,18 @@ var GameFloorplanStructure = (() => {
5378
5395
  if (k < 0 || j.spec.kind !== "tie") return k;
5379
5396
  return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
5380
5397
  };
5398
+ let poseVersion = 0;
5399
+ const contactMemo = /* @__PURE__ */ new WeakMap();
5381
5400
  const contactBox = (p, support) => {
5401
+ let byTouching = contactMemo.get(p);
5402
+ if (!byTouching) contactMemo.set(p, byTouching = /* @__PURE__ */ new Map());
5403
+ const known = byTouching.get(support);
5404
+ if (known && known.version === poseVersion) return known.box;
5405
+ const box2 = measureContact(p, support);
5406
+ byTouching.set(support, { version: poseVersion, box: box2 });
5407
+ return box2;
5408
+ };
5409
+ const measureContact = (p, support) => {
5382
5410
  const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
5383
5411
  const lo = [0, 0, 0], hi = [0, 0, 0];
5384
5412
  for (let k = 0; k < 3; k++) {
@@ -6173,7 +6201,7 @@ var GameFloorplanStructure = (() => {
6173
6201
  p.st.holes.push(hole);
6174
6202
  if (p.st.holes.length > maxHoles) p.st.holes.shift();
6175
6203
  markDamaged(p);
6176
- emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
6204
+ emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {}, ...p.spec.powder ? { powder: { ...p.spec.powder } } : {} });
6177
6205
  setStage(o, p, 1, {}, src);
6178
6206
  return hole;
6179
6207
  };
@@ -6185,11 +6213,27 @@ var GameFloorplanStructure = (() => {
6185
6213
  emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
6186
6214
  setStage(o, p, 1, {}, src);
6187
6215
  };
6188
- const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
6216
+ const debris = (o, p, kind, point, normal, count, speed, src, material) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed, ...material && material !== p.spec.material ? { material } : {} });
6189
6217
  const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
6190
6218
  const detachEvents = (o, opened, by, src) => {
6191
6219
  for (const j of opened) emit({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
6192
6220
  };
6221
+ const slideOffResting = (o, rotation) => {
6222
+ const upY = 1 - 2 * (rotation[0] * rotation[0] + rotation[2] * rotation[2]);
6223
+ if (upY >= RESTING_HOLD_COS) return;
6224
+ const opened = o.joints.filter((j) => j.spec.kind === "resting" && !j.st.open);
6225
+ if (opened.length === 0) return;
6226
+ run(() => {
6227
+ for (const j of opened) {
6228
+ j.st.open = true;
6229
+ j.st.openedBy = "break";
6230
+ }
6231
+ solveStructure(o);
6232
+ recomputeGroups(o);
6233
+ detachEvents(o, opened, "break");
6234
+ collapseCheck(o);
6235
+ });
6236
+ };
6193
6237
  const checkDestroyed = (o, src) => {
6194
6238
  if (!o.st.destroyed && o.parts.length > 0 && o.parts.every(isLost)) {
6195
6239
  o.st.destroyed = true;
@@ -6226,7 +6270,13 @@ var GameFloorplanStructure = (() => {
6226
6270
  const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
6227
6271
  return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
6228
6272
  };
6229
- const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false) => {
6273
+ const jointLength = (o, j) => {
6274
+ const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
6275
+ let len = 0;
6276
+ for (let k = 0; k < 3; k++) len = Math.max(len, Math.min(A.max[k], B.max[k]) - Math.max(A.min[k], B.min[k]));
6277
+ return len;
6278
+ };
6279
+ const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false, span) => {
6230
6280
  if (amount <= 0) return;
6231
6281
  const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
6232
6282
  if (split && mine.length) amount /= mine.length;
@@ -6234,7 +6284,7 @@ var GameFloorplanStructure = (() => {
6234
6284
  if (j.st.open) continue;
6235
6285
  if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
6236
6286
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
6237
- j.st.hp -= amount;
6287
+ j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
6238
6288
  if (j.st.hp <= 0) {
6239
6289
  j.st.hp = 0;
6240
6290
  j.st.open = true;
@@ -6263,7 +6313,7 @@ var GameFloorplanStructure = (() => {
6263
6313
  if (spalls.length > MAX_SPALLS) spalls.shift();
6264
6314
  markDamaged(p);
6265
6315
  emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
6266
- debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
6316
+ debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src, layer.mat.id);
6267
6317
  setStage(o, p, 1, {}, src);
6268
6318
  };
6269
6319
  const crater = (o, p, wp, normal, radius, src) => {
@@ -6284,7 +6334,7 @@ var GameFloorplanStructure = (() => {
6284
6334
  const localHolesOf = (p) => p.st.holes.map((h) => ({ point: toLocal(p.pos, p.q, h.point), normal: rotateVec(quat.conjugate(p.q), h.normal), diameter: h.diameter }));
6285
6335
  const unitsOf2 = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
6286
6336
  const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
6287
- const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
6337
+ const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag, inner2 = false) => {
6288
6338
  const { c, q } = partXf(o, p);
6289
6339
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
6290
6340
  const [u, v] = planeAxes(p.thin);
@@ -6308,7 +6358,8 @@ var GameFloorplanStructure = (() => {
6308
6358
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
6309
6359
  angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
6310
6360
  mass: mass * faceArea(cell) / total,
6311
- ...marks.length > 0 ? { marks } : {}
6361
+ ...marks.length > 0 ? { marks } : {},
6362
+ ...inner2 ? { inner: inner2 } : {}
6312
6363
  };
6313
6364
  });
6314
6365
  };
@@ -6349,7 +6400,7 @@ var GameFloorplanStructure = (() => {
6349
6400
  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);
6350
6401
  };
6351
6402
  const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
6352
- var _a, _b;
6403
+ var _a, _b, _c;
6353
6404
  if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
6354
6405
  const rule = p.mat.hits?.[kind];
6355
6406
  const perKg = rule?.unitJoules ?? chipEnergy;
@@ -6359,7 +6410,9 @@ var GameFloorplanStructure = (() => {
6359
6410
  const lp = toLocal(p.pos, p.q, objLocal(o, wp));
6360
6411
  const grid = gridOf(p);
6361
6412
  const worn = (_a = p.rt).chips ?? (_a.chips = {});
6413
+ const cut = (_b = p.rt).craters ?? (_b.craters = {});
6362
6414
  const out = [];
6415
+ const shot = kind === "ballistic";
6363
6416
  for (let i = 0; i < grid.length; i++) {
6364
6417
  const cell = grid[i];
6365
6418
  if (worn[i] === -1) continue;
@@ -6372,18 +6425,39 @@ var GameFloorplanStructure = (() => {
6372
6425
  worn[i] = total;
6373
6426
  continue;
6374
6427
  }
6375
- worn[i] = -1;
6376
- out.push(cell);
6428
+ if (!shot) {
6429
+ worn[i] = -1;
6430
+ out.push(cell);
6431
+ continue;
6432
+ }
6433
+ const size = [cell.size[0], cell.size[1], cell.size[2]];
6434
+ const center = [cell.center[0], cell.center[1], cell.center[2]];
6435
+ for (const a of [u, v]) {
6436
+ const w = Math.min(cell.size[a], craterAcross(hitEnergy) * (0.85 + 0.3 * rnd()));
6437
+ const lo = cell.center[a] - cell.size[a] / 2 + w / 2, hi = cell.center[a] + cell.size[a] / 2 - w / 2;
6438
+ size[a] = w;
6439
+ center[a] = clamp22(lp[a], lo, hi);
6440
+ }
6441
+ const taken2 = (cut[i] ?? 0) + size[u] * size[v];
6442
+ if (taken2 >= cell.size[u] * cell.size[v] * CRATER_SPENT) {
6443
+ worn[i] = -1;
6444
+ out.push({ ...cell, crater: true });
6445
+ continue;
6446
+ }
6447
+ worn[i] = 0;
6448
+ cut[i] = taken2;
6449
+ out.push({ center, size, crater: true });
6377
6450
  }
6378
6451
  if (out.length === 0) return;
6379
6452
  const { c, q } = partXf(o, p);
6380
- const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
6453
+ const chipped = (_c = p.st).chipped ?? (_c.chipped = []);
6381
6454
  const holes = localHolesOf(p);
6382
6455
  for (const cell of out) {
6383
6456
  const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
6384
6457
  const center = toWorld(c, q, cell.center);
6385
6458
  const speed = clamp22(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
6386
- const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
6459
+ const tag = `c${chipped.length}_`;
6460
+ const fragments = cell.crater ? craterFragments(o, p, cell, dir, speed, mass, tag) : cellFragments(o, p, [cell], wp, dir, speed, mass, tag, true);
6387
6461
  p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
6388
6462
  chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
6389
6463
  p.st.mass = Math.max(0, p.st.mass - mass);
@@ -6400,6 +6474,44 @@ var GameFloorplanStructure = (() => {
6400
6474
  collapseCheck(o, src);
6401
6475
  }
6402
6476
  };
6477
+ const craterAcross = (energy) => clamp22(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
6478
+ const craterFragments = (o, p, cell, dir, speed, mass, tag) => {
6479
+ const { c, q } = partXf(o, p);
6480
+ const [u, v] = planeAxes(p.thin);
6481
+ const d = dir ?? rotateVec(q, [p.thin === 0 ? 1 : 0, p.thin === 1 ? 1 : 0, p.thin === 2 ? 1 : 0]);
6482
+ const exitSign = Math.sign(rotateVec(quat.conjugate(q), d)[p.thin]) || 1;
6483
+ const bit = Math.max(0.01, Math.min(cell.size[u], cell.size[v]) / 3);
6484
+ const n = clamp22(Math.round(cell.size[u] * cell.size[v] / (bit * bit * 3)), 4, 10) + Math.floor(rnd() * 3);
6485
+ const out = [];
6486
+ for (let i = 0; i < n; i++) {
6487
+ const back = i % 3 === 2;
6488
+ const lc = [cell.center[0], cell.center[1], cell.center[2]];
6489
+ lc[u] += (rnd() - 0.5) * cell.size[u] * 0.7;
6490
+ lc[v] += (rnd() - 0.5) * cell.size[v] * 0.7;
6491
+ lc[p.thin] += (back ? -exitSign : exitSign) * (p.thickness / 2 + 0.01);
6492
+ const size = [0, 0, 0];
6493
+ size[u] = bit * (0.6 + 0.8 * rnd());
6494
+ size[v] = bit * (0.5 + 0.7 * rnd());
6495
+ size[p.thin] = bit * (0.4 + 0.6 * rnd());
6496
+ const spread3 = [(rnd() - 0.5) * 0.9, (rnd() - 0.3) * 0.9, (rnd() - 0.5) * 0.9];
6497
+ const k = speed * (back ? 0.4 + 0.4 * rnd() : 0.7 + 0.8 * rnd());
6498
+ const vdir = vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3));
6499
+ out.push({
6500
+ id: `${p.spec.id}#${tag}${i}`,
6501
+ part: p.spec.id,
6502
+ material: p.spec.material,
6503
+ shape: "chunk",
6504
+ center: toWorld(c, q, lc),
6505
+ size,
6506
+ rotation: q,
6507
+ velocity: [vdir[0] * k, vdir[1] * k + 0.5 * rnd(), vdir[2] * k],
6508
+ angularVelocity: [(rnd() - 0.5) * 20, (rnd() - 0.5) * 20, (rnd() - 0.5) * 20],
6509
+ mass: mass / n,
6510
+ inner: true
6511
+ });
6512
+ }
6513
+ return out;
6514
+ };
6403
6515
  const exposeRebar = (o, p, src) => {
6404
6516
  const r = p.spec.reinforcement;
6405
6517
  if (!r) return;
@@ -6687,7 +6799,7 @@ var GameFloorplanStructure = (() => {
6687
6799
  });
6688
6800
  }
6689
6801
  const patch = [scale * 4, scale * 4, scale * 4];
6690
- emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
6802
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments, torn: true });
6691
6803
  };
6692
6804
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
6693
6805
  const mat = p.mat;
@@ -6746,12 +6858,13 @@ var GameFloorplanStructure = (() => {
6746
6858
  } else if (mat.ductility >= 0.5) {
6747
6859
  addDent(o, p, entry2, n, Math.max(caliber * 3 / 1e3, 0.012), clamp22(Math.min(Math.max(p.thickness * 3, 3e-3), ratio * 0.01), 1e-3, 0.05), src);
6748
6860
  } else if (brittle) {
6749
- addCrack(o, p, entry2, n, crackLen, src);
6861
+ if (!p.spec.units) addCrack(o, p, entry2, n, crackLen, src);
6750
6862
  if (mat.fracture !== "shatter") addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
6751
6863
  } else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
6752
6864
  debris(o, p, mat.hitDebris, entry2, n, clamp22(Math.round(E / 60) + 2, 2, 14), clamp22(Math.sqrt(E) / 6, 1, 8), src);
6753
- wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
6754
- const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
6865
+ wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
6866
+ const hollowTear = mat.fracture === "tear" && p.spec.fill !== void 0 && p.spec.fill < 0.5;
6867
+ const absorbed = penetrated && (mat.ductility >= 0.5 && mat.fracture !== "tear" || hollowTear) ? Math.min(E, thr) : E;
6755
6868
  const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
6756
6869
  if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
6757
6870
  chipUnits(o, p, "ballistic", entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
@@ -6836,10 +6949,25 @@ var GameFloorplanStructure = (() => {
6836
6949
  if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
6837
6950
  return reach;
6838
6951
  };
6952
+ const blastProbe = { partsTested: 0, partsNear: 0 };
6953
+ const blastCandidates = (o, point, radius) => {
6954
+ const local = toLocal(o.st.position, o.st.rotation, point);
6955
+ const near = [];
6956
+ for (const p of o.parts) {
6957
+ const reach = radius + p.bound;
6958
+ const dx = local[0] - p.pos[0];
6959
+ const dy = local[1] - p.pos[1];
6960
+ const dz = local[2] - p.pos[2];
6961
+ if (dx * dx + dy * dy + dz * dz < reach * reach) near.push(p);
6962
+ }
6963
+ blastProbe.partsTested += o.parts.length;
6964
+ blastProbe.partsNear += near.length;
6965
+ return near;
6966
+ };
6839
6967
  const blastObject = (o, point, energy, radius, src) => {
6840
6968
  const start = cur.length;
6841
6969
  const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
6842
- for (const p of o.parts) blastPart(o, p, point, energy, radius, src, res);
6970
+ for (const p of blastCandidates(o, point, radius)) blastPart(o, p, point, energy, radius, src, res);
6843
6971
  res.events = cur.slice(start);
6844
6972
  return res;
6845
6973
  };
@@ -6963,7 +7091,7 @@ var GameFloorplanStructure = (() => {
6963
7091
  let order = 0;
6964
7092
  for (const o of inRange) {
6965
7093
  if (only && only !== o) continue;
6966
- for (const p of o.parts) {
7094
+ for (const p of blastCandidates(o, point, radius)) {
6967
7095
  if (isLost(p)) continue;
6968
7096
  const dist = distToPart(o, p, point);
6969
7097
  if (dist < radius) items.push({ dist, order: order++, o, p });
@@ -7510,6 +7638,7 @@ var GameFloorplanStructure = (() => {
7510
7638
  const p = getPart(o, partId);
7511
7639
  p.pos = [position[0], position[1], position[2]];
7512
7640
  p.q = q;
7641
+ poseVersion++;
7513
7642
  updateBound(o);
7514
7643
  },
7515
7644
  remove(id) {
@@ -7528,6 +7657,7 @@ var GameFloorplanStructure = (() => {
7528
7657
  const o = getObj(id);
7529
7658
  o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
7530
7659
  applySoilPose(o);
7660
+ slideOffResting(o, rotation);
7531
7661
  },
7532
7662
  splitGroup(id, group, newId) {
7533
7663
  if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
@@ -7551,7 +7681,7 @@ var GameFloorplanStructure = (() => {
7551
7681
  if (o.spec.name !== void 0) s.name = o.spec.name;
7552
7682
  if (o.spec.tags) s.tags = [...o.spec.tags];
7553
7683
  if (o.spec.soil !== void 0) s.soil = o.spec.soil;
7554
- return clone(s);
7684
+ return s;
7555
7685
  };
7556
7686
  const inNew = (pid) => moved.has(pid);
7557
7687
  const inOld = (pid) => !moved.has(pid);
@@ -7578,7 +7708,12 @@ var GameFloorplanStructure = (() => {
7578
7708
  const p = getPart(o, partId);
7579
7709
  return whole(() => doHit(o, h, p));
7580
7710
  },
7711
+ blastProbe() {
7712
+ return { ...blastProbe };
7713
+ },
7581
7714
  explode(point, energy, radius, source, blast) {
7715
+ blastProbe.partsTested = 0;
7716
+ blastProbe.partsNear = 0;
7582
7717
  return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
7583
7718
  },
7584
7719
  raycast(origin, direction, maxDistance = Infinity) {
@@ -7588,7 +7723,8 @@ var GameFloorplanStructure = (() => {
7588
7723
  sweepBox(box2, direction, distance2, options2 = {}) {
7589
7724
  const d = vec.normalize(direction);
7590
7725
  const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
7591
- const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
7726
+ const hosted = options2.ignore === void 0 || ignored ? void 0 : impls.get(options2.ignore);
7727
+ const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : hosted ? new Set(hosted.impl.describe().parts.map((p) => `${hosted.impl.id}\0${p.id}`)) : void 0;
7592
7728
  let best = null;
7593
7729
  for (const origin of sweepOrigins(box2, d, options2.spacing ?? 0.2)) {
7594
7730
  const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);