@adatechnology/game-test-room 0.5.0 → 0.6.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/dist/index.d.ts +8 -0
- package/dist/index.global.js +839 -67
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +23 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.global.js
CHANGED
|
@@ -134,6 +134,31 @@ var GameTestRoom = (() => {
|
|
|
134
134
|
var positive = (n) => finite(n) && n > 0;
|
|
135
135
|
var vec3 = (v) => Array.isArray(v) && v.length === 3 && v.every(finite);
|
|
136
136
|
var nonNegative = (n) => n === void 0 || finite(n) && n >= 0;
|
|
137
|
+
var FACES = ["both", "+", "-"];
|
|
138
|
+
var BONDS = ["running", "stack"];
|
|
139
|
+
var checkStagedFields = (p, label, materials, problems) => {
|
|
140
|
+
const known = (m, what) => {
|
|
141
|
+
if (materials && !materials.has(m)) problems.push(`${label} ${what} has unknown material "${String(m)}"`);
|
|
142
|
+
};
|
|
143
|
+
(p?.layers ?? []).forEach((l, i) => {
|
|
144
|
+
known(l?.material, `layer ${i}`);
|
|
145
|
+
if (!positive(l?.thickness)) problems.push(`${label} layer ${i} thickness must be finite and positive`);
|
|
146
|
+
if (!FACES.includes(l?.faces)) problems.push(`${label} layer ${i} has an invalid faces "${String(l?.faces)}"`);
|
|
147
|
+
});
|
|
148
|
+
const r = p?.reinforcement;
|
|
149
|
+
if (r) {
|
|
150
|
+
known(r.material, "reinforcement");
|
|
151
|
+
if (!positive(r.spacing) || !positive(r.diameter)) problems.push(`${label} reinforcement spacing and diameter must be finite and positive`);
|
|
152
|
+
if (r.residualCapacity !== void 0 && !(finite(r.residualCapacity) && r.residualCapacity >= 0 && r.residualCapacity <= 1)) problems.push(`${label} reinforcement residualCapacity must be between 0 and 1`);
|
|
153
|
+
}
|
|
154
|
+
const u = p?.units;
|
|
155
|
+
if (u) {
|
|
156
|
+
known(u.mortar, "units mortar");
|
|
157
|
+
if (!vec3(u.size) || !u.size.every(positive)) problems.push(`${label} units size must be three finite positive numbers`);
|
|
158
|
+
if (!BONDS.includes(u.bond)) problems.push(`${label} units has an invalid bond "${String(u.bond)}"`);
|
|
159
|
+
}
|
|
160
|
+
if (p?.breach && p.breach.area !== void 0 && !positive(p.breach.area)) problems.push(`${label} breach area must be finite and positive`);
|
|
161
|
+
};
|
|
137
162
|
var checkSpec = (spec, options = {}) => {
|
|
138
163
|
const problems = [];
|
|
139
164
|
const warnings = [];
|
|
@@ -166,6 +191,7 @@ var GameTestRoom = (() => {
|
|
|
166
191
|
if (!nonNegative(p.structure.capacity)) problems.push(`${label} structure capacity must be a finite number >= 0`);
|
|
167
192
|
}
|
|
168
193
|
if (p?.contains && !nonNegative(p.contains.liters)) problems.push(`${label} contains.liters must be a finite number >= 0`);
|
|
194
|
+
checkStagedFields(p, label, materials, problems);
|
|
169
195
|
}
|
|
170
196
|
const jointIds = /* @__PURE__ */ new Set();
|
|
171
197
|
const edges = [];
|
|
@@ -242,6 +268,32 @@ var GameTestRoom = (() => {
|
|
|
242
268
|
});
|
|
243
269
|
return { ok: problems.length === 0, problems };
|
|
244
270
|
};
|
|
271
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
272
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
273
|
+
var deepMerge = (base, patch) => {
|
|
274
|
+
if (!patch) return base;
|
|
275
|
+
const result2 = { ...base };
|
|
276
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
277
|
+
if (value === void 0 || UNSAFE_KEYS.has(key)) continue;
|
|
278
|
+
const current = result2[key];
|
|
279
|
+
result2[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
|
|
280
|
+
}
|
|
281
|
+
return result2;
|
|
282
|
+
};
|
|
283
|
+
var mergeById = (shipped, extra) => {
|
|
284
|
+
const entries = /* @__PURE__ */ new Map();
|
|
285
|
+
for (const entry of [...shipped, ...extra ?? []]) entries.set(entry.id, entry);
|
|
286
|
+
return entries;
|
|
287
|
+
};
|
|
288
|
+
var applyPatches = (entries, patches) => {
|
|
289
|
+
for (const [id, patch] of Object.entries(patches ?? {})) {
|
|
290
|
+
const entry = entries.get(id);
|
|
291
|
+
if (entry) entries.set(id, deepMerge(entry, patch));
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
var removeIds = (entries, ids) => {
|
|
295
|
+
for (const id of ids ?? []) entries.delete(id);
|
|
296
|
+
};
|
|
245
297
|
var SNAKE_CASE = /^[a-z][a-z0-9_]*$/;
|
|
246
298
|
var finite2 = (n) => typeof n === "number" && Number.isFinite(n);
|
|
247
299
|
function checkControl(control, problems) {
|
|
@@ -691,6 +743,147 @@ var GameTestRoom = (() => {
|
|
|
691
743
|
if (out.length === 0) out.push({ center: [0, 0, 0], size: [dims[u], dims[v], th], q: frame(u, v, t), shape: "shard", weight: 1 });
|
|
692
744
|
return out;
|
|
693
745
|
};
|
|
746
|
+
var planeAxes = (thin) => thin === 1 ? [0, 2] : [thin === 0 ? 2 : 0, 1];
|
|
747
|
+
var unitGrid = (dims, thin, units, cap) => {
|
|
748
|
+
const [u, v] = planeAxes(thin);
|
|
749
|
+
let unitU = Math.min(units.size[0], dims[u]), unitV = Math.min(units.size[1], dims[v]);
|
|
750
|
+
const rowsFor = (h) => Math.ceil(dims[v] / h - 1e-9);
|
|
751
|
+
const colsFor = (w) => Math.ceil(dims[u] / w - 1e-9) + (units.bond === "running" ? 1 : 0);
|
|
752
|
+
const merge = Math.ceil(Math.sqrt(rowsFor(unitV) * colsFor(unitU) / cap));
|
|
753
|
+
if (merge > 1) {
|
|
754
|
+
unitU = Math.min(unitU * merge, dims[u]);
|
|
755
|
+
unitV = Math.min(unitV * merge, dims[v]);
|
|
756
|
+
}
|
|
757
|
+
const cells = [];
|
|
758
|
+
for (let row = 0; row < rowsFor(unitV); row++) {
|
|
759
|
+
const v0 = -dims[v] / 2 + row * unitV, v1 = Math.min(v0 + unitV, dims[v] / 2);
|
|
760
|
+
const shift = units.bond === "running" && row % 2 === 1 ? unitU / 2 : 0;
|
|
761
|
+
for (let col = 0; col < colsFor(unitU); col++) {
|
|
762
|
+
const u0 = Math.max(-dims[u] / 2 + col * unitU - shift, -dims[u] / 2), u1 = Math.min(-dims[u] / 2 + (col + 1) * unitU - shift, dims[u] / 2);
|
|
763
|
+
if (u1 - u0 < 0.01) continue;
|
|
764
|
+
const center = [0, 0, 0], size = [0, 0, 0];
|
|
765
|
+
center[u] = (u0 + u1) / 2;
|
|
766
|
+
center[v] = (v0 + v1) / 2;
|
|
767
|
+
size[u] = u1 - u0;
|
|
768
|
+
size[v] = v1 - v0;
|
|
769
|
+
size[thin] = dims[thin];
|
|
770
|
+
cells.push({ center, size });
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return cells;
|
|
774
|
+
};
|
|
775
|
+
var planeDistance = (cell, thin, point) => {
|
|
776
|
+
const [u, v] = planeAxes(thin);
|
|
777
|
+
const du = cell.center[u] - point[u], dv = cell.center[v] - point[v];
|
|
778
|
+
return Math.sqrt(du * du + dv * dv);
|
|
779
|
+
};
|
|
780
|
+
var cellsWithin = (cells, thin, point, radius) => {
|
|
781
|
+
const inside = cells.filter((c) => planeDistance(c, thin, point) <= radius);
|
|
782
|
+
if (inside.length > 0 || cells.length === 0) return inside;
|
|
783
|
+
return [cells.reduce((best, c) => planeDistance(c, thin, point) < planeDistance(best, thin, point) ? c : best)];
|
|
784
|
+
};
|
|
785
|
+
var cellsOutside = (cells, thin, point, radius) => cells.filter((c) => planeDistance(c, thin, point) > radius);
|
|
786
|
+
var MAX_MARKS_PER_FRAGMENT = 12;
|
|
787
|
+
var marksInBox = (center, size, rotation, holes) => {
|
|
788
|
+
const inverse = rotation ? quat.conjugate(rotation) : void 0;
|
|
789
|
+
const marks = [];
|
|
790
|
+
for (const hole of holes) {
|
|
791
|
+
const offset = [hole.point[0] - center[0], hole.point[1] - center[1], hole.point[2] - center[2]];
|
|
792
|
+
const point = inverse ? rotateVec(inverse, offset) : offset;
|
|
793
|
+
const inside = point.every((value, k) => Math.abs(value) <= size[k] / 2 + hole.diameter / 2);
|
|
794
|
+
if (!inside) continue;
|
|
795
|
+
marks.push({ point, normal: inverse ? rotateVec(inverse, hole.normal) : hole.normal, diameter: hole.diameter });
|
|
796
|
+
if (marks.length >= MAX_MARKS_PER_FRAGMENT) break;
|
|
797
|
+
}
|
|
798
|
+
return marks;
|
|
799
|
+
};
|
|
800
|
+
var MAX_SPALLS = 64;
|
|
801
|
+
var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
|
|
802
|
+
var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
|
|
803
|
+
var onFace = (layer, face) => layer.def.faces === "both" || layer.def.faces === (face > 0 ? "+" : "-");
|
|
804
|
+
var isSpalled = (spalls, layer, face, point) => {
|
|
805
|
+
for (const s of spalls ?? []) {
|
|
806
|
+
if (s[0] !== layer || s[1] !== face) continue;
|
|
807
|
+
const dx = s[2] - point[0], dy = s[3] - point[1], dz = s[4] - point[2];
|
|
808
|
+
if (dx * dx + dy * dy + dz * dz <= s[5] * s[5]) return true;
|
|
809
|
+
}
|
|
810
|
+
return false;
|
|
811
|
+
};
|
|
812
|
+
var outerLayer = (layers, spalls, face, point) => layers.find((l) => onFace(l, face) && !isSpalled(spalls, l.index, face, point));
|
|
813
|
+
var layersEnergy = (layers, spalls, face, point) => {
|
|
814
|
+
let sum2 = 0;
|
|
815
|
+
for (const l of layers) if (onFace(l, face) && !isSpalled(spalls, l.index, face, point)) sum2 += l.energy;
|
|
816
|
+
return sum2;
|
|
817
|
+
};
|
|
818
|
+
var layersThickness = (layers, face) => {
|
|
819
|
+
let sum2 = 0;
|
|
820
|
+
for (const l of layers) if (onFace(l, face)) sum2 += l.def.thickness;
|
|
821
|
+
return sum2;
|
|
822
|
+
};
|
|
823
|
+
var COVER = 0.03;
|
|
824
|
+
var MAX_BARS_PER_DIRECTION = 24;
|
|
825
|
+
var MAX_MEMBER_BARS_PER_SIDE = 6;
|
|
826
|
+
var STEEL_DENSITY = 7850;
|
|
827
|
+
var spread = (length3, cover, spacing, cap) => {
|
|
828
|
+
const room = Math.max(0, length3 - 2 * cover);
|
|
829
|
+
const count = Math.min(cap, Math.max(2, Math.floor(room / spacing) + 1));
|
|
830
|
+
const step = count > 1 && room / (count - 1) < spacing ? room / (count - 1) : spacing;
|
|
831
|
+
return Array.from({ length: count }, (_, i) => (i - (count - 1) / 2) * step);
|
|
832
|
+
};
|
|
833
|
+
var bar = (axis, along, fixed) => {
|
|
834
|
+
const a = [0, 0, 0], b = [0, 0, 0];
|
|
835
|
+
for (const [index, value] of fixed) {
|
|
836
|
+
a[index] = value;
|
|
837
|
+
b[index] = value;
|
|
838
|
+
}
|
|
839
|
+
a[axis] = -along;
|
|
840
|
+
b[axis] = along;
|
|
841
|
+
return [a, b];
|
|
842
|
+
};
|
|
843
|
+
var isMember = (dims) => {
|
|
844
|
+
const [a = 0, b = 0, c = 0] = [...dims].sort((x, y) => x - y);
|
|
845
|
+
return b / a < 3 && c / b >= 3;
|
|
846
|
+
};
|
|
847
|
+
var rebarBars = (dims, thin, spacing) => {
|
|
848
|
+
const out = [];
|
|
849
|
+
if (isMember(dims)) {
|
|
850
|
+
const long = dims.indexOf(Math.max(...dims));
|
|
851
|
+
const [u2 = 0, v2 = 0] = [0, 1, 2].filter((i) => i !== long);
|
|
852
|
+
const us = spread(dims[u2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
|
|
853
|
+
const vs = spread(dims[v2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
|
|
854
|
+
us.forEach((x, i) => vs.forEach((y, j) => {
|
|
855
|
+
if (i === 0 || j === 0 || i === us.length - 1 || j === vs.length - 1) out.push(bar(long, dims[long] / 2 - 0.02, [[u2, x], [v2, y]]));
|
|
856
|
+
}));
|
|
857
|
+
return out;
|
|
858
|
+
}
|
|
859
|
+
const [u = 0, v = 0] = [0, 1, 2].filter((i) => i !== thin);
|
|
860
|
+
for (const y of spread(dims[v], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(u, dims[u] / 2 - 0.02, [[v, y], [thin, 0]]));
|
|
861
|
+
for (const x of spread(dims[u], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(v, dims[v] / 2 - 0.02, [[u, x], [thin, 0]]));
|
|
862
|
+
return out;
|
|
863
|
+
};
|
|
864
|
+
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
865
|
+
const out = [];
|
|
866
|
+
for (const [a, b] of bars) {
|
|
867
|
+
const axis = a[u] !== b[u] ? u : v;
|
|
868
|
+
const other = axis === u ? v : u;
|
|
869
|
+
const off = a[other] - center[other === u ? 0 : 1];
|
|
870
|
+
if (Math.abs(off) >= radius) continue;
|
|
871
|
+
const half = Math.sqrt(radius * radius - off * off);
|
|
872
|
+
const mid = center[axis === u ? 0 : 1];
|
|
873
|
+
const lo = Math.max(Math.min(a[axis], b[axis]), mid - half), hi = Math.min(Math.max(a[axis], b[axis]), mid + half);
|
|
874
|
+
if (hi - lo < 1e-6) continue;
|
|
875
|
+
const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
|
|
876
|
+
p[axis] = lo;
|
|
877
|
+
q[axis] = hi;
|
|
878
|
+
out.push([p, q]);
|
|
879
|
+
}
|
|
880
|
+
return out;
|
|
881
|
+
};
|
|
882
|
+
var rebarMass = (bars, diameter, density = STEEL_DENSITY) => {
|
|
883
|
+
let length3 = 0;
|
|
884
|
+
for (const [a, b] of bars) length3 += Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]);
|
|
885
|
+
return Math.PI * (diameter / 2) * (diameter / 2) * length3 * density;
|
|
886
|
+
};
|
|
694
887
|
var materials_default = [
|
|
695
888
|
{
|
|
696
889
|
id: "steel",
|
|
@@ -1692,9 +1885,19 @@ var GameTestRoom = (() => {
|
|
|
1692
1885
|
var DMG_PER_J = 0.02;
|
|
1693
1886
|
var BLAST_COUPLING = 0.1;
|
|
1694
1887
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1888
|
+
var BREACH_RADIUS = 0.55;
|
|
1889
|
+
var BREACH_BLAST_FACTOR = 6;
|
|
1890
|
+
var BREACH_AREA = 0.25;
|
|
1891
|
+
var RESIDUAL_CAPACITY = 0.15;
|
|
1892
|
+
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1893
|
+
var BREACH_GRID_CAP = 4096;
|
|
1695
1894
|
var CUT_SLICE = 1;
|
|
1696
1895
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
1697
1896
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
1897
|
+
var capacityScale = (p) => {
|
|
1898
|
+
if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
1899
|
+
return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
|
|
1900
|
+
};
|
|
1698
1901
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
1699
1902
|
var clone = (v) => structuredClone(v);
|
|
1700
1903
|
var overloadRate = (stress) => Math.min(0.6, 0.12 + 0.8 * (stress - 1));
|
|
@@ -1717,6 +1920,8 @@ var GameTestRoom = (() => {
|
|
|
1717
1920
|
const maxDents = options.maxDents ?? 24;
|
|
1718
1921
|
const maxHoles = options.maxHoles ?? 32;
|
|
1719
1922
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1923
|
+
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
1924
|
+
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1720
1925
|
const rng = createRng(options.seed ?? 1);
|
|
1721
1926
|
const rnd = () => rng.next();
|
|
1722
1927
|
let time = 0;
|
|
@@ -1821,6 +2026,13 @@ var GameTestRoom = (() => {
|
|
|
1821
2026
|
const maxHp = Math.max(1, ps.hp ?? mass0 * mat.hpPerKg);
|
|
1822
2027
|
const fuel0 = mat.thermal.ignitionC !== void 0 ? mass0 * (mat.thermal.fuelFraction ?? 0) : 0;
|
|
1823
2028
|
const c = carry?.parts.get(ps.id);
|
|
2029
|
+
const staged = !!(ps.layers?.length || ps.reinforcement || ps.units || ps.breach);
|
|
2030
|
+
const layers = (ps.layers ?? []).map((l, i) => {
|
|
2031
|
+
const lm = materials.get(l.material);
|
|
2032
|
+
if (!lm) throw new Error(`unknown material "${l.material}" on layer ${i} of part "${ps.id}"`);
|
|
2033
|
+
return resolveLayer(l, i, lm);
|
|
2034
|
+
});
|
|
2035
|
+
if (ps.reinforcement && !materials.has(ps.reinforcement.material)) throw new Error(`unknown material "${ps.reinforcement.material}" on the reinforcement of part "${ps.id}"`);
|
|
1824
2036
|
const pst = c?.st ?? {
|
|
1825
2037
|
id: ps.id,
|
|
1826
2038
|
material: ps.material,
|
|
@@ -1836,7 +2048,8 @@ var GameTestRoom = (() => {
|
|
|
1836
2048
|
dents: [],
|
|
1837
2049
|
holes: [],
|
|
1838
2050
|
cracks: [],
|
|
1839
|
-
group: 0
|
|
2051
|
+
group: 0,
|
|
2052
|
+
...staged ? { stage: 0 } : {}
|
|
1840
2053
|
};
|
|
1841
2054
|
if (!c && ps.contains) pst.liters = ps.contains.liters;
|
|
1842
2055
|
const p = {
|
|
@@ -1855,7 +2068,10 @@ var GameTestRoom = (() => {
|
|
|
1855
2068
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
1856
2069
|
structural: !!ps.structure && ps.structure.role !== "none",
|
|
1857
2070
|
anchored: !!ps.anchored || ps.structure?.role === "foundation",
|
|
1858
|
-
cap: ps.structure ? partCapacity(ps, mat) : 0
|
|
2071
|
+
cap: ps.structure ? partCapacity(ps, mat) : 0,
|
|
2072
|
+
staged,
|
|
2073
|
+
thin: thinAxisOf(shapeDims(ps.shape)),
|
|
2074
|
+
layers
|
|
1859
2075
|
};
|
|
1860
2076
|
if (p.structural) o.hasStructure = true;
|
|
1861
2077
|
if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
|
|
@@ -1995,7 +2211,7 @@ var GameTestRoom = (() => {
|
|
|
1995
2211
|
o.unsup[i] = o.anyAnchor && !isLost(p);
|
|
1996
2212
|
continue;
|
|
1997
2213
|
}
|
|
1998
|
-
p.st.stress = p.st.load / p.cap;
|
|
2214
|
+
p.st.stress = p.st.load / (p.cap * capacityScale(p));
|
|
1999
2215
|
if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
|
|
2000
2216
|
}
|
|
2001
2217
|
if (o.maxStress > 0.8) o.active = true;
|
|
@@ -2014,6 +2230,7 @@ var GameTestRoom = (() => {
|
|
|
2014
2230
|
for (const p of ps) {
|
|
2015
2231
|
o.collapsed.add(p.spec.id);
|
|
2016
2232
|
p.st.status = "collapsed";
|
|
2233
|
+
setStage(o, p, 3, {}, src);
|
|
2017
2234
|
}
|
|
2018
2235
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum2, p) => sum2 + p.st.mass, 0) });
|
|
2019
2236
|
}
|
|
@@ -2067,6 +2284,7 @@ var GameTestRoom = (() => {
|
|
|
2067
2284
|
if (isLost(p) || ignore?.has(`${o.id}\0${p.spec.id}`)) continue;
|
|
2068
2285
|
const r = rayPart(o, p, origin, d);
|
|
2069
2286
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
2287
|
+
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
2070
2288
|
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 };
|
|
2071
2289
|
}
|
|
2072
2290
|
}
|
|
@@ -2175,6 +2393,7 @@ var GameTestRoom = (() => {
|
|
|
2175
2393
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
2176
2394
|
markDamaged(p);
|
|
2177
2395
|
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
2396
|
+
setStage(o, p, 1, {}, src);
|
|
2178
2397
|
return hole;
|
|
2179
2398
|
};
|
|
2180
2399
|
const addCrack = (o, p, wp, wn, length3, src) => {
|
|
@@ -2183,6 +2402,7 @@ var GameTestRoom = (() => {
|
|
|
2183
2402
|
if (p.st.cracks.length > 16) p.st.cracks.shift();
|
|
2184
2403
|
markDamaged(p);
|
|
2185
2404
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
2405
|
+
setStage(o, p, 1, {}, src);
|
|
2186
2406
|
};
|
|
2187
2407
|
const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
|
|
2188
2408
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
@@ -2231,6 +2451,7 @@ var GameTestRoom = (() => {
|
|
|
2231
2451
|
if (split && mine.length) amount /= mine.length;
|
|
2232
2452
|
for (const j of mine) {
|
|
2233
2453
|
if (j.st.open) continue;
|
|
2454
|
+
if (j.spec.kind === "rebar" && (o.parts[j.ia].spec.reinforcement || o.parts[j.ib].spec.reinforcement)) continue;
|
|
2234
2455
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
2235
2456
|
j.st.hp -= amount;
|
|
2236
2457
|
if (j.st.hp <= 0) {
|
|
@@ -2244,23 +2465,172 @@ var GameTestRoom = (() => {
|
|
|
2244
2465
|
}
|
|
2245
2466
|
}
|
|
2246
2467
|
};
|
|
2468
|
+
const setStage = (o, p, stage, extra = {}, src) => {
|
|
2469
|
+
if (!p.staged || (p.st.stage ?? 0) >= stage) return;
|
|
2470
|
+
p.st.stage = stage;
|
|
2471
|
+
emit({ ...base(o, src), type: "stage", part: p.spec.id, stage, ...extra });
|
|
2472
|
+
};
|
|
2473
|
+
const faceOf = (o, p, worldNormal) => rotateVec(quat.conjugate(partXf(o, p).q), worldNormal)[p.thin] >= 0 ? 1 : -1;
|
|
2474
|
+
const spallOuter = (o, p, wp, face, normal, radius, src) => {
|
|
2475
|
+
var _a;
|
|
2476
|
+
if (p.layers.length === 0) return;
|
|
2477
|
+
const lp = objLocal(o, wp);
|
|
2478
|
+
const layer = outerLayer(p.layers, p.rt.spalls, face, lp);
|
|
2479
|
+
if (!layer) return;
|
|
2480
|
+
const spalls = (_a = p.rt).spalls ?? (_a.spalls = []);
|
|
2481
|
+
spalls.push([layer.index, face, lp[0], lp[1], lp[2], radius]);
|
|
2482
|
+
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
2483
|
+
markDamaged(p);
|
|
2484
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
2485
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
2486
|
+
setStage(o, p, 1, {}, src);
|
|
2487
|
+
};
|
|
2488
|
+
const insideOpening = (o, p, objectPoint) => {
|
|
2489
|
+
const op = p.st.opening;
|
|
2490
|
+
if (!op) return false;
|
|
2491
|
+
const [u, v] = planeAxes(p.thin);
|
|
2492
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
2493
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
2494
|
+
return Math.hypot(lp[u] - lc[u], lp[v] - lc[v]) <= op.radius;
|
|
2495
|
+
};
|
|
2496
|
+
const breachAreaOf = (p, damage, blast) => damage / (p.mat.hpPerKg * p.mat.density) / Math.max(p.thickness, 5e-3) * (blast ? breachBlastFactor : 1);
|
|
2497
|
+
const localHolesOf = (p) => p.st.holes.map((h) => ({ point: toLocal(p.pos, p.q, h.point), normal: rotateVec(quat.conjugate(p.q), h.normal), diameter: h.diameter }));
|
|
2498
|
+
const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
2499
|
+
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
2500
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
2501
|
+
const { c, q } = partXf(o, p);
|
|
2502
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2503
|
+
const [u, v] = planeAxes(p.thin);
|
|
2504
|
+
const faceArea = (cell) => cell.size[u] * cell.size[v];
|
|
2505
|
+
const total = cells.reduce((sum2, cell) => sum2 + faceArea(cell), 0) || 1;
|
|
2506
|
+
const shape = p.spec.units ? "chunk" : FRAGMENT_SHAPE[p.mat.fracture] ?? "chunk";
|
|
2507
|
+
return cells.map((cell, i) => {
|
|
2508
|
+
const cw = toWorld(c, q, cell.center);
|
|
2509
|
+
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2510
|
+
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2511
|
+
const k = speed * (0.6 + 0.8 * rnd());
|
|
2512
|
+
const marks = marksInBox(cell.center, cell.size, void 0, holes);
|
|
2513
|
+
return {
|
|
2514
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
2515
|
+
part: p.spec.id,
|
|
2516
|
+
material: p.spec.material,
|
|
2517
|
+
shape,
|
|
2518
|
+
center: cw,
|
|
2519
|
+
size: [cell.size[0], cell.size[1], cell.size[2]],
|
|
2520
|
+
rotation: q,
|
|
2521
|
+
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2522
|
+
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
2523
|
+
mass: mass * faceArea(cell) / total,
|
|
2524
|
+
...marks.length > 0 ? { marks } : {}
|
|
2525
|
+
};
|
|
2526
|
+
});
|
|
2527
|
+
};
|
|
2528
|
+
const worldBars = (o, p, bars) => {
|
|
2529
|
+
const { c, q } = partXf(o, p);
|
|
2530
|
+
return bars.map(([a, b]) => [toWorld(c, q, a), toWorld(c, q, b)]);
|
|
2531
|
+
};
|
|
2532
|
+
const addBreach = (o, p, wp, area, energy, dir, src) => {
|
|
2533
|
+
var _a;
|
|
2534
|
+
if (!p.staged || area <= 0 || (p.st.stage ?? 0) >= 2 || p.st.stripped || isLost(p)) return;
|
|
2535
|
+
const lp = objLocal(o, wp);
|
|
2536
|
+
const list = (_a = p.rt).breach ?? (_a.breach = []);
|
|
2537
|
+
list.push([lp[0], lp[1], lp[2], area]);
|
|
2538
|
+
if (list.length > MAX_SPALLS) list.shift();
|
|
2539
|
+
let total = 0;
|
|
2540
|
+
const sum2 = [0, 0, 0];
|
|
2541
|
+
for (const [x, y, z, a] of list) {
|
|
2542
|
+
if (vec.distance([x, y, z], lp) > breachRadius) continue;
|
|
2543
|
+
total += a;
|
|
2544
|
+
sum2[0] += x * a;
|
|
2545
|
+
sum2[1] += y * a;
|
|
2546
|
+
sum2[2] += z * a;
|
|
2547
|
+
}
|
|
2548
|
+
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
2549
|
+
openBreach(o, p, [sum2[0] / total, sum2[1] / total, sum2[2] / total], total, energy, dir, src);
|
|
2550
|
+
};
|
|
2551
|
+
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
2552
|
+
const [u, v] = planeAxes(p.thin);
|
|
2553
|
+
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
2554
|
+
const lc = toLocal(p.pos, p.q, center);
|
|
2555
|
+
lc[p.thin] = 0;
|
|
2556
|
+
const { c, q } = partXf(o, p);
|
|
2557
|
+
const worldCenter = toWorld(c, q, lc);
|
|
2558
|
+
if (2 * radius >= Math.min(p.dims[u], p.dims[v])) {
|
|
2559
|
+
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
|
|
2563
|
+
const removed = p.st.mass * cells.reduce((sum2, cell) => sum2 + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
2564
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
2565
|
+
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
2566
|
+
const holes = localHolesOf(p);
|
|
2567
|
+
p.st.holes = p.st.holes.filter((_, i) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[i]]).length > 0));
|
|
2568
|
+
p.st.mass = Math.max(0, p.st.mass - removed);
|
|
2569
|
+
p.rt.removed = (p.rt.removed ?? 0) + removed;
|
|
2570
|
+
p.st.opening = { center: objLocal(o, worldCenter), radius };
|
|
2571
|
+
markDamaged(p);
|
|
2572
|
+
setStage(o, p, 1, {}, src);
|
|
2573
|
+
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
2574
|
+
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
2575
|
+
const reinforcement = p.spec.reinforcement;
|
|
2576
|
+
if (reinforcement) {
|
|
2577
|
+
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
2578
|
+
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
2579
|
+
}
|
|
2580
|
+
if (p.structural) solveStructure(o);
|
|
2581
|
+
};
|
|
2582
|
+
const stripPart = (o, p, reinforcement, src) => {
|
|
2583
|
+
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
2584
|
+
p.st.stripped = true;
|
|
2585
|
+
p.st.hp = p.st.maxHp;
|
|
2586
|
+
p.st.mass = rebarMass(bars, reinforcement.diameter, materials.get(reinforcement.material)?.density);
|
|
2587
|
+
p.st.holes = [];
|
|
2588
|
+
p.st.burning = false;
|
|
2589
|
+
p.rt.jet = false;
|
|
2590
|
+
markDamaged(p);
|
|
2591
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
2592
|
+
solveStructure(o);
|
|
2593
|
+
collapseCheck(o, src);
|
|
2594
|
+
};
|
|
2595
|
+
const unitFragmentPlan = (p, cap) => {
|
|
2596
|
+
const grid = unitGrid(p.dims, p.thin, unitsOf(p), cap);
|
|
2597
|
+
const op = p.st.opening;
|
|
2598
|
+
const cells = op ? cellsOutside(grid, p.thin, toLocal(p.pos, p.q, op.center), op.radius) : grid;
|
|
2599
|
+
const [u, v] = planeAxes(p.thin);
|
|
2600
|
+
return cells.map((cell) => ({ center: cell.center, size: cell.size, q: quat.identity(), shape: "chunk", weight: cell.size[u] * cell.size[v] }));
|
|
2601
|
+
};
|
|
2602
|
+
const loseCage = (o, p, src) => {
|
|
2603
|
+
p.st.status = "broken";
|
|
2604
|
+
p.st.hp = 0;
|
|
2605
|
+
p.st.mass = 0;
|
|
2606
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: [] });
|
|
2607
|
+
debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
|
|
2608
|
+
partLost(o, p, src);
|
|
2609
|
+
};
|
|
2247
2610
|
const failPart = (o, p, origin, dir, fragEnergy, cap, src) => {
|
|
2248
2611
|
if (isLost(p)) return;
|
|
2612
|
+
if (p.st.stripped) {
|
|
2613
|
+
loseCage(o, p, src);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2249
2616
|
const ex = p.spec.explodes;
|
|
2250
2617
|
const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
|
|
2251
2618
|
const { c, q } = partXf(o, p);
|
|
2252
2619
|
const hitW = origin ?? c;
|
|
2253
2620
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
2254
|
-
const
|
|
2621
|
+
const hitLocal = toLocal(c, q, hitW);
|
|
2622
|
+
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
2623
|
+
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
2255
2624
|
style,
|
|
2256
2625
|
dims: p.dims,
|
|
2257
2626
|
thickness: p.thickness,
|
|
2258
2627
|
grain,
|
|
2259
|
-
hit:
|
|
2628
|
+
hit: hitLocal,
|
|
2260
2629
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
2261
2630
|
maxFragments,
|
|
2262
2631
|
rnd
|
|
2263
2632
|
});
|
|
2633
|
+
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
2264
2634
|
const mass = p.st.mass;
|
|
2265
2635
|
const totW = plan.reduce((s, f) => s + f.weight, 0) || 1;
|
|
2266
2636
|
const energy = ex ? ex.energy * 0.2 : fragEnergy;
|
|
@@ -2270,6 +2640,7 @@ var GameTestRoom = (() => {
|
|
|
2270
2640
|
const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
|
|
2271
2641
|
const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
|
|
2272
2642
|
const k = speed * (0.6 + 0.8 * rnd());
|
|
2643
|
+
const marks = marksInBox(f.center, f.size, f.q, holes);
|
|
2273
2644
|
return {
|
|
2274
2645
|
id: `${p.spec.id}#${i}`,
|
|
2275
2646
|
part: p.spec.id,
|
|
@@ -2280,16 +2651,25 @@ var GameTestRoom = (() => {
|
|
|
2280
2651
|
rotation: quat.mul(q, f.q),
|
|
2281
2652
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
2282
2653
|
angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
|
|
2283
|
-
mass: mass * f.weight / totW
|
|
2654
|
+
mass: mass * f.weight / totW,
|
|
2655
|
+
...marks.length > 0 ? { marks } : {}
|
|
2284
2656
|
};
|
|
2285
2657
|
});
|
|
2286
|
-
p.
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2658
|
+
const reinforcement = p.spec.reinforcement;
|
|
2659
|
+
if (!reinforcement) {
|
|
2660
|
+
p.st.status = "broken";
|
|
2661
|
+
p.st.hp = 0;
|
|
2662
|
+
p.st.mass = 0;
|
|
2663
|
+
p.st.burning = false;
|
|
2664
|
+
p.rt.jet = false;
|
|
2665
|
+
}
|
|
2291
2666
|
emit({ ...base(o, src), type: "fracture", part: p.spec.id, style, fragments, point: hitW });
|
|
2292
2667
|
debris(o, p, p.mat.breakDebris, c, [0, 1, 0], clamp2(Math.round(Math.cbrt(partVolume(p.spec.shape)) * 30), 3, 20), 2, src);
|
|
2668
|
+
setStage(o, p, 3, {}, src);
|
|
2669
|
+
if (reinforcement) {
|
|
2670
|
+
stripPart(o, p, reinforcement, src);
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2293
2673
|
if (ex) {
|
|
2294
2674
|
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius });
|
|
2295
2675
|
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
|
|
@@ -2323,10 +2703,17 @@ var GameTestRoom = (() => {
|
|
|
2323
2703
|
};
|
|
2324
2704
|
const ballistic2 = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2325
2705
|
const mat = p.mat;
|
|
2706
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2707
|
+
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
2708
|
+
}
|
|
2326
2709
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
2327
2710
|
const angle = Math.asin(sin) * 180 / Math.PI;
|
|
2328
|
-
const
|
|
2329
|
-
const
|
|
2711
|
+
const slant = Math.max(sin, 0.3);
|
|
2712
|
+
const effM = p.thickness / slant;
|
|
2713
|
+
const face = p.layers.length > 0 ? faceOf(o, p, n) : 1;
|
|
2714
|
+
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2715
|
+
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;
|
|
2716
|
+
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2330
2717
|
const penetrated = E > thr;
|
|
2331
2718
|
const scale2 = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2332
2719
|
const id = p.spec.id;
|
|
@@ -2337,13 +2724,16 @@ var GameTestRoom = (() => {
|
|
|
2337
2724
|
const dmg2 = applyDamage(o, p, E * DMG_PER_J * scale2 * 0.2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2338
2725
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
2339
2726
|
}
|
|
2727
|
+
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2340
2728
|
const brittle = mat.ductility < 0.3;
|
|
2341
2729
|
const ratio = E / Math.max(thr, 1e-6);
|
|
2342
2730
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2343
2731
|
let exitPoint;
|
|
2344
2732
|
if (penetrated) {
|
|
2345
|
-
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM));
|
|
2733
|
+
exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
|
|
2346
2734
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2735
|
+
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2736
|
+
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
2347
2737
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2348
2738
|
if (p.spec.contains && (p.st.liters ?? 0) > 0) {
|
|
2349
2739
|
emit({
|
|
@@ -2388,8 +2778,11 @@ var GameTestRoom = (() => {
|
|
|
2388
2778
|
}
|
|
2389
2779
|
if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
2390
2780
|
else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2781
|
+
if (kind === "blunt" && E > 0 && p.layers.length > 0) spallOuter(o, p, point, faceOf(o, p, n), n, Math.max(contactR, 0.05), src);
|
|
2391
2782
|
const scale2 = mat.damageScale?.[kind] ?? 1;
|
|
2392
|
-
|
|
2783
|
+
const done = applyDamage(o, p, Ec * DMG_PER_J * scale2 * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
2784
|
+
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2785
|
+
return done;
|
|
2393
2786
|
};
|
|
2394
2787
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2395
2788
|
const start = cur.length;
|
|
@@ -2425,7 +2818,10 @@ var GameTestRoom = (() => {
|
|
|
2425
2818
|
}
|
|
2426
2819
|
}
|
|
2427
2820
|
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2428
|
-
|
|
2821
|
+
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);
|
|
2822
|
+
const dealt = applyDamage(o, p, Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1), "explosive", point, null, Ein * 0.3, 40, src);
|
|
2823
|
+
res.damage += dealt;
|
|
2824
|
+
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2429
2825
|
}
|
|
2430
2826
|
res.events = cur.slice(start);
|
|
2431
2827
|
return res;
|
|
@@ -2696,7 +3092,7 @@ var GameTestRoom = (() => {
|
|
|
2696
3092
|
}
|
|
2697
3093
|
o.active = hot;
|
|
2698
3094
|
};
|
|
2699
|
-
const
|
|
3095
|
+
const spread2 = (sources, dt) => {
|
|
2700
3096
|
for (const src of sources) {
|
|
2701
3097
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
2702
3098
|
const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
|
|
@@ -2997,7 +3393,7 @@ var GameTestRoom = (() => {
|
|
|
2997
3393
|
time += dt;
|
|
2998
3394
|
const sources = [];
|
|
2999
3395
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
3000
|
-
if (sources.length)
|
|
3396
|
+
if (sources.length) spread2(sources, dt);
|
|
3001
3397
|
detonatePrimed(time);
|
|
3002
3398
|
for (const im of [...impls.values()]) {
|
|
3003
3399
|
if (!im.impl.update) continue;
|
|
@@ -3036,7 +3432,7 @@ var GameTestRoom = (() => {
|
|
|
3036
3432
|
counter,
|
|
3037
3433
|
objects: [...objects.values()].map((o) => {
|
|
3038
3434
|
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
3039
|
-
for (const p of o.parts) runtime.parts[p.spec.id] =
|
|
3435
|
+
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
3040
3436
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
3041
3437
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
3042
3438
|
}),
|
|
@@ -3053,7 +3449,7 @@ var GameTestRoom = (() => {
|
|
|
3053
3449
|
const state = clone(e.state);
|
|
3054
3450
|
const carry = {
|
|
3055
3451
|
parts: new Map(
|
|
3056
|
-
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ?
|
|
3452
|
+
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
3057
3453
|
),
|
|
3058
3454
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
3059
3455
|
collapsed: e.runtime?.collapsed
|
|
@@ -3250,6 +3646,10 @@ var GameTestRoom = (() => {
|
|
|
3250
3646
|
primed: "about to explode",
|
|
3251
3647
|
stress: "overload",
|
|
3252
3648
|
collapse: "collapsed",
|
|
3649
|
+
spall: "plaster came off",
|
|
3650
|
+
stage: "damage stage",
|
|
3651
|
+
expose: "rebar bare",
|
|
3652
|
+
body: "body",
|
|
3253
3653
|
destroyed: "destroyed"
|
|
3254
3654
|
},
|
|
3255
3655
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3332,6 +3732,10 @@ var GameTestRoom = (() => {
|
|
|
3332
3732
|
primed: "prestes a explodir",
|
|
3333
3733
|
stress: "sobrecarga",
|
|
3334
3734
|
collapse: "desabou",
|
|
3735
|
+
spall: "reboco soltou",
|
|
3736
|
+
stage: "est\xE1gio de dano",
|
|
3737
|
+
expose: "vergalh\xE3o \xE0 mostra",
|
|
3738
|
+
body: "corpo",
|
|
3335
3739
|
destroyed: "destru\xEDdo"
|
|
3336
3740
|
},
|
|
3337
3741
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|
|
@@ -3396,6 +3800,8 @@ var GameTestRoom = (() => {
|
|
|
3396
3800
|
__publicField(this, "v");
|
|
3397
3801
|
__publicField(this, "w");
|
|
3398
3802
|
__publicField(this, "rng", createRng2(1374496523));
|
|
3803
|
+
__publicField(this, "markGeometry", null);
|
|
3804
|
+
__publicField(this, "markMaterial", null);
|
|
3399
3805
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
3400
3806
|
this.q = new THREE.Quaternion();
|
|
3401
3807
|
this.dq = new THREE.Quaternion();
|
|
@@ -3442,6 +3848,25 @@ var GameTestRoom = (() => {
|
|
|
3442
3848
|
}
|
|
3443
3849
|
return g;
|
|
3444
3850
|
}
|
|
3851
|
+
/**
|
|
3852
|
+
* Bullet holes that stay on a fragment (`Fragment.marks`): dark discs that are children of its mesh, so they fall, spin and settle with it. The mesh is
|
|
3853
|
+
* a unit box scaled to the fragment, so each disc is scaled back; exact for a mark on a face, which is where a bullet hole is.
|
|
3854
|
+
*/
|
|
3855
|
+
addMarks(mesh, f) {
|
|
3856
|
+
if (!f.marks?.length) return;
|
|
3857
|
+
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
3858
|
+
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
3859
|
+
const sc = mesh.scale;
|
|
3860
|
+
for (const mark of f.marks) {
|
|
3861
|
+
const disc = new this.THREE.Mesh(this.markGeometry, this.markMaterial);
|
|
3862
|
+
const radius = Math.max(4e-3, mark.diameter) * 0.6;
|
|
3863
|
+
const n = this.v.set(mark.normal[0], mark.normal[1], mark.normal[2]).normalize();
|
|
3864
|
+
disc.position.set(mark.point[0] / sc.x + n.x * 2e-3 / sc.x, mark.point[1] / sc.y + n.y * 2e-3 / sc.y, mark.point[2] / sc.z + n.z * 2e-3 / sc.z);
|
|
3865
|
+
disc.quaternion.setFromUnitVectors(this.w.set(0, 0, 1), n);
|
|
3866
|
+
disc.scale.set(radius / sc.x, radius / sc.y, radius / sc.z);
|
|
3867
|
+
mesh.add(disc);
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3445
3870
|
/** Create the mesh of a fragment and start its motion. `maxAllowed` is the live budget. */
|
|
3446
3871
|
spawn(f, material, home, maxAllowed) {
|
|
3447
3872
|
const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
|
|
@@ -3475,6 +3900,8 @@ var GameTestRoom = (() => {
|
|
|
3475
3900
|
alive--;
|
|
3476
3901
|
}
|
|
3477
3902
|
}
|
|
3903
|
+
this.addMarks(mesh, f);
|
|
3904
|
+
return mesh;
|
|
3478
3905
|
}
|
|
3479
3906
|
/** Add velocity to every fragment within `radius` of a point (blasts). */
|
|
3480
3907
|
blast(point, radius, speed) {
|
|
@@ -3598,6 +4025,8 @@ var GameTestRoom = (() => {
|
|
|
3598
4025
|
dispose() {
|
|
3599
4026
|
this.clear();
|
|
3600
4027
|
this.box.dispose();
|
|
4028
|
+
this.markGeometry?.dispose();
|
|
4029
|
+
this.markMaterial?.dispose();
|
|
3601
4030
|
for (const g of this.shardGeos) g?.dispose();
|
|
3602
4031
|
this.shardGeos = [];
|
|
3603
4032
|
for (const m of this.materials.values()) m.dispose();
|
|
@@ -4342,6 +4771,76 @@ void main() {
|
|
|
4342
4771
|
}
|
|
4343
4772
|
}
|
|
4344
4773
|
};
|
|
4774
|
+
var RebarCages = class {
|
|
4775
|
+
constructor(THREE, scene, radius = 4e-3) {
|
|
4776
|
+
__publicField(this, "THREE", THREE);
|
|
4777
|
+
__publicField(this, "scene", scene);
|
|
4778
|
+
__publicField(this, "cages", /* @__PURE__ */ new Map());
|
|
4779
|
+
__publicField(this, "geometry");
|
|
4780
|
+
__publicField(this, "material");
|
|
4781
|
+
__publicField(this, "up");
|
|
4782
|
+
__publicField(this, "dir");
|
|
4783
|
+
__publicField(this, "mid");
|
|
4784
|
+
__publicField(this, "q");
|
|
4785
|
+
__publicField(this, "scale");
|
|
4786
|
+
__publicField(this, "m");
|
|
4787
|
+
this.geometry = new THREE.CylinderGeometry(radius, radius, 1, 6);
|
|
4788
|
+
this.material = new THREE.MeshStandardMaterial({ color: 7628380, roughness: 0.55, metalness: 0.6 });
|
|
4789
|
+
this.up = new THREE.Vector3(0, 1, 0);
|
|
4790
|
+
this.dir = new THREE.Vector3();
|
|
4791
|
+
this.mid = new THREE.Vector3();
|
|
4792
|
+
this.q = new THREE.Quaternion();
|
|
4793
|
+
this.scale = new THREE.Vector3();
|
|
4794
|
+
this.m = new THREE.Matrix4();
|
|
4795
|
+
}
|
|
4796
|
+
/** Bars drawn now, over every part. */
|
|
4797
|
+
get count() {
|
|
4798
|
+
let n = 0;
|
|
4799
|
+
for (const mesh of this.cages.values()) n += mesh.count;
|
|
4800
|
+
return n;
|
|
4801
|
+
}
|
|
4802
|
+
/** Meshes of the live cages (tests and picking). */
|
|
4803
|
+
meshes() {
|
|
4804
|
+
return [...this.cages.values()];
|
|
4805
|
+
}
|
|
4806
|
+
/** Draw `bars` (world space) as the cage of `key`; an empty list removes it. */
|
|
4807
|
+
set(key, bars) {
|
|
4808
|
+
this.remove(key);
|
|
4809
|
+
if (bars.length === 0) return;
|
|
4810
|
+
const mesh = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
|
|
4811
|
+
bars.forEach(([a, b], i) => {
|
|
4812
|
+
this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
4813
|
+
const length3 = this.dir.length();
|
|
4814
|
+
this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
|
|
4815
|
+
this.q.setFromUnitVectors(this.up, length3 > 1e-9 ? this.dir.divideScalar(length3) : this.up);
|
|
4816
|
+
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length3, 1e-4), 1));
|
|
4817
|
+
mesh.setMatrixAt(i, this.m);
|
|
4818
|
+
});
|
|
4819
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
4820
|
+
mesh.castShadow = true;
|
|
4821
|
+
this.scene.add(mesh);
|
|
4822
|
+
this.cages.set(key, mesh);
|
|
4823
|
+
}
|
|
4824
|
+
remove(key) {
|
|
4825
|
+
const mesh = this.cages.get(key);
|
|
4826
|
+
if (!mesh) return;
|
|
4827
|
+
this.scene.remove(mesh);
|
|
4828
|
+
mesh.dispose();
|
|
4829
|
+
this.cages.delete(key);
|
|
4830
|
+
}
|
|
4831
|
+
/** Remove the cages of every part of `object` (keys are `object|part`). */
|
|
4832
|
+
removeObject(object) {
|
|
4833
|
+
for (const key of [...this.cages.keys()]) if (key.startsWith(`${object}|`)) this.remove(key);
|
|
4834
|
+
}
|
|
4835
|
+
clear() {
|
|
4836
|
+
for (const key of [...this.cages.keys()]) this.remove(key);
|
|
4837
|
+
}
|
|
4838
|
+
dispose() {
|
|
4839
|
+
this.clear();
|
|
4840
|
+
this.geometry.dispose();
|
|
4841
|
+
this.material.dispose();
|
|
4842
|
+
}
|
|
4843
|
+
};
|
|
4345
4844
|
function partHintOf(value) {
|
|
4346
4845
|
if (typeof value === "string") return value ? { id: value } : null;
|
|
4347
4846
|
if (!value || typeof value !== "object") return null;
|
|
@@ -4540,6 +5039,7 @@ void main() {
|
|
|
4540
5039
|
__publicField(this, "disc", null);
|
|
4541
5040
|
__publicField(this, "discMat", null);
|
|
4542
5041
|
__publicField(this, "lineMats", /* @__PURE__ */ new Map());
|
|
5042
|
+
__publicField(this, "patchMats", /* @__PURE__ */ new Map());
|
|
4543
5043
|
}
|
|
4544
5044
|
discGeometry() {
|
|
4545
5045
|
return this.disc ?? (this.disc = new this.THREE.CircleGeometry(1, 14));
|
|
@@ -4555,6 +5055,15 @@ void main() {
|
|
|
4555
5055
|
polygonOffsetUnits: -4
|
|
4556
5056
|
}));
|
|
4557
5057
|
}
|
|
5058
|
+
/** Opaque patch in the colour of what shows under a spalled coat. */
|
|
5059
|
+
patchMaterial(color) {
|
|
5060
|
+
let m = this.patchMats.get(color);
|
|
5061
|
+
if (!m) {
|
|
5062
|
+
m = new this.THREE.MeshBasicMaterial({ color, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
|
|
5063
|
+
this.patchMats.set(color, m);
|
|
5064
|
+
}
|
|
5065
|
+
return m;
|
|
5066
|
+
}
|
|
4558
5067
|
lineMaterial(color) {
|
|
4559
5068
|
let m = this.lineMats.get(color);
|
|
4560
5069
|
if (!m) {
|
|
@@ -4567,6 +5076,8 @@ void main() {
|
|
|
4567
5076
|
this.disc?.dispose();
|
|
4568
5077
|
this.discMat?.dispose();
|
|
4569
5078
|
for (const m of this.lineMats.values()) m.dispose();
|
|
5079
|
+
for (const m of this.patchMats.values()) m.dispose();
|
|
5080
|
+
this.patchMats.clear();
|
|
4570
5081
|
this.disc = null;
|
|
4571
5082
|
this.discMat = null;
|
|
4572
5083
|
this.lineMats.clear();
|
|
@@ -4809,9 +5320,9 @@ void main() {
|
|
|
4809
5320
|
const n = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
|
|
4810
5321
|
const n2 = (Math.imul(v + 7, 40503) >>> 0) % 1e3 / 1e3;
|
|
4811
5322
|
const lowness = 1 - (work[i + 1] - minY) / h;
|
|
4812
|
-
const
|
|
4813
|
-
arr[i] = work[i] *
|
|
4814
|
-
arr[i + 2] = work[i + 2] *
|
|
5323
|
+
const spread2 = 1 + 0.12 * melt * (0.4 + n2) * (0.3 + lowness);
|
|
5324
|
+
arr[i] = work[i] * spread2;
|
|
5325
|
+
arr[i + 2] = work[i + 2] * spread2;
|
|
4815
5326
|
arr[i + 1] = Math.max(minY, work[i + 1] - n * melt * h * 0.08);
|
|
4816
5327
|
}
|
|
4817
5328
|
}
|
|
@@ -4857,6 +5368,16 @@ void main() {
|
|
|
4857
5368
|
disc.renderOrder = 2;
|
|
4858
5369
|
this.addDecal(mv, { obj: disc });
|
|
4859
5370
|
}
|
|
5371
|
+
/** A patch where a surface coat came off, in the colour of the core under it; drawn under the bullet holes. */
|
|
5372
|
+
addSpall(mv, worldPoint, worldNormal, radius, color) {
|
|
5373
|
+
const { s, pos, quat: quat2 } = this.decalPose(mv.mesh, worldPoint, worldNormal, 15e-4);
|
|
5374
|
+
const disc = new this.THREE.Mesh(this.kit.discGeometry(), this.kit.patchMaterial(color));
|
|
5375
|
+
disc.position.copy(pos);
|
|
5376
|
+
disc.quaternion.copy(quat2);
|
|
5377
|
+
disc.scale.setScalar(Math.max(0.01, radius) / s);
|
|
5378
|
+
disc.renderOrder = 1;
|
|
5379
|
+
this.addDecal(mv, { obj: disc });
|
|
5380
|
+
}
|
|
4860
5381
|
/** A star of jagged lines: `rays` rays up to `length` metres, seeded so the same event always draws the same star. */
|
|
4861
5382
|
addCrack(mv, worldPoint, worldNormal, rays, length3, color, seed) {
|
|
4862
5383
|
const { s, pos, quat: quat2 } = this.decalPose(mv.mesh, worldPoint, worldNormal, 2e-3);
|
|
@@ -5380,6 +5901,7 @@ void main() {
|
|
|
5380
5901
|
__publicField(this, "fragments");
|
|
5381
5902
|
__publicField(this, "ground");
|
|
5382
5903
|
__publicField(this, "pieceSys");
|
|
5904
|
+
__publicField(this, "cages");
|
|
5383
5905
|
__publicField(this, "active", /* @__PURE__ */ new Map());
|
|
5384
5906
|
__publicField(this, "fires", /* @__PURE__ */ new Map());
|
|
5385
5907
|
__publicField(this, "leaks", []);
|
|
@@ -5431,6 +5953,7 @@ void main() {
|
|
|
5431
5953
|
max: this.maxFragments,
|
|
5432
5954
|
life: o.fragmentLife ?? 12
|
|
5433
5955
|
});
|
|
5956
|
+
this.cages = new RebarCages(THREE, o.scene);
|
|
5434
5957
|
this.pieceSys = new PieceSystem(THREE, {
|
|
5435
5958
|
groundAt: this.groundAt,
|
|
5436
5959
|
gravity: this.gravity,
|
|
@@ -5732,6 +6255,15 @@ void main() {
|
|
|
5732
6255
|
this.sound("ignite", ev.point, 0.7);
|
|
5733
6256
|
}
|
|
5734
6257
|
break;
|
|
6258
|
+
case "spall":
|
|
6259
|
+
this.onSpall(ev);
|
|
6260
|
+
break;
|
|
6261
|
+
case "stage":
|
|
6262
|
+
this.onStage(ev);
|
|
6263
|
+
break;
|
|
6264
|
+
case "expose":
|
|
6265
|
+
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
6266
|
+
break;
|
|
5735
6267
|
case "extinguish": {
|
|
5736
6268
|
const f = this.fires.get(`${ev.object}|${ev.part}`);
|
|
5737
6269
|
if (f) {
|
|
@@ -5897,6 +6429,43 @@ void main() {
|
|
|
5897
6429
|
}
|
|
5898
6430
|
this.sound(impactKey(def), ev.worldPoint, 0.5);
|
|
5899
6431
|
}
|
|
6432
|
+
/** Outward normal of the face of `mv` nearest to a world point: the thin axis of its world box (right for a wall; a sloped face is left to the vertex-range binding). */
|
|
6433
|
+
faceNormal(mv, point) {
|
|
6434
|
+
const box2 = this.visuals.worldBox(mv, this.box);
|
|
6435
|
+
const size = box2.getSize(this.v1);
|
|
6436
|
+
const axis = size.x <= size.y && size.x <= size.z ? "x" : size.y <= size.z ? "y" : "z";
|
|
6437
|
+
const side = point[axis] >= (box2.min[axis] + box2.max[axis]) / 2 ? 1 : -1;
|
|
6438
|
+
return new this.THREE.Vector3(axis === "x" ? side : 0, axis === "y" ? side : 0, axis === "z" ? side : 0);
|
|
6439
|
+
}
|
|
6440
|
+
/** A coat came off: a patch in the colour of the core under it, below the bullet holes and cracks. */
|
|
6441
|
+
onSpall(ev) {
|
|
6442
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
6443
|
+
if (!pv) return;
|
|
6444
|
+
const wp = new this.THREE.Vector3(ev.point[0], ev.point[1], ev.point[2]);
|
|
6445
|
+
const mv = this.nearestMesh(pv, wp);
|
|
6446
|
+
if (!mv) return;
|
|
6447
|
+
const def = this.world.materials.get(pv.material);
|
|
6448
|
+
this.visuals.addSpall(mv, wp, this.faceNormal(mv, wp), Math.sqrt(ev.area / Math.PI), fallbackColor(pv.material, def));
|
|
6449
|
+
}
|
|
6450
|
+
/**
|
|
6451
|
+
* Stage 2 of a staged part: the unit-sized fragments come out and the part stays drawn (hiding it is what `fracture` does). The opening itself
|
|
6452
|
+
* (cutting the wall's faces) is not drawn yet: it needs the per-vertex binding of the house renderer.
|
|
6453
|
+
*/
|
|
6454
|
+
onStage(ev) {
|
|
6455
|
+
if (!ev.fragments || !ev.opening) return;
|
|
6456
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
6457
|
+
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
6458
|
+
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
6459
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
6460
|
+
const step = this.low ? 2 : 1;
|
|
6461
|
+
for (let i = 0; i < ev.fragments.length; i += step) {
|
|
6462
|
+
const fr = ev.fragments[i];
|
|
6463
|
+
const fdef = this.world.materials.get(fr.material);
|
|
6464
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, fdef === def || !fdef ? color : fallbackColor(fr.material, fdef), false), ev.object, maxAlive);
|
|
6465
|
+
}
|
|
6466
|
+
this.particles.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
6467
|
+
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
6468
|
+
}
|
|
5900
6469
|
onFracture(ev) {
|
|
5901
6470
|
const b = this.bounds.get(ev.object);
|
|
5902
6471
|
const pv = b?.parts.get(ev.part);
|
|
@@ -6053,8 +6622,8 @@ void main() {
|
|
|
6053
6622
|
const speed = Math.min(30, Math.sqrt(2 * ev.energy * 0.02 / pc.mass)) * fall;
|
|
6054
6623
|
const inv = d > 1e-4 ? 1 / d : 0;
|
|
6055
6624
|
const dir = d > 1e-4 ? [dx * inv, dy * inv + 0.3, dz * inv] : [0, 1, 0];
|
|
6056
|
-
const
|
|
6057
|
-
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) *
|
|
6625
|
+
const spread2 = pc.halfExtents[0];
|
|
6626
|
+
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]);
|
|
6058
6627
|
}
|
|
6059
6628
|
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(ev.energy) * 0.08));
|
|
6060
6629
|
this.sound("explode", p, clamp3(0.5 + ev.radius / 10, 0.5, 1));
|
|
@@ -6207,6 +6776,7 @@ void main() {
|
|
|
6207
6776
|
for (const home of homes) {
|
|
6208
6777
|
this.fragments.removeHome(home);
|
|
6209
6778
|
this.ground.removeHome(home);
|
|
6779
|
+
this.cages.removeObject(home);
|
|
6210
6780
|
}
|
|
6211
6781
|
}
|
|
6212
6782
|
restoreHome(home) {
|
|
@@ -6243,6 +6813,7 @@ void main() {
|
|
|
6243
6813
|
}
|
|
6244
6814
|
this.fragments.clear();
|
|
6245
6815
|
this.ground.clear();
|
|
6816
|
+
this.cages.clear();
|
|
6246
6817
|
this.particles.clear();
|
|
6247
6818
|
if (this.light) {
|
|
6248
6819
|
this.light.intensity = 0;
|
|
@@ -6253,6 +6824,9 @@ void main() {
|
|
|
6253
6824
|
stats() {
|
|
6254
6825
|
return { fragments: this.fragments.count, particles: this.particles.live, pieces: this.pieceSys.list.length, decals: this.visuals.decalCount + this.ground.count };
|
|
6255
6826
|
}
|
|
6827
|
+
rebarCount() {
|
|
6828
|
+
return this.cages.count;
|
|
6829
|
+
}
|
|
6256
6830
|
dispose() {
|
|
6257
6831
|
if (this.disposed) return;
|
|
6258
6832
|
this.unsubscribe();
|
|
@@ -6260,6 +6834,7 @@ void main() {
|
|
|
6260
6834
|
for (const id of [...this.bounds.keys()]) this.unbind(id);
|
|
6261
6835
|
this.fragments.dispose();
|
|
6262
6836
|
this.ground.dispose();
|
|
6837
|
+
this.cages.dispose();
|
|
6263
6838
|
this.particles.dispose();
|
|
6264
6839
|
this.visuals.dispose();
|
|
6265
6840
|
this.light?.dispose?.();
|
|
@@ -17340,6 +17915,207 @@ void main() {
|
|
|
17340
17915
|
if (context.isCompatible && !context.isCompatible(def, attachment, fitted)) return { ok: false, reason: "rejected" };
|
|
17341
17916
|
return { ok: true, mount: place.kind };
|
|
17342
17917
|
};
|
|
17918
|
+
var melee_weapons_default = [
|
|
17919
|
+
{
|
|
17920
|
+
id: "tactical_knife",
|
|
17921
|
+
name: "Tactical knife",
|
|
17922
|
+
category: "melee_blade",
|
|
17923
|
+
action: {
|
|
17924
|
+
kind: "melee",
|
|
17925
|
+
damage: "slash",
|
|
17926
|
+
energy: 45,
|
|
17927
|
+
radius: 0.03,
|
|
17928
|
+
reach: 0.6,
|
|
17929
|
+
rpm: 100,
|
|
17930
|
+
noise: 1
|
|
17931
|
+
},
|
|
17932
|
+
mass: 0.3
|
|
17933
|
+
},
|
|
17934
|
+
{
|
|
17935
|
+
id: "meat_cleaver",
|
|
17936
|
+
name: "Meat cleaver",
|
|
17937
|
+
category: "melee_blade",
|
|
17938
|
+
action: {
|
|
17939
|
+
kind: "melee",
|
|
17940
|
+
damage: "slash",
|
|
17941
|
+
energy: 120,
|
|
17942
|
+
radius: 0.05,
|
|
17943
|
+
reach: 0.6,
|
|
17944
|
+
rpm: 70,
|
|
17945
|
+
noise: 2
|
|
17946
|
+
},
|
|
17947
|
+
mass: 0.6
|
|
17948
|
+
},
|
|
17949
|
+
{
|
|
17950
|
+
id: "hatchet",
|
|
17951
|
+
name: "Hatchet",
|
|
17952
|
+
category: "melee_blade",
|
|
17953
|
+
action: {
|
|
17954
|
+
kind: "melee",
|
|
17955
|
+
damage: "slash",
|
|
17956
|
+
energy: 180,
|
|
17957
|
+
radius: 0.06,
|
|
17958
|
+
reach: 0.7,
|
|
17959
|
+
rpm: 55,
|
|
17960
|
+
noise: 2
|
|
17961
|
+
},
|
|
17962
|
+
mass: 0.8
|
|
17963
|
+
},
|
|
17964
|
+
{
|
|
17965
|
+
id: "nail_bat",
|
|
17966
|
+
name: "Nail bat",
|
|
17967
|
+
category: "melee_blunt",
|
|
17968
|
+
action: {
|
|
17969
|
+
kind: "melee",
|
|
17970
|
+
damage: "blunt",
|
|
17971
|
+
energy: 170,
|
|
17972
|
+
radius: 0.1,
|
|
17973
|
+
reach: 1,
|
|
17974
|
+
rpm: 60,
|
|
17975
|
+
noise: 3
|
|
17976
|
+
},
|
|
17977
|
+
mass: 1.2
|
|
17978
|
+
},
|
|
17979
|
+
{
|
|
17980
|
+
id: "iron_pipe",
|
|
17981
|
+
name: "Iron pipe",
|
|
17982
|
+
category: "melee_blunt",
|
|
17983
|
+
action: {
|
|
17984
|
+
kind: "melee",
|
|
17985
|
+
damage: "blunt",
|
|
17986
|
+
energy: 140,
|
|
17987
|
+
radius: 0.06,
|
|
17988
|
+
reach: 1,
|
|
17989
|
+
rpm: 60,
|
|
17990
|
+
noise: 4
|
|
17991
|
+
},
|
|
17992
|
+
mass: 1.2
|
|
17993
|
+
},
|
|
17994
|
+
{
|
|
17995
|
+
id: "pickaxe",
|
|
17996
|
+
name: "Pickaxe",
|
|
17997
|
+
category: "melee_blade",
|
|
17998
|
+
action: {
|
|
17999
|
+
kind: "melee",
|
|
18000
|
+
damage: "slash",
|
|
18001
|
+
energy: 260,
|
|
18002
|
+
radius: 0.05,
|
|
18003
|
+
reach: 1,
|
|
18004
|
+
rpm: 40,
|
|
18005
|
+
noise: 4
|
|
18006
|
+
},
|
|
18007
|
+
mass: 2.8,
|
|
18008
|
+
tags: [
|
|
18009
|
+
"heavy"
|
|
18010
|
+
]
|
|
18011
|
+
},
|
|
18012
|
+
{
|
|
18013
|
+
id: "sickle",
|
|
18014
|
+
name: "Sickle",
|
|
18015
|
+
category: "melee_blade",
|
|
18016
|
+
action: {
|
|
18017
|
+
kind: "melee",
|
|
18018
|
+
damage: "slash",
|
|
18019
|
+
energy: 70,
|
|
18020
|
+
radius: 0.05,
|
|
18021
|
+
reach: 1.1,
|
|
18022
|
+
rpm: 70,
|
|
18023
|
+
noise: 2
|
|
18024
|
+
},
|
|
18025
|
+
mass: 1.2
|
|
18026
|
+
},
|
|
18027
|
+
{
|
|
18028
|
+
id: "police_baton",
|
|
18029
|
+
name: "Police baton",
|
|
18030
|
+
category: "melee_blunt",
|
|
18031
|
+
action: {
|
|
18032
|
+
kind: "melee",
|
|
18033
|
+
damage: "blunt",
|
|
18034
|
+
energy: 70,
|
|
18035
|
+
radius: 0.04,
|
|
18036
|
+
reach: 0.7,
|
|
18037
|
+
rpm: 90,
|
|
18038
|
+
noise: 3
|
|
18039
|
+
},
|
|
18040
|
+
mass: 0.6
|
|
18041
|
+
},
|
|
18042
|
+
{
|
|
18043
|
+
id: "cast_iron_skillet",
|
|
18044
|
+
name: "Cast iron skillet",
|
|
18045
|
+
category: "melee_blunt",
|
|
18046
|
+
action: {
|
|
18047
|
+
kind: "melee",
|
|
18048
|
+
damage: "blunt",
|
|
18049
|
+
energy: 110,
|
|
18050
|
+
radius: 0.12,
|
|
18051
|
+
reach: 0.6,
|
|
18052
|
+
rpm: 60,
|
|
18053
|
+
noise: 8
|
|
18054
|
+
},
|
|
18055
|
+
mass: 1.2
|
|
18056
|
+
},
|
|
18057
|
+
{
|
|
18058
|
+
id: "improvised_spear",
|
|
18059
|
+
name: "Improvised spear",
|
|
18060
|
+
category: "melee_blade",
|
|
18061
|
+
action: {
|
|
18062
|
+
kind: "melee",
|
|
18063
|
+
damage: "slash",
|
|
18064
|
+
energy: 90,
|
|
18065
|
+
radius: 0.02,
|
|
18066
|
+
reach: 1.6,
|
|
18067
|
+
rpm: 50,
|
|
18068
|
+
noise: 1
|
|
18069
|
+
},
|
|
18070
|
+
mass: 1.1
|
|
18071
|
+
},
|
|
18072
|
+
{
|
|
18073
|
+
id: "chainsaw",
|
|
18074
|
+
name: "Chainsaw",
|
|
18075
|
+
category: "melee_blade",
|
|
18076
|
+
action: {
|
|
18077
|
+
kind: "melee",
|
|
18078
|
+
damage: "slash",
|
|
18079
|
+
energy: 400,
|
|
18080
|
+
radius: 0.1,
|
|
18081
|
+
reach: 0.9,
|
|
18082
|
+
rpm: 300,
|
|
18083
|
+
noise: 120
|
|
18084
|
+
},
|
|
18085
|
+
mass: 6,
|
|
18086
|
+
tags: [
|
|
18087
|
+
"heavy"
|
|
18088
|
+
]
|
|
18089
|
+
},
|
|
18090
|
+
{
|
|
18091
|
+
id: "recurve_bow",
|
|
18092
|
+
name: "Recurve bow",
|
|
18093
|
+
category: "bow",
|
|
18094
|
+
action: {
|
|
18095
|
+
kind: "firearm",
|
|
18096
|
+
caliber: "arrow",
|
|
18097
|
+
rpm: 20,
|
|
18098
|
+
modes: [
|
|
18099
|
+
"single"
|
|
18100
|
+
],
|
|
18101
|
+
magazine: 1,
|
|
18102
|
+
reload: 1.5,
|
|
18103
|
+
barrel: 1,
|
|
18104
|
+
spreadDeg: 0.4,
|
|
18105
|
+
bloomDeg: 0.4,
|
|
18106
|
+
recoveryDegPerS: 2,
|
|
18107
|
+
recoil: [
|
|
18108
|
+
0.3,
|
|
18109
|
+
0.1
|
|
18110
|
+
],
|
|
18111
|
+
range: 40
|
|
18112
|
+
},
|
|
18113
|
+
mass: 1.2,
|
|
18114
|
+
tags: [
|
|
18115
|
+
"silent"
|
|
18116
|
+
]
|
|
18117
|
+
}
|
|
18118
|
+
];
|
|
17343
18119
|
var findDuplicateIds = (kind, entries) => {
|
|
17344
18120
|
const seen = /* @__PURE__ */ new Set();
|
|
17345
18121
|
const problems = [];
|
|
@@ -17424,18 +18200,6 @@ void main() {
|
|
|
17424
18200
|
...[...explosives.values()].flatMap(checkExplosiveNumbers),
|
|
17425
18201
|
...checkRuleDefaults(rules)
|
|
17426
18202
|
];
|
|
17427
|
-
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
17428
|
-
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
17429
|
-
var deepMerge = (base, patch) => {
|
|
17430
|
-
if (!patch) return base;
|
|
17431
|
-
const result2 = { ...base };
|
|
17432
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
17433
|
-
if (value === void 0 || UNSAFE_KEYS.has(key)) continue;
|
|
17434
|
-
const current = result2[key];
|
|
17435
|
-
result2[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
|
|
17436
|
-
}
|
|
17437
|
-
return result2;
|
|
17438
|
-
};
|
|
17439
18203
|
var modesEn = { single: "single action", semi: "semi-automatic", burst: "burst", auto: "automatic", pump: "pump action", bolt: "bolt action", lever: "lever action", break: "break action", volley: "volley" };
|
|
17440
18204
|
var categoriesEn = {
|
|
17441
18205
|
pistol: "pistol",
|
|
@@ -17627,20 +18391,6 @@ void main() {
|
|
|
17627
18391
|
if (explosive.fuseRange) return explosive.fuseRange[0] + (explosive.fuseRange[1] - explosive.fuseRange[0]) * random();
|
|
17628
18392
|
return explosive.fuse;
|
|
17629
18393
|
};
|
|
17630
|
-
var mergeById = (shipped, extra) => {
|
|
17631
|
-
const entries = /* @__PURE__ */ new Map();
|
|
17632
|
-
for (const entry of [...shipped, ...extra ?? []]) entries.set(entry.id, entry);
|
|
17633
|
-
return entries;
|
|
17634
|
-
};
|
|
17635
|
-
var applyPatches = (entries, patches) => {
|
|
17636
|
-
for (const [id, patch] of Object.entries(patches ?? {})) {
|
|
17637
|
-
const entry = entries.get(id);
|
|
17638
|
-
if (entry) entries.set(id, deepMerge(entry, patch));
|
|
17639
|
-
}
|
|
17640
|
-
};
|
|
17641
|
-
var removeIds = (entries, ids) => {
|
|
17642
|
-
for (const id of ids ?? []) entries.delete(id);
|
|
17643
|
-
};
|
|
17644
18394
|
var resolveThrowables = (weapons, explosives) => {
|
|
17645
18395
|
for (const [id, def] of weapons) {
|
|
17646
18396
|
if (def.action.kind !== "throwable" || def.action.explosive === void 0) continue;
|
|
@@ -18864,8 +19614,8 @@ void main() {
|
|
|
18864
19614
|
return energy * f * f;
|
|
18865
19615
|
};
|
|
18866
19616
|
const bloomNow = this.extraAt(t);
|
|
18867
|
-
const
|
|
18868
|
-
const aim = sampleCone(dir,
|
|
19617
|
+
const spread2 = a.spreadDeg + bloomNow;
|
|
19618
|
+
const aim = sampleCone(dir, spread2, this.rng);
|
|
18869
19619
|
const pellets = cal.pellets ?? 1;
|
|
18870
19620
|
const pelletSpread = pellets > 1 ? a.pelletSpreadDeg ?? 3 : 0;
|
|
18871
19621
|
for (let p = 0; p < pellets; p++) {
|
|
@@ -18876,6 +19626,7 @@ void main() {
|
|
|
18876
19626
|
caliber: cal.diameter,
|
|
18877
19627
|
caliberId: cal.id,
|
|
18878
19628
|
speed,
|
|
19629
|
+
mass: cal.bulletGrams,
|
|
18879
19630
|
energyAt,
|
|
18880
19631
|
pellet: p,
|
|
18881
19632
|
weapon: this.def.id
|
|
@@ -19061,7 +19812,7 @@ void main() {
|
|
|
19061
19812
|
var EXPLOSIVES = explosives_default;
|
|
19062
19813
|
var ARCHETYPES = archetypes_default;
|
|
19063
19814
|
var ARCHETYPE_IDS = ARCHETYPES.map((def) => def.id);
|
|
19064
|
-
var WEAPONS = [...ARCHETYPES, ...firearms_default, ...launchers_default, ...explosive_weapons_default];
|
|
19815
|
+
var WEAPONS = [...ARCHETYPES, ...firearms_default, ...launchers_default, ...explosive_weapons_default, ...melee_weapons_default];
|
|
19065
19816
|
var DEFAULT_LOADOUT = [
|
|
19066
19817
|
"hand",
|
|
19067
19818
|
"kick",
|
|
@@ -25509,19 +26260,19 @@ void main() {
|
|
|
25509
26260
|
const dd = mk("dd");
|
|
25510
26261
|
const text = mk("span", void 0, row.text);
|
|
25511
26262
|
dd.appendChild(text);
|
|
25512
|
-
let
|
|
26263
|
+
let bar2 = null;
|
|
25513
26264
|
if (row.ratio !== void 0) {
|
|
25514
|
-
|
|
25515
|
-
|
|
25516
|
-
dd.appendChild(
|
|
26265
|
+
bar2 = mk("div", "tr-bar");
|
|
26266
|
+
bar2.appendChild(mk("i"));
|
|
26267
|
+
dd.appendChild(bar2);
|
|
25517
26268
|
}
|
|
25518
26269
|
metricList.append(mk("dt", void 0, row.name), dd);
|
|
25519
|
-
metricNodes.set(row.id, { text, bar });
|
|
26270
|
+
metricNodes.set(row.id, { text, bar: bar2 });
|
|
25520
26271
|
}
|
|
25521
26272
|
};
|
|
25522
|
-
const fillBar = (
|
|
25523
|
-
|
|
25524
|
-
const fill =
|
|
26273
|
+
const fillBar = (bar2, ratio) => {
|
|
26274
|
+
bar2.className = `tr-bar${ratio > 1 ? " bad" : ratio >= 0.8 ? " warn" : ""}`;
|
|
26275
|
+
const fill = bar2.firstElementChild;
|
|
25525
26276
|
fill.style.width = `${Math.round(Math.max(0, Math.min(1, ratio)) * 100)}%`;
|
|
25526
26277
|
};
|
|
25527
26278
|
return {
|
|
@@ -25576,6 +26327,10 @@ void main() {
|
|
|
25576
26327
|
.tr-search{width:100%;margin:0 0 6px;padding:5px 8px;border-radius:5px;border:1px solid #3a404c;background:#0f1116;color:#e8eaee;font:inherit}
|
|
25577
26328
|
.tr-search::placeholder{color:#7d8594}
|
|
25578
26329
|
.tr-item small{color:#7d8594;margin-left:4px}
|
|
26330
|
+
.tr-thumb{display:block;width:100%;height:26px;background:currentColor;-webkit-mask:var(--tr-thumb) center/contain no-repeat;mask:var(--tr-thumb) center/contain no-repeat}
|
|
26331
|
+
.tr-item{min-width:0}
|
|
26332
|
+
.tr-item .tr-btn.has-thumb{display:flex;flex-direction:column;gap:3px;min-width:0}.tr-item .tr-btn.has-thumb .tr-name{overflow:hidden;text-overflow:ellipsis}
|
|
26333
|
+
.tr-insp .tr-thumb{width:140px;height:28px;margin-bottom:3px}
|
|
25579
26334
|
.tr-empty{color:#9aa3b2;padding:4px 2px;grid-column:1/-1}
|
|
25580
26335
|
.tr-cross{position:absolute;left:50%;top:50%;width:18px;height:18px;margin:-9px 0 0 -9px}
|
|
25581
26336
|
.tr-cross:before,.tr-cross:after{content:"";position:absolute;background:#fff;opacity:.85;box-shadow:0 0 2px #000}
|
|
@@ -25641,6 +26396,17 @@ void main() {
|
|
|
25641
26396
|
if (text !== void 0) e.textContent = text;
|
|
25642
26397
|
return e;
|
|
25643
26398
|
};
|
|
26399
|
+
var thumbSource = (thumbnail) => {
|
|
26400
|
+
const source = thumbnail.trim().startsWith("<") ? `data:image/svg+xml,${encodeURIComponent(thumbnail.trim())}` : thumbnail.trim();
|
|
26401
|
+
return source.replace(/["\\\n\r]/g, (char) => encodeURIComponent(char));
|
|
26402
|
+
};
|
|
26403
|
+
var thumb = (thumbnail, credit) => {
|
|
26404
|
+
const e = mk2("span", "tr-thumb");
|
|
26405
|
+
if (credit) e.title = credit;
|
|
26406
|
+
else e.setAttribute("aria-hidden", "true");
|
|
26407
|
+
e.style.setProperty("--tr-thumb", `url("${thumbSource(thumbnail)}")`);
|
|
26408
|
+
return e;
|
|
26409
|
+
};
|
|
25644
26410
|
function createUi(o) {
|
|
25645
26411
|
const { t } = o;
|
|
25646
26412
|
const style = mk2("style");
|
|
@@ -25794,7 +26560,8 @@ void main() {
|
|
|
25794
26560
|
if (!found.length) items.appendChild(mk2("div", "tr-empty", t("ui.no_results")));
|
|
25795
26561
|
for (const { item: it, group } of found) {
|
|
25796
26562
|
const wrap = mk2("div", "tr-item");
|
|
25797
|
-
const b = mk2("button", "tr-btn", it.name);
|
|
26563
|
+
const b = mk2("button", it.thumbnail ? "tr-btn has-thumb" : "tr-btn", it.thumbnail ? void 0 : it.name);
|
|
26564
|
+
if (it.thumbnail) b.append(thumb(it.thumbnail), mk2("span", "tr-name", it.name));
|
|
25798
26565
|
if (searching) b.appendChild(mk2("small", void 0, group));
|
|
25799
26566
|
b.type = "button";
|
|
25800
26567
|
b.title = it.note ?? it.name;
|
|
@@ -25930,7 +26697,7 @@ void main() {
|
|
|
25930
26697
|
};
|
|
25931
26698
|
toggle.onclick = togglePanels;
|
|
25932
26699
|
root.append(cross4, top, bottom, help, left, right, toggle);
|
|
25933
|
-
const
|
|
26700
|
+
const bar2 = (value, max, cls) => {
|
|
25934
26701
|
const b = mk2("div", `tr-bar ${cls}`);
|
|
25935
26702
|
const i = mk2("i");
|
|
25936
26703
|
i.style.width = `${Math.max(0, Math.min(100, value / Math.max(1e-9, max) * 100))}%`;
|
|
@@ -25953,12 +26720,13 @@ void main() {
|
|
|
25953
26720
|
insp.appendChild(mk2("div", void 0, t("ui.aim_at_something")));
|
|
25954
26721
|
return;
|
|
25955
26722
|
}
|
|
26723
|
+
if (i.thumbnail) insp.appendChild(thumb(i.thumbnail, i.thumbnailCredit));
|
|
25956
26724
|
insp.appendChild(mk2("div", "t", `${i.object} \u203A ${i.part}`));
|
|
25957
26725
|
insp.appendChild(row(t("ins.material"), i.material));
|
|
25958
26726
|
insp.appendChild(row(t("ins.status"), i.status + (i.burning ? ` \xB7 ${t("ins.burning")}` : "")));
|
|
25959
26727
|
insp.appendChild(row(t("ins.hp"), `${num(i.hp, 0)} / ${num(i.maxHp, 0)}`));
|
|
25960
26728
|
const frac = i.hp / Math.max(1e-9, i.maxHp);
|
|
25961
|
-
insp.appendChild(
|
|
26729
|
+
insp.appendChild(bar2(i.hp, i.maxHp, frac < 0.25 ? "bad" : frac < 0.6 ? "warn" : "hp"));
|
|
25962
26730
|
insp.appendChild(row(t("ins.temperature"), `${num(i.temperature, 0)} \xB0C`));
|
|
25963
26731
|
if (i.char > 0.01) insp.appendChild(row(t("ins.char"), `${num(i.char * 100, 0)} %`));
|
|
25964
26732
|
if (i.melt > 0.01) insp.appendChild(row(t("ins.melt"), `${num(i.melt * 100, 0)} %`));
|
|
@@ -25969,7 +26737,7 @@ void main() {
|
|
|
25969
26737
|
if (i.load !== void 0) insp.appendChild(row(t("ins.load"), `${num(i.load, 0)} kg`));
|
|
25970
26738
|
if (i.stress !== void 0) {
|
|
25971
26739
|
insp.appendChild(row(t("ins.stress"), `${num(i.stress * 100, 0)} %`));
|
|
25972
|
-
insp.appendChild(
|
|
26740
|
+
insp.appendChild(bar2(Math.min(i.stress, 1.2), 1.2, i.stress > 1 ? "bad" : i.stress > 0.7 ? "warn" : "hp"));
|
|
25973
26741
|
}
|
|
25974
26742
|
if (i.joints.length) {
|
|
25975
26743
|
insp.appendChild(mk2("div", void 0, t("ins.joints") + ":"));
|
|
@@ -27324,6 +28092,8 @@ void main() {
|
|
|
27324
28092
|
stress: ext.stress,
|
|
27325
28093
|
joints,
|
|
27326
28094
|
note: e?.item?.note,
|
|
28095
|
+
thumbnail: e?.item?.thumbnail,
|
|
28096
|
+
thumbnailCredit: e?.item?.thumbnailCredit,
|
|
27327
28097
|
distance: r.distance
|
|
27328
28098
|
};
|
|
27329
28099
|
};
|
|
@@ -27723,7 +28493,9 @@ void main() {
|
|
|
27723
28493
|
fluid: ti.fluid,
|
|
27724
28494
|
weapon: ti.weapon,
|
|
27725
28495
|
impl: ti.impl,
|
|
27726
|
-
animations: ti.animations
|
|
28496
|
+
animations: ti.animations,
|
|
28497
|
+
thumbnail: ti.thumbnail,
|
|
28498
|
+
thumbnailCredit: ti.thumbnailCredit
|
|
27727
28499
|
};
|
|
27728
28500
|
catalogMap.set(item.id, item);
|
|
27729
28501
|
const at = catalog.findIndex((c) => c.id === item.id);
|