@adatechnology/game-destruction 0.6.1 → 0.7.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.
- 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.js
CHANGED
|
@@ -836,7 +836,8 @@ var materials_default = [
|
|
|
836
836
|
0.25
|
|
837
837
|
],
|
|
838
838
|
damageScale: {
|
|
839
|
-
blunt: 1.5
|
|
839
|
+
blunt: 1.5,
|
|
840
|
+
shelter: 3
|
|
840
841
|
},
|
|
841
842
|
hitDebris: "shards",
|
|
842
843
|
breakDebris: "shards",
|
|
@@ -887,7 +888,10 @@ var materials_default = [
|
|
|
887
888
|
"glass",
|
|
888
889
|
"brittle"
|
|
889
890
|
],
|
|
890
|
-
compressive: 2e5
|
|
891
|
+
compressive: 2e5,
|
|
892
|
+
damageScale: {
|
|
893
|
+
shelter: 3
|
|
894
|
+
}
|
|
891
895
|
},
|
|
892
896
|
{
|
|
893
897
|
id: "ceramic",
|
|
@@ -1573,10 +1577,14 @@ var fragility = (ductility, thickness) => {
|
|
|
1573
1577
|
// src/world.ts
|
|
1574
1578
|
var DMG_PER_J = 0.02;
|
|
1575
1579
|
var BLAST_COUPLING = 0.1;
|
|
1580
|
+
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
1581
|
+
var FRAGMENT_HIT_DEFAULTS = { max: 24, energyFactor: 0.5, hitType: "blunt", range: 6, flightTime: 0.4, spread: 10, aim: 40 };
|
|
1582
|
+
var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 };
|
|
1576
1583
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1577
1584
|
var BREACH_RADIUS = 0.55;
|
|
1578
1585
|
var BREACH_BLAST_FACTOR = 6;
|
|
1579
1586
|
var BREACH_AREA = 0.25;
|
|
1587
|
+
var BREACH_BLAST_REACH = Infinity;
|
|
1580
1588
|
var RESIDUAL_CAPACITY = 0.15;
|
|
1581
1589
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1582
1590
|
var BREACH_GRID_CAP = 4096;
|
|
@@ -1611,6 +1619,7 @@ var createDestruction = (options = {}) => {
|
|
|
1611
1619
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1612
1620
|
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
1613
1621
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1622
|
+
const breachBlastReach = options.breach?.blastReach ?? BREACH_BLAST_REACH;
|
|
1614
1623
|
const rng = createRng(options.seed ?? 1);
|
|
1615
1624
|
const rnd = () => rng.next();
|
|
1616
1625
|
let time = 0;
|
|
@@ -1966,7 +1975,7 @@ var createDestruction = (options = {}) => {
|
|
|
1966
1975
|
if (disc < 0) return true;
|
|
1967
1976
|
return -b - Math.sqrt(disc) > maxDist;
|
|
1968
1977
|
};
|
|
1969
|
-
const raycastInternal = (origin, dir, maxDist, ignore) => {
|
|
1978
|
+
const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false) => {
|
|
1970
1979
|
const d = vec.normalize(dir);
|
|
1971
1980
|
let best = null;
|
|
1972
1981
|
for (const o of objects.values()) {
|
|
@@ -1979,7 +1988,7 @@ var createDestruction = (options = {}) => {
|
|
|
1979
1988
|
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 };
|
|
1980
1989
|
}
|
|
1981
1990
|
}
|
|
1982
|
-
for (const im of impls.values()) {
|
|
1991
|
+
for (const im of worldOnly ? [] : impls.values()) {
|
|
1983
1992
|
for (const ps of im.impl.describe().parts) {
|
|
1984
1993
|
if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
|
|
1985
1994
|
const ov = im.poses.get(ps.id);
|
|
@@ -2392,7 +2401,7 @@ var createDestruction = (options = {}) => {
|
|
|
2392
2401
|
else if (chained && (trigger === "explosion" || 1 - p.st.hp / p.st.maxHp >= shotDamageFractionOf(p.spec))) prime(o, p, trigger, src);
|
|
2393
2402
|
return done;
|
|
2394
2403
|
};
|
|
2395
|
-
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2404
|
+
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, piercing = 1) => {
|
|
2396
2405
|
const mat = p.mat;
|
|
2397
2406
|
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2398
2407
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
@@ -2405,7 +2414,9 @@ var createDestruction = (options = {}) => {
|
|
|
2405
2414
|
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2406
2415
|
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;
|
|
2407
2416
|
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2408
|
-
const
|
|
2417
|
+
const hard = mat.tags?.includes("metal") || mat.tags?.includes("masonry");
|
|
2418
|
+
const Ep = piercing === 1 || !hard ? E : E * piercing;
|
|
2419
|
+
const penetrated = Ep > thr;
|
|
2409
2420
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2410
2421
|
const id = p.spec.id;
|
|
2411
2422
|
if (!penetrated && mat.ricochetDeg && angle < mat.ricochetDeg) {
|
|
@@ -2417,7 +2428,7 @@ var createDestruction = (options = {}) => {
|
|
|
2417
2428
|
}
|
|
2418
2429
|
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2419
2430
|
const brittle = mat.ductility < 0.3;
|
|
2420
|
-
const ratio =
|
|
2431
|
+
const ratio = Ep / Math.max(thr, 1e-6);
|
|
2421
2432
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2422
2433
|
let exitPoint;
|
|
2423
2434
|
if (penetrated) {
|
|
@@ -2447,7 +2458,7 @@ var createDestruction = (options = {}) => {
|
|
|
2447
2458
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
2448
2459
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
2449
2460
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2450
|
-
return { penetrated, exitEnergy: penetrated ? E - thr : 0, exitPoint, damage: dmg };
|
|
2461
|
+
return { penetrated, exitEnergy: penetrated ? E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
2451
2462
|
};
|
|
2452
2463
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
2453
2464
|
const mat = p.mat;
|
|
@@ -2475,54 +2486,110 @@ var createDestruction = (options = {}) => {
|
|
|
2475
2486
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2476
2487
|
return done;
|
|
2477
2488
|
};
|
|
2489
|
+
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
2490
|
+
if (isLost(p)) return null;
|
|
2491
|
+
const { c, q } = partXf(o, p);
|
|
2492
|
+
const lp = toLocal(c, q, point);
|
|
2493
|
+
const dist = distanceToShape(p.spec.shape, lp);
|
|
2494
|
+
if (dist >= radius) return null;
|
|
2495
|
+
const sp = surfacePoint(p.spec.shape, lp);
|
|
2496
|
+
const wp = toWorld(c, q, sp.point);
|
|
2497
|
+
const reach = reachAt ? reachAt(wp, p) : 1;
|
|
2498
|
+
const f = (1 - dist / radius) ** 2 * reach;
|
|
2499
|
+
const Ein = energy * f * BLAST_COUPLING;
|
|
2500
|
+
const wn = rotateVec(q, sp.normal);
|
|
2501
|
+
if (!res.part) {
|
|
2502
|
+
res.part = p.spec.id;
|
|
2503
|
+
res.point = wp;
|
|
2504
|
+
}
|
|
2505
|
+
const mat = p.mat;
|
|
2506
|
+
if (Ein > 20) {
|
|
2507
|
+
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);
|
|
2508
|
+
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2509
|
+
debris(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2510
|
+
}
|
|
2511
|
+
const th = mat.thermal;
|
|
2512
|
+
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2513
|
+
p.st.temperature += 300 * f;
|
|
2514
|
+
o.active = true;
|
|
2515
|
+
if (f >= 0.25 && !p.st.burning) {
|
|
2516
|
+
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2517
|
+
checkIgnite(o, p, src);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2521
|
+
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
2522
|
+
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
2523
|
+
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
2524
|
+
return reach;
|
|
2525
|
+
}
|
|
2526
|
+
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);
|
|
2527
|
+
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2528
|
+
res.damage += dealt;
|
|
2529
|
+
if (dist <= breachBlastReach * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2530
|
+
return reach;
|
|
2531
|
+
};
|
|
2478
2532
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2479
2533
|
const start = cur.length;
|
|
2480
2534
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
2481
|
-
for (const p of o.parts)
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
const
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
if (!res.part) {
|
|
2493
|
-
res.part = p.spec.id;
|
|
2494
|
-
res.point = wp;
|
|
2495
|
-
}
|
|
2496
|
-
const mat = p.mat;
|
|
2497
|
-
if (Ein > 20) {
|
|
2498
|
-
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);
|
|
2499
|
-
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2500
|
-
debris(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2501
|
-
}
|
|
2502
|
-
const th = mat.thermal;
|
|
2503
|
-
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2504
|
-
p.st.temperature += 300 * f;
|
|
2505
|
-
o.active = true;
|
|
2506
|
-
if (f >= 0.25 && !p.st.burning) {
|
|
2507
|
-
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2508
|
-
checkIgnite(o, p, src);
|
|
2509
|
-
}
|
|
2535
|
+
for (const p of o.parts) blastPart(o, p, point, energy, radius, src, res);
|
|
2536
|
+
res.events = cur.slice(start);
|
|
2537
|
+
return res;
|
|
2538
|
+
};
|
|
2539
|
+
const blastCfg = (call) => {
|
|
2540
|
+
const pick = (key) => call && call[key] !== void 0 ? call[key] : options.blast?.[key];
|
|
2541
|
+
const merge = (v, defaults) => {
|
|
2542
|
+
if (!v) return null;
|
|
2543
|
+
const out = { ...defaults };
|
|
2544
|
+
if (v !== true) {
|
|
2545
|
+
for (const [key, value] of Object.entries(v)) if (value !== void 0) out[key] = value;
|
|
2510
2546
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2547
|
+
return out;
|
|
2548
|
+
};
|
|
2549
|
+
const conf = pick("confinement");
|
|
2550
|
+
return {
|
|
2551
|
+
shelter: merge(pick("shelter"), SHELTER_DEFAULTS),
|
|
2552
|
+
frag: merge(pick("fragmentHits"), FRAGMENT_HIT_DEFAULTS),
|
|
2553
|
+
conf: typeof conf === "number" ? Number.isFinite(conf) ? Math.max(0, conf) : 1 : merge(conf, CONFINEMENT_DEFAULTS)
|
|
2554
|
+
};
|
|
2555
|
+
};
|
|
2556
|
+
const confinementRays = /* @__PURE__ */ new Map();
|
|
2557
|
+
const raysOf = (n) => {
|
|
2558
|
+
let rays = confinementRays.get(n);
|
|
2559
|
+
if (rays) return rays;
|
|
2560
|
+
const r = createRng((options.seed ?? 1) ^ 1540483477);
|
|
2561
|
+
const off = r.next();
|
|
2562
|
+
const phi0 = r.next() * Math.PI * 2;
|
|
2563
|
+
rays = [];
|
|
2564
|
+
for (let i = 0; i < n; i++) {
|
|
2565
|
+
const y = 1 - 2 * (i + off) / n;
|
|
2566
|
+
const s = Math.sqrt(Math.max(0, 1 - y * y));
|
|
2567
|
+
const phi = phi0 + i * 2.399963229728653;
|
|
2568
|
+
rays.push([Math.cos(phi) * s, y, Math.sin(phi) * s]);
|
|
2569
|
+
}
|
|
2570
|
+
confinementRays.set(n, rays);
|
|
2571
|
+
return rays;
|
|
2572
|
+
};
|
|
2573
|
+
const confinementOf = (conf, point, radius) => {
|
|
2574
|
+
if (conf === null) return 1;
|
|
2575
|
+
if (typeof conf === "number") return conf;
|
|
2576
|
+
let hits = 0;
|
|
2577
|
+
let sum = 0;
|
|
2578
|
+
for (const d of raysOf(Math.max(1, Math.round(conf.rays)))) {
|
|
2579
|
+
const h = raycastInternal(point, d, radius * conf.reach, void 0, true);
|
|
2580
|
+
if (h) {
|
|
2581
|
+
hits++;
|
|
2582
|
+
sum += h.distance;
|
|
2516
2583
|
}
|
|
2517
|
-
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);
|
|
2518
|
-
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2519
|
-
res.damage += dealt;
|
|
2520
|
-
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2521
2584
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2585
|
+
if (hits === 0) return 1;
|
|
2586
|
+
const closed = hits / Math.max(1, Math.round(conf.rays));
|
|
2587
|
+
const ratio = clamp2(radius / Math.max(sum / hits, 1e-3), 0, 2);
|
|
2588
|
+
return Math.min(Math.max(1, conf.max), 1 + conf.gain * closed ** conf.power * ratio);
|
|
2524
2589
|
};
|
|
2525
|
-
const
|
|
2590
|
+
const FLAG_LOST = 1;
|
|
2591
|
+
const FLAG_OPEN = 2;
|
|
2592
|
+
const blastPlain = (point, energy, radius, src) => {
|
|
2526
2593
|
const out = [];
|
|
2527
2594
|
for (const o of [...objects.values()]) {
|
|
2528
2595
|
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
@@ -2530,16 +2597,225 @@ var createDestruction = (options = {}) => {
|
|
|
2530
2597
|
if (r.part) out.push(r);
|
|
2531
2598
|
}
|
|
2532
2599
|
for (const im of [...impls.values()]) {
|
|
2533
|
-
|
|
2534
|
-
const ov = im.poses.get(ps.id);
|
|
2535
|
-
const c = toWorld(im.position, im.rotation, ov?.pos ?? ps.position);
|
|
2536
|
-
const q = quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]));
|
|
2537
|
-
return distanceToShape(ps.shape, toLocal(c, q, point)) < radius;
|
|
2538
|
-
});
|
|
2539
|
-
if (inRange) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
2600
|
+
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
2540
2601
|
}
|
|
2541
2602
|
return out;
|
|
2542
2603
|
};
|
|
2604
|
+
const implParts = (im) => im.impl.describe().parts.map((ps) => {
|
|
2605
|
+
const ov = im.poses.get(ps.id);
|
|
2606
|
+
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])) };
|
|
2607
|
+
});
|
|
2608
|
+
const implInRange = (im, point, radius) => implParts(im).some(({ ps, c, q }) => distanceToShape(ps.shape, toLocal(c, q, point)) < radius);
|
|
2609
|
+
const blastScene = (point, energy, radius, src, cfg, only) => {
|
|
2610
|
+
const mult = confinementOf(cfg.conf, point, radius);
|
|
2611
|
+
const E = energy * mult;
|
|
2612
|
+
const shelter = cfg.shelter;
|
|
2613
|
+
const flags = /* @__PURE__ */ new Map();
|
|
2614
|
+
const inRange = [];
|
|
2615
|
+
for (const o of objects.values()) {
|
|
2616
|
+
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
2617
|
+
inRange.push(o);
|
|
2618
|
+
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));
|
|
2619
|
+
}
|
|
2620
|
+
const shields = /* @__PURE__ */ new Map();
|
|
2621
|
+
const reachAt = (tp, self) => {
|
|
2622
|
+
if (!shelter) return 1;
|
|
2623
|
+
const L = vec.distance(point, tp);
|
|
2624
|
+
if (L < 1e-4) return 1;
|
|
2625
|
+
const d = vec.scale(vec.sub(tp, point), 1 / L);
|
|
2626
|
+
let t = 1;
|
|
2627
|
+
for (const o of inRange) {
|
|
2628
|
+
if (raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
|
|
2629
|
+
for (const p of o.parts) {
|
|
2630
|
+
if (p === self) continue;
|
|
2631
|
+
const was = flags.get(p) ?? 0;
|
|
2632
|
+
if (was & FLAG_LOST) continue;
|
|
2633
|
+
const r = rayPart(o, p, point, d);
|
|
2634
|
+
if (!r) continue;
|
|
2635
|
+
const t0 = Math.max(r.t0, 0);
|
|
2636
|
+
const path = Math.min(r.t1, L) - t0;
|
|
2637
|
+
if (path < shelter.minPath) continue;
|
|
2638
|
+
const mid = vec.madd(point, d, t0 + path / 2);
|
|
2639
|
+
const opening = p.st.opening && insideOpening(o, p, mid);
|
|
2640
|
+
if (opening && was & FLAG_OPEN) continue;
|
|
2641
|
+
if (p.st.stripped && was & FLAG_OPEN) continue;
|
|
2642
|
+
const resistance = p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1);
|
|
2643
|
+
let ti = Math.exp(-resistance / shelter.k);
|
|
2644
|
+
if (isLost(p) || opening || p.st.stripped && !(was & FLAG_OPEN)) ti += (1 - ti) * shelter.brokenTransmission;
|
|
2645
|
+
t *= ti;
|
|
2646
|
+
if (cfg.frag) {
|
|
2647
|
+
let list = shields.get(p);
|
|
2648
|
+
if (!list) shields.set(p, list = []);
|
|
2649
|
+
list.push(tp);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
return t;
|
|
2654
|
+
};
|
|
2655
|
+
const items = [];
|
|
2656
|
+
let order = 0;
|
|
2657
|
+
for (const o of inRange) {
|
|
2658
|
+
if (only && only !== o) continue;
|
|
2659
|
+
for (const p of o.parts) {
|
|
2660
|
+
if (isLost(p)) continue;
|
|
2661
|
+
const dist = distToPart(o, p, point);
|
|
2662
|
+
if (dist < radius) items.push({ dist, order: order++, o, p });
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
for (const im of impls.values()) {
|
|
2666
|
+
if (only && only !== im) continue;
|
|
2667
|
+
let dist = Infinity;
|
|
2668
|
+
for (const { ps, c, q } of implParts(im)) dist = Math.min(dist, distanceToShape(ps.shape, toLocal(c, q, point)));
|
|
2669
|
+
if (dist < radius) items.push({ dist, order: order++, im });
|
|
2670
|
+
}
|
|
2671
|
+
items.sort((a, b) => a.dist - b.dist || a.order - b.order);
|
|
2672
|
+
const results = /* @__PURE__ */ new Map();
|
|
2673
|
+
const reports = /* @__PURE__ */ new Map();
|
|
2674
|
+
const report = (key) => {
|
|
2675
|
+
let r = reports.get(key);
|
|
2676
|
+
if (!r) reports.set(key, r = { shelter: 0, confinement: mult, fragmentHits: 0 });
|
|
2677
|
+
return r;
|
|
2678
|
+
};
|
|
2679
|
+
const thrown = [];
|
|
2680
|
+
const sweep = (from) => {
|
|
2681
|
+
if (!cfg.frag) return;
|
|
2682
|
+
for (let i = from; i < cur.length; i++) {
|
|
2683
|
+
const e = cur[i];
|
|
2684
|
+
if (e.type !== "fracture" && e.type !== "stage") continue;
|
|
2685
|
+
for (const frag of e.fragments ?? []) thrown.push({ frag, object: e.object, part: objects.get(e.object)?.byId.get(e.part) ?? null });
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
for (const it of items) {
|
|
2689
|
+
if (it.o && it.p) {
|
|
2690
|
+
const o = it.o;
|
|
2691
|
+
let res = results.get(o);
|
|
2692
|
+
if (!res) results.set(o, res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] });
|
|
2693
|
+
const start = cur.length;
|
|
2694
|
+
const k = blastPart(o, it.p, point, E, radius, src, res, shelter ? reachAt : void 0);
|
|
2695
|
+
res.events.push(...cur.slice(start));
|
|
2696
|
+
sweep(start);
|
|
2697
|
+
if (k !== null) {
|
|
2698
|
+
const rep = report(o);
|
|
2699
|
+
rep.shelter = Math.max(rep.shelter, k);
|
|
2700
|
+
if (shelter) (rep.parts ?? (rep.parts = {}))[it.p.spec.id] = k;
|
|
2701
|
+
}
|
|
2702
|
+
} else if (it.im) {
|
|
2703
|
+
const im = it.im;
|
|
2704
|
+
let num = 0;
|
|
2705
|
+
let den = 0;
|
|
2706
|
+
for (const { ps, c, q } of implParts(im)) {
|
|
2707
|
+
const lp = toLocal(c, q, point);
|
|
2708
|
+
const dist = distanceToShape(ps.shape, lp);
|
|
2709
|
+
if (dist >= radius) continue;
|
|
2710
|
+
const f = (1 - dist / radius) ** 2;
|
|
2711
|
+
const sp = surfacePoint(ps.shape, lp);
|
|
2712
|
+
num += f * reachAt(toWorld(c, q, sp.point), null);
|
|
2713
|
+
den += f;
|
|
2714
|
+
}
|
|
2715
|
+
const k = den > 0 ? num / den : 1;
|
|
2716
|
+
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
2717
|
+
results.set(im, res);
|
|
2718
|
+
report(im).shelter = k;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
if (cfg.frag && thrown.length > 0) {
|
|
2722
|
+
const g = cfg.frag;
|
|
2723
|
+
const energyOf = (f) => 0.5 * f.mass * (f.velocity[0] ** 2 + f.velocity[1] ** 2 + f.velocity[2] ** 2);
|
|
2724
|
+
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)));
|
|
2725
|
+
list.forEach((t, i) => throwFragment(t.frag, t.object, t.part, g, point, i, src, shields, results, reports, mult));
|
|
2726
|
+
}
|
|
2727
|
+
const out = [];
|
|
2728
|
+
const reportShelter = (key, res) => {
|
|
2729
|
+
res.blast = reports.get(key) ?? { shelter: 1, confinement: mult, fragmentHits: 0 };
|
|
2730
|
+
};
|
|
2731
|
+
const summary = [];
|
|
2732
|
+
let fragmentHits = 0;
|
|
2733
|
+
for (const o of objects.values()) {
|
|
2734
|
+
const r = results.get(o);
|
|
2735
|
+
if (!r?.part) continue;
|
|
2736
|
+
reportShelter(o, r);
|
|
2737
|
+
out.push(r);
|
|
2738
|
+
for (const [part, factor] of Object.entries(r.blast?.parts ?? {})) summary.push({ object: o.id, part, factor });
|
|
2739
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
2740
|
+
}
|
|
2741
|
+
for (const im of impls.values()) {
|
|
2742
|
+
const r = results.get(im);
|
|
2743
|
+
if (!r) continue;
|
|
2744
|
+
reportShelter(im, r);
|
|
2745
|
+
out.push(r);
|
|
2746
|
+
if (shelter) summary.push({ object: im.impl.id, factor: r.blast?.shelter ?? 1 });
|
|
2747
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
2748
|
+
}
|
|
2749
|
+
emit({ type: "blast", object: "", time, ...src !== void 0 ? { source: src } : {}, point, energy: E, radius, confinement: mult, shelter: summary, fragmentHits });
|
|
2750
|
+
return out;
|
|
2751
|
+
};
|
|
2752
|
+
const seedOf = (...nums) => {
|
|
2753
|
+
let h = 2166136261;
|
|
2754
|
+
for (const n of nums) {
|
|
2755
|
+
h = Math.imul(h ^ (Math.round(n * 1e3) | 0), 16777619);
|
|
2756
|
+
h ^= h >>> 15;
|
|
2757
|
+
}
|
|
2758
|
+
return h | 0;
|
|
2759
|
+
};
|
|
2760
|
+
const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
|
|
2761
|
+
const v = frag.velocity;
|
|
2762
|
+
const speed = Math.hypot(v[0], v[1], v[2]);
|
|
2763
|
+
const energy = g.energyFactor * 0.5 * frag.mass * speed * speed;
|
|
2764
|
+
if (speed < 0.5 || energy < 1) return;
|
|
2765
|
+
const r = createRng(seedOf(options.seed ?? 1, centre[0], centre[1], centre[2], index, frag.center[0], frag.center[1], frag.center[2]));
|
|
2766
|
+
let dir = vec.scale(v, 1 / speed);
|
|
2767
|
+
const behind = from ? shields.get(from) : void 0;
|
|
2768
|
+
if (behind) {
|
|
2769
|
+
let best = Math.cos(g.aim * Math.PI / 180);
|
|
2770
|
+
for (const tp of behind) {
|
|
2771
|
+
const to = vec.sub(tp, frag.center);
|
|
2772
|
+
const len = vec.length(to);
|
|
2773
|
+
if (len < 1e-4) continue;
|
|
2774
|
+
const cos = vec.dot(dir, to) / len;
|
|
2775
|
+
if (cos >= best) {
|
|
2776
|
+
best = cos;
|
|
2777
|
+
dir = vec.scale(to, 1 / len);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
const spread3 = Math.tan(g.spread * Math.PI / 180);
|
|
2782
|
+
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);
|
|
2783
|
+
const range = Math.min(g.range, speed * g.flightTime);
|
|
2784
|
+
const origin = vec.madd(frag.center, dir, 0.02);
|
|
2785
|
+
const rh = raycastInternal(origin, dir, range, from ? /* @__PURE__ */ new Set([`${objectId}\0${from.spec.id}`]) : void 0);
|
|
2786
|
+
if (!rh) return;
|
|
2787
|
+
const hit = {
|
|
2788
|
+
type: g.hitType,
|
|
2789
|
+
point: rh.point,
|
|
2790
|
+
direction: dir,
|
|
2791
|
+
energy,
|
|
2792
|
+
radius: 0.05,
|
|
2793
|
+
part: rh.part,
|
|
2794
|
+
...g.hitType === "ballistic" ? { caliber: clamp2(Math.max(...frag.size) * 1e3, 3, 30), mass: frag.mass * 1e3 } : {},
|
|
2795
|
+
...src !== void 0 ? { source: src } : {}
|
|
2796
|
+
};
|
|
2797
|
+
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 });
|
|
2798
|
+
const im = impls.get(rh.object);
|
|
2799
|
+
const o = im ? void 0 : objects.get(rh.object);
|
|
2800
|
+
const key = im ?? o;
|
|
2801
|
+
if (!key) return;
|
|
2802
|
+
const res = im ? implHit(im, hit) : doHit(o, hit, o.byId.get(rh.part));
|
|
2803
|
+
const have = results.get(key);
|
|
2804
|
+
if (have) {
|
|
2805
|
+
have.damage += res.damage;
|
|
2806
|
+
have.events.push(...res.events);
|
|
2807
|
+
if (!have.part) have.part = res.part;
|
|
2808
|
+
if (res.penetrated) have.penetrated = true;
|
|
2809
|
+
} else results.set(key, { ...res, events: [...res.events] });
|
|
2810
|
+
let rep = reports.get(key);
|
|
2811
|
+
if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
|
|
2812
|
+
rep.fragmentHits++;
|
|
2813
|
+
};
|
|
2814
|
+
const blastWorld = (point, energy, radius, src, call, only) => {
|
|
2815
|
+
const cfg = blastCfg(call);
|
|
2816
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return blastPlain(point, energy, radius, src);
|
|
2817
|
+
return blastScene(point, energy, radius, src, cfg, only);
|
|
2818
|
+
};
|
|
2543
2819
|
const heatObject = (o, point, T, radius, duration, src) => {
|
|
2544
2820
|
const start = cur.length;
|
|
2545
2821
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
@@ -2576,7 +2852,7 @@ var createDestruction = (options = {}) => {
|
|
|
2576
2852
|
if (hit.type === "ballistic") {
|
|
2577
2853
|
const loc = locate(o, point, dir, 0.05, only);
|
|
2578
2854
|
if (loc) {
|
|
2579
|
-
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src);
|
|
2855
|
+
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit.piercing ?? 1);
|
|
2580
2856
|
res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
|
|
2581
2857
|
}
|
|
2582
2858
|
} else if (hit.type === "blunt" || hit.type === "slash") {
|
|
@@ -2598,7 +2874,13 @@ var createDestruction = (options = {}) => {
|
|
|
2598
2874
|
}
|
|
2599
2875
|
}
|
|
2600
2876
|
} else if (hit.type === "explosive") {
|
|
2601
|
-
|
|
2877
|
+
const call = hit.blast || hit.confinement !== void 0 ? { ...hit.blast, ...hit.confinement !== void 0 ? { confinement: hit.confinement } : {} } : void 0;
|
|
2878
|
+
const cfg = blastCfg(call);
|
|
2879
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) res = blastObject(o, point, E, hit.radius ?? 2, src);
|
|
2880
|
+
else {
|
|
2881
|
+
res = blastWorld(point, E, hit.radius ?? 2, src, call, o)[0] ?? res;
|
|
2882
|
+
res.events = cur.slice(start);
|
|
2883
|
+
}
|
|
2602
2884
|
} else {
|
|
2603
2885
|
res = heatObject(o, point, hit.temperature ?? 800, hit.radius ?? 0.25, hit.duration ?? 1, src);
|
|
2604
2886
|
}
|
|
@@ -2855,6 +3137,14 @@ var createDestruction = (options = {}) => {
|
|
|
2855
3137
|
for (const e of r.events) if (e.type === "collapse" && !r.value.some((h) => h.events.includes(e))) last.events.push(e);
|
|
2856
3138
|
return r.value;
|
|
2857
3139
|
};
|
|
3140
|
+
const blastImpl = (im, h) => {
|
|
3141
|
+
const call = h.blast || h.confinement !== void 0 ? { ...h.blast, ...h.confinement !== void 0 ? { confinement: h.confinement } : {} } : void 0;
|
|
3142
|
+
const cfg = blastCfg(call);
|
|
3143
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return implHit(im, h);
|
|
3144
|
+
const world2 = h.space === "local" ? toWorld(im.position, im.rotation, h.point) : h.point;
|
|
3145
|
+
const r = blastWorld(world2, h.energy ?? 0, h.radius ?? 2, h.source, call, im);
|
|
3146
|
+
return r.find((x) => x.object === im.impl.id) ?? implHit(im, h);
|
|
3147
|
+
};
|
|
2858
3148
|
const whole = (fn) => {
|
|
2859
3149
|
const r = run(fn);
|
|
2860
3150
|
r.value.events = r.events;
|
|
@@ -2942,7 +3232,7 @@ var createDestruction = (options = {}) => {
|
|
|
2942
3232
|
},
|
|
2943
3233
|
hit(objectId, h) {
|
|
2944
3234
|
const im = impls.get(objectId);
|
|
2945
|
-
if (im) return whole(() => implHit(im, h));
|
|
3235
|
+
if (im) return whole(() => h.type === "explosive" ? blastImpl(im, h) : implHit(im, h));
|
|
2946
3236
|
const o = getObj(objectId);
|
|
2947
3237
|
return whole(() => doHit(o, h));
|
|
2948
3238
|
},
|
|
@@ -2953,14 +3243,14 @@ var createDestruction = (options = {}) => {
|
|
|
2953
3243
|
const p = getPart(o, partId);
|
|
2954
3244
|
return whole(() => doHit(o, h, p));
|
|
2955
3245
|
},
|
|
2956
|
-
explode(point, energy, radius, source) {
|
|
2957
|
-
return withCollapses(run(() => blastWorld(point, energy, radius, source)));
|
|
3246
|
+
explode(point, energy, radius, source, blast) {
|
|
3247
|
+
return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
|
|
2958
3248
|
},
|
|
2959
3249
|
raycast(origin, direction, maxDistance = Infinity) {
|
|
2960
3250
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
2961
3251
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
2962
3252
|
},
|
|
2963
|
-
shoot(origin, direction, energy, caliber, source) {
|
|
3253
|
+
shoot(origin, direction, energy, caliber, source, piercing) {
|
|
2964
3254
|
return withCollapses(run(() => {
|
|
2965
3255
|
const results = [];
|
|
2966
3256
|
const d = vec.normalize(direction);
|
|
@@ -2972,7 +3262,7 @@ var createDestruction = (options = {}) => {
|
|
|
2972
3262
|
if (!rh) break;
|
|
2973
3263
|
const im = impls.get(rh.object);
|
|
2974
3264
|
if (im) {
|
|
2975
|
-
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...source !== void 0 ? { source } : {} });
|
|
3265
|
+
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...piercing !== void 0 ? { piercing } : {}, ...source !== void 0 ? { source } : {} });
|
|
2976
3266
|
r.part = rh.part;
|
|
2977
3267
|
r.point = rh.point;
|
|
2978
3268
|
r.exitEnergy = r.penetrated ? E * 0.5 : 0;
|
|
@@ -2986,7 +3276,7 @@ var createDestruction = (options = {}) => {
|
|
|
2986
3276
|
const o = getObj(rh.object);
|
|
2987
3277
|
const p = getPart(o, rh.part);
|
|
2988
3278
|
const start = cur.length;
|
|
2989
|
-
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source);
|
|
3279
|
+
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, piercing ?? 1);
|
|
2990
3280
|
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) });
|
|
2991
3281
|
ignore.add(`${o.id}\0${p.spec.id}`);
|
|
2992
3282
|
if (!out.penetrated || !out.exitPoint) break;
|
|
@@ -3174,6 +3464,21 @@ var createDestruction = (options = {}) => {
|
|
|
3174
3464
|
return world;
|
|
3175
3465
|
};
|
|
3176
3466
|
|
|
3467
|
+
// src/piercing.ts
|
|
3468
|
+
var PIERCING_BY_KIND = {
|
|
3469
|
+
fmj: 1,
|
|
3470
|
+
hp: 0.6,
|
|
3471
|
+
ap: 1.6,
|
|
3472
|
+
buck: 0.5,
|
|
3473
|
+
slug: 0.25,
|
|
3474
|
+
birdshot: 0.3,
|
|
3475
|
+
rimfire: 1,
|
|
3476
|
+
arrow: 1,
|
|
3477
|
+
bolt: 1
|
|
3478
|
+
};
|
|
3479
|
+
var PIERCING_BY_CALIBER = { "5_56x45": 0.5, "5_45x39": 0.5, "22lr": 0.5 };
|
|
3480
|
+
var piercingOf = (caliber, kind) => PIERCING_BY_CALIBER[caliber] ?? PIERCING_BY_KIND[kind ?? "fmj"] ?? 1;
|
|
3481
|
+
|
|
3177
3482
|
// src/data/assemblies.json
|
|
3178
3483
|
var assemblies_default = [
|
|
3179
3484
|
{
|
|
@@ -3506,6 +3811,8 @@ var locales = {
|
|
|
3506
3811
|
stage: "damage stage",
|
|
3507
3812
|
expose: "rebar bare",
|
|
3508
3813
|
body: "body",
|
|
3814
|
+
blast: "blast",
|
|
3815
|
+
fragment_hit: "fragment hit",
|
|
3509
3816
|
destroyed: "destroyed"
|
|
3510
3817
|
},
|
|
3511
3818
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3592,6 +3899,8 @@ var locales = {
|
|
|
3592
3899
|
stage: "est\xE1gio de dano",
|
|
3593
3900
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
3594
3901
|
body: "corpo",
|
|
3902
|
+
blast: "explos\xE3o",
|
|
3903
|
+
fragment_hit: "estilha\xE7o atingiu",
|
|
3595
3904
|
destroyed: "destru\xEDdo"
|
|
3596
3905
|
},
|
|
3597
3906
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|
|
@@ -3609,11 +3918,16 @@ var checkDestructible = (impl, options = {}) => {
|
|
|
3609
3918
|
};
|
|
3610
3919
|
export {
|
|
3611
3920
|
ASSEMBLIES,
|
|
3921
|
+
CONFINEMENT_DEFAULTS,
|
|
3612
3922
|
FIRE_PRIME_C,
|
|
3923
|
+
FRAGMENT_HIT_DEFAULTS,
|
|
3613
3924
|
IMPACT_CONTRACT,
|
|
3614
3925
|
JOINT_BY_MATERIALS,
|
|
3615
3926
|
JOINT_DEFAULTS,
|
|
3616
3927
|
MATERIALS,
|
|
3928
|
+
PIERCING_BY_CALIBER,
|
|
3929
|
+
PIERCING_BY_KIND,
|
|
3930
|
+
SHELTER_DEFAULTS,
|
|
3617
3931
|
assemblyDepth,
|
|
3618
3932
|
assemblyPart,
|
|
3619
3933
|
buildFrame,
|
|
@@ -3630,6 +3944,7 @@ export {
|
|
|
3630
3944
|
partMass,
|
|
3631
3945
|
partThickness,
|
|
3632
3946
|
partVolume,
|
|
3947
|
+
piercingOf,
|
|
3633
3948
|
quat,
|
|
3634
3949
|
quatFromEuler,
|
|
3635
3950
|
registerAssembly,
|