@adatechnology/game-destruction 0.13.2 → 0.15.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
@@ -2796,6 +2796,7 @@ var createDestruction = (options = {}) => {
2796
2796
  const ignitionRules = resolveIgnitionRules(options.ignition);
2797
2797
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
2798
2798
  const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
2799
+ const chainLinkCap = options.chainLinksPerUpdate !== void 0 && options.chainLinksPerUpdate >= 1 ? Math.floor(options.chainLinksPerUpdate) : Infinity;
2799
2800
  const rng = createRng(options.seed ?? 1);
2800
2801
  const rnd = () => rng.next();
2801
2802
  let time = 0;
@@ -2818,7 +2819,13 @@ var createDestruction = (options = {}) => {
2818
2819
  const typed = listeners.get(e.type);
2819
2820
  if (typed) for (const fn of [...typed]) fn(e);
2820
2821
  };
2821
- const base = (o, source) => source === void 0 ? { object: o.id, time } : { object: o.id, time, source };
2822
+ let blow;
2823
+ const base = (o, source) => ({
2824
+ object: o.id,
2825
+ time,
2826
+ ...source === void 0 ? {} : { source },
2827
+ ...blow === void 0 ? {} : { blow }
2828
+ });
2822
2829
  const pendingCollapse = /* @__PURE__ */ new Map();
2823
2830
  let runDepth = 0;
2824
2831
  const drainBlasts = () => {
@@ -3743,7 +3750,7 @@ var createDestruction = (options = {}) => {
3743
3750
  if (disc < 0) return true;
3744
3751
  return -b - Math.sqrt(disc) > maxDist;
3745
3752
  };
3746
- const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false) => {
3753
+ const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false, precise = false) => {
3747
3754
  const d = vec.normalize(dir);
3748
3755
  let best = null;
3749
3756
  for (const o of objects.values()) {
@@ -3758,13 +3765,25 @@ var createDestruction = (options = {}) => {
3758
3765
  }
3759
3766
  }
3760
3767
  for (const im of worldOnly ? [] : impls.values()) {
3768
+ let boxHit = null;
3761
3769
  for (const ps of im.impl.describe().parts) {
3762
3770
  if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
3763
3771
  const ov = im.poses.get(ps.id);
3764
3772
  const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), origin, d, ov?.pos);
3765
- if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
3766
- if (!best || r.t0 < best.distance) best = { object: im.impl.id, part: ps.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
3773
+ if (!r || r.t1 < 0 || r.t0 > maxDist) continue;
3774
+ if (r.t0 < 0) {
3775
+ if (precise && im.impl.surfaceRay) boxHit = boxHit ?? { object: im.impl.id, part: ps.id, point: origin, normal: r.n, distance: 0, pathLen: r.t1 };
3776
+ continue;
3777
+ }
3778
+ if (!boxHit || r.t0 < boxHit.distance) boxHit = { object: im.impl.id, part: ps.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
3767
3779
  }
3780
+ if (!boxHit) continue;
3781
+ if (precise && im.impl.surfaceRay) {
3782
+ const far = best ? Math.min(maxDist, best.distance) : maxDist;
3783
+ const implId = im.impl.id;
3784
+ const s = im.impl.surfaceRay({ origin, direction: d, maxDistance: far, ...ignore ? { ignored: (part) => ignore.has(`${implId}\0${part}`) } : {} });
3785
+ if (s && (!best || s.distance < best.distance)) best = { object: implId, part: s.part, point: s.point, normal: s.normal, distance: s.distance, pathLen: s.pathLen };
3786
+ } else if (!best || boxHit.distance < best.distance) best = boxHit;
3768
3787
  }
3769
3788
  return best;
3770
3789
  };
@@ -4951,7 +4970,17 @@ var createDestruction = (options = {}) => {
4951
4970
  res.events = cur.slice(start);
4952
4971
  return res;
4953
4972
  };
4954
- const doHit = (o, hit, only) => {
4973
+ const withBlow = (type, fn) => () => {
4974
+ const outer = blow;
4975
+ blow = type;
4976
+ try {
4977
+ return fn();
4978
+ } finally {
4979
+ blow = outer;
4980
+ }
4981
+ };
4982
+ const doHit = (o, hit, only) => withBlow(hit.type, () => resolveHit(o, hit, only))();
4983
+ const resolveHit = (o, hit, only) => {
4955
4984
  let point = hit.point;
4956
4985
  let dir = hit.direction;
4957
4986
  if (hit.space === "local") {
@@ -5248,7 +5277,9 @@ var createDestruction = (options = {}) => {
5248
5277
  };
5249
5278
  const detonatePrimed = (until) => {
5250
5279
  const end = time;
5251
- for (let next = nextPrimed(until); next; next = nextPrimed(until)) {
5280
+ let fired = 0;
5281
+ for (let next = nextPrimed(until); next && fired < chainLinkCap; next = nextPrimed(until)) {
5282
+ fired++;
5252
5283
  primed.delete(next.p);
5253
5284
  time = Math.max(0, Math.min(end, next.p.rt.chainDue));
5254
5285
  next.p.rt.chainDue = void 0;
@@ -5318,6 +5349,7 @@ var createDestruction = (options = {}) => {
5318
5349
  updateBound(o);
5319
5350
  },
5320
5351
  remove(id) {
5352
+ impls.get(id)?.impl.dispose?.();
5321
5353
  objects.delete(id);
5322
5354
  impls.delete(id);
5323
5355
  },
@@ -5387,13 +5419,51 @@ var createDestruction = (options = {}) => {
5387
5419
  blastProbe() {
5388
5420
  return { ...blastProbe };
5389
5421
  },
5422
+ blastReach(point, target, options2 = {}) {
5423
+ const L = vec.distance(point, target);
5424
+ if (L < 1e-4) return 1;
5425
+ const shelter = { ...SHELTER_DEFAULTS, ...options2.shelter };
5426
+ const ignore = new Set(options2.ignore ?? []);
5427
+ const d = vec.scale(vec.sub(target, point), 1 / L);
5428
+ let t = 1;
5429
+ for (const o of objects.values()) {
5430
+ if (ignore.has(o.id) || raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
5431
+ for (const p of o.parts) {
5432
+ if (isLost(p) || p.st.stripped) continue;
5433
+ const r = rayPart(o, p, point, d);
5434
+ if (!r) continue;
5435
+ const t0 = Math.max(r.t0, 0);
5436
+ const path = Math.min(r.t1, L) - t0;
5437
+ if (path < shelter.minPath) continue;
5438
+ if (p.st.opening && insideOpening(o, p, toLocal(o.st.position, o.st.rotation, vec.madd(point, d, t0 + path / 2)))) continue;
5439
+ t *= Math.exp(-(p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1)) / shelter.k);
5440
+ if (t < 1e-4) return 0;
5441
+ }
5442
+ }
5443
+ for (const im of impls.values()) {
5444
+ if (ignore.has(im.impl.id)) continue;
5445
+ for (const ps of im.impl.describe().parts) {
5446
+ const mat = materials.get(ps.material);
5447
+ if (!mat) continue;
5448
+ const ov = im.poses.get(ps.id);
5449
+ const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), point, d, ov?.pos);
5450
+ if (!r || r.t1 < 0 || r.t0 > L) continue;
5451
+ let path = Math.min(r.t1, L) - Math.max(r.t0, 0);
5452
+ if (ps.thickness !== void 0 && ps.thickness > 0) path = Math.min(path, 2 * ps.thickness);
5453
+ else if (path < shelter.minPath) continue;
5454
+ t *= Math.exp(-(mat.hpPerKg * mat.density * path * (mat.damageScale?.shelter ?? 1)) / shelter.k);
5455
+ if (t < 1e-4) return 0;
5456
+ }
5457
+ }
5458
+ return t;
5459
+ },
5390
5460
  explode(point, energy, radius, source, blast) {
5391
5461
  blastProbe.partsTested = 0;
5392
5462
  blastProbe.partsNear = 0;
5393
5463
  return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
5394
5464
  },
5395
5465
  raycast(origin, direction, maxDistance = Infinity) {
5396
- const r = raycastInternal(origin, direction, maxDistance);
5466
+ const r = raycastInternal(origin, direction, maxDistance, void 0, false, true);
5397
5467
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
5398
5468
  },
5399
5469
  sweepBox(box, direction, distance, options2 = {}) {
@@ -5411,21 +5481,23 @@ var createDestruction = (options = {}) => {
5411
5481
  },
5412
5482
  shoot(origin, direction, energy, caliber, source, round) {
5413
5483
  const extras = typeof round === "number" ? { piercing: round } : round ?? {};
5414
- return withCollapses(run(() => {
5484
+ return withCollapses(run(withBlow("ballistic", () => {
5415
5485
  const results = [];
5416
5486
  const d = vec.normalize(direction);
5417
5487
  const ignore = /* @__PURE__ */ new Set();
5418
5488
  let org = origin;
5419
5489
  let E = energy;
5420
5490
  for (let i = 0; i < 4 && E > 0; i++) {
5421
- const rh = raycastInternal(org, d, Infinity, ignore);
5491
+ const rh = raycastInternal(org, d, Infinity, ignore, false, true);
5422
5492
  if (!rh) break;
5423
5493
  const im = impls.get(rh.object);
5424
5494
  if (im) {
5425
- const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
5495
+ const scaled = extras.penetration && E < energy ? { ...extras, penetration: Object.fromEntries(Object.entries(extras.penetration).map(([k, mm]) => [k, mm * (E / energy)])) } : extras;
5496
+ const r = implHit(im, { type: "ballistic", point: rh.point, part: rh.part, normal: rh.normal, direction: d, energy: E, caliber, ...scaled, ...source !== void 0 ? { source } : {} });
5426
5497
  r.part = rh.part;
5427
5498
  r.point = rh.point;
5428
- r.exitEnergy = r.penetrated ? E * 0.5 : 0;
5499
+ const left = r.events.find((e) => e.type === "hole" && e.energyLeft !== void 0);
5500
+ r.exitEnergy = r.penetrated ? left?.energyLeft ?? E * 0.5 : 0;
5429
5501
  results.push(r);
5430
5502
  ignore.add(`${im.impl.id}\0${rh.part}`);
5431
5503
  if (!r.penetrated) break;
@@ -5444,7 +5516,7 @@ var createDestruction = (options = {}) => {
5444
5516
  E = out.exitEnergy;
5445
5517
  }
5446
5518
  return results;
5447
- }));
5519
+ })));
5448
5520
  },
5449
5521
  disassemble(objectId, jointId, tool, dt) {
5450
5522
  const im = impls.get(objectId);
@@ -5683,6 +5755,9 @@ var createDestruction = (options = {}) => {
5683
5755
  return world;
5684
5756
  };
5685
5757
 
5758
+ // src/assemblies.ts
5759
+ import { applyPatches, mergeById, removeIds } from "@adatechnology/game-contracts";
5760
+
5686
5761
  // src/data/assemblies.json
5687
5762
  var assemblies_default = [
5688
5763
  {
@@ -5748,17 +5823,34 @@ var assemblies_default = [
5748
5823
  ];
5749
5824
 
5750
5825
  // src/assemblies.ts
5826
+ var SHIPPED = structuredClone(assemblies_default);
5751
5827
  var ASSEMBLIES = assemblies_default;
5752
- var registerAssembly = (def) => {
5753
- const at = ASSEMBLIES.findIndex((a) => a.id === def.id);
5754
- if (at >= 0) ASSEMBLIES[at] = def;
5755
- else ASSEMBLIES.push(def);
5828
+ var assembliesOver = (defs) => {
5829
+ const get = (id) => defs.find((a) => a.id === id);
5830
+ return {
5831
+ list: () => defs,
5832
+ get,
5833
+ register(def) {
5834
+ const at = defs.findIndex((a) => a.id === def.id);
5835
+ if (at >= 0) defs[at] = def;
5836
+ else defs.push(def);
5837
+ },
5838
+ part: (assemblyId, options) => buildAssemblyPart(get(assemblyId), assemblyId, options)
5839
+ };
5840
+ };
5841
+ var createAssemblies = (options = {}) => {
5842
+ const entries = mergeById(SHIPPED.map((def) => structuredClone(def)), (options.packs ?? []).flat());
5843
+ applyPatches(entries, options.patch);
5844
+ removeIds(entries, options.remove);
5845
+ return assembliesOver([...entries.values()]);
5756
5846
  };
5757
- var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
5847
+ var defaultSet;
5848
+ var defaultAssemblies = () => defaultSet ?? (defaultSet = assembliesOver(ASSEMBLIES));
5849
+ var registerAssembly = (def) => defaultAssemblies().register(def);
5850
+ var getAssembly = (id) => defaultAssemblies().get(id);
5758
5851
  var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
5759
5852
  var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
5760
- var assemblyPart = (assemblyId, options) => {
5761
- const def = getAssembly(assemblyId);
5853
+ var buildAssemblyPart = (def, assemblyId, options) => {
5762
5854
  if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
5763
5855
  const densityOf = (id) => {
5764
5856
  const m = options.materials?.get(id) ?? MATERIALS.find((x) => x.id === id);
@@ -5785,6 +5877,7 @@ var assemblyPart = (assemblyId, options) => {
5785
5877
  ...options.extra
5786
5878
  };
5787
5879
  };
5880
+ var assemblyPart = (assemblyId, options) => defaultAssemblies().part(assemblyId, options);
5788
5881
 
5789
5882
  // src/frame.ts
5790
5883
  var buildFrame = (o = {}) => {
@@ -6224,7 +6317,7 @@ var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
6224
6317
  import { checkDestructible as check } from "@adatechnology/game-contracts";
6225
6318
  import { IMPACT_CONTRACT, checkHit } from "@adatechnology/game-contracts";
6226
6319
  var checkDestructible = (impl, options = {}) => {
6227
- const contractOptions = { materials: [...MATERIALS.map((m) => m.id), ...options.materials ?? []] };
6320
+ const contractOptions = { materials: [...MATERIALS.map((m) => m.id), ...options.materials ?? []], ...options.cost === void 0 ? {} : { cost: options.cost } };
6228
6321
  return check(impl, contractOptions);
6229
6322
  };
6230
6323
  export {
@@ -6254,9 +6347,11 @@ export {
6254
6347
  checkDestructible,
6255
6348
  checkHit,
6256
6349
  crackingMoment,
6350
+ createAssemblies,
6257
6351
  createDestruction,
6258
6352
  damperAmplification,
6259
6353
  damperTuning,
6354
+ defaultAssemblies,
6260
6355
  elasticEnergy,
6261
6356
  explosivePart,
6262
6357
  exposureOf,