@adatechnology/game-destruction 0.6.1 → 0.7.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/README.md +57 -2
- package/dist/index.d.ts +44 -8
- package/dist/index.global.js +379 -64
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +379 -64
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.global.js
CHANGED
|
@@ -22,11 +22,16 @@ var GameDestruction = (() => {
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
ASSEMBLIES: () => ASSEMBLIES,
|
|
25
|
+
CONFINEMENT_DEFAULTS: () => CONFINEMENT_DEFAULTS,
|
|
25
26
|
FIRE_PRIME_C: () => FIRE_PRIME_C,
|
|
27
|
+
FRAGMENT_HIT_DEFAULTS: () => FRAGMENT_HIT_DEFAULTS,
|
|
26
28
|
IMPACT_CONTRACT: () => IMPACT_CONTRACT,
|
|
27
29
|
JOINT_BY_MATERIALS: () => JOINT_BY_MATERIALS,
|
|
28
30
|
JOINT_DEFAULTS: () => JOINT_DEFAULTS,
|
|
29
31
|
MATERIALS: () => MATERIALS,
|
|
32
|
+
PIERCING_BY_CALIBER: () => PIERCING_BY_CALIBER,
|
|
33
|
+
PIERCING_BY_KIND: () => PIERCING_BY_KIND,
|
|
34
|
+
SHELTER_DEFAULTS: () => SHELTER_DEFAULTS,
|
|
30
35
|
assemblyDepth: () => assemblyDepth,
|
|
31
36
|
assemblyPart: () => assemblyPart,
|
|
32
37
|
buildFrame: () => buildFrame,
|
|
@@ -43,6 +48,7 @@ var GameDestruction = (() => {
|
|
|
43
48
|
partMass: () => partMass,
|
|
44
49
|
partThickness: () => partThickness,
|
|
45
50
|
partVolume: () => partVolume,
|
|
51
|
+
piercingOf: () => piercingOf,
|
|
46
52
|
quat: () => quat,
|
|
47
53
|
quatFromEuler: () => quatFromEuler,
|
|
48
54
|
registerAssembly: () => registerAssembly,
|
|
@@ -892,7 +898,8 @@ var GameDestruction = (() => {
|
|
|
892
898
|
0.25
|
|
893
899
|
],
|
|
894
900
|
damageScale: {
|
|
895
|
-
blunt: 1.5
|
|
901
|
+
blunt: 1.5,
|
|
902
|
+
shelter: 3
|
|
896
903
|
},
|
|
897
904
|
hitDebris: "shards",
|
|
898
905
|
breakDebris: "shards",
|
|
@@ -943,7 +950,10 @@ var GameDestruction = (() => {
|
|
|
943
950
|
"glass",
|
|
944
951
|
"brittle"
|
|
945
952
|
],
|
|
946
|
-
compressive: 2e5
|
|
953
|
+
compressive: 2e5,
|
|
954
|
+
damageScale: {
|
|
955
|
+
shelter: 3
|
|
956
|
+
}
|
|
947
957
|
},
|
|
948
958
|
{
|
|
949
959
|
id: "ceramic",
|
|
@@ -1629,10 +1639,14 @@ var GameDestruction = (() => {
|
|
|
1629
1639
|
// src/world.ts
|
|
1630
1640
|
var DMG_PER_J = 0.02;
|
|
1631
1641
|
var BLAST_COUPLING = 0.1;
|
|
1642
|
+
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
1643
|
+
var FRAGMENT_HIT_DEFAULTS = { max: 24, energyFactor: 0.5, hitType: "blunt", range: 6, flightTime: 0.4, spread: 10, aim: 40 };
|
|
1644
|
+
var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 };
|
|
1632
1645
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1633
1646
|
var BREACH_RADIUS = 0.55;
|
|
1634
1647
|
var BREACH_BLAST_FACTOR = 6;
|
|
1635
1648
|
var BREACH_AREA = 0.25;
|
|
1649
|
+
var BREACH_BLAST_REACH = Infinity;
|
|
1636
1650
|
var RESIDUAL_CAPACITY = 0.15;
|
|
1637
1651
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1638
1652
|
var BREACH_GRID_CAP = 4096;
|
|
@@ -1667,6 +1681,7 @@ var GameDestruction = (() => {
|
|
|
1667
1681
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1668
1682
|
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
1669
1683
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1684
|
+
const breachBlastReach = options.breach?.blastReach ?? BREACH_BLAST_REACH;
|
|
1670
1685
|
const rng = createRng(options.seed ?? 1);
|
|
1671
1686
|
const rnd = () => rng.next();
|
|
1672
1687
|
let time = 0;
|
|
@@ -2022,7 +2037,7 @@ var GameDestruction = (() => {
|
|
|
2022
2037
|
if (disc < 0) return true;
|
|
2023
2038
|
return -b - Math.sqrt(disc) > maxDist;
|
|
2024
2039
|
};
|
|
2025
|
-
const raycastInternal = (origin, dir, maxDist, ignore) => {
|
|
2040
|
+
const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false) => {
|
|
2026
2041
|
const d = vec.normalize(dir);
|
|
2027
2042
|
let best = null;
|
|
2028
2043
|
for (const o of objects.values()) {
|
|
@@ -2035,7 +2050,7 @@ var GameDestruction = (() => {
|
|
|
2035
2050
|
if (!best || r.t0 < best.distance) best = { object: o.id, part: p.spec.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
|
|
2036
2051
|
}
|
|
2037
2052
|
}
|
|
2038
|
-
for (const im of impls.values()) {
|
|
2053
|
+
for (const im of worldOnly ? [] : impls.values()) {
|
|
2039
2054
|
for (const ps of im.impl.describe().parts) {
|
|
2040
2055
|
if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
|
|
2041
2056
|
const ov = im.poses.get(ps.id);
|
|
@@ -2448,7 +2463,7 @@ var GameDestruction = (() => {
|
|
|
2448
2463
|
else if (chained && (trigger === "explosion" || 1 - p.st.hp / p.st.maxHp >= shotDamageFractionOf(p.spec))) prime(o, p, trigger, src);
|
|
2449
2464
|
return done;
|
|
2450
2465
|
};
|
|
2451
|
-
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2466
|
+
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, piercing = 1) => {
|
|
2452
2467
|
const mat = p.mat;
|
|
2453
2468
|
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2454
2469
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
@@ -2461,7 +2476,9 @@ var GameDestruction = (() => {
|
|
|
2461
2476
|
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2462
2477
|
const coats = p.layers.length > 0 ? layersEnergy(p.layers, p.rt.spalls, face, objLocal(o, entry)) + layersEnergy(p.layers, p.rt.spalls, -face, exitLocal) : 0;
|
|
2463
2478
|
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2464
|
-
const
|
|
2479
|
+
const hard = mat.tags?.includes("metal") || mat.tags?.includes("masonry");
|
|
2480
|
+
const Ep = piercing === 1 || !hard ? E : E * piercing;
|
|
2481
|
+
const penetrated = Ep > thr;
|
|
2465
2482
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2466
2483
|
const id = p.spec.id;
|
|
2467
2484
|
if (!penetrated && mat.ricochetDeg && angle < mat.ricochetDeg) {
|
|
@@ -2473,7 +2490,7 @@ var GameDestruction = (() => {
|
|
|
2473
2490
|
}
|
|
2474
2491
|
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2475
2492
|
const brittle = mat.ductility < 0.3;
|
|
2476
|
-
const ratio =
|
|
2493
|
+
const ratio = Ep / Math.max(thr, 1e-6);
|
|
2477
2494
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2478
2495
|
let exitPoint;
|
|
2479
2496
|
if (penetrated) {
|
|
@@ -2503,7 +2520,7 @@ var GameDestruction = (() => {
|
|
|
2503
2520
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
2504
2521
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
2505
2522
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2506
|
-
return { penetrated, exitEnergy: penetrated ? E - thr : 0, exitPoint, damage: dmg };
|
|
2523
|
+
return { penetrated, exitEnergy: penetrated ? E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
2507
2524
|
};
|
|
2508
2525
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
2509
2526
|
const mat = p.mat;
|
|
@@ -2531,54 +2548,110 @@ var GameDestruction = (() => {
|
|
|
2531
2548
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2532
2549
|
return done;
|
|
2533
2550
|
};
|
|
2551
|
+
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
2552
|
+
if (isLost(p)) return null;
|
|
2553
|
+
const { c, q } = partXf(o, p);
|
|
2554
|
+
const lp = toLocal(c, q, point);
|
|
2555
|
+
const dist = distanceToShape(p.spec.shape, lp);
|
|
2556
|
+
if (dist >= radius) return null;
|
|
2557
|
+
const sp = surfacePoint(p.spec.shape, lp);
|
|
2558
|
+
const wp = toWorld(c, q, sp.point);
|
|
2559
|
+
const reach = reachAt ? reachAt(wp, p) : 1;
|
|
2560
|
+
const f = (1 - dist / radius) ** 2 * reach;
|
|
2561
|
+
const Ein = energy * f * BLAST_COUPLING;
|
|
2562
|
+
const wn = rotateVec(q, sp.normal);
|
|
2563
|
+
if (!res.part) {
|
|
2564
|
+
res.part = p.spec.id;
|
|
2565
|
+
res.point = wp;
|
|
2566
|
+
}
|
|
2567
|
+
const mat = p.mat;
|
|
2568
|
+
if (Ein > 20) {
|
|
2569
|
+
if (mat.ductility >= 0.5) addDent(o, p, wp, wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), clamp2(4e-3 + Ein * 2e-5, 4e-3, 0.08), src);
|
|
2570
|
+
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2571
|
+
debris(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2572
|
+
}
|
|
2573
|
+
const th = mat.thermal;
|
|
2574
|
+
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2575
|
+
p.st.temperature += 300 * f;
|
|
2576
|
+
o.active = true;
|
|
2577
|
+
if (f >= 0.25 && !p.st.burning) {
|
|
2578
|
+
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2579
|
+
checkIgnite(o, p, src);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2583
|
+
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
2584
|
+
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
2585
|
+
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
2586
|
+
return reach;
|
|
2587
|
+
}
|
|
2588
|
+
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);
|
|
2589
|
+
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2590
|
+
res.damage += dealt;
|
|
2591
|
+
if (dist <= breachBlastReach * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2592
|
+
return reach;
|
|
2593
|
+
};
|
|
2534
2594
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2535
2595
|
const start = cur.length;
|
|
2536
2596
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
2537
|
-
for (const p of o.parts)
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
const
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
if (!res.part) {
|
|
2549
|
-
res.part = p.spec.id;
|
|
2550
|
-
res.point = wp;
|
|
2551
|
-
}
|
|
2552
|
-
const mat = p.mat;
|
|
2553
|
-
if (Ein > 20) {
|
|
2554
|
-
if (mat.ductility >= 0.5) addDent(o, p, wp, wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), clamp2(4e-3 + Ein * 2e-5, 4e-3, 0.08), src);
|
|
2555
|
-
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2556
|
-
debris(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2557
|
-
}
|
|
2558
|
-
const th = mat.thermal;
|
|
2559
|
-
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2560
|
-
p.st.temperature += 300 * f;
|
|
2561
|
-
o.active = true;
|
|
2562
|
-
if (f >= 0.25 && !p.st.burning) {
|
|
2563
|
-
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2564
|
-
checkIgnite(o, p, src);
|
|
2565
|
-
}
|
|
2597
|
+
for (const p of o.parts) blastPart(o, p, point, energy, radius, src, res);
|
|
2598
|
+
res.events = cur.slice(start);
|
|
2599
|
+
return res;
|
|
2600
|
+
};
|
|
2601
|
+
const blastCfg = (call) => {
|
|
2602
|
+
const pick = (key) => call && call[key] !== void 0 ? call[key] : options.blast?.[key];
|
|
2603
|
+
const merge = (v, defaults) => {
|
|
2604
|
+
if (!v) return null;
|
|
2605
|
+
const out = { ...defaults };
|
|
2606
|
+
if (v !== true) {
|
|
2607
|
+
for (const [key, value] of Object.entries(v)) if (value !== void 0) out[key] = value;
|
|
2566
2608
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2609
|
+
return out;
|
|
2610
|
+
};
|
|
2611
|
+
const conf = pick("confinement");
|
|
2612
|
+
return {
|
|
2613
|
+
shelter: merge(pick("shelter"), SHELTER_DEFAULTS),
|
|
2614
|
+
frag: merge(pick("fragmentHits"), FRAGMENT_HIT_DEFAULTS),
|
|
2615
|
+
conf: typeof conf === "number" ? Number.isFinite(conf) ? Math.max(0, conf) : 1 : merge(conf, CONFINEMENT_DEFAULTS)
|
|
2616
|
+
};
|
|
2617
|
+
};
|
|
2618
|
+
const confinementRays = /* @__PURE__ */ new Map();
|
|
2619
|
+
const raysOf = (n) => {
|
|
2620
|
+
let rays = confinementRays.get(n);
|
|
2621
|
+
if (rays) return rays;
|
|
2622
|
+
const r = createRng((options.seed ?? 1) ^ 1540483477);
|
|
2623
|
+
const off = r.next();
|
|
2624
|
+
const phi0 = r.next() * Math.PI * 2;
|
|
2625
|
+
rays = [];
|
|
2626
|
+
for (let i = 0; i < n; i++) {
|
|
2627
|
+
const y = 1 - 2 * (i + off) / n;
|
|
2628
|
+
const s = Math.sqrt(Math.max(0, 1 - y * y));
|
|
2629
|
+
const phi = phi0 + i * 2.399963229728653;
|
|
2630
|
+
rays.push([Math.cos(phi) * s, y, Math.sin(phi) * s]);
|
|
2631
|
+
}
|
|
2632
|
+
confinementRays.set(n, rays);
|
|
2633
|
+
return rays;
|
|
2634
|
+
};
|
|
2635
|
+
const confinementOf = (conf, point, radius) => {
|
|
2636
|
+
if (conf === null) return 1;
|
|
2637
|
+
if (typeof conf === "number") return conf;
|
|
2638
|
+
let hits = 0;
|
|
2639
|
+
let sum = 0;
|
|
2640
|
+
for (const d of raysOf(Math.max(1, Math.round(conf.rays)))) {
|
|
2641
|
+
const h = raycastInternal(point, d, radius * conf.reach, void 0, true);
|
|
2642
|
+
if (h) {
|
|
2643
|
+
hits++;
|
|
2644
|
+
sum += h.distance;
|
|
2572
2645
|
}
|
|
2573
|
-
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);
|
|
2574
|
-
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2575
|
-
res.damage += dealt;
|
|
2576
|
-
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2577
2646
|
}
|
|
2578
|
-
|
|
2579
|
-
|
|
2647
|
+
if (hits === 0) return 1;
|
|
2648
|
+
const closed = hits / Math.max(1, Math.round(conf.rays));
|
|
2649
|
+
const ratio = clamp2(radius / Math.max(sum / hits, 1e-3), 0, 2);
|
|
2650
|
+
return Math.min(Math.max(1, conf.max), 1 + conf.gain * closed ** conf.power * ratio);
|
|
2580
2651
|
};
|
|
2581
|
-
const
|
|
2652
|
+
const FLAG_LOST = 1;
|
|
2653
|
+
const FLAG_OPEN = 2;
|
|
2654
|
+
const blastPlain = (point, energy, radius, src) => {
|
|
2582
2655
|
const out = [];
|
|
2583
2656
|
for (const o of [...objects.values()]) {
|
|
2584
2657
|
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
@@ -2586,16 +2659,225 @@ var GameDestruction = (() => {
|
|
|
2586
2659
|
if (r.part) out.push(r);
|
|
2587
2660
|
}
|
|
2588
2661
|
for (const im of [...impls.values()]) {
|
|
2589
|
-
|
|
2590
|
-
const ov = im.poses.get(ps.id);
|
|
2591
|
-
const c = toWorld(im.position, im.rotation, ov?.pos ?? ps.position);
|
|
2592
|
-
const q = quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]));
|
|
2593
|
-
return distanceToShape(ps.shape, toLocal(c, q, point)) < radius;
|
|
2594
|
-
});
|
|
2595
|
-
if (inRange) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
2662
|
+
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
2596
2663
|
}
|
|
2597
2664
|
return out;
|
|
2598
2665
|
};
|
|
2666
|
+
const implParts = (im) => im.impl.describe().parts.map((ps) => {
|
|
2667
|
+
const ov = im.poses.get(ps.id);
|
|
2668
|
+
return { ps, c: toWorld(im.position, im.rotation, ov?.pos ?? ps.position), q: quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0])) };
|
|
2669
|
+
});
|
|
2670
|
+
const implInRange = (im, point, radius) => implParts(im).some(({ ps, c, q }) => distanceToShape(ps.shape, toLocal(c, q, point)) < radius);
|
|
2671
|
+
const blastScene = (point, energy, radius, src, cfg, only) => {
|
|
2672
|
+
const mult = confinementOf(cfg.conf, point, radius);
|
|
2673
|
+
const E = energy * mult;
|
|
2674
|
+
const shelter = cfg.shelter;
|
|
2675
|
+
const flags = /* @__PURE__ */ new Map();
|
|
2676
|
+
const inRange = [];
|
|
2677
|
+
for (const o of objects.values()) {
|
|
2678
|
+
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
2679
|
+
inRange.push(o);
|
|
2680
|
+
if (shelter) for (const p of o.parts) flags.set(p, (isLost(p) ? FLAG_LOST : 0) | (p.st.opening || p.st.stripped ? FLAG_OPEN : 0));
|
|
2681
|
+
}
|
|
2682
|
+
const shields = /* @__PURE__ */ new Map();
|
|
2683
|
+
const reachAt = (tp, self) => {
|
|
2684
|
+
if (!shelter) return 1;
|
|
2685
|
+
const L = vec.distance(point, tp);
|
|
2686
|
+
if (L < 1e-4) return 1;
|
|
2687
|
+
const d = vec.scale(vec.sub(tp, point), 1 / L);
|
|
2688
|
+
let t = 1;
|
|
2689
|
+
for (const o of inRange) {
|
|
2690
|
+
if (raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
|
|
2691
|
+
for (const p of o.parts) {
|
|
2692
|
+
if (p === self) continue;
|
|
2693
|
+
const was = flags.get(p) ?? 0;
|
|
2694
|
+
if (was & FLAG_LOST) continue;
|
|
2695
|
+
const r = rayPart(o, p, point, d);
|
|
2696
|
+
if (!r) continue;
|
|
2697
|
+
const t0 = Math.max(r.t0, 0);
|
|
2698
|
+
const path = Math.min(r.t1, L) - t0;
|
|
2699
|
+
if (path < shelter.minPath) continue;
|
|
2700
|
+
const mid = vec.madd(point, d, t0 + path / 2);
|
|
2701
|
+
const opening = p.st.opening && insideOpening(o, p, mid);
|
|
2702
|
+
if (opening && was & FLAG_OPEN) continue;
|
|
2703
|
+
if (p.st.stripped && was & FLAG_OPEN) continue;
|
|
2704
|
+
const resistance = p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1);
|
|
2705
|
+
let ti = Math.exp(-resistance / shelter.k);
|
|
2706
|
+
if (isLost(p) || opening || p.st.stripped && !(was & FLAG_OPEN)) ti += (1 - ti) * shelter.brokenTransmission;
|
|
2707
|
+
t *= ti;
|
|
2708
|
+
if (cfg.frag) {
|
|
2709
|
+
let list = shields.get(p);
|
|
2710
|
+
if (!list) shields.set(p, list = []);
|
|
2711
|
+
list.push(tp);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
return t;
|
|
2716
|
+
};
|
|
2717
|
+
const items = [];
|
|
2718
|
+
let order = 0;
|
|
2719
|
+
for (const o of inRange) {
|
|
2720
|
+
if (only && only !== o) continue;
|
|
2721
|
+
for (const p of o.parts) {
|
|
2722
|
+
if (isLost(p)) continue;
|
|
2723
|
+
const dist = distToPart(o, p, point);
|
|
2724
|
+
if (dist < radius) items.push({ dist, order: order++, o, p });
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
for (const im of impls.values()) {
|
|
2728
|
+
if (only && only !== im) continue;
|
|
2729
|
+
let dist = Infinity;
|
|
2730
|
+
for (const { ps, c, q } of implParts(im)) dist = Math.min(dist, distanceToShape(ps.shape, toLocal(c, q, point)));
|
|
2731
|
+
if (dist < radius) items.push({ dist, order: order++, im });
|
|
2732
|
+
}
|
|
2733
|
+
items.sort((a, b) => a.dist - b.dist || a.order - b.order);
|
|
2734
|
+
const results = /* @__PURE__ */ new Map();
|
|
2735
|
+
const reports = /* @__PURE__ */ new Map();
|
|
2736
|
+
const report = (key) => {
|
|
2737
|
+
let r = reports.get(key);
|
|
2738
|
+
if (!r) reports.set(key, r = { shelter: 0, confinement: mult, fragmentHits: 0 });
|
|
2739
|
+
return r;
|
|
2740
|
+
};
|
|
2741
|
+
const thrown = [];
|
|
2742
|
+
const sweep = (from) => {
|
|
2743
|
+
if (!cfg.frag) return;
|
|
2744
|
+
for (let i = from; i < cur.length; i++) {
|
|
2745
|
+
const e = cur[i];
|
|
2746
|
+
if (e.type !== "fracture" && e.type !== "stage") continue;
|
|
2747
|
+
for (const frag of e.fragments ?? []) thrown.push({ frag, object: e.object, part: objects.get(e.object)?.byId.get(e.part) ?? null });
|
|
2748
|
+
}
|
|
2749
|
+
};
|
|
2750
|
+
for (const it of items) {
|
|
2751
|
+
if (it.o && it.p) {
|
|
2752
|
+
const o = it.o;
|
|
2753
|
+
let res = results.get(o);
|
|
2754
|
+
if (!res) results.set(o, res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] });
|
|
2755
|
+
const start = cur.length;
|
|
2756
|
+
const k = blastPart(o, it.p, point, E, radius, src, res, shelter ? reachAt : void 0);
|
|
2757
|
+
res.events.push(...cur.slice(start));
|
|
2758
|
+
sweep(start);
|
|
2759
|
+
if (k !== null) {
|
|
2760
|
+
const rep = report(o);
|
|
2761
|
+
rep.shelter = Math.max(rep.shelter, k);
|
|
2762
|
+
if (shelter) (rep.parts ?? (rep.parts = {}))[it.p.spec.id] = k;
|
|
2763
|
+
}
|
|
2764
|
+
} else if (it.im) {
|
|
2765
|
+
const im = it.im;
|
|
2766
|
+
let num = 0;
|
|
2767
|
+
let den = 0;
|
|
2768
|
+
for (const { ps, c, q } of implParts(im)) {
|
|
2769
|
+
const lp = toLocal(c, q, point);
|
|
2770
|
+
const dist = distanceToShape(ps.shape, lp);
|
|
2771
|
+
if (dist >= radius) continue;
|
|
2772
|
+
const f = (1 - dist / radius) ** 2;
|
|
2773
|
+
const sp = surfacePoint(ps.shape, lp);
|
|
2774
|
+
num += f * reachAt(toWorld(c, q, sp.point), null);
|
|
2775
|
+
den += f;
|
|
2776
|
+
}
|
|
2777
|
+
const k = den > 0 ? num / den : 1;
|
|
2778
|
+
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
2779
|
+
results.set(im, res);
|
|
2780
|
+
report(im).shelter = k;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
if (cfg.frag && thrown.length > 0) {
|
|
2784
|
+
const g = cfg.frag;
|
|
2785
|
+
const energyOf = (f) => 0.5 * f.mass * (f.velocity[0] ** 2 + f.velocity[1] ** 2 + f.velocity[2] ** 2);
|
|
2786
|
+
const list = thrown.map((t, i) => ({ ...t, i, e: energyOf(t.frag) })).sort((a, b) => b.e - a.e || a.i - b.i).slice(0, Math.max(0, Math.floor(g.max)));
|
|
2787
|
+
list.forEach((t, i) => throwFragment(t.frag, t.object, t.part, g, point, i, src, shields, results, reports, mult));
|
|
2788
|
+
}
|
|
2789
|
+
const out = [];
|
|
2790
|
+
const reportShelter = (key, res) => {
|
|
2791
|
+
res.blast = reports.get(key) ?? { shelter: 1, confinement: mult, fragmentHits: 0 };
|
|
2792
|
+
};
|
|
2793
|
+
const summary = [];
|
|
2794
|
+
let fragmentHits = 0;
|
|
2795
|
+
for (const o of objects.values()) {
|
|
2796
|
+
const r = results.get(o);
|
|
2797
|
+
if (!r?.part) continue;
|
|
2798
|
+
reportShelter(o, r);
|
|
2799
|
+
out.push(r);
|
|
2800
|
+
for (const [part, factor] of Object.entries(r.blast?.parts ?? {})) summary.push({ object: o.id, part, factor });
|
|
2801
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
2802
|
+
}
|
|
2803
|
+
for (const im of impls.values()) {
|
|
2804
|
+
const r = results.get(im);
|
|
2805
|
+
if (!r) continue;
|
|
2806
|
+
reportShelter(im, r);
|
|
2807
|
+
out.push(r);
|
|
2808
|
+
if (shelter) summary.push({ object: im.impl.id, factor: r.blast?.shelter ?? 1 });
|
|
2809
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
2810
|
+
}
|
|
2811
|
+
emit({ type: "blast", object: "", time, ...src !== void 0 ? { source: src } : {}, point, energy: E, radius, confinement: mult, shelter: summary, fragmentHits });
|
|
2812
|
+
return out;
|
|
2813
|
+
};
|
|
2814
|
+
const seedOf = (...nums) => {
|
|
2815
|
+
let h = 2166136261;
|
|
2816
|
+
for (const n of nums) {
|
|
2817
|
+
h = Math.imul(h ^ (Math.round(n * 1e3) | 0), 16777619);
|
|
2818
|
+
h ^= h >>> 15;
|
|
2819
|
+
}
|
|
2820
|
+
return h | 0;
|
|
2821
|
+
};
|
|
2822
|
+
const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
|
|
2823
|
+
const v = frag.velocity;
|
|
2824
|
+
const speed = Math.hypot(v[0], v[1], v[2]);
|
|
2825
|
+
const energy = g.energyFactor * 0.5 * frag.mass * speed * speed;
|
|
2826
|
+
if (speed < 0.5 || energy < 1) return;
|
|
2827
|
+
const r = createRng(seedOf(options.seed ?? 1, centre[0], centre[1], centre[2], index, frag.center[0], frag.center[1], frag.center[2]));
|
|
2828
|
+
let dir = vec.scale(v, 1 / speed);
|
|
2829
|
+
const behind = from ? shields.get(from) : void 0;
|
|
2830
|
+
if (behind) {
|
|
2831
|
+
let best = Math.cos(g.aim * Math.PI / 180);
|
|
2832
|
+
for (const tp of behind) {
|
|
2833
|
+
const to = vec.sub(tp, frag.center);
|
|
2834
|
+
const len = vec.length(to);
|
|
2835
|
+
if (len < 1e-4) continue;
|
|
2836
|
+
const cos = vec.dot(dir, to) / len;
|
|
2837
|
+
if (cos >= best) {
|
|
2838
|
+
best = cos;
|
|
2839
|
+
dir = vec.scale(to, 1 / len);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
const spread3 = Math.tan(g.spread * Math.PI / 180);
|
|
2844
|
+
dir = vec.normalize([dir[0] + (r.next() - 0.5) * 2 * spread3, dir[1] + (r.next() - 0.5) * 2 * spread3, dir[2] + (r.next() - 0.5) * 2 * spread3], dir);
|
|
2845
|
+
const range = Math.min(g.range, speed * g.flightTime);
|
|
2846
|
+
const origin = vec.madd(frag.center, dir, 0.02);
|
|
2847
|
+
const rh = raycastInternal(origin, dir, range, from ? /* @__PURE__ */ new Set([`${objectId}\0${from.spec.id}`]) : void 0);
|
|
2848
|
+
if (!rh) return;
|
|
2849
|
+
const hit = {
|
|
2850
|
+
type: g.hitType,
|
|
2851
|
+
point: rh.point,
|
|
2852
|
+
direction: dir,
|
|
2853
|
+
energy,
|
|
2854
|
+
radius: 0.05,
|
|
2855
|
+
part: rh.part,
|
|
2856
|
+
...g.hitType === "ballistic" ? { caliber: clamp2(Math.max(...frag.size) * 1e3, 3, 30), mass: frag.mass * 1e3 } : {},
|
|
2857
|
+
...src !== void 0 ? { source: src } : {}
|
|
2858
|
+
};
|
|
2859
|
+
emit({ type: "fragment_hit", object: objectId, time, ...src !== void 0 ? { source: src } : {}, from: { object: objectId, part: frag.part }, target: { object: rh.object, part: rh.part }, point: rh.point, direction: dir, energy, mass: frag.mass, speed });
|
|
2860
|
+
const im = impls.get(rh.object);
|
|
2861
|
+
const o = im ? void 0 : objects.get(rh.object);
|
|
2862
|
+
const key = im ?? o;
|
|
2863
|
+
if (!key) return;
|
|
2864
|
+
const res = im ? implHit(im, hit) : doHit(o, hit, o.byId.get(rh.part));
|
|
2865
|
+
const have = results.get(key);
|
|
2866
|
+
if (have) {
|
|
2867
|
+
have.damage += res.damage;
|
|
2868
|
+
have.events.push(...res.events);
|
|
2869
|
+
if (!have.part) have.part = res.part;
|
|
2870
|
+
if (res.penetrated) have.penetrated = true;
|
|
2871
|
+
} else results.set(key, { ...res, events: [...res.events] });
|
|
2872
|
+
let rep = reports.get(key);
|
|
2873
|
+
if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
|
|
2874
|
+
rep.fragmentHits++;
|
|
2875
|
+
};
|
|
2876
|
+
const blastWorld = (point, energy, radius, src, call, only) => {
|
|
2877
|
+
const cfg = blastCfg(call);
|
|
2878
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return blastPlain(point, energy, radius, src);
|
|
2879
|
+
return blastScene(point, energy, radius, src, cfg, only);
|
|
2880
|
+
};
|
|
2599
2881
|
const heatObject = (o, point, T, radius, duration, src) => {
|
|
2600
2882
|
const start = cur.length;
|
|
2601
2883
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
@@ -2632,7 +2914,7 @@ var GameDestruction = (() => {
|
|
|
2632
2914
|
if (hit.type === "ballistic") {
|
|
2633
2915
|
const loc = locate(o, point, dir, 0.05, only);
|
|
2634
2916
|
if (loc) {
|
|
2635
|
-
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src);
|
|
2917
|
+
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit.piercing ?? 1);
|
|
2636
2918
|
res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
|
|
2637
2919
|
}
|
|
2638
2920
|
} else if (hit.type === "blunt" || hit.type === "slash") {
|
|
@@ -2654,7 +2936,13 @@ var GameDestruction = (() => {
|
|
|
2654
2936
|
}
|
|
2655
2937
|
}
|
|
2656
2938
|
} else if (hit.type === "explosive") {
|
|
2657
|
-
|
|
2939
|
+
const call = hit.blast || hit.confinement !== void 0 ? { ...hit.blast, ...hit.confinement !== void 0 ? { confinement: hit.confinement } : {} } : void 0;
|
|
2940
|
+
const cfg = blastCfg(call);
|
|
2941
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) res = blastObject(o, point, E, hit.radius ?? 2, src);
|
|
2942
|
+
else {
|
|
2943
|
+
res = blastWorld(point, E, hit.radius ?? 2, src, call, o)[0] ?? res;
|
|
2944
|
+
res.events = cur.slice(start);
|
|
2945
|
+
}
|
|
2658
2946
|
} else {
|
|
2659
2947
|
res = heatObject(o, point, hit.temperature ?? 800, hit.radius ?? 0.25, hit.duration ?? 1, src);
|
|
2660
2948
|
}
|
|
@@ -2911,6 +3199,14 @@ var GameDestruction = (() => {
|
|
|
2911
3199
|
for (const e of r.events) if (e.type === "collapse" && !r.value.some((h) => h.events.includes(e))) last.events.push(e);
|
|
2912
3200
|
return r.value;
|
|
2913
3201
|
};
|
|
3202
|
+
const blastImpl = (im, h) => {
|
|
3203
|
+
const call = h.blast || h.confinement !== void 0 ? { ...h.blast, ...h.confinement !== void 0 ? { confinement: h.confinement } : {} } : void 0;
|
|
3204
|
+
const cfg = blastCfg(call);
|
|
3205
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return implHit(im, h);
|
|
3206
|
+
const world2 = h.space === "local" ? toWorld(im.position, im.rotation, h.point) : h.point;
|
|
3207
|
+
const r = blastWorld(world2, h.energy ?? 0, h.radius ?? 2, h.source, call, im);
|
|
3208
|
+
return r.find((x) => x.object === im.impl.id) ?? implHit(im, h);
|
|
3209
|
+
};
|
|
2914
3210
|
const whole = (fn) => {
|
|
2915
3211
|
const r = run(fn);
|
|
2916
3212
|
r.value.events = r.events;
|
|
@@ -2998,7 +3294,7 @@ var GameDestruction = (() => {
|
|
|
2998
3294
|
},
|
|
2999
3295
|
hit(objectId, h) {
|
|
3000
3296
|
const im = impls.get(objectId);
|
|
3001
|
-
if (im) return whole(() => implHit(im, h));
|
|
3297
|
+
if (im) return whole(() => h.type === "explosive" ? blastImpl(im, h) : implHit(im, h));
|
|
3002
3298
|
const o = getObj(objectId);
|
|
3003
3299
|
return whole(() => doHit(o, h));
|
|
3004
3300
|
},
|
|
@@ -3009,14 +3305,14 @@ var GameDestruction = (() => {
|
|
|
3009
3305
|
const p = getPart(o, partId);
|
|
3010
3306
|
return whole(() => doHit(o, h, p));
|
|
3011
3307
|
},
|
|
3012
|
-
explode(point, energy, radius, source) {
|
|
3013
|
-
return withCollapses(run(() => blastWorld(point, energy, radius, source)));
|
|
3308
|
+
explode(point, energy, radius, source, blast) {
|
|
3309
|
+
return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
|
|
3014
3310
|
},
|
|
3015
3311
|
raycast(origin, direction, maxDistance = Infinity) {
|
|
3016
3312
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
3017
3313
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
3018
3314
|
},
|
|
3019
|
-
shoot(origin, direction, energy, caliber, source) {
|
|
3315
|
+
shoot(origin, direction, energy, caliber, source, piercing) {
|
|
3020
3316
|
return withCollapses(run(() => {
|
|
3021
3317
|
const results = [];
|
|
3022
3318
|
const d = vec.normalize(direction);
|
|
@@ -3028,7 +3324,7 @@ var GameDestruction = (() => {
|
|
|
3028
3324
|
if (!rh) break;
|
|
3029
3325
|
const im = impls.get(rh.object);
|
|
3030
3326
|
if (im) {
|
|
3031
|
-
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...source !== void 0 ? { source } : {} });
|
|
3327
|
+
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...piercing !== void 0 ? { piercing } : {}, ...source !== void 0 ? { source } : {} });
|
|
3032
3328
|
r.part = rh.part;
|
|
3033
3329
|
r.point = rh.point;
|
|
3034
3330
|
r.exitEnergy = r.penetrated ? E * 0.5 : 0;
|
|
@@ -3042,7 +3338,7 @@ var GameDestruction = (() => {
|
|
|
3042
3338
|
const o = getObj(rh.object);
|
|
3043
3339
|
const p = getPart(o, rh.part);
|
|
3044
3340
|
const start = cur.length;
|
|
3045
|
-
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source);
|
|
3341
|
+
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, piercing ?? 1);
|
|
3046
3342
|
results.push({ object: o.id, part: p.spec.id, point: rh.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy, events: cur.slice(start) });
|
|
3047
3343
|
ignore.add(`${o.id}\0${p.spec.id}`);
|
|
3048
3344
|
if (!out.penetrated || !out.exitPoint) break;
|
|
@@ -3230,6 +3526,21 @@ var GameDestruction = (() => {
|
|
|
3230
3526
|
return world;
|
|
3231
3527
|
};
|
|
3232
3528
|
|
|
3529
|
+
// src/piercing.ts
|
|
3530
|
+
var PIERCING_BY_KIND = {
|
|
3531
|
+
fmj: 1,
|
|
3532
|
+
hp: 0.6,
|
|
3533
|
+
ap: 1.6,
|
|
3534
|
+
buck: 0.5,
|
|
3535
|
+
slug: 0.25,
|
|
3536
|
+
birdshot: 0.3,
|
|
3537
|
+
rimfire: 1,
|
|
3538
|
+
arrow: 1,
|
|
3539
|
+
bolt: 1
|
|
3540
|
+
};
|
|
3541
|
+
var PIERCING_BY_CALIBER = { "5_56x45": 0.5, "5_45x39": 0.5, "22lr": 0.5 };
|
|
3542
|
+
var piercingOf = (caliber, kind) => PIERCING_BY_CALIBER[caliber] ?? PIERCING_BY_KIND[kind ?? "fmj"] ?? 1;
|
|
3543
|
+
|
|
3233
3544
|
// src/data/assemblies.json
|
|
3234
3545
|
var assemblies_default = [
|
|
3235
3546
|
{
|
|
@@ -3562,6 +3873,8 @@ var GameDestruction = (() => {
|
|
|
3562
3873
|
stage: "damage stage",
|
|
3563
3874
|
expose: "rebar bare",
|
|
3564
3875
|
body: "body",
|
|
3876
|
+
blast: "blast",
|
|
3877
|
+
fragment_hit: "fragment hit",
|
|
3565
3878
|
destroyed: "destroyed"
|
|
3566
3879
|
},
|
|
3567
3880
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3648,6 +3961,8 @@ var GameDestruction = (() => {
|
|
|
3648
3961
|
stage: "est\xE1gio de dano",
|
|
3649
3962
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
3650
3963
|
body: "corpo",
|
|
3964
|
+
blast: "explos\xE3o",
|
|
3965
|
+
fragment_hit: "estilha\xE7o atingiu",
|
|
3651
3966
|
destroyed: "destru\xEDdo"
|
|
3652
3967
|
},
|
|
3653
3968
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|