@adatechnology/game-test-room 0.8.0 → 0.10.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 +2 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.global.js +449 -71
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +85 -5
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.global.js
CHANGED
|
@@ -1190,7 +1190,8 @@ var GameTestRoom = (() => {
|
|
|
1190
1190
|
0.25
|
|
1191
1191
|
],
|
|
1192
1192
|
damageScale: {
|
|
1193
|
-
blunt: 1.5
|
|
1193
|
+
blunt: 1.5,
|
|
1194
|
+
shelter: 3
|
|
1194
1195
|
},
|
|
1195
1196
|
hitDebris: "shards",
|
|
1196
1197
|
breakDebris: "shards",
|
|
@@ -1241,7 +1242,10 @@ var GameTestRoom = (() => {
|
|
|
1241
1242
|
"glass",
|
|
1242
1243
|
"brittle"
|
|
1243
1244
|
],
|
|
1244
|
-
compressive: 2e5
|
|
1245
|
+
compressive: 2e5,
|
|
1246
|
+
damageScale: {
|
|
1247
|
+
shelter: 3
|
|
1248
|
+
}
|
|
1245
1249
|
},
|
|
1246
1250
|
{
|
|
1247
1251
|
id: "ceramic",
|
|
@@ -1921,10 +1925,14 @@ var GameTestRoom = (() => {
|
|
|
1921
1925
|
};
|
|
1922
1926
|
var DMG_PER_J = 0.02;
|
|
1923
1927
|
var BLAST_COUPLING = 0.1;
|
|
1928
|
+
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
1929
|
+
var FRAGMENT_HIT_DEFAULTS = { max: 24, energyFactor: 0.5, hitType: "blunt", range: 6, flightTime: 0.4, spread: 10, aim: 40 };
|
|
1930
|
+
var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 };
|
|
1924
1931
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1925
1932
|
var BREACH_RADIUS = 0.55;
|
|
1926
1933
|
var BREACH_BLAST_FACTOR = 6;
|
|
1927
1934
|
var BREACH_AREA = 0.25;
|
|
1935
|
+
var BREACH_BLAST_REACH = Infinity;
|
|
1928
1936
|
var RESIDUAL_CAPACITY = 0.15;
|
|
1929
1937
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1930
1938
|
var BREACH_GRID_CAP = 4096;
|
|
@@ -1959,6 +1967,7 @@ var GameTestRoom = (() => {
|
|
|
1959
1967
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1960
1968
|
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
1961
1969
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1970
|
+
const breachBlastReach = options.breach?.blastReach ?? BREACH_BLAST_REACH;
|
|
1962
1971
|
const rng = createRng(options.seed ?? 1);
|
|
1963
1972
|
const rnd2 = () => rng.next();
|
|
1964
1973
|
let time = 0;
|
|
@@ -2314,7 +2323,7 @@ var GameTestRoom = (() => {
|
|
|
2314
2323
|
if (disc < 0) return true;
|
|
2315
2324
|
return -b - Math.sqrt(disc) > maxDist;
|
|
2316
2325
|
};
|
|
2317
|
-
const raycastInternal = (origin, dir, maxDist, ignore) => {
|
|
2326
|
+
const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false) => {
|
|
2318
2327
|
const d = vec.normalize(dir);
|
|
2319
2328
|
let best = null;
|
|
2320
2329
|
for (const o of objects.values()) {
|
|
@@ -2327,7 +2336,7 @@ var GameTestRoom = (() => {
|
|
|
2327
2336
|
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 };
|
|
2328
2337
|
}
|
|
2329
2338
|
}
|
|
2330
|
-
for (const im of impls.values()) {
|
|
2339
|
+
for (const im of worldOnly ? [] : impls.values()) {
|
|
2331
2340
|
for (const ps of im.impl.describe().parts) {
|
|
2332
2341
|
if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
|
|
2333
2342
|
const ov = im.poses.get(ps.id);
|
|
@@ -2740,7 +2749,7 @@ var GameTestRoom = (() => {
|
|
|
2740
2749
|
else if (chained && (trigger === "explosion" || 1 - p.st.hp / p.st.maxHp >= shotDamageFractionOf(p.spec))) prime(o, p, trigger, src);
|
|
2741
2750
|
return done;
|
|
2742
2751
|
};
|
|
2743
|
-
const ballistic2 = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2752
|
+
const ballistic2 = (o, p, entry, n, d, E, caliber, pathLen, src, piercing = 1) => {
|
|
2744
2753
|
const mat = p.mat;
|
|
2745
2754
|
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2746
2755
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
@@ -2753,7 +2762,9 @@ var GameTestRoom = (() => {
|
|
|
2753
2762
|
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2754
2763
|
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;
|
|
2755
2764
|
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2756
|
-
const
|
|
2765
|
+
const hard = mat.tags?.includes("metal") || mat.tags?.includes("masonry");
|
|
2766
|
+
const Ep = piercing === 1 || !hard ? E : E * piercing;
|
|
2767
|
+
const penetrated = Ep > thr;
|
|
2757
2768
|
const scale2 = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2758
2769
|
const id = p.spec.id;
|
|
2759
2770
|
if (!penetrated && mat.ricochetDeg && angle < mat.ricochetDeg) {
|
|
@@ -2765,7 +2776,7 @@ var GameTestRoom = (() => {
|
|
|
2765
2776
|
}
|
|
2766
2777
|
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2767
2778
|
const brittle = mat.ductility < 0.3;
|
|
2768
|
-
const ratio =
|
|
2779
|
+
const ratio = Ep / Math.max(thr, 1e-6);
|
|
2769
2780
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2770
2781
|
let exitPoint;
|
|
2771
2782
|
if (penetrated) {
|
|
@@ -2795,7 +2806,7 @@ var GameTestRoom = (() => {
|
|
|
2795
2806
|
debris2(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
2796
2807
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
2797
2808
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2798
|
-
return { penetrated, exitEnergy: penetrated ? E - thr : 0, exitPoint, damage: dmg };
|
|
2809
|
+
return { penetrated, exitEnergy: penetrated ? E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
2799
2810
|
};
|
|
2800
2811
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
2801
2812
|
const mat = p.mat;
|
|
@@ -2823,54 +2834,110 @@ var GameTestRoom = (() => {
|
|
|
2823
2834
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2824
2835
|
return done;
|
|
2825
2836
|
};
|
|
2837
|
+
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
2838
|
+
if (isLost(p)) return null;
|
|
2839
|
+
const { c, q } = partXf(o, p);
|
|
2840
|
+
const lp = toLocal(c, q, point);
|
|
2841
|
+
const dist = distanceToShape(p.spec.shape, lp);
|
|
2842
|
+
if (dist >= radius) return null;
|
|
2843
|
+
const sp = surfacePoint(p.spec.shape, lp);
|
|
2844
|
+
const wp = toWorld(c, q, sp.point);
|
|
2845
|
+
const reach = reachAt ? reachAt(wp, p) : 1;
|
|
2846
|
+
const f = (1 - dist / radius) ** 2 * reach;
|
|
2847
|
+
const Ein = energy * f * BLAST_COUPLING;
|
|
2848
|
+
const wn = rotateVec(q, sp.normal);
|
|
2849
|
+
if (!res.part) {
|
|
2850
|
+
res.part = p.spec.id;
|
|
2851
|
+
res.point = wp;
|
|
2852
|
+
}
|
|
2853
|
+
const mat = p.mat;
|
|
2854
|
+
if (Ein > 20) {
|
|
2855
|
+
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);
|
|
2856
|
+
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2857
|
+
debris2(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2858
|
+
}
|
|
2859
|
+
const th = mat.thermal;
|
|
2860
|
+
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2861
|
+
p.st.temperature += 300 * f;
|
|
2862
|
+
o.active = true;
|
|
2863
|
+
if (f >= 0.25 && !p.st.burning) {
|
|
2864
|
+
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2865
|
+
checkIgnite(o, p, src);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2869
|
+
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
2870
|
+
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
2871
|
+
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
2872
|
+
return reach;
|
|
2873
|
+
}
|
|
2874
|
+
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);
|
|
2875
|
+
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2876
|
+
res.damage += dealt;
|
|
2877
|
+
if (dist <= breachBlastReach * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2878
|
+
return reach;
|
|
2879
|
+
};
|
|
2826
2880
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2827
2881
|
const start = cur.length;
|
|
2828
2882
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
2829
|
-
for (const p of o.parts)
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
const
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
if (!res.part) {
|
|
2841
|
-
res.part = p.spec.id;
|
|
2842
|
-
res.point = wp;
|
|
2843
|
-
}
|
|
2844
|
-
const mat = p.mat;
|
|
2845
|
-
if (Ein > 20) {
|
|
2846
|
-
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);
|
|
2847
|
-
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2848
|
-
debris2(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2849
|
-
}
|
|
2850
|
-
const th = mat.thermal;
|
|
2851
|
-
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2852
|
-
p.st.temperature += 300 * f;
|
|
2853
|
-
o.active = true;
|
|
2854
|
-
if (f >= 0.25 && !p.st.burning) {
|
|
2855
|
-
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2856
|
-
checkIgnite(o, p, src);
|
|
2857
|
-
}
|
|
2883
|
+
for (const p of o.parts) blastPart(o, p, point, energy, radius, src, res);
|
|
2884
|
+
res.events = cur.slice(start);
|
|
2885
|
+
return res;
|
|
2886
|
+
};
|
|
2887
|
+
const blastCfg = (call3) => {
|
|
2888
|
+
const pick3 = (key) => call3 && call3[key] !== void 0 ? call3[key] : options.blast?.[key];
|
|
2889
|
+
const merge = (v, defaults) => {
|
|
2890
|
+
if (!v) return null;
|
|
2891
|
+
const out = { ...defaults };
|
|
2892
|
+
if (v !== true) {
|
|
2893
|
+
for (const [key, value] of Object.entries(v)) if (value !== void 0) out[key] = value;
|
|
2858
2894
|
}
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2895
|
+
return out;
|
|
2896
|
+
};
|
|
2897
|
+
const conf = pick3("confinement");
|
|
2898
|
+
return {
|
|
2899
|
+
shelter: merge(pick3("shelter"), SHELTER_DEFAULTS),
|
|
2900
|
+
frag: merge(pick3("fragmentHits"), FRAGMENT_HIT_DEFAULTS),
|
|
2901
|
+
conf: typeof conf === "number" ? Number.isFinite(conf) ? Math.max(0, conf) : 1 : merge(conf, CONFINEMENT_DEFAULTS)
|
|
2902
|
+
};
|
|
2903
|
+
};
|
|
2904
|
+
const confinementRays = /* @__PURE__ */ new Map();
|
|
2905
|
+
const raysOf = (n) => {
|
|
2906
|
+
let rays = confinementRays.get(n);
|
|
2907
|
+
if (rays) return rays;
|
|
2908
|
+
const r = createRng((options.seed ?? 1) ^ 1540483477);
|
|
2909
|
+
const off = r.next();
|
|
2910
|
+
const phi0 = r.next() * Math.PI * 2;
|
|
2911
|
+
rays = [];
|
|
2912
|
+
for (let i = 0; i < n; i++) {
|
|
2913
|
+
const y = 1 - 2 * (i + off) / n;
|
|
2914
|
+
const s = Math.sqrt(Math.max(0, 1 - y * y));
|
|
2915
|
+
const phi = phi0 + i * 2.399963229728653;
|
|
2916
|
+
rays.push([Math.cos(phi) * s, y, Math.sin(phi) * s]);
|
|
2917
|
+
}
|
|
2918
|
+
confinementRays.set(n, rays);
|
|
2919
|
+
return rays;
|
|
2920
|
+
};
|
|
2921
|
+
const confinementOf = (conf, point, radius) => {
|
|
2922
|
+
if (conf === null) return 1;
|
|
2923
|
+
if (typeof conf === "number") return conf;
|
|
2924
|
+
let hits = 0;
|
|
2925
|
+
let sum2 = 0;
|
|
2926
|
+
for (const d of raysOf(Math.max(1, Math.round(conf.rays)))) {
|
|
2927
|
+
const h = raycastInternal(point, d, radius * conf.reach, void 0, true);
|
|
2928
|
+
if (h) {
|
|
2929
|
+
hits++;
|
|
2930
|
+
sum2 += h.distance;
|
|
2864
2931
|
}
|
|
2865
|
-
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);
|
|
2866
|
-
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2867
|
-
res.damage += dealt;
|
|
2868
|
-
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2869
2932
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
2933
|
+
if (hits === 0) return 1;
|
|
2934
|
+
const closed = hits / Math.max(1, Math.round(conf.rays));
|
|
2935
|
+
const ratio = clamp2(radius / Math.max(sum2 / hits, 1e-3), 0, 2);
|
|
2936
|
+
return Math.min(Math.max(1, conf.max), 1 + conf.gain * closed ** conf.power * ratio);
|
|
2872
2937
|
};
|
|
2873
|
-
const
|
|
2938
|
+
const FLAG_LOST = 1;
|
|
2939
|
+
const FLAG_OPEN = 2;
|
|
2940
|
+
const blastPlain = (point, energy, radius, src) => {
|
|
2874
2941
|
const out = [];
|
|
2875
2942
|
for (const o of [...objects.values()]) {
|
|
2876
2943
|
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
@@ -2878,16 +2945,225 @@ var GameTestRoom = (() => {
|
|
|
2878
2945
|
if (r.part) out.push(r);
|
|
2879
2946
|
}
|
|
2880
2947
|
for (const im of [...impls.values()]) {
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2948
|
+
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
2949
|
+
}
|
|
2950
|
+
return out;
|
|
2951
|
+
};
|
|
2952
|
+
const implParts = (im) => im.impl.describe().parts.map((ps) => {
|
|
2953
|
+
const ov = im.poses.get(ps.id);
|
|
2954
|
+
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])) };
|
|
2955
|
+
});
|
|
2956
|
+
const implInRange = (im, point, radius) => implParts(im).some(({ ps, c, q }) => distanceToShape(ps.shape, toLocal(c, q, point)) < radius);
|
|
2957
|
+
const blastScene = (point, energy, radius, src, cfg, only) => {
|
|
2958
|
+
const mult = confinementOf(cfg.conf, point, radius);
|
|
2959
|
+
const E = energy * mult;
|
|
2960
|
+
const shelter = cfg.shelter;
|
|
2961
|
+
const flags = /* @__PURE__ */ new Map();
|
|
2962
|
+
const inRange = [];
|
|
2963
|
+
for (const o of objects.values()) {
|
|
2964
|
+
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
2965
|
+
inRange.push(o);
|
|
2966
|
+
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));
|
|
2967
|
+
}
|
|
2968
|
+
const shields = /* @__PURE__ */ new Map();
|
|
2969
|
+
const reachAt = (tp, self) => {
|
|
2970
|
+
if (!shelter) return 1;
|
|
2971
|
+
const L = vec.distance(point, tp);
|
|
2972
|
+
if (L < 1e-4) return 1;
|
|
2973
|
+
const d = vec.scale(vec.sub(tp, point), 1 / L);
|
|
2974
|
+
let t = 1;
|
|
2975
|
+
for (const o of inRange) {
|
|
2976
|
+
if (raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
|
|
2977
|
+
for (const p of o.parts) {
|
|
2978
|
+
if (p === self) continue;
|
|
2979
|
+
const was = flags.get(p) ?? 0;
|
|
2980
|
+
if (was & FLAG_LOST) continue;
|
|
2981
|
+
const r = rayPart(o, p, point, d);
|
|
2982
|
+
if (!r) continue;
|
|
2983
|
+
const t0 = Math.max(r.t0, 0);
|
|
2984
|
+
const path = Math.min(r.t1, L) - t0;
|
|
2985
|
+
if (path < shelter.minPath) continue;
|
|
2986
|
+
const mid = vec.madd(point, d, t0 + path / 2);
|
|
2987
|
+
const opening = p.st.opening && insideOpening(o, p, mid);
|
|
2988
|
+
if (opening && was & FLAG_OPEN) continue;
|
|
2989
|
+
if (p.st.stripped && was & FLAG_OPEN) continue;
|
|
2990
|
+
const resistance = p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1);
|
|
2991
|
+
let ti = Math.exp(-resistance / shelter.k);
|
|
2992
|
+
if (isLost(p) || opening || p.st.stripped && !(was & FLAG_OPEN)) ti += (1 - ti) * shelter.brokenTransmission;
|
|
2993
|
+
t *= ti;
|
|
2994
|
+
if (cfg.frag) {
|
|
2995
|
+
let list = shields.get(p);
|
|
2996
|
+
if (!list) shields.set(p, list = []);
|
|
2997
|
+
list.push(tp);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
return t;
|
|
3002
|
+
};
|
|
3003
|
+
const items = [];
|
|
3004
|
+
let order = 0;
|
|
3005
|
+
for (const o of inRange) {
|
|
3006
|
+
if (only && only !== o) continue;
|
|
3007
|
+
for (const p of o.parts) {
|
|
3008
|
+
if (isLost(p)) continue;
|
|
3009
|
+
const dist = distToPart(o, p, point);
|
|
3010
|
+
if (dist < radius) items.push({ dist, order: order++, o, p });
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
for (const im of impls.values()) {
|
|
3014
|
+
if (only && only !== im) continue;
|
|
3015
|
+
let dist = Infinity;
|
|
3016
|
+
for (const { ps, c, q } of implParts(im)) dist = Math.min(dist, distanceToShape(ps.shape, toLocal(c, q, point)));
|
|
3017
|
+
if (dist < radius) items.push({ dist, order: order++, im });
|
|
3018
|
+
}
|
|
3019
|
+
items.sort((a, b) => a.dist - b.dist || a.order - b.order);
|
|
3020
|
+
const results = /* @__PURE__ */ new Map();
|
|
3021
|
+
const reports = /* @__PURE__ */ new Map();
|
|
3022
|
+
const report = (key) => {
|
|
3023
|
+
let r = reports.get(key);
|
|
3024
|
+
if (!r) reports.set(key, r = { shelter: 0, confinement: mult, fragmentHits: 0 });
|
|
3025
|
+
return r;
|
|
3026
|
+
};
|
|
3027
|
+
const thrown = [];
|
|
3028
|
+
const sweep = (from) => {
|
|
3029
|
+
if (!cfg.frag) return;
|
|
3030
|
+
for (let i = from; i < cur.length; i++) {
|
|
3031
|
+
const e = cur[i];
|
|
3032
|
+
if (e.type !== "fracture" && e.type !== "stage") continue;
|
|
3033
|
+
for (const frag of e.fragments ?? []) thrown.push({ frag, object: e.object, part: objects.get(e.object)?.byId.get(e.part) ?? null });
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
for (const it of items) {
|
|
3037
|
+
if (it.o && it.p) {
|
|
3038
|
+
const o = it.o;
|
|
3039
|
+
let res = results.get(o);
|
|
3040
|
+
if (!res) results.set(o, res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] });
|
|
3041
|
+
const start = cur.length;
|
|
3042
|
+
const k = blastPart(o, it.p, point, E, radius, src, res, shelter ? reachAt : void 0);
|
|
3043
|
+
res.events.push(...cur.slice(start));
|
|
3044
|
+
sweep(start);
|
|
3045
|
+
if (k !== null) {
|
|
3046
|
+
const rep = report(o);
|
|
3047
|
+
rep.shelter = Math.max(rep.shelter, k);
|
|
3048
|
+
if (shelter) (rep.parts ?? (rep.parts = {}))[it.p.spec.id] = k;
|
|
3049
|
+
}
|
|
3050
|
+
} else if (it.im) {
|
|
3051
|
+
const im = it.im;
|
|
3052
|
+
let num = 0;
|
|
3053
|
+
let den = 0;
|
|
3054
|
+
for (const { ps, c, q } of implParts(im)) {
|
|
3055
|
+
const lp = toLocal(c, q, point);
|
|
3056
|
+
const dist = distanceToShape(ps.shape, lp);
|
|
3057
|
+
if (dist >= radius) continue;
|
|
3058
|
+
const f = (1 - dist / radius) ** 2;
|
|
3059
|
+
const sp = surfacePoint(ps.shape, lp);
|
|
3060
|
+
num += f * reachAt(toWorld(c, q, sp.point), null);
|
|
3061
|
+
den += f;
|
|
3062
|
+
}
|
|
3063
|
+
const k = den > 0 ? num / den : 1;
|
|
3064
|
+
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
3065
|
+
results.set(im, res);
|
|
3066
|
+
report(im).shelter = k;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
if (cfg.frag && thrown.length > 0) {
|
|
3070
|
+
const g = cfg.frag;
|
|
3071
|
+
const energyOf2 = (f) => 0.5 * f.mass * (f.velocity[0] ** 2 + f.velocity[1] ** 2 + f.velocity[2] ** 2);
|
|
3072
|
+
const list = thrown.map((t, i) => ({ ...t, i, e: energyOf2(t.frag) })).sort((a, b) => b.e - a.e || a.i - b.i).slice(0, Math.max(0, Math.floor(g.max)));
|
|
3073
|
+
list.forEach((t, i) => throwFragment(t.frag, t.object, t.part, g, point, i, src, shields, results, reports, mult));
|
|
2888
3074
|
}
|
|
3075
|
+
const out = [];
|
|
3076
|
+
const reportShelter = (key, res) => {
|
|
3077
|
+
res.blast = reports.get(key) ?? { shelter: 1, confinement: mult, fragmentHits: 0 };
|
|
3078
|
+
};
|
|
3079
|
+
const summary = [];
|
|
3080
|
+
let fragmentHits = 0;
|
|
3081
|
+
for (const o of objects.values()) {
|
|
3082
|
+
const r = results.get(o);
|
|
3083
|
+
if (!r?.part) continue;
|
|
3084
|
+
reportShelter(o, r);
|
|
3085
|
+
out.push(r);
|
|
3086
|
+
for (const [part, factor] of Object.entries(r.blast?.parts ?? {})) summary.push({ object: o.id, part, factor });
|
|
3087
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
3088
|
+
}
|
|
3089
|
+
for (const im of impls.values()) {
|
|
3090
|
+
const r = results.get(im);
|
|
3091
|
+
if (!r) continue;
|
|
3092
|
+
reportShelter(im, r);
|
|
3093
|
+
out.push(r);
|
|
3094
|
+
if (shelter) summary.push({ object: im.impl.id, factor: r.blast?.shelter ?? 1 });
|
|
3095
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
3096
|
+
}
|
|
3097
|
+
emit2({ type: "blast", object: "", time, ...src !== void 0 ? { source: src } : {}, point, energy: E, radius, confinement: mult, shelter: summary, fragmentHits });
|
|
2889
3098
|
return out;
|
|
2890
3099
|
};
|
|
3100
|
+
const seedOf = (...nums) => {
|
|
3101
|
+
let h = 2166136261;
|
|
3102
|
+
for (const n of nums) {
|
|
3103
|
+
h = Math.imul(h ^ (Math.round(n * 1e3) | 0), 16777619);
|
|
3104
|
+
h ^= h >>> 15;
|
|
3105
|
+
}
|
|
3106
|
+
return h | 0;
|
|
3107
|
+
};
|
|
3108
|
+
const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
|
|
3109
|
+
const v = frag.velocity;
|
|
3110
|
+
const speed = Math.hypot(v[0], v[1], v[2]);
|
|
3111
|
+
const energy = g.energyFactor * 0.5 * frag.mass * speed * speed;
|
|
3112
|
+
if (speed < 0.5 || energy < 1) return;
|
|
3113
|
+
const r = createRng(seedOf(options.seed ?? 1, centre[0], centre[1], centre[2], index, frag.center[0], frag.center[1], frag.center[2]));
|
|
3114
|
+
let dir = vec.scale(v, 1 / speed);
|
|
3115
|
+
const behind = from ? shields.get(from) : void 0;
|
|
3116
|
+
if (behind) {
|
|
3117
|
+
let best = Math.cos(g.aim * Math.PI / 180);
|
|
3118
|
+
for (const tp of behind) {
|
|
3119
|
+
const to = vec.sub(tp, frag.center);
|
|
3120
|
+
const len = vec.length(to);
|
|
3121
|
+
if (len < 1e-4) continue;
|
|
3122
|
+
const cos2 = vec.dot(dir, to) / len;
|
|
3123
|
+
if (cos2 >= best) {
|
|
3124
|
+
best = cos2;
|
|
3125
|
+
dir = vec.scale(to, 1 / len);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
const spread3 = Math.tan(g.spread * Math.PI / 180);
|
|
3130
|
+
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);
|
|
3131
|
+
const range2 = Math.min(g.range, speed * g.flightTime);
|
|
3132
|
+
const origin = vec.madd(frag.center, dir, 0.02);
|
|
3133
|
+
const rh = raycastInternal(origin, dir, range2, from ? /* @__PURE__ */ new Set([`${objectId}\0${from.spec.id}`]) : void 0);
|
|
3134
|
+
if (!rh) return;
|
|
3135
|
+
const hit = {
|
|
3136
|
+
type: g.hitType,
|
|
3137
|
+
point: rh.point,
|
|
3138
|
+
direction: dir,
|
|
3139
|
+
energy,
|
|
3140
|
+
radius: 0.05,
|
|
3141
|
+
part: rh.part,
|
|
3142
|
+
...g.hitType === "ballistic" ? { caliber: clamp2(Math.max(...frag.size) * 1e3, 3, 30), mass: frag.mass * 1e3 } : {},
|
|
3143
|
+
...src !== void 0 ? { source: src } : {}
|
|
3144
|
+
};
|
|
3145
|
+
emit2({ 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 });
|
|
3146
|
+
const im = impls.get(rh.object);
|
|
3147
|
+
const o = im ? void 0 : objects.get(rh.object);
|
|
3148
|
+
const key = im ?? o;
|
|
3149
|
+
if (!key) return;
|
|
3150
|
+
const res = im ? implHit(im, hit) : doHit(o, hit, o.byId.get(rh.part));
|
|
3151
|
+
const have = results.get(key);
|
|
3152
|
+
if (have) {
|
|
3153
|
+
have.damage += res.damage;
|
|
3154
|
+
have.events.push(...res.events);
|
|
3155
|
+
if (!have.part) have.part = res.part;
|
|
3156
|
+
if (res.penetrated) have.penetrated = true;
|
|
3157
|
+
} else results.set(key, { ...res, events: [...res.events] });
|
|
3158
|
+
let rep = reports.get(key);
|
|
3159
|
+
if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
|
|
3160
|
+
rep.fragmentHits++;
|
|
3161
|
+
};
|
|
3162
|
+
const blastWorld = (point, energy, radius, src, call3, only) => {
|
|
3163
|
+
const cfg = blastCfg(call3);
|
|
3164
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return blastPlain(point, energy, radius, src);
|
|
3165
|
+
return blastScene(point, energy, radius, src, cfg, only);
|
|
3166
|
+
};
|
|
2891
3167
|
const heatObject = (o, point, T, radius, duration, src) => {
|
|
2892
3168
|
const start = cur.length;
|
|
2893
3169
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
@@ -2924,7 +3200,7 @@ var GameTestRoom = (() => {
|
|
|
2924
3200
|
if (hit.type === "ballistic") {
|
|
2925
3201
|
const loc = locate(o, point, dir, 0.05, only);
|
|
2926
3202
|
if (loc) {
|
|
2927
|
-
const out = ballistic2(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src);
|
|
3203
|
+
const out = ballistic2(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit.piercing ?? 1);
|
|
2928
3204
|
res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
|
|
2929
3205
|
}
|
|
2930
3206
|
} else if (hit.type === "blunt" || hit.type === "slash") {
|
|
@@ -2946,7 +3222,13 @@ var GameTestRoom = (() => {
|
|
|
2946
3222
|
}
|
|
2947
3223
|
}
|
|
2948
3224
|
} else if (hit.type === "explosive") {
|
|
2949
|
-
|
|
3225
|
+
const call3 = hit.blast || hit.confinement !== void 0 ? { ...hit.blast, ...hit.confinement !== void 0 ? { confinement: hit.confinement } : {} } : void 0;
|
|
3226
|
+
const cfg = blastCfg(call3);
|
|
3227
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) res = blastObject(o, point, E, hit.radius ?? 2, src);
|
|
3228
|
+
else {
|
|
3229
|
+
res = blastWorld(point, E, hit.radius ?? 2, src, call3, o)[0] ?? res;
|
|
3230
|
+
res.events = cur.slice(start);
|
|
3231
|
+
}
|
|
2950
3232
|
} else {
|
|
2951
3233
|
res = heatObject(o, point, hit.temperature ?? 800, hit.radius ?? 0.25, hit.duration ?? 1, src);
|
|
2952
3234
|
}
|
|
@@ -3203,6 +3485,14 @@ var GameTestRoom = (() => {
|
|
|
3203
3485
|
for (const e of r.events) if (e.type === "collapse" && !r.value.some((h) => h.events.includes(e))) last.events.push(e);
|
|
3204
3486
|
return r.value;
|
|
3205
3487
|
};
|
|
3488
|
+
const blastImpl = (im, h) => {
|
|
3489
|
+
const call3 = h.blast || h.confinement !== void 0 ? { ...h.blast, ...h.confinement !== void 0 ? { confinement: h.confinement } : {} } : void 0;
|
|
3490
|
+
const cfg = blastCfg(call3);
|
|
3491
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return implHit(im, h);
|
|
3492
|
+
const world2 = h.space === "local" ? toWorld(im.position, im.rotation, h.point) : h.point;
|
|
3493
|
+
const r = blastWorld(world2, h.energy ?? 0, h.radius ?? 2, h.source, call3, im);
|
|
3494
|
+
return r.find((x) => x.object === im.impl.id) ?? implHit(im, h);
|
|
3495
|
+
};
|
|
3206
3496
|
const whole = (fn) => {
|
|
3207
3497
|
const r = run(fn);
|
|
3208
3498
|
r.value.events = r.events;
|
|
@@ -3290,7 +3580,7 @@ var GameTestRoom = (() => {
|
|
|
3290
3580
|
},
|
|
3291
3581
|
hit(objectId, h) {
|
|
3292
3582
|
const im = impls.get(objectId);
|
|
3293
|
-
if (im) return whole(() => implHit(im, h));
|
|
3583
|
+
if (im) return whole(() => h.type === "explosive" ? blastImpl(im, h) : implHit(im, h));
|
|
3294
3584
|
const o = getObj(objectId);
|
|
3295
3585
|
return whole(() => doHit(o, h));
|
|
3296
3586
|
},
|
|
@@ -3301,14 +3591,14 @@ var GameTestRoom = (() => {
|
|
|
3301
3591
|
const p = getPart(o, partId);
|
|
3302
3592
|
return whole(() => doHit(o, h, p));
|
|
3303
3593
|
},
|
|
3304
|
-
explode(point, energy, radius, source) {
|
|
3305
|
-
return withCollapses(run(() => blastWorld(point, energy, radius, source)));
|
|
3594
|
+
explode(point, energy, radius, source, blast) {
|
|
3595
|
+
return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
|
|
3306
3596
|
},
|
|
3307
3597
|
raycast(origin, direction, maxDistance = Infinity) {
|
|
3308
3598
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
3309
3599
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
3310
3600
|
},
|
|
3311
|
-
shoot(origin, direction, energy, caliber, source) {
|
|
3601
|
+
shoot(origin, direction, energy, caliber, source, piercing) {
|
|
3312
3602
|
return withCollapses(run(() => {
|
|
3313
3603
|
const results = [];
|
|
3314
3604
|
const d = vec.normalize(direction);
|
|
@@ -3320,7 +3610,7 @@ var GameTestRoom = (() => {
|
|
|
3320
3610
|
if (!rh) break;
|
|
3321
3611
|
const im = impls.get(rh.object);
|
|
3322
3612
|
if (im) {
|
|
3323
|
-
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...source !== void 0 ? { source } : {} });
|
|
3613
|
+
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...piercing !== void 0 ? { piercing } : {}, ...source !== void 0 ? { source } : {} });
|
|
3324
3614
|
r.part = rh.part;
|
|
3325
3615
|
r.point = rh.point;
|
|
3326
3616
|
r.exitEnergy = r.penetrated ? E * 0.5 : 0;
|
|
@@ -3334,7 +3624,7 @@ var GameTestRoom = (() => {
|
|
|
3334
3624
|
const o = getObj(rh.object);
|
|
3335
3625
|
const p = getPart(o, rh.part);
|
|
3336
3626
|
const start = cur.length;
|
|
3337
|
-
const out = ballistic2(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source);
|
|
3627
|
+
const out = ballistic2(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, piercing ?? 1);
|
|
3338
3628
|
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) });
|
|
3339
3629
|
ignore.add(`${o.id}\0${p.spec.id}`);
|
|
3340
3630
|
if (!out.penetrated || !out.exitPoint) break;
|
|
@@ -3704,6 +3994,8 @@ var GameTestRoom = (() => {
|
|
|
3704
3994
|
stage: "damage stage",
|
|
3705
3995
|
expose: "rebar bare",
|
|
3706
3996
|
body: "body",
|
|
3997
|
+
blast: "blast",
|
|
3998
|
+
fragment_hit: "fragment hit",
|
|
3707
3999
|
destroyed: "destroyed"
|
|
3708
4000
|
},
|
|
3709
4001
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3790,6 +4082,8 @@ var GameTestRoom = (() => {
|
|
|
3790
4082
|
stage: "est\xE1gio de dano",
|
|
3791
4083
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
3792
4084
|
body: "corpo",
|
|
4085
|
+
blast: "explos\xE3o",
|
|
4086
|
+
fragment_hit: "estilha\xE7o atingiu",
|
|
3793
4087
|
destroyed: "destru\xEDdo"
|
|
3794
4088
|
},
|
|
3795
4089
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|
|
@@ -15699,6 +15993,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15699
15993
|
if (options.visual !== false) this.playExplosion(ev, options);
|
|
15700
15994
|
this.vfx.push(p, ev.radius, 4);
|
|
15701
15995
|
const c = this.v1;
|
|
15996
|
+
const report = options.report;
|
|
15997
|
+
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
15998
|
+
const energy = ev.energy * confinement;
|
|
15702
15999
|
for (const pc of this.pieceSys.list) {
|
|
15703
16000
|
this.pieceSys.centerOf(pc, c);
|
|
15704
16001
|
const dx = c.x - p[0];
|
|
@@ -15707,13 +16004,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15707
16004
|
const d = Math.hypot(dx, dy, dz);
|
|
15708
16005
|
if (d > ev.radius) continue;
|
|
15709
16006
|
const fall = (1 - d / ev.radius) ** 2;
|
|
15710
|
-
const
|
|
16007
|
+
const reach = report?.find((r) => r.object === pc.objectId)?.blast?.shelter ?? 1;
|
|
16008
|
+
const speed = Math.min(30, Math.sqrt(2 * energy * reach * 0.02 / pc.mass)) * fall;
|
|
15711
16009
|
const inv = d > 1e-4 ? 1 / d : 0;
|
|
15712
16010
|
const dir = d > 1e-4 ? [dx * inv, dy * inv + 0.3, dz * inv] : [0, 1, 0];
|
|
15713
16011
|
const spread2 = pc.halfExtents[0];
|
|
15714
16012
|
this.pieceSys.impulse(pc, [dir[0] * speed * pc.mass, dir[1] * speed * pc.mass, dir[2] * speed * pc.mass], [c.x + (this.rng() - 0.5) * spread2, c.y + spread2 * 0.5, c.z + (this.rng() - 0.5) * spread2]);
|
|
15715
16013
|
}
|
|
15716
|
-
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(
|
|
16014
|
+
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(energy) * 0.08));
|
|
15717
16015
|
this.sound("explode", p, clamp6(0.5 + ev.radius / 10, 0.5, 1));
|
|
15718
16016
|
}
|
|
15719
16017
|
playExplosion(ev, options) {
|
|
@@ -34349,6 +34647,16 @@ void main(){
|
|
|
34349
34647
|
"ui.slow": "Slow motion 0.25\xD7",
|
|
34350
34648
|
"ui.fast": "Time \xD710",
|
|
34351
34649
|
"ui.tap": "Tap",
|
|
34650
|
+
"ui.blast.shelter": "Shelter",
|
|
34651
|
+
"ui.blast.fragmentHits": "Fragments",
|
|
34652
|
+
"ui.blast.confinement": "Confinement",
|
|
34653
|
+
"ui.blast.shelter.hint": "Blasts: walls and other parts between the blast and the target take their share",
|
|
34654
|
+
"ui.blast.fragmentHits.hint": "Blasts: the pieces of what breaks fly on and hit what is behind",
|
|
34655
|
+
"ui.blast.confinement.hint": "Blasts: pressure builds up in an enclosed room",
|
|
34656
|
+
"metric.blast_shelter": "Blast reaching (%)",
|
|
34657
|
+
"metric.blast_confinement": "Blast pressure (x)",
|
|
34658
|
+
"metric.blast_fragments": "Fragment hits",
|
|
34659
|
+
"act.blast_report": "Blast: {shelter}% reached the most sheltered target, pressure x{confinement}, {fragments} fragment hits, {n} targets",
|
|
34352
34660
|
"ui.quality": "Quality",
|
|
34353
34661
|
"ui.quality.low": "low",
|
|
34354
34662
|
"ui.quality.medium": "medium",
|
|
@@ -34674,6 +34982,16 @@ void main(){
|
|
|
34674
34982
|
"ui.slow": "C\xE2mera lenta 0,25\xD7",
|
|
34675
34983
|
"ui.fast": "Tempo \xD710",
|
|
34676
34984
|
"ui.tap": "Torneira",
|
|
34985
|
+
"ui.blast.shelter": "Abrigo",
|
|
34986
|
+
"ui.blast.fragmentHits": "Estilha\xE7os",
|
|
34987
|
+
"ui.blast.confinement": "Confinamento",
|
|
34988
|
+
"ui.blast.shelter.hint": "Explos\xF5es: paredes e outras partes entre a explos\xE3o e o alvo ficam com a sua parte",
|
|
34989
|
+
"ui.blast.fragmentHits.hint": "Explos\xF5es: os peda\xE7os do que quebra voam e acertam o que est\xE1 atr\xE1s",
|
|
34990
|
+
"ui.blast.confinement.hint": "Explos\xF5es: a press\xE3o sobe num ambiente fechado",
|
|
34991
|
+
"metric.blast_shelter": "Explos\xE3o que chegou (%)",
|
|
34992
|
+
"metric.blast_confinement": "Press\xE3o da explos\xE3o (x)",
|
|
34993
|
+
"metric.blast_fragments": "Estilha\xE7os que acertaram",
|
|
34994
|
+
"act.blast_report": "Explos\xE3o: {shelter}% chegou ao alvo mais abrigado, press\xE3o x{confinement}, {fragments} estilha\xE7os acertaram, {n} alvos",
|
|
34677
34995
|
"ui.quality": "Qualidade",
|
|
34678
34996
|
"ui.quality.low": "baixa",
|
|
34679
34997
|
"ui.quality.medium": "m\xE9dia",
|
|
@@ -35396,6 +35714,16 @@ void main(){
|
|
|
35396
35714
|
|
|
35397
35715
|
// src/explosives.ts
|
|
35398
35716
|
var CONE_BLAST_RADIUS = 0.5;
|
|
35717
|
+
var summarizeBlast = (results) => {
|
|
35718
|
+
const reports = results.flatMap((r) => r.blast ? [r.blast] : []);
|
|
35719
|
+
if (reports.length === 0) return void 0;
|
|
35720
|
+
return {
|
|
35721
|
+
shelter: Math.min(...reports.map((r) => r.shelter)),
|
|
35722
|
+
confinement: reports[0].confinement,
|
|
35723
|
+
fragmentHits: reports.reduce((n, r) => n + r.fragmentHits, 0),
|
|
35724
|
+
targets: reports.length
|
|
35725
|
+
};
|
|
35726
|
+
};
|
|
35399
35727
|
var toArray = (vector) => [vector.x, vector.y, vector.z];
|
|
35400
35728
|
function createExplosives(context) {
|
|
35401
35729
|
const { THREE, scene: scene2, camera: camera2, world, renderer, arsenal, translate, format, action } = context;
|
|
@@ -35542,17 +35870,27 @@ void main(){
|
|
|
35542
35870
|
if (box2.isEmpty()) box2.expandByPoint(object.position);
|
|
35543
35871
|
const at3 = mostExposedPoint(box2, detonation);
|
|
35544
35872
|
if (!at3) continue;
|
|
35545
|
-
|
|
35873
|
+
const results = world.explode(at3, detonation.energy * blastEffect(detonation, at3).falloff, CONE_BLAST_RADIUS, "player", context.blast());
|
|
35874
|
+
reached += results.length;
|
|
35875
|
+
told(results);
|
|
35546
35876
|
}
|
|
35547
35877
|
return reached;
|
|
35548
35878
|
};
|
|
35879
|
+
const told = (results) => {
|
|
35880
|
+
const summary = summarizeBlast(results);
|
|
35881
|
+
if (!summary) return;
|
|
35882
|
+
context.blasted(summary);
|
|
35883
|
+
action("blast_report", translate("act.blast_report", { shelter: format(summary.shelter * 100, 0), confinement: format(summary.confinement, 2), fragments: summary.fragmentHits, n: summary.targets }), { data: { ...summary } });
|
|
35884
|
+
};
|
|
35549
35885
|
const detonate = (projectile, detonation) => {
|
|
35550
35886
|
const weaponName = context.toolName(projectile.weapon);
|
|
35551
35887
|
const reach = Math.max(detonation.injuryRadius, detonation.lethalRadius);
|
|
35552
|
-
const
|
|
35888
|
+
const results = detonation.energy <= 0 || detonation.cone ? [] : world.explode(detonation.point, detonation.energy, reach, "player", context.blast());
|
|
35889
|
+
const reached = detonation.energy <= 0 ? 0 : detonation.cone ? coneBlast(detonation) : results.length;
|
|
35890
|
+
if (!detonation.cone) told(results);
|
|
35553
35891
|
if (detonation.energy > 0) {
|
|
35554
35892
|
const isPlayed = context.effects.explosion(detonation.explosive, detonation.point, { direction: detonation.direction }) !== null;
|
|
35555
|
-
renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed });
|
|
35893
|
+
renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed, report: results });
|
|
35556
35894
|
}
|
|
35557
35895
|
if (detonation.stunSeconds !== void 0) renderer.emit("flash", detonation.point, [0, 1, 0], 1, 1);
|
|
35558
35896
|
const onPlayer = blastEffect(detonation, toArray(camera2.position));
|
|
@@ -35849,7 +36187,7 @@ void main(){
|
|
|
35849
36187
|
}
|
|
35850
36188
|
|
|
35851
36189
|
// src/metrics.ts
|
|
35852
|
-
var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals"];
|
|
36190
|
+
var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals", "blast_shelter", "blast_confinement", "blast_fragments"];
|
|
35853
36191
|
var WINDOW_S = 0.5;
|
|
35854
36192
|
function createRoomMetrics(sources) {
|
|
35855
36193
|
const wanted = new Set(sources.ids);
|
|
@@ -35886,6 +36224,12 @@ void main(){
|
|
|
35886
36224
|
const render = ownsRender ? { calls, triangles } : gl.info?.render;
|
|
35887
36225
|
put("draw_calls", render?.calls);
|
|
35888
36226
|
put("triangles", render?.triangles);
|
|
36227
|
+
const blast = sources.blast?.();
|
|
36228
|
+
if (blast) {
|
|
36229
|
+
put("blast_shelter", blast.shelter * 100);
|
|
36230
|
+
put("blast_confinement", blast.confinement);
|
|
36231
|
+
put("blast_fragments", blast.fragmentHits);
|
|
36232
|
+
}
|
|
35889
36233
|
if (!["objects", "fragments", "particles", "decals"].some((id) => wanted.has(id))) return out;
|
|
35890
36234
|
const destruction = sources.destruction.stats();
|
|
35891
36235
|
const fluids = sources.fluids.stats();
|
|
@@ -36439,6 +36783,9 @@ void main(){
|
|
|
36439
36783
|
const blocked = (x, z) => {
|
|
36440
36784
|
if (Math.abs(x) > o.limit - PLAYER_RADIUS || Math.abs(z) > o.limit - PLAYER_RADIUS) return true;
|
|
36441
36785
|
for (const c of o.colliders) if (x > c[0] - PLAYER_RADIUS && x < c[2] + PLAYER_RADIUS && z > c[1] - PLAYER_RADIUS && z < c[3] + PLAYER_RADIUS) return true;
|
|
36786
|
+
if (o.blockers) {
|
|
36787
|
+
for (const blocked2 of o.blockers) if (blocked2(x, z, PLAYER_RADIUS)) return true;
|
|
36788
|
+
}
|
|
36442
36789
|
return false;
|
|
36443
36790
|
};
|
|
36444
36791
|
const move = (dx, dz) => {
|
|
@@ -37567,6 +37914,8 @@ void main(){
|
|
|
37567
37914
|
o.onFast(fast);
|
|
37568
37915
|
});
|
|
37569
37916
|
const tapBtn = ctl(t("ui.tap"), () => o.onTap());
|
|
37917
|
+
const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key) => ({ key, b: ctl(t(`ui.blast.${key}`), () => o.onBlast(key)) }));
|
|
37918
|
+
for (const { key, b } of blastBtns) b.title = t(`ui.blast.${key}.hint`);
|
|
37570
37919
|
const qSel = mk2("select");
|
|
37571
37920
|
for (const q of ["low", "medium", "high"]) {
|
|
37572
37921
|
const op = mk2("option", void 0, t(`ui.quality.${q}`));
|
|
@@ -37771,6 +38120,7 @@ void main(){
|
|
|
37771
38120
|
}
|
|
37772
38121
|
slotsEl.forEach((b, i) => b.classList.toggle("on", i === s.slot));
|
|
37773
38122
|
tapBtn.classList.toggle("on", s.tap);
|
|
38123
|
+
for (const { key: key2, b } of blastBtns) b.classList.toggle("on", s.blast[key2]);
|
|
37774
38124
|
count.textContent = t("ui.objects", { n: s.objects });
|
|
37775
38125
|
const ak = JSON.stringify(s.animations);
|
|
37776
38126
|
if (ak !== lastAnim) {
|
|
@@ -38084,10 +38434,12 @@ void main(){
|
|
|
38084
38434
|
log.setBeforeRead(flush);
|
|
38085
38435
|
let useHeld = false;
|
|
38086
38436
|
let useFirst = false;
|
|
38437
|
+
const blockers = /* @__PURE__ */ new Set();
|
|
38087
38438
|
const player = createPlayer(THREE, {
|
|
38088
38439
|
camera: camera2,
|
|
38089
38440
|
dom,
|
|
38090
38441
|
colliders: arena.colliders,
|
|
38442
|
+
blockers,
|
|
38091
38443
|
limit: arenaSize,
|
|
38092
38444
|
start: arena.playerStart,
|
|
38093
38445
|
hooks: {
|
|
@@ -38270,8 +38622,10 @@ void main(){
|
|
|
38270
38622
|
film.clear();
|
|
38271
38623
|
fluids.setTap(TAP_ID, false);
|
|
38272
38624
|
};
|
|
38625
|
+
const clearListeners = /* @__PURE__ */ new Set();
|
|
38273
38626
|
const clear = () => {
|
|
38274
38627
|
clearEverything();
|
|
38628
|
+
for (const listener of [...clearListeners]) listener();
|
|
38275
38629
|
restingTops.length = 0;
|
|
38276
38630
|
pending = [];
|
|
38277
38631
|
action("clear", t("act.clear"));
|
|
@@ -38652,6 +39006,12 @@ void main(){
|
|
|
38652
39006
|
bodies.push({ id: "player", box: new THREE.Box3(new THREE.Vector3(x - PLAYER_RADIUS, feet, z - PLAYER_RADIUS), new THREE.Vector3(x + PLAYER_RADIUS, y + 0.1, z + PLAYER_RADIUS)) });
|
|
38653
39007
|
return bodies;
|
|
38654
39008
|
}
|
|
39009
|
+
let blastOptions = { ...options.blast };
|
|
39010
|
+
let lastBlast;
|
|
39011
|
+
const blastOn = (key) => Boolean(blastOptions[key]);
|
|
39012
|
+
const toggleBlast = (key) => {
|
|
39013
|
+
blastOptions = { ...blastOptions, [key]: blastOn(key) ? false : true };
|
|
39014
|
+
};
|
|
38655
39015
|
const explosives = createExplosives({
|
|
38656
39016
|
THREE,
|
|
38657
39017
|
scene: scene2,
|
|
@@ -38666,6 +39026,10 @@ void main(){
|
|
|
38666
39026
|
toolName,
|
|
38667
39027
|
withoutHeld,
|
|
38668
39028
|
groundAt,
|
|
39029
|
+
blast: () => blastOptions,
|
|
39030
|
+
blasted: (summary) => {
|
|
39031
|
+
lastBlast = summary;
|
|
39032
|
+
},
|
|
38669
39033
|
translate: t,
|
|
38670
39034
|
format: num,
|
|
38671
39035
|
action,
|
|
@@ -39092,7 +39456,7 @@ void main(){
|
|
|
39092
39456
|
let metricsAcc = 0;
|
|
39093
39457
|
let ownsLoop = false;
|
|
39094
39458
|
const roomMetricIds = options.metrics === false ? [] : options.metrics ?? ROOM_METRICS;
|
|
39095
|
-
const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds });
|
|
39459
|
+
const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds, blast: () => lastBlast });
|
|
39096
39460
|
let qualityName = quality;
|
|
39097
39461
|
const syncContainers = (sdt) => {
|
|
39098
39462
|
for (const e of entries.values()) {
|
|
@@ -39211,6 +39575,7 @@ void main(){
|
|
|
39211
39575
|
timeScale,
|
|
39212
39576
|
fast: fastForward,
|
|
39213
39577
|
tap: isTapOn(),
|
|
39578
|
+
blast: { shelter: blastOn("shelter"), fragmentHits: blastOn("fragmentHits"), confinement: blastOn("confinement") },
|
|
39214
39579
|
quality: qualityName,
|
|
39215
39580
|
objects: [...entries.values()].filter((e) => !e.arena).length
|
|
39216
39581
|
};
|
|
@@ -39459,6 +39824,14 @@ void main(){
|
|
|
39459
39824
|
},
|
|
39460
39825
|
log(text, data) {
|
|
39461
39826
|
action("module_note", text, { data });
|
|
39827
|
+
},
|
|
39828
|
+
addBlocker(blocked) {
|
|
39829
|
+
blockers.add(blocked);
|
|
39830
|
+
return () => blockers.delete(blocked);
|
|
39831
|
+
},
|
|
39832
|
+
onClear(listener) {
|
|
39833
|
+
clearListeners.add(listener);
|
|
39834
|
+
return () => clearListeners.delete(listener);
|
|
39462
39835
|
}
|
|
39463
39836
|
};
|
|
39464
39837
|
const lendBench = (store) => ({
|
|
@@ -39726,6 +40099,10 @@ void main(){
|
|
|
39726
40099
|
heldTilt = rad;
|
|
39727
40100
|
},
|
|
39728
40101
|
toggleTap,
|
|
40102
|
+
blast: () => ({ ...blastOptions }),
|
|
40103
|
+
setBlast: (next) => {
|
|
40104
|
+
blastOptions = { ...blastOptions, ...next };
|
|
40105
|
+
},
|
|
39729
40106
|
pause() {
|
|
39730
40107
|
paused = true;
|
|
39731
40108
|
},
|
|
@@ -39836,6 +40213,7 @@ void main(){
|
|
|
39836
40213
|
setTool(loadout[index]);
|
|
39837
40214
|
},
|
|
39838
40215
|
onTap: () => toggleTap(),
|
|
40216
|
+
onBlast: (key) => toggleBlast(key),
|
|
39839
40217
|
onPlay: (objectId, clipId) => {
|
|
39840
40218
|
playAnimation(clipId, objectId);
|
|
39841
40219
|
},
|