@adatechnology/game-destruction-three 0.12.0 → 0.12.1

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.
@@ -1357,11 +1357,231 @@ var GameDestructionThree = (() => {
1357
1357
  glow.outIntensity = glow.i * Math.pow(1 - age, glow.curve) * flicker;
1358
1358
  }
1359
1359
  }
1360
+ var LEHMER_MULTIPLIER = 16807;
1361
+ var LEHMER_MODULUS = 2147483647;
1362
+ var HASH_MIX = 2654435761;
1363
+ function normalizeLehmerSeed(seed) {
1364
+ const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
1365
+ return whole === 0 ? 1 : whole;
1366
+ }
1367
+ function lehmer(seed) {
1368
+ let state = normalizeLehmerSeed(seed);
1369
+ const next = () => {
1370
+ state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
1371
+ return (state - 1) / (LEHMER_MODULUS - 1);
1372
+ };
1373
+ return {
1374
+ next,
1375
+ between: (min2, max2) => min2 + (max2 - min2) * next(),
1376
+ direction(out) {
1377
+ const up = next() * 2 - 1;
1378
+ const angle = next() * Math.PI * 2;
1379
+ const ring2 = Math.sqrt(1 - up * up);
1380
+ out.x = ring2 * Math.cos(angle);
1381
+ out.y = up;
1382
+ out.z = ring2 * Math.sin(angle);
1383
+ return out;
1384
+ },
1385
+ get state() {
1386
+ return state;
1387
+ }
1388
+ };
1389
+ }
1390
+ function mixEventSeed(eventSeed) {
1391
+ let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
1392
+ mixed ^= mixed >>> 13;
1393
+ mixed = Math.imul(mixed, 3266489909);
1394
+ mixed ^= mixed >>> 16;
1395
+ return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
1396
+ }
1397
+ function streamFor(eventSeed) {
1398
+ return lehmer(mixEventSeed(eventSeed));
1399
+ }
1400
+ var SKIN = 5e-3;
1401
+ var MAX_PAD = 0.45;
1402
+ function rotation(q) {
1403
+ const [x, y, z, w] = q ?? [0, 0, 0, 1];
1404
+ const n = Math.hypot(x, y, z, w) || 1;
1405
+ const qx = x / n;
1406
+ const qy = y / n;
1407
+ const qz = z / n;
1408
+ const qw = w / n;
1409
+ return new Float64Array([
1410
+ 1 - 2 * (qy * qy + qz * qz),
1411
+ 2 * (qx * qy - qz * qw),
1412
+ 2 * (qx * qz + qy * qw),
1413
+ 2 * (qx * qy + qz * qw),
1414
+ 1 - 2 * (qx * qx + qz * qz),
1415
+ 2 * (qy * qz - qx * qw),
1416
+ 2 * (qx * qz - qy * qw),
1417
+ 2 * (qy * qz + qx * qw),
1418
+ 1 - 2 * (qx * qx + qy * qy)
1419
+ ]);
1420
+ }
1421
+ var Enclosures = class {
1422
+ constructor(seed = 20973) {
1423
+ __publicField2(this, "spaces", /* @__PURE__ */ new Map());
1424
+ __publicField2(this, "list", []);
1425
+ __publicField2(this, "random");
1426
+ __publicField2(this, "out", { x: 0, y: 0, z: 0, nx: 0, ny: 0, nz: 0 });
1427
+ __publicField2(this, "l0", new Float64Array(3));
1428
+ __publicField2(this, "l1", new Float64Array(3));
1429
+ __publicField2(this, "q", new Float64Array(3));
1430
+ this.random = lehmer(seed);
1431
+ }
1432
+ get empty() {
1433
+ return this.list.length === 0;
1434
+ }
1435
+ get size() {
1436
+ return this.list.length;
1437
+ }
1438
+ /** Declares, moves or (with `null`) ends the space `id`. */
1439
+ set(id, enclosure) {
1440
+ if (!enclosure) {
1441
+ if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
1442
+ return;
1443
+ }
1444
+ const [hx, hy, hz] = enclosure.halfSize;
1445
+ const space = {
1446
+ cx: enclosure.center[0],
1447
+ cy: enclosure.center[1],
1448
+ cz: enclosure.center[2],
1449
+ hx: Math.abs(hx),
1450
+ hy: Math.abs(hy),
1451
+ hz: Math.abs(hz),
1452
+ r: rotation(enclosure.orientation),
1453
+ radius: Math.hypot(hx, hy, hz),
1454
+ openings: enclosure.openings ?? [],
1455
+ leak: Math.max(0, enclosure.leak ?? 0)
1456
+ };
1457
+ const old = this.spaces.get(id);
1458
+ this.spaces.set(id, space);
1459
+ const at3 = old ? this.list.indexOf(old) : -1;
1460
+ if (at3 >= 0) this.list[at3] = space;
1461
+ else this.list.push(space);
1462
+ }
1463
+ get(id) {
1464
+ const s = this.spaces.get(id);
1465
+ if (!s) return void 0;
1466
+ return { center: [s.cx, s.cy, s.cz], halfSize: [s.hx, s.hy, s.hz], openings: s.openings, leak: s.leak };
1467
+ }
1468
+ ids() {
1469
+ return [...this.spaces.keys()];
1470
+ }
1471
+ clear() {
1472
+ this.spaces.clear();
1473
+ this.list = [];
1474
+ }
1475
+ /** Whether a point is inside any space (on the inside of its walls). */
1476
+ inside(x, y, z) {
1477
+ for (const s of this.list) {
1478
+ this.toLocal(s, x, y, z, this.l0);
1479
+ if (Math.abs(this.l0[0]) <= s.hx && Math.abs(this.l0[1]) <= s.hy && Math.abs(this.l0[2]) <= s.hz) return true;
1480
+ }
1481
+ return false;
1482
+ }
1483
+ toLocal(s, x, y, z, out) {
1484
+ const dx = x - s.cx;
1485
+ const dy = y - s.cy;
1486
+ const dz = z - s.cz;
1487
+ const r = s.r;
1488
+ out[0] = r[0] * dx + r[3] * dy + r[6] * dz;
1489
+ out[1] = r[1] * dx + r[4] * dy + r[7] * dz;
1490
+ out[2] = r[2] * dx + r[5] * dy + r[8] * dz;
1491
+ }
1492
+ /** Passes at `rate` per second. */
1493
+ chance(rate, dt) {
1494
+ return rate > 0 && this.random.next() < 1 - Math.exp(-rate * dt);
1495
+ }
1496
+ /** The opening the wall point `q` (local) is in lets it through now. */
1497
+ through(s, q, dt) {
1498
+ for (const o of s.openings) {
1499
+ if (Math.abs(q[0] - o.center[0]) > o.halfSize[0] || Math.abs(q[1] - o.center[1]) > o.halfSize[1] || Math.abs(q[2] - o.center[2]) > o.halfSize[2]) continue;
1500
+ if (o.rate === void 0 || this.chance(o.rate, dt)) return true;
1501
+ }
1502
+ return this.chance(s.leak, dt);
1503
+ }
1504
+ confine(x0, y0, z0, x1, y1, z1, pad, dt) {
1505
+ const list = this.list;
1506
+ if (list.length === 0) return false;
1507
+ const p = Math.min(Math.max(0, pad), MAX_PAD);
1508
+ const l0 = this.l0;
1509
+ const l1 = this.l1;
1510
+ const q = this.q;
1511
+ for (const s of list) {
1512
+ const reach = s.radius + p + 0.05;
1513
+ const dx = x1 - s.cx;
1514
+ const dy = y1 - s.cy;
1515
+ const dz = z1 - s.cz;
1516
+ if (dx * dx + dy * dy + dz * dz > reach * reach) {
1517
+ const ex = x0 - s.cx;
1518
+ const ey = y0 - s.cy;
1519
+ const ez = z0 - s.cz;
1520
+ if (ex * ex + ey * ey + ez * ez > reach * reach) continue;
1521
+ }
1522
+ this.toLocal(s, x0, y0, z0, l0);
1523
+ this.toLocal(s, x1, y1, z1, l1);
1524
+ const h = [s.hx, s.hy, s.hz];
1525
+ const in0 = Math.abs(l0[0]) <= s.hx && Math.abs(l0[1]) <= s.hy && Math.abs(l0[2]) <= s.hz;
1526
+ let axis2 = 0;
1527
+ let sign = 1;
1528
+ if (in0) {
1529
+ if (Math.abs(l1[0]) <= s.hx && Math.abs(l1[1]) <= s.hy && Math.abs(l1[2]) <= s.hz) continue;
1530
+ for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
1531
+ if (this.through(s, q, dt)) continue;
1532
+ let worst = -Infinity;
1533
+ for (let a = 0; a < 3; a++) {
1534
+ const over = Math.abs(l1[a]) - h[a];
1535
+ if (over > worst) {
1536
+ worst = over;
1537
+ axis2 = a;
1538
+ }
1539
+ }
1540
+ sign = l1[axis2] < 0 ? -1 : 1;
1541
+ for (let a = 0; a < 3; a++) l1[a] = Math.min(h[a] - SKIN, Math.max(-(h[a] - SKIN), l1[a]));
1542
+ sign = -sign;
1543
+ } else {
1544
+ const padded0 = Math.abs(l0[0]) <= s.hx + p && Math.abs(l0[1]) <= s.hy + p && Math.abs(l0[2]) <= s.hz + p;
1545
+ const bound = padded0 ? 0 : p;
1546
+ if (!(Math.abs(l1[0]) <= s.hx + bound && Math.abs(l1[1]) <= s.hy + bound && Math.abs(l1[2]) <= s.hz + bound)) continue;
1547
+ for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
1548
+ if (this.through(s, q, dt)) continue;
1549
+ let worst = -Infinity;
1550
+ for (let a = 0; a < 3; a++) {
1551
+ const over = Math.abs(l0[a]) - (h[a] + bound);
1552
+ if (over > worst) {
1553
+ worst = over;
1554
+ axis2 = a;
1555
+ }
1556
+ }
1557
+ sign = l0[axis2] < 0 ? -1 : 1;
1558
+ l1[axis2] = sign * (h[axis2] + bound + SKIN);
1559
+ }
1560
+ const r = s.r;
1561
+ const out = this.out;
1562
+ out.x = s.cx + r[0] * l1[0] + r[1] * l1[1] + r[2] * l1[2];
1563
+ out.y = s.cy + r[3] * l1[0] + r[4] * l1[1] + r[5] * l1[2];
1564
+ out.z = s.cz + r[6] * l1[0] + r[7] * l1[1] + r[8] * l1[2];
1565
+ out.nx = r[axis2] * sign;
1566
+ out.ny = r[3 + axis2] * sign;
1567
+ out.nz = r[6 + axis2] * sign;
1568
+ return true;
1569
+ }
1570
+ return false;
1571
+ }
1572
+ };
1573
+ function slideAlong(out, vx2, vy2, vz2) {
1574
+ const into = vx2 * out.nx + vy2 * out.ny + vz2 * out.nz;
1575
+ if (into >= 0) return [vx2, vy2, vz2];
1576
+ return [vx2 - into * out.nx, vy2 - into * out.ny, vz2 - into * out.nz];
1577
+ }
1360
1578
  var FLOOR_FRACTION = 0.4;
1361
1579
  var FLOOR_BOUNCE = -0.2;
1362
1580
  var ARRIVAL_FRAME = 30;
1363
1581
  var ARRIVAL_BASE = 0.3;
1364
1582
  var GROWTH_CAP_SECONDS = 3.5;
1583
+ var CARRIED_KINDS = [true, true, false, true, false];
1584
+ var PAD_OF_SIZE = 0.35;
1365
1585
  function createPuffPool(capacity) {
1366
1586
  const placeholder = { next: () => 0, between: (min2) => min2, direction: (out) => out, state: 0 };
1367
1587
  return new OrderedPool(capacity, () => ({
@@ -1456,13 +1676,24 @@ var GameDestructionThree = (() => {
1456
1676
  if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
1457
1677
  }
1458
1678
  }
1459
- function updatePuffs(pool, dt, wind) {
1679
+ function updatePuffs(pool, dt, wind, confiner) {
1460
1680
  pool.compact(ageOrKeep2, dt);
1681
+ const confining = confiner !== void 0 && !confiner.empty;
1461
1682
  for (const puff3 of pool.items) {
1462
1683
  const age = puff3.t / puff3.life;
1463
1684
  const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
1685
+ const x0 = puff3.x;
1686
+ const y0 = puff3.y;
1687
+ const z0 = puff3.z;
1464
1688
  if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
1465
1689
  else moveFree(puff3, dt, age, heat, wind);
1690
+ if (confining && CARRIED_KINDS[puff3.kind] && confiner.confine(x0, y0, z0, puff3.x, puff3.y, puff3.z, puff3.size * PAD_OF_SIZE, dt)) {
1691
+ const out = confiner.out;
1692
+ puff3.x = out.x;
1693
+ puff3.y = out.y;
1694
+ puff3.z = out.z;
1695
+ [puff3.vx, puff3.vy, puff3.vz] = slideAlong(out, puff3.vx, puff3.vy, puff3.vz);
1696
+ }
1466
1697
  const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
1467
1698
  puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
1468
1699
  puff3.age = age;
@@ -1988,10 +2219,10 @@ var GameDestructionThree = (() => {
1988
2219
  ])
1989
2220
  ]
1990
2221
  };
1991
- function localToWorld(translation, rotation, local2) {
2222
+ function localToWorld(translation, rotation2, local2) {
1992
2223
  const [x, y, z] = local2;
1993
- const [a, c, e] = [Math.cos(rotation[0]), Math.cos(rotation[1]), Math.cos(rotation[2])];
1994
- const [b, d, f] = [Math.sin(rotation[0]), Math.sin(rotation[1]), Math.sin(rotation[2])];
2224
+ const [a, c, e] = [Math.cos(rotation2[0]), Math.cos(rotation2[1]), Math.cos(rotation2[2])];
2225
+ const [b, d, f] = [Math.sin(rotation2[0]), Math.sin(rotation2[1]), Math.sin(rotation2[2])];
1995
2226
  const [ae, af, be, bf] = [a * e, a * f, b * e, b * f];
1996
2227
  return [
1997
2228
  c * e * x + -c * f * y + d * z + translation[0],
@@ -3654,46 +3885,6 @@ var GameDestructionThree = (() => {
3654
3885
  var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, RAIN_AND_PUDDLES, FOG_AND_ASH, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, HEADLIGHTS_IN_RAIN, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
3655
3886
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
3656
3887
  var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
3657
- var LEHMER_MULTIPLIER = 16807;
3658
- var LEHMER_MODULUS = 2147483647;
3659
- var HASH_MIX = 2654435761;
3660
- function normalizeLehmerSeed(seed) {
3661
- const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
3662
- return whole === 0 ? 1 : whole;
3663
- }
3664
- function lehmer(seed) {
3665
- let state = normalizeLehmerSeed(seed);
3666
- const next = () => {
3667
- state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
3668
- return (state - 1) / (LEHMER_MODULUS - 1);
3669
- };
3670
- return {
3671
- next,
3672
- between: (min2, max2) => min2 + (max2 - min2) * next(),
3673
- direction(out) {
3674
- const up = next() * 2 - 1;
3675
- const angle = next() * Math.PI * 2;
3676
- const ring2 = Math.sqrt(1 - up * up);
3677
- out.x = ring2 * Math.cos(angle);
3678
- out.y = up;
3679
- out.z = ring2 * Math.sin(angle);
3680
- return out;
3681
- },
3682
- get state() {
3683
- return state;
3684
- }
3685
- };
3686
- }
3687
- function mixEventSeed(eventSeed) {
3688
- let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
3689
- mixed ^= mixed >>> 13;
3690
- mixed = Math.imul(mixed, 3266489909);
3691
- mixed ^= mixed >>> 16;
3692
- return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
3693
- }
3694
- function streamFor(eventSeed) {
3695
- return lehmer(mixEventSeed(eventSeed));
3696
- }
3697
3888
  var TABLE = {
3698
3889
  concrete: { dust: 9078138, chip: 7236195, spark: 2 },
3699
3890
  metal: { dust: 0, chip: 0, spark: 18 },
@@ -4997,8 +5188,8 @@ var GameDestructionThree = (() => {
4997
5188
  function addScorch(store, random, x, z, radius) {
4998
5189
  const variant = Math.floor(random.next() * SCORCH_VARIANTS);
4999
5190
  const lift = 0.03 + nextLift(store) * 1e-3;
5000
- const rotation = random.next() * 6;
5001
- store.add({ kind: "scorch", x, z, radius, variant, lift, rotation });
5191
+ const rotation2 = random.next() * 6;
5192
+ store.add({ kind: "scorch", x, z, radius, variant, lift, rotation: rotation2 });
5002
5193
  }
5003
5194
  function addRimRocks(store, ground, random, x, z, radius) {
5004
5195
  const count = Math.round(6 + radius * 7);
@@ -5027,8 +5218,8 @@ var GameDestructionThree = (() => {
5027
5218
  function addCrater(store, ground, random, spec) {
5028
5219
  ground?.dent(random, spec.x, spec.z, spec.radius, spec.depth);
5029
5220
  if (!spec.marks) return;
5030
- const rotation = random.next() * 6;
5031
- store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation });
5221
+ const rotation2 = random.next() * 6;
5222
+ store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation: rotation2 });
5032
5223
  addRimRocks(store, ground, random, spec.x, spec.z, spec.radius);
5033
5224
  }
5034
5225
  function addBloodSplat(store, ground, random, spec) {
@@ -5584,6 +5775,7 @@ var GameDestructionThree = (() => {
5584
5775
  state.gas = Math.max(0, state.gas - dt / DECAY.gasSeconds);
5585
5776
  state.beat *= Math.exp(-DECAY.beat * dt);
5586
5777
  }
5778
+ var PAD_OF_SIZE2 = 0.35;
5587
5779
  var clamp4 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
5588
5780
  var lerp4 = (a, b, t) => a + (b - a) * t;
5589
5781
  function rgb(hex) {
@@ -5743,9 +5935,20 @@ var GameDestructionThree = (() => {
5743
5935
  vy2 *= k;
5744
5936
  vz2 *= k;
5745
5937
  }
5746
- const x = this.position[i3] + vx2 * dt;
5938
+ let x = this.position[i3] + vx2 * dt;
5747
5939
  let y = this.position[i3 + 1] + vy2 * dt;
5748
- const z = this.position[i3 + 2] + vz2 * dt;
5940
+ let z = this.position[i3 + 2] + vz2 * dt;
5941
+ const confiner = host.confiner;
5942
+ if (def.carried && confiner && !confiner.empty) {
5943
+ const pad = this.size0[i] * lerp4(1, def.grow, t) * PAD_OF_SIZE2;
5944
+ if (confiner.confine(this.position[i3], this.position[i3 + 1], this.position[i3 + 2], x, y, z, pad, dt)) {
5945
+ const out = confiner.out;
5946
+ x = out.x;
5947
+ y = out.y;
5948
+ z = out.z;
5949
+ [vx2, vy2, vz2] = slideAlong(out, vx2, vy2, vz2);
5950
+ }
5951
+ }
5749
5952
  if (def.ground !== "none") {
5750
5953
  const own = this.floor[i];
5751
5954
  const gy = Number.isNaN(own) ? host.groundAt(x, z) + 5e-3 : own;
@@ -5831,7 +6034,8 @@ var GameDestructionThree = (() => {
5831
6034
  alpha: 0.35,
5832
6035
  fadeIn: 0.15,
5833
6036
  cone: 0.6,
5834
- ground: "none"
6037
+ ground: "none",
6038
+ carried: true
5835
6039
  },
5836
6040
  sparks: {
5837
6041
  id: "sparks",
@@ -5875,7 +6079,8 @@ var GameDestructionThree = (() => {
5875
6079
  alpha: 0.4,
5876
6080
  fadeIn: 0.1,
5877
6081
  cone: 0.4,
5878
- ground: "none"
6082
+ ground: "none",
6083
+ carried: true
5879
6084
  },
5880
6085
  shards: {
5881
6086
  id: "shards",
@@ -6033,7 +6238,8 @@ var GameDestructionThree = (() => {
6033
6238
  fadeIn: 0.1,
6034
6239
  cone: 0.5,
6035
6240
  ground: "stop",
6036
- flutter: 0.6
6241
+ flutter: 0.6,
6242
+ carried: true
6037
6243
  },
6038
6244
  steam: {
6039
6245
  id: "steam",
@@ -6055,7 +6261,8 @@ var GameDestructionThree = (() => {
6055
6261
  alpha: 0.35,
6056
6262
  fadeIn: 0.15,
6057
6263
  cone: 0.6,
6058
- ground: "none"
6264
+ ground: "none",
6265
+ carried: true
6059
6266
  },
6060
6267
  flash: {
6061
6268
  id: "flash",
@@ -6188,7 +6395,8 @@ var GameDestructionThree = (() => {
6188
6395
  const host = {
6189
6396
  groundAt: this.options.groundAt,
6190
6397
  gravity: this.options.gravity,
6191
- land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag)
6398
+ land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag),
6399
+ ...this.options.confiner ? { confiner: this.options.confiner } : {}
6192
6400
  };
6193
6401
  if (this.additive.live > 0 || this.additive.drawCount > 0) this.additive.update(dt, host);
6194
6402
  if (this.normal.live > 0 || this.normal.drawCount > 0) this.normal.update(dt, host);
@@ -6394,8 +6602,8 @@ var GameDestructionThree = (() => {
6394
6602
  * Takes away the marks inside a box: world `center`, `half` extents along the axes of the world `rotation` (a quaternion, x y z w; none means the
6395
6603
  * world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
6396
6604
  */
6397
- clearInside(center, half, rotation = [0, 0, 0, 1]) {
6398
- const qx = -rotation[0], qy = -rotation[1], qz = -rotation[2], qw = rotation[3];
6605
+ clearInside(center, half, rotation2 = [0, 0, 0, 1]) {
6606
+ const qx = -rotation2[0], qy = -rotation2[1], qz = -rotation2[2], qw = rotation2[3];
6399
6607
  let gone = 0;
6400
6608
  for (const decal2 of this.decals) {
6401
6609
  if (!decal2.on) continue;
@@ -6516,10 +6724,14 @@ var GameDestructionThree = (() => {
6516
6724
  var DEFAULT_FADE_IN = 0.1;
6517
6725
  var FLOATS_PER_VECTOR = 3;
6518
6726
  var BYTE_MAX2 = 255;
6727
+ var CARRIED_GRAVITY = 3;
6728
+ var PAD_OF_SIZE3 = 0.35;
6519
6729
  var SpritePool = class {
6520
- constructor(capacity, context) {
6730
+ /** `carried`: the normal-blend pool (smoke, dust, steam), whose light sprites go round the closed spaces; the additive one (flames, sparks) does not. */
6731
+ constructor(capacity, context, carried = false) {
6521
6732
  __publicField2(this, "capacity", capacity);
6522
6733
  __publicField2(this, "context", context);
6734
+ __publicField2(this, "carried", carried);
6523
6735
  __publicField2(this, "count", 0);
6524
6736
  __publicField2(this, "position");
6525
6737
  __publicField2(this, "velocity");
@@ -6619,6 +6831,7 @@ var GameDestructionThree = (() => {
6619
6831
  }
6620
6832
  update(dt, leaveDecal) {
6621
6833
  const { position, velocity } = this;
6834
+ const confiner = this.context.confiner;
6622
6835
  for (let index = this.count - 1; index >= 0; index--) {
6623
6836
  this.life[index] -= dt;
6624
6837
  if (this.life[index] <= 0) {
@@ -6632,9 +6845,25 @@ var GameDestructionThree = (() => {
6632
6845
  velocity[offset] *= factor;
6633
6846
  velocity[offset + 1] *= factor;
6634
6847
  velocity[offset + 2] *= factor;
6848
+ const x0 = position[offset];
6849
+ const y0 = position[offset + 1];
6850
+ const z0 = position[offset + 2];
6635
6851
  position[offset] += velocity[offset] * dt;
6636
6852
  position[offset + 1] += velocity[offset + 1] * dt;
6637
6853
  position[offset + 2] += velocity[offset + 2] * dt;
6854
+ if (this.carried && confiner && !confiner.empty && Math.abs(this.gravity[index]) < CARRIED_GRAVITY) {
6855
+ const size = this.startSize[index] * (1 + (this.growth[index] - 1) * age);
6856
+ if (confiner.confine(x0, y0, z0, position[offset], position[offset + 1], position[offset + 2], size * PAD_OF_SIZE3, dt)) {
6857
+ const out = confiner.out;
6858
+ position[offset] = out.x;
6859
+ position[offset + 1] = out.y;
6860
+ position[offset + 2] = out.z;
6861
+ const [vx2, vy2, vz2] = slideAlong(out, velocity[offset], velocity[offset + 1], velocity[offset + 2]);
6862
+ velocity[offset] = vx2;
6863
+ velocity[offset + 1] = vy2;
6864
+ velocity[offset + 2] = vz2;
6865
+ }
6866
+ }
6638
6867
  const mode = this.groundMode[index];
6639
6868
  if (mode) {
6640
6869
  const groundHeight = this.context.groundAt(position[offset], position[offset + 2]);
@@ -6911,9 +7140,9 @@ var GameDestructionThree = (() => {
6911
7140
  __publicField2(this, "placement");
6912
7141
  __publicField2(this, "accumulators", /* @__PURE__ */ new Map());
6913
7142
  const profile = options.profile ?? PROFILES.high;
6914
- this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace };
7143
+ this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace, ...options.confiner ? { confiner: options.confiner } : {} };
6915
7144
  this.add = new SpritePool(profile.sprites.add, this.context);
6916
- this.normal = new SpritePool(profile.sprites.normal, this.context);
7145
+ this.normal = new SpritePool(profile.sprites.normal, this.context, true);
6917
7146
  this.streaks = new StreakPool(profile.streaks, this.context);
6918
7147
  this.debris = new DebrisPool(this.context);
6919
7148
  this.decals = new DecalPool(this.context);
@@ -7101,12 +7330,14 @@ var GameDestructionThree = (() => {
7101
7330
  const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
7102
7331
  const handles = { next: 0, events: 0 };
7103
7332
  const low = options.profile === "low";
7333
+ const enclosures = new Enclosures(20973 + (options.seed ?? 0));
7104
7334
  const spriteSystem = new SpriteKindSystem({
7105
7335
  capacity: options.sprites?.capacity ?? (low ? 1250 : 2500),
7106
7336
  density: options.sprites?.density ?? (low ? 0.6 : 1),
7107
7337
  seed: 2654435761 + (options.seed ?? 0),
7108
7338
  groundAt,
7109
7339
  gravity: options.gravity ?? DEFAULT_GRAVITY,
7340
+ confiner: enclosures,
7110
7341
  ...options.sprites?.kinds ? { kinds: options.sprites.kinds } : {}
7111
7342
  });
7112
7343
  const core = {
@@ -7156,7 +7387,7 @@ var GameDestructionThree = (() => {
7156
7387
  return [camera2.x, camera2.y, camera2.z];
7157
7388
  }, forward: (local2) => [local2[0] ?? 0, local2[1] ?? 0, local2[2] ?? 0] };
7158
7389
  function workshop() {
7159
- return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt }));
7390
+ return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt, confiner: enclosures }));
7160
7391
  }
7161
7392
  function effectPlayer() {
7162
7393
  return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
@@ -7215,6 +7446,10 @@ var GameDestructionThree = (() => {
7215
7446
  puff3.vz += dz * strength;
7216
7447
  }
7217
7448
  },
7449
+ enclose(id, enclosure) {
7450
+ enclosures.set(id, enclosure);
7451
+ },
7452
+ enclosures: () => enclosures,
7218
7453
  stop(handle) {
7219
7454
  player?.stop(handle);
7220
7455
  const fires = core.fires;
@@ -7242,7 +7477,7 @@ var GameDestructionThree = (() => {
7242
7477
  core.clock.time += dt;
7243
7478
  core.schedule.update(core.clock.time);
7244
7479
  updateRings(core.rings, dt);
7245
- updatePuffs(core.puffs, dt, wind);
7480
+ updatePuffs(core.puffs, dt, wind, enclosures);
7246
7481
  updateSparks(core.sparks, { dt, groundAt });
7247
7482
  updateGlows(core.glows, dt);
7248
7483
  updateShocks(core.shocks, core.puffs.items, dt);
@@ -8642,6 +8877,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8642
8877
  });
8643
8878
  },
8644
8879
  stop: (handle) => world.stop(handle),
8880
+ // a module's closed space (a shut car's cabin): the world's smoke, dust and steam go round it
8881
+ enclose: (id, enclosure) => world.enclose(id, enclosure),
8645
8882
  configure(settings) {
8646
8883
  const band = settings.lod_band;
8647
8884
  if (typeof band === "string" && band in LOD_BANDS) world.forceLod(LOD_BANDS[band] ?? null);
@@ -11922,8 +12159,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
11922
12159
  * Takes away the decals of a mesh that sit inside a box (world `center`, `half` extents along the axes of the world `rotation`): the surface they
11923
12160
  * marked is gone (a unit came out of the wall with its bullet holes and spalls).
11924
12161
  */
11925
- clearDecals(mv, center, half, rotation) {
11926
- const inv = rotation.clone().invert();
12162
+ clearDecals(mv, center, half, rotation2) {
12163
+ const inv = rotation2.clone().invert();
11927
12164
  const p = new this.THREE.Vector3();
11928
12165
  for (let i = mv.decals.length - 1; i >= 0; i--) {
11929
12166
  const d = mv.decals[i];
@@ -13364,8 +13601,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13364
13601
  const T = this.THREE;
13365
13602
  const center = new T.Vector3(ev.center[0], ev.center[1], ev.center[2]);
13366
13603
  const half = new T.Vector3(ev.size[0] / 2 + CHIP_DECAL_EDGE, ev.size[1] / 2 + CHIP_DECAL_EDGE, ev.size[2] / 2 + CHIP_DECAL_EDGE);
13367
- const rotation = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
13368
- for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation);
13604
+ const rotation2 = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
13605
+ for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation2);
13369
13606
  this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
13370
13607
  this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
13371
13608
  this.sound(impactKey(def), ev.center, 0.7);