@adatechnology/game-contracts 0.13.0 → 0.14.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
@@ -1,9 +1,27 @@
1
+ // src/blast-shove.ts
2
+ var DEFAULT_BLAST_SHOVE = { coupling: 0.02, maxSpeed: 20, lift: 0.3, minSpeed: 0.3, falloffPower: 2 };
3
+ function blastShove(blast, target, options = {}) {
4
+ const o = { ...DEFAULT_BLAST_SHOVE, ...options };
5
+ const dx = target.position[0] - blast.point[0];
6
+ const dy = target.position[1] - blast.point[1];
7
+ const dz = target.position[2] - blast.point[2];
8
+ const distance = Math.hypot(dx, dy, dz);
9
+ if (!(blast.radius > 0) || distance > blast.radius) return void 0;
10
+ const falloff = (1 - distance / blast.radius) ** o.falloffPower;
11
+ const mass = Math.max(target.mass, 0.05);
12
+ const speed = Math.min(o.maxSpeed, Math.sqrt(2 * blast.energy * (blast.reach ?? 1) * o.coupling / mass)) * falloff;
13
+ if (!(speed >= o.minSpeed)) return void 0;
14
+ const direction = distance > 1e-4 ? [dx / distance, dy / distance + o.lift, dz / distance] : [0, 1, 0];
15
+ const length = Math.hypot(direction[0], direction[1], direction[2]);
16
+ return { speed, direction: [direction[0] / length, direction[1] / length, direction[2] / length], distance, falloff };
17
+ }
18
+
1
19
  // src/impact.ts
2
20
  var IMPACT_CONTRACT = {
3
21
  ballistic: { required: ["type", "point", "direction", "energy", "caliber"], optional: ["space", "source"], note: "bullets and pellets: joules and diameter in millimetres" },
4
22
  blunt: { required: ["type", "point", "energy"], optional: ["direction", "radius", "space", "source"], note: "contact radius in metres (default 3 cm)" },
5
23
  slash: { required: ["type", "point", "energy"], optional: ["direction", "radius", "space", "source"], note: "axes, knives, machetes" },
6
- explosive: { required: ["type", "point", "energy", "radius"], optional: ["space", "source"], note: "point is the centre of the blast, radius the range in metres" },
24
+ explosive: { required: ["type", "point", "energy", "radius"], optional: ["lethalRadius", "space", "source"], note: "point is the centre of the blast, radius the range in metres" },
7
25
  fire: { required: ["type", "point", "temperature", "radius"], optional: ["duration", "space", "source"], note: "flame temperature in \xB0C; duration in seconds (call every frame with `dt`)" },
8
26
  heat: { required: ["type", "point", "temperature", "radius"], optional: ["duration", "space", "source"], note: "heat without flame (cutting torch, hot plate)" }
9
27
  };
@@ -226,6 +244,19 @@ var checkSpec = (spec, options = {}) => {
226
244
  };
227
245
 
228
246
  // src/weapons.ts
247
+ var shotExtras = (shot, distance = 0) => {
248
+ const out = {};
249
+ if (shot.penetration) {
250
+ const k = shot.energy > 0 ? Math.min(1, Math.max(0, shot.energyAt(distance) / shot.energy)) : 1;
251
+ const scaled = {};
252
+ for (const key of Object.keys(shot.penetration)) scaled[key] = shot.penetration[key] * k;
253
+ out.penetration = scaled;
254
+ }
255
+ if (shot.incendiary) out.incendiary = true;
256
+ if (shot.tracer) out.tracer = true;
257
+ if (shot.ignition) out.ignition = { spark: shot.ignition.spark, flame: shot.ignition.flame };
258
+ return out;
259
+ };
229
260
  var shotToHit = (shot, point, normal) => {
230
261
  void normal;
231
262
  const dx = point[0] - shot.origin[0];
@@ -239,9 +270,79 @@ var shotToHit = (shot, point, normal) => {
239
270
  energy: shot.energyAt(distance),
240
271
  caliber: shot.caliber,
241
272
  ...shot.mass === void 0 ? {} : { mass: shot.mass },
273
+ ...shotExtras(shot, distance),
242
274
  source: shot.source ?? shot.weapon
243
275
  };
244
276
  };
277
+ var useToHit = (use, point, normal) => {
278
+ void normal;
279
+ const p = [point[0], point[1], point[2]];
280
+ switch (use.kind) {
281
+ case "melee":
282
+ return { type: use.damage, point: p, direction: [use.direction[0], use.direction[1], use.direction[2]], energy: use.energy, radius: use.radius };
283
+ case "flame":
284
+ case "heat":
285
+ return {
286
+ type: use.kind === "flame" ? "fire" : "heat",
287
+ point: p,
288
+ direction: [use.direction[0], use.direction[1], use.direction[2]],
289
+ temperature: use.temperature,
290
+ radius: Math.min(use.radius, 0.3),
291
+ duration: use.duration
292
+ };
293
+ case "throw":
294
+ if (use.energy > 0) return { type: "explosive", point: p, energy: use.energy, radius: use.radius };
295
+ if (use.fire) return { type: "fire", point: p, temperature: use.fire.temperature, radius: use.fire.radius, duration: use.fire.seconds };
296
+ return null;
297
+ default:
298
+ return null;
299
+ }
300
+ };
301
+
302
+ // src/ignition.ts
303
+ var DEFAULT_IGNITION_RULES = {
304
+ fillCurve: [
305
+ [0, 0.6],
306
+ [0.1, 1],
307
+ [0.35, 0.6],
308
+ [0.65, 0.3],
309
+ [0.9, 0.15],
310
+ [1, 0.12]
311
+ ],
312
+ flameFillInfluence: 0.5,
313
+ flameDefaults: { incendiary: 0.7, tracer: 0.2 },
314
+ sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
315
+ sparkMetalDefault: 0.5,
316
+ sparkFluid: { diesel: 0.15 },
317
+ fireNear: { radius: 1.5, chancePerSecond: 0.5 }
318
+ };
319
+ var resolveIgnitionRules = (rules) => ({
320
+ ...DEFAULT_IGNITION_RULES,
321
+ ...rules,
322
+ flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
323
+ sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
324
+ sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
325
+ fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
326
+ });
327
+ var fillFactor = (fill, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
328
+ if (curve.length === 0) return 1;
329
+ const f = Math.min(1, Math.max(0, fill));
330
+ if (f <= curve[0][0]) return curve[0][1];
331
+ for (let i = 1; i < curve.length; i++) {
332
+ const [x1, y1] = curve[i];
333
+ const [x0, y0] = curve[i - 1];
334
+ if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
335
+ }
336
+ return curve[curve.length - 1][1];
337
+ };
338
+ var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
339
+ var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
340
+ const k = fillFactor(target.fill, rules.fillCurve);
341
+ const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
342
+ const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
343
+ const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
344
+ return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
345
+ };
245
346
 
246
347
  // src/animation.ts
247
348
  var finite3 = (v) => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === "number" && Number.isFinite(n));
@@ -457,6 +558,9 @@ var createToolClipPlayer = (options) => {
457
558
  },
458
559
  get playing() {
459
560
  return instances.map((instance) => instance.name);
561
+ },
562
+ get running() {
563
+ return instances.filter((instance) => instance.delay <= 0).map((instance) => ({ name: instance.name, time: instance.time, clip: instance.clip }));
460
564
  }
461
565
  };
462
566
  };
@@ -570,15 +674,124 @@ function createEnvironmentBus(initial = {}) {
570
674
  }
571
675
  };
572
676
  }
677
+
678
+ // src/categories.ts
679
+ var defineCategories = (defs, options = {}) => {
680
+ const problems = [];
681
+ const duplicates = (list) => {
682
+ const ids = /* @__PURE__ */ new Set();
683
+ for (const def of list) {
684
+ if (ids.has(def.id)) problems.push(`duplicate category "${def.id}"`);
685
+ ids.add(def.id);
686
+ }
687
+ };
688
+ duplicates(defs);
689
+ duplicates(options.extra ?? []);
690
+ const entries = mergeById(defs, options.extra);
691
+ applyPatches(entries, options.patches);
692
+ removeIds(entries, options.remove);
693
+ const chain = (id) => {
694
+ const path = [];
695
+ let current = id;
696
+ while (current !== void 0) {
697
+ const def = entries.get(current);
698
+ if (!def) throw new Error(path.length === 0 ? `unknown category "${current}"` : `category "${path[0]}" has unknown parent "${current}"`);
699
+ if (path.includes(current)) throw new Error(`category cycle: ${[...path, current].reverse().join(" -> ")}`);
700
+ path.push(current);
701
+ current = def.extends;
702
+ }
703
+ return path.reverse();
704
+ };
705
+ const registry = {
706
+ get: (id) => entries.get(id),
707
+ has: (id) => entries.has(id),
708
+ ids: () => [...entries.keys()],
709
+ chain,
710
+ defaults: (id) => chain(id).reduce((acc, ancestor) => deepMerge(acc, entries.get(ancestor)?.defaults), {}),
711
+ requires: (id) => {
712
+ const out = [];
713
+ for (const ancestor of chain(id)) for (const contract of entries.get(ancestor)?.requires ?? []) if (!out.includes(contract)) out.push(contract);
714
+ return out;
715
+ },
716
+ isA: (id, ancestorId) => {
717
+ try {
718
+ return chain(id).includes(ancestorId);
719
+ } catch {
720
+ return false;
721
+ }
722
+ },
723
+ check: () => {
724
+ const found = [...problems];
725
+ for (const [id, def] of entries) {
726
+ if (def.extends !== void 0 && !entries.has(def.extends)) found.push(`category "${id}" extends unknown "${def.extends}"`);
727
+ else {
728
+ try {
729
+ chain(id);
730
+ } catch (error) {
731
+ const message = error.message;
732
+ if (message.startsWith("category cycle") && !found.includes(message)) found.push(message);
733
+ }
734
+ }
735
+ }
736
+ return found;
737
+ }
738
+ };
739
+ return registry;
740
+ };
741
+ var resolveEntry = (registry, entry, kindField = "kind") => deepMerge(registry.defaults(String(entry[kindField])), entry);
742
+ var checkCategorized = (entries, registry, checkers, options = {}) => {
743
+ const kindField = options.kindField ?? "kind";
744
+ const byId = new Map(checkers.map((checker) => [checker.id, checker]));
745
+ const problems = [...registry.check()];
746
+ const allowed = [];
747
+ for (const entry of entries) {
748
+ const kind = String(entry[kindField]);
749
+ if (!registry.has(kind)) {
750
+ problems.push(`"${entry.id}" has unknown kind "${kind}"`);
751
+ continue;
752
+ }
753
+ if (registry.get(kind)?.abstract) {
754
+ problems.push(`"${entry.id}" has abstract kind "${kind}"`);
755
+ continue;
756
+ }
757
+ let path;
758
+ try {
759
+ path = registry.chain(kind);
760
+ } catch (error) {
761
+ problems.push(`"${entry.id}" (${kind}): ${error.message}`);
762
+ continue;
763
+ }
764
+ const resolved = resolveEntry(registry, entry, kindField);
765
+ for (const contract of registry.requires(kind)) {
766
+ const owner = path.find((ancestor) => registry.get(ancestor)?.requires?.includes(contract)) ?? kind;
767
+ const checker = byId.get(contract);
768
+ if (!checker) {
769
+ problems.push(`kind "${kind}" requires contract "${contract}" but no checker is registered`);
770
+ continue;
771
+ }
772
+ const prefix = `"${entry.id}" (${kind}): `;
773
+ for (const problem of checker.check(resolved, { kind, chain: path })) {
774
+ const text = `${prefix}${problem}, required by ${owner}`;
775
+ if ((options.allow?.[entry.id] ?? []).some((start) => problem.startsWith(start))) allowed.push(text);
776
+ else problems.push(text);
777
+ }
778
+ }
779
+ }
780
+ return { ok: problems.length === 0, problems: [...new Set(problems)], allowed };
781
+ };
573
782
  export {
783
+ DEFAULT_BLAST_SHOVE,
574
784
  DEFAULT_ENVIRONMENT,
785
+ DEFAULT_IGNITION_RULES,
575
786
  IMPACT_CONTRACT,
576
787
  MISSING_BITS,
577
788
  STAGE_EVENT,
578
789
  TOOL_CLIP,
579
790
  applyPatches,
791
+ blastShove,
580
792
  checkAnimation,
581
793
  checkBench,
794
+ checkCategorized,
582
795
  checkDestructible,
583
796
  checkHit,
584
797
  checkSpec,
@@ -586,11 +799,19 @@ export {
586
799
  createEnvironmentBus,
587
800
  createToolClipPlayer,
588
801
  deepMerge,
802
+ defineCategories,
803
+ fillFactor,
804
+ ignitionChance,
589
805
  mergeById,
590
806
  removeIds,
807
+ resolveEntry,
808
+ resolveIgnitionRules,
591
809
  sampleClip,
592
810
  scaleClip,
811
+ shotExtras,
593
812
  shotToHit,
594
- toolAnimationStep
813
+ sparkTargetFactor,
814
+ toolAnimationStep,
815
+ useToHit
595
816
  };
596
817
  //# sourceMappingURL=index.js.map