@adatechnology/game-destruction 0.18.2 → 0.20.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.
@@ -3068,6 +3068,26 @@ var GameDestruction = (() => {
3068
3068
 
3069
3069
  // ../game-contracts/dist/index.js
3070
3070
  var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
3071
+ var NEAREST = 0.3;
3072
+ function sideOnImpulse(tnt, distance) {
3073
+ if (!(tnt > 0)) return 0;
3074
+ const w3 = Math.cbrt(tnt);
3075
+ const z = Math.max(distance, NEAREST) / w3;
3076
+ const scaled = 0.067 * Math.sqrt(1 + (z / 0.23) ** 4) / (z * z * Math.cbrt(1 + (z / 1.55) ** 3));
3077
+ return scaled * w3 * 100;
3078
+ }
3079
+ var ATMOSPHERE = 101325;
3080
+ function sideOnOverpressure(tnt, distance) {
3081
+ if (!(tnt > 0)) return 0;
3082
+ const z = Math.max(distance, NEAREST) / Math.cbrt(tnt);
3083
+ const ratio = 808 * (1 + (z / 4.5) ** 2) / (Math.sqrt(1 + (z / 0.048) ** 2) * Math.sqrt(1 + (z / 0.32) ** 2) * Math.sqrt(1 + (z / 1.35) ** 2));
3084
+ return ratio * ATMOSPHERE;
3085
+ }
3086
+ function bodyImpulse(tnt, distance) {
3087
+ if (!(tnt > 0)) return 0;
3088
+ const z = Math.max(distance, NEAREST) / Math.cbrt(tnt);
3089
+ return sideOnImpulse(tnt, distance) * (2 + 4 / (1 + z * z));
3090
+ }
3071
3091
  var DEFAULT_COST_READS = 1e4;
3072
3092
  var DEFAULT_COST_CEILING_MS = 50;
3073
3093
  var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
@@ -3389,9 +3409,15 @@ var GameDestruction = (() => {
3389
3409
  var BREACH_AREA = 0.25;
3390
3410
  var BREACH_BLAST_REACH = 0.12;
3391
3411
  var MEMBER_CUT_REACH = 0.5;
3412
+ var FAR_MEMBER_HP_FLOOR = 0.25;
3392
3413
  var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
3393
3414
  var BLAST_EDGE_RAGGED = 0.3;
3394
3415
  var BLAST_EDGE_LOOSE = 1.35;
3416
+ var PANEL_PRESSURE = 7e4;
3417
+ var PANEL_IMPULSE = 150;
3418
+ var PANEL_PI = 6e6;
3419
+ var PANEL_POINTS = [[0, 0], [-0.3, 0], [0.3, 0], [0, -0.3], [0, 0.3]];
3420
+ var PANEL_POINTS_LOADED = 4;
3395
3421
  var BLAST_EDGE_WEAR = 0.6;
3396
3422
  var RESIDUAL_CAPACITY = 0.15;
3397
3423
  var SEVER_BAND = 0.1;
@@ -3416,23 +3442,42 @@ var GameDestruction = (() => {
3416
3442
  const v = table[mat.penetrationClass ?? mat.id] ?? table[mat.id];
3417
3443
  return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
3418
3444
  };
3419
- var capacityScale = (p) => {
3445
+ var BURIED_BLAST_REACH = 0.5;
3446
+ var GIVE_AT_CUT_HP = 0.5;
3447
+ var capacityScale = (p, borne, through) => {
3420
3448
  const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
3421
3449
  if (p.st.stripped) return residual;
3422
- const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
3450
+ const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, sectionKept(p, residual, borne, through));
3423
3451
  return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
3424
3452
  };
3425
- var sectionLoss = (p) => {
3453
+ var sectionKept = (p, residual, borne, through) => {
3454
+ let kept = 1;
3455
+ forEachSection(p, borne, (c, along, gone) => {
3456
+ const share = through?.(c.center, along) ?? 1;
3457
+ if (share <= 0) return;
3458
+ kept = Math.min(kept, Math.max(p.spec.reinforcement ? residual : 0, 1 - gone) / share);
3459
+ });
3460
+ return kept;
3461
+ };
3462
+ var sectionLoss = (p, borne) => {
3463
+ let worst = 0;
3464
+ forEachSection(p, borne, (_c, _along, gone) => {
3465
+ worst = Math.max(worst, gone);
3466
+ });
3467
+ return Math.min(1, worst);
3468
+ };
3469
+ var forEachSection = (p, borne, visit) => {
3426
3470
  const cells = p.st.chipped;
3427
- if (!cells?.length) return 0;
3471
+ if (!cells?.length) return;
3428
3472
  const [u, v] = planeAxes(p.thin);
3429
3473
  const role = p.spec.structure?.role;
3430
3474
  const up = rotateVec(p.q, [0, 0, 0].map((_, k) => k === v ? 1 : 0));
3431
3475
  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;
3432
3476
  const along = (role === "wall" || role === "partition" || role === "column") && upright >= 0 ? upright : p.dims[u] >= p.dims[v] ? u : v;
3433
3477
  const across = along === u ? v : u;
3434
- let worst = 0;
3478
+ const lying = along !== upright || upright < 0;
3435
3479
  for (const c of cells) {
3480
+ if (lying && borne?.(c.center, along)) continue;
3436
3481
  const spans = [];
3437
3482
  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]);
3438
3483
  spans.sort((a, b) => a[0] - b[0]);
@@ -3442,9 +3487,8 @@ var GameDestruction = (() => {
3442
3487
  gone += b - Math.max(a, end);
3443
3488
  end = b;
3444
3489
  }
3445
- worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
3490
+ visit(c, along, Math.min(1, gone / Math.max(p.dims[across], 1e-6)));
3446
3491
  }
3447
- return Math.min(1, worst);
3448
3492
  };
3449
3493
  var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
3450
3494
  var clone = (v) => structuredClone(v);
@@ -3755,6 +3799,26 @@ var GameDestruction = (() => {
3755
3799
  byTouching.set(support, { version: poseVersion, box });
3756
3800
  return box;
3757
3801
  };
3802
+ const throughOf = (p) => {
3803
+ const load = p.st.load ?? 0;
3804
+ if (load <= 0) return void 0;
3805
+ const above = Math.max(0, load - p.st.mass);
3806
+ const up = rotateVec(p.q, [0, 1, 0]);
3807
+ return (local, along) => {
3808
+ const axis = [0, 0, 0];
3809
+ axis[along] = 1;
3810
+ const sign = Math.sign(rotateVec(p.q, axis)[1]);
3811
+ if (Math.abs(up[1]) < 0.7 && Math.abs(rotateVec(p.q, axis)[1]) < 0.7) return 1;
3812
+ const half = p.dims[along] / 2;
3813
+ const share = clamp2((half - sign * local[along]) / Math.max(p.dims[along], 1e-6), 0, 1);
3814
+ return (above + p.st.mass * share) / load;
3815
+ };
3816
+ };
3817
+ const borneOn = (p, supports) => {
3818
+ if (supports.length === 0) return void 0;
3819
+ const boxes = supports.map((support) => contactBox(p, support));
3820
+ return (local, along) => boxes.some(([lo, hi]) => local[along] >= lo[along] - SUPPORT_TOLERANCE && local[along] <= hi[along] + SUPPORT_TOLERANCE);
3821
+ };
3758
3822
  const measureContact = (p, support) => {
3759
3823
  const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
3760
3824
  const lo = [0, 0, 0], hi = [0, 0, 0];
@@ -3962,7 +4026,7 @@ var GameDestruction = (() => {
3962
4026
  o.unsup[i] = o.anyAnchor && !isLost(p) && !p.orphan;
3963
4027
  continue;
3964
4028
  }
3965
- p.st.stress = p.st.load * structureSafety / (p.cap * capacityScale(p));
4029
+ p.st.stress = p.st.load * structureSafety / (p.cap * capacityScale(p, borneOn(p, (lowerOf.get(i) ?? []).map((k) => parts[k])), throughOf(p)));
3966
4030
  if (p.bend) {
3967
4031
  p.bendLoad = p.st.mass + bending[i];
3968
4032
  const bent = restFlexure(o, p, lowerOf.get(i) ?? []);
@@ -4947,7 +5011,7 @@ var GameDestruction = (() => {
4947
5011
  return BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3) * clamp2(Math.cbrt(BLAST_REFERENCE_RESISTANCE / resistance), 0.3, 2);
4948
5012
  };
4949
5013
  const blastUnits = (o, p, wp, energy, dist, src) => {
4950
- var _a, _b;
5014
+ var _a;
4951
5015
  if (!p.staged || p.st.stripped || isLost(p) || chipEnergy <= 0) return false;
4952
5016
  const reach = blastReachOf(p, energy);
4953
5017
  if (dist >= reach) return false;
@@ -4977,6 +5041,62 @@ var GameDestruction = (() => {
4977
5041
  }
4978
5042
  }
4979
5043
  if (out.length === 0) return false;
5044
+ return knockOutUnits(o, p, wp, energy, out, src);
5045
+ };
5046
+ const blastPanel = (o, p, point, energy, src) => {
5047
+ var _a;
5048
+ if (!p.staged || p.st.stripped || isLost(p) || !p.spec.units || p.spec.units.mortar === p.spec.material || p.spec.reinforcement) return false;
5049
+ const [u, v] = planeAxes(p.thin);
5050
+ const lp = toLocal(p.pos, p.q, objLocal(o, point));
5051
+ const half = p.dims[u] / 2;
5052
+ const posts = [];
5053
+ for (const m of o.parts) {
5054
+ if (m === p || !m.spec.reinforcement || isLost(m) || m.spec.shape.kind !== "box") continue;
5055
+ const mp = toLocal(p.pos, p.q, m.pos);
5056
+ if (Math.abs(mp[p.thin]) > p.dims[p.thin] + 0.15 || Math.abs(mp[u]) > half + 0.15 || m.dims[v] < p.dims[v] * 0.5) continue;
5057
+ posts.push(mp[u]);
5058
+ }
5059
+ let from = -half, to = half;
5060
+ for (const x of posts) {
5061
+ if (x <= lp[u] && x > from) from = x;
5062
+ if (x >= lp[u] && x < to) to = x;
5063
+ }
5064
+ if (to - from < 0.2) return false;
5065
+ const tnt = energy * DEFAULT_BLAST_IMPULSE.tntPerJoule;
5066
+ const centre = objLocal(o, point);
5067
+ const span = to - from, height = p.dims[v];
5068
+ let loaded = 0;
5069
+ for (const [fu, fv] of PANEL_POINTS) {
5070
+ const at = [0, 0, 0];
5071
+ at[u] = (from + to) / 2 + fu * span;
5072
+ at[v] = fv * height;
5073
+ at[p.thin] = Math.sign(lp[p.thin]) * p.dims[p.thin] / 2;
5074
+ const r = vec.distance(toWorld(p.pos, p.q, at), centre);
5075
+ const side = sideOnOverpressure(tnt, r);
5076
+ const reflected = 2 * side + 6 * side * side / (side + 7 * 101325);
5077
+ const impulse = bodyImpulse(tnt, r);
5078
+ if (reflected > PANEL_PRESSURE && impulse > PANEL_IMPULSE && (reflected - PANEL_PRESSURE) * (impulse - PANEL_IMPULSE) >= PANEL_PI) loaded++;
5079
+ }
5080
+ if (loaded < PANEL_POINTS_LOADED) return false;
5081
+ const grid = gridOf(p);
5082
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
5083
+ const out = [];
5084
+ for (let i = 0; i < grid.length; i++) {
5085
+ const cell = grid[i];
5086
+ if (worn[i] === -1) continue;
5087
+ if (cell.center[u] - cell.size[u] / 2 >= from - 1e-6 && cell.center[u] + cell.size[u] / 2 <= to + 1e-6) out.push(i);
5088
+ }
5089
+ if (out.length === 0) return false;
5090
+ for (const i of out) worn[i] = -1;
5091
+ return knockOutUnits(o, p, point, energy, out, src);
5092
+ };
5093
+ const knockOutUnits = (o, p, wp, energy, out, src) => {
5094
+ var _a, _b;
5095
+ const [u, v] = planeAxes(p.thin);
5096
+ const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
5097
+ const grid = gridOf(p);
5098
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
5099
+ for (const i of out) worn[i] = -1;
4980
5100
  const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !(op && p.st.opening && Math.hypot(grid[i].center[u] - op[u], grid[i].center[v] - op[v]) <= p.st.opening.radius));
4981
5101
  for (const i of loose) worn[i] = -1;
4982
5102
  const blown = out.map((i) => grid[i]), fallen = loose.map((i) => grid[i]);
@@ -5052,6 +5172,27 @@ var GameDestruction = (() => {
5052
5172
  debris(o, p, p.mat.breakDebris, center, [0, -1, 0], 6, 1, src);
5053
5173
  return loose.length;
5054
5174
  };
5175
+ const giveAtCut = (o, p, src) => {
5176
+ if (!p.spec.reinforcement || !p.spec.units || !p.st.chipped?.length || sectionLoss(p) <= 0) return false;
5177
+ const opened = [];
5178
+ for (const j of o.byPart.get(p.spec.id) ?? []) {
5179
+ if (j.st.open) continue;
5180
+ const onIt = j.bears === "a_on_b" && j.spec.b === p.spec.id || j.bears === "b_on_a" && j.spec.a === p.spec.id;
5181
+ if (!onIt) continue;
5182
+ j.st.open = true;
5183
+ j.st.openedBy = "break";
5184
+ opened.push(j);
5185
+ }
5186
+ if (opened.length === 0) return false;
5187
+ p.st.hp = Math.max(p.st.hp, p.st.maxHp * GIVE_AT_CUT_HP);
5188
+ markDamaged(p);
5189
+ solveStructure(o);
5190
+ recomputeGroups(o);
5191
+ detachEvents(o, opened, "break", src);
5192
+ exposeRebar(o, p, src);
5193
+ collapseCheck(o, src);
5194
+ return true;
5195
+ };
5055
5196
  const stripPart = (o, p, reinforcement, src) => {
5056
5197
  const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
5057
5198
  p.st.stripped = true;
@@ -5109,6 +5250,7 @@ var GameDestruction = (() => {
5109
5250
  hingeDrop(o, p, src);
5110
5251
  return;
5111
5252
  }
5253
+ if (!force && giveAtCut(o, p, src)) return;
5112
5254
  const ex = p.spec.explodes;
5113
5255
  const style = force ?? (p.snap ? "snap" : p.mat.fracture === "none" ? "snap" : p.mat.fracture);
5114
5256
  const { c, q } = partXf(o, p);
@@ -5437,12 +5579,15 @@ var GameDestruction = (() => {
5437
5579
  return reach;
5438
5580
  }
5439
5581
  if (Ein > 20 && p.layers.length > 0) spallOuter(o, p, wp, faceOf(o, p, wn), wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), src);
5440
- const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
5582
+ const breachReach = BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3);
5583
+ const farMember = Boolean(p.spec.reinforcement && p.spec.units) && dist > MEMBER_CUT_REACH * breachReach;
5584
+ const dealt = farMember ? applyDamage(o, p, Math.min(amount, Math.max(0, p.st.hp - p.st.maxHp * FAR_MEMBER_HP_FLOOR)), "explosive", null, null, Ein * 0.3, 40, src) : applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
5441
5585
  res.damage += dealt;
5442
5586
  if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
5443
- const breachReach = BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3);
5444
- if (p.spec.units && p.spec.units.mortar !== p.spec.material) blastUnits(o, p, wp, energy, dist, src);
5445
- else if (p.spec.reinforcement && p.spec.units && dist > MEMBER_CUT_REACH * breachReach) blastUnits(o, p, wp, energy, dist, src);
5587
+ if (p.spec.units && p.spec.units.mortar !== p.spec.material) {
5588
+ blastPanel(o, p, point, energy, src);
5589
+ if (!isLost(p)) blastUnits(o, p, wp, energy, dist, src);
5590
+ } else if (farMember) blastUnits(o, p, wp, energy, dist, src);
5446
5591
  else if (dist <= breachReach) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
5447
5592
  return reach;
5448
5593
  };
@@ -5455,7 +5600,9 @@ var GameDestruction = (() => {
5455
5600
  const dx = local[0] - p.pos[0];
5456
5601
  const dy = local[1] - p.pos[1];
5457
5602
  const dz = local[2] - p.pos[2];
5458
- if (dx * dx + dy * dy + dz * dz < reach * reach) near.push(p);
5603
+ if (dx * dx + dy * dy + dz * dz >= reach * reach) continue;
5604
+ if (p.spec.anchored && p.spec.structure?.role === "foundation" && distToPart(o, p, point) > BURIED_BLAST_REACH) continue;
5605
+ near.push(p);
5459
5606
  }
5460
5607
  blastProbe.partsTested += o.parts.length;
5461
5608
  blastProbe.partsNear += near.length;