@adatechnology/game-destruction 0.16.0 → 0.18.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 +13 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.global.js +441 -16
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +441 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.global.js
CHANGED
|
@@ -452,6 +452,22 @@ var GameDestruction = (() => {
|
|
|
452
452
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
453
453
|
};
|
|
454
454
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
455
|
+
var splitAlong = (box, count, axis, rnd) => {
|
|
456
|
+
const shares = Array.from({ length: count }, () => 0.25 + 1.5 * rnd());
|
|
457
|
+
const total = shares.reduce((sum, share) => sum + share, 0);
|
|
458
|
+
const out = [];
|
|
459
|
+
let from = box.c[axis] - box.s[axis] / 2;
|
|
460
|
+
for (const share of shares) {
|
|
461
|
+
const length = box.s[axis] * share / total;
|
|
462
|
+
const c = [box.c[0], box.c[1], box.c[2]];
|
|
463
|
+
const size = [box.s[0], box.s[1], box.s[2]];
|
|
464
|
+
c[axis] = from + length / 2;
|
|
465
|
+
size[axis] = length;
|
|
466
|
+
out.push({ c, s: size });
|
|
467
|
+
from += length;
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
};
|
|
455
471
|
var splinterBox = (inp, box, limit = inp.maxFragments) => {
|
|
456
472
|
const { rnd } = inp;
|
|
457
473
|
const dims = box.s;
|
|
@@ -461,35 +477,81 @@ var GameDestruction = (() => {
|
|
|
461
477
|
const w = Math.max(fmin * 1.5, 0.01);
|
|
462
478
|
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
463
479
|
const maxLen = fmax * 4;
|
|
464
|
-
const cells =
|
|
480
|
+
const cells = [];
|
|
481
|
+
for (const cell of bsp(box, c, [a, b], rnd)) {
|
|
482
|
+
const wide = cell.s[a] >= cell.s[b] ? a : b;
|
|
483
|
+
if (rnd() < 0.4 && cell.s[wide] > fmin * 0.5) for (const part of splitAlong(cell, 2, wide, rnd)) cells.push(part);
|
|
484
|
+
else cells.push(cell);
|
|
485
|
+
}
|
|
465
486
|
const pieces = [];
|
|
466
487
|
for (const cell of cells) {
|
|
467
|
-
const extra = rnd()
|
|
488
|
+
const extra = Math.floor(rnd() * 3);
|
|
468
489
|
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
469
|
-
for (const p of
|
|
490
|
+
for (const p of splitAlong(cell, k, g, rnd)) pieces.push(p);
|
|
470
491
|
}
|
|
471
492
|
return pieces.slice(0, limit).map((p) => ({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] }));
|
|
472
493
|
};
|
|
494
|
+
var PLY = 3e-3;
|
|
495
|
+
var PLY_GAP = 0.6;
|
|
496
|
+
var PLY_SHEET = 0.012;
|
|
497
|
+
var plyBreak = (inp, root, limit) => {
|
|
498
|
+
const { rnd } = inp;
|
|
499
|
+
const dims = root.s;
|
|
500
|
+
const sorted = [0, 1, 2].sort((x, y) => dims[x] - dims[y]);
|
|
501
|
+
const t = sorted[0];
|
|
502
|
+
const [u, v] = [sorted[1], sorted[2]].sort((x, y) => x - y);
|
|
503
|
+
const along = dims[u] >= dims[v] ? u : v;
|
|
504
|
+
const across = along === u ? v : u;
|
|
505
|
+
const layers = clamp(Math.round(dims[t] / PLY / 2), 2, 3);
|
|
506
|
+
const pitch = dims[t] / layers;
|
|
507
|
+
const th = Math.min(pitch, PLY_SHEET);
|
|
508
|
+
const per = Math.max(2, Math.floor(limit / layers));
|
|
509
|
+
const out = [];
|
|
510
|
+
for (let k = 0; k < layers; k++) {
|
|
511
|
+
const layer = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
512
|
+
layer.c[t] = root.c[t] + (-dims[t] / 2 + (k + 0.5) * pitch) * (pitch > PLY_SHEET ? 1 : 1 + PLY_GAP);
|
|
513
|
+
layer.s[t] = th;
|
|
514
|
+
for (const cell of bsp(layer, per, [u, v], rnd)) {
|
|
515
|
+
if (out.length >= limit) return out;
|
|
516
|
+
if (rnd() < 0.35) {
|
|
517
|
+
const strand = cell.s[across] * (0.15 + 0.2 * rnd());
|
|
518
|
+
out.push({ center: cell.c, size: [cell.s[along], strand, th * 0.7], q: frame(along, across, t), shape: "splinter", weight: cell.s[along] * strand * th });
|
|
519
|
+
} else {
|
|
520
|
+
out.push({ center: cell.c, size: [cell.s[u], cell.s[v], th], q: frame(u, v, t, (rnd() - 0.5) * 0.5), shape: "scrap", weight: cell.s[u] * cell.s[v] * th });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return out;
|
|
525
|
+
};
|
|
526
|
+
var granules = (inp, box, count) => bsp(box, count, [0, 1, 2], inp.rnd).map((b) => ({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] }));
|
|
527
|
+
var CRUMBS = 6;
|
|
473
528
|
var severAt = (inp, root, at, gap) => {
|
|
474
529
|
const dims = root.s;
|
|
475
530
|
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
476
531
|
const half = dims[L] / 2;
|
|
477
532
|
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
478
533
|
const out = [];
|
|
479
|
-
const
|
|
534
|
+
const fibrous = inp.form === "slivers" || inp.form === "plies";
|
|
535
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== L);
|
|
536
|
+
const stub = (from, to, breakAtEnd) => {
|
|
480
537
|
if (to - from < 0.02) return;
|
|
481
538
|
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
482
539
|
c[L] = (from + to) / 2;
|
|
483
540
|
sz[L] = to - from;
|
|
484
|
-
|
|
541
|
+
const weight = sz[0] * sz[1] * sz[2];
|
|
542
|
+
if (fibrous) out.push({ center: c, size: [sz[L], sz[a], sz[b]], q: frame(L, a, b, breakAtEnd ? 0 : Math.PI), shape: "slab", weight });
|
|
543
|
+
else out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight });
|
|
485
544
|
};
|
|
486
|
-
stub(-half, lo);
|
|
487
|
-
stub(hi, half);
|
|
545
|
+
stub(-half, lo, true);
|
|
546
|
+
stub(hi, half, false);
|
|
488
547
|
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
489
548
|
band.c[L] = (lo + hi) / 2;
|
|
490
549
|
band.s[L] = Math.max(hi - lo, 0.01);
|
|
491
|
-
|
|
492
|
-
|
|
550
|
+
const room = inp.maxFragments - out.length;
|
|
551
|
+
if (inp.style === "snap" && inp.form === "plies") for (const p of plyBreak(inp, band, Math.max(3, room))) out.push(p);
|
|
552
|
+
else if (inp.style === "snap" && inp.form === "granules") for (const p of granules(inp, band, clamp(room, 3, 12))) out.push(p);
|
|
553
|
+
else if (inp.style === "snap") {
|
|
554
|
+
for (const b2 of bsp(band, Math.max(3, Math.min(6, room)), [0, 1, 2], inp.rnd)) out.push({ center: b2.c, size: b2.s, q: quat.identity(), shape: "chunk", weight: b2.s[0] * b2.s[1] * b2.s[2] });
|
|
493
555
|
} else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
494
556
|
return out;
|
|
495
557
|
};
|
|
@@ -522,8 +584,18 @@ var GameDestruction = (() => {
|
|
|
522
584
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
523
585
|
if (style === "snap") {
|
|
524
586
|
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
587
|
+
if (inp.form === "plies") return plyBreak(inp, root, maxFragments);
|
|
525
588
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
526
589
|
for (const b of bsp(root, n2, [0, 1, 2], rnd)) out.push({ center: b.c, size: b.s, q: idq, shape: "slab", weight: b.s[0] * b.s[1] * b.s[2] });
|
|
590
|
+
if (inp.form !== "granules") return out;
|
|
591
|
+
const crumbs = Math.min(CRUMBS, maxFragments - out.length);
|
|
592
|
+
if (crumbs <= 0) return out;
|
|
593
|
+
const band = { c: [0, 0, 0], s: [0, 0, 0] };
|
|
594
|
+
for (let i = 0; i < 3; i++) {
|
|
595
|
+
band.s[i] = Math.min(dims[i], Math.max(fmin, dims[i] * 0.2));
|
|
596
|
+
band.c[i] = clamp(inp.hit[i], -dims[i] / 2 + band.s[i] / 2, dims[i] / 2 - band.s[i] / 2);
|
|
597
|
+
}
|
|
598
|
+
for (const p of granules(inp, band, crumbs)) out.push({ ...p, weight: p.weight * 0.5 });
|
|
527
599
|
return out;
|
|
528
600
|
}
|
|
529
601
|
if (style === "crumble" || style === "none") {
|
|
@@ -632,6 +704,88 @@ var GameDestruction = (() => {
|
|
|
632
704
|
}
|
|
633
705
|
return marks;
|
|
634
706
|
};
|
|
707
|
+
var JOINT_GAP = 1e-4;
|
|
708
|
+
var MIN_BEARING_SHARE = 0.25;
|
|
709
|
+
var unsupportedCells = (cells, thin, dims, standing) => {
|
|
710
|
+
if (thin === 1) return [];
|
|
711
|
+
const [u, v] = planeAxes(thin);
|
|
712
|
+
const bottom = -dims[v] / 2;
|
|
713
|
+
const rowOf = (y) => Math.round((y - bottom) * 1e4);
|
|
714
|
+
const rows = /* @__PURE__ */ new Map();
|
|
715
|
+
for (let i = 0; i < cells.length; i++) {
|
|
716
|
+
if (!standing(i)) continue;
|
|
717
|
+
const cell = cells[i];
|
|
718
|
+
const key = rowOf(cell.center[v] - cell.size[v] / 2);
|
|
719
|
+
const row = rows.get(key);
|
|
720
|
+
if (row) row.push(i);
|
|
721
|
+
else rows.set(key, [i]);
|
|
722
|
+
}
|
|
723
|
+
const held = /* @__PURE__ */ new Set();
|
|
724
|
+
const queue = [];
|
|
725
|
+
for (const i of rows.get(0) ?? []) {
|
|
726
|
+
held.add(i);
|
|
727
|
+
queue.push(i);
|
|
728
|
+
}
|
|
729
|
+
const overlap = (a, b) => Math.min(a.center[u] + a.size[u] / 2, b.center[u] + b.size[u] / 2) - Math.max(a.center[u] - a.size[u] / 2, b.center[u] - b.size[u] / 2);
|
|
730
|
+
const climb = () => {
|
|
731
|
+
while (queue.length > 0) {
|
|
732
|
+
const cell = cells[queue.pop()];
|
|
733
|
+
for (const j of rows.get(rowOf(cell.center[v] + cell.size[v] / 2)) ?? []) {
|
|
734
|
+
const other = cells[j];
|
|
735
|
+
if (held.has(j) || overlap(cell, other) <= Math.max(JOINT_GAP, MIN_BEARING_SHARE * Math.min(cell.size[u], other.size[u]))) continue;
|
|
736
|
+
held.add(j);
|
|
737
|
+
queue.push(j);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
for (const row of rows.values()) row.sort((a, b) => cells[a].center[u] - cells[b].center[u]);
|
|
742
|
+
const touching = (a, b) => Math.abs(a.center[u] + a.size[u] / 2 - (b.center[u] - b.size[u] / 2)) <= 1e-3;
|
|
743
|
+
for (let wedged = true; wedged; ) {
|
|
744
|
+
climb();
|
|
745
|
+
wedged = false;
|
|
746
|
+
for (const row of rows.values()) {
|
|
747
|
+
for (let k = 1; k < row.length - 1; k++) {
|
|
748
|
+
const i = row[k];
|
|
749
|
+
if (held.has(i) || !held.has(row[k - 1]) || !held.has(row[k + 1])) continue;
|
|
750
|
+
if (!touching(cells[row[k - 1]], cells[i]) || !touching(cells[i], cells[row[k + 1]])) continue;
|
|
751
|
+
held.add(i);
|
|
752
|
+
queue.push(i);
|
|
753
|
+
wedged = true;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const loose = [];
|
|
758
|
+
for (const row of rows.values()) for (const i of row) if (!held.has(i)) loose.push(i);
|
|
759
|
+
return loose;
|
|
760
|
+
};
|
|
761
|
+
var clumpCells = (cells, thin, cap) => {
|
|
762
|
+
if (cells.length <= cap) return [...cells];
|
|
763
|
+
const [u, v] = planeAxes(thin);
|
|
764
|
+
let lo0 = Infinity, lo1 = Infinity, hi0 = -Infinity, hi1 = -Infinity;
|
|
765
|
+
for (const cell of cells) {
|
|
766
|
+
lo0 = Math.min(lo0, cell.center[u] - cell.size[u] / 2);
|
|
767
|
+
hi0 = Math.max(hi0, cell.center[u] + cell.size[u] / 2);
|
|
768
|
+
lo1 = Math.min(lo1, cell.center[v] - cell.size[v] / 2);
|
|
769
|
+
hi1 = Math.max(hi1, cell.center[v] + cell.size[v] / 2);
|
|
770
|
+
}
|
|
771
|
+
let side = Math.sqrt((hi0 - lo0) * (hi1 - lo1) / cap);
|
|
772
|
+
for (; ; ) {
|
|
773
|
+
const bins = /* @__PURE__ */ new Map();
|
|
774
|
+
for (const cell of cells) {
|
|
775
|
+
const key = `${Math.floor((cell.center[u] - lo0) / side)}:${Math.floor((cell.center[v] - lo1) / side)}`;
|
|
776
|
+
const bin = bins.get(key) ?? { lo: [Infinity, Infinity, Infinity], hi: [-Infinity, -Infinity, -Infinity] };
|
|
777
|
+
for (let k = 0; k < 3; k++) {
|
|
778
|
+
bin.lo[k] = Math.min(bin.lo[k], cell.center[k] - cell.size[k] / 2);
|
|
779
|
+
bin.hi[k] = Math.max(bin.hi[k], cell.center[k] + cell.size[k] / 2);
|
|
780
|
+
}
|
|
781
|
+
bins.set(key, bin);
|
|
782
|
+
}
|
|
783
|
+
if (bins.size <= cap) {
|
|
784
|
+
return [...bins.values()].map(({ lo, hi }) => ({ center: [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2], size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]] }));
|
|
785
|
+
}
|
|
786
|
+
side *= 1.25;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
635
789
|
|
|
636
790
|
// src/layers.ts
|
|
637
791
|
var MAX_SPALLS = 64;
|
|
@@ -1672,6 +1826,7 @@ var GameDestruction = (() => {
|
|
|
1672
1826
|
},
|
|
1673
1827
|
{
|
|
1674
1828
|
id: "wood_soft",
|
|
1829
|
+
splitForm: "slivers",
|
|
1675
1830
|
density: 500,
|
|
1676
1831
|
hpPerKg: 90,
|
|
1677
1832
|
penetration: 9,
|
|
@@ -1717,6 +1872,7 @@ var GameDestruction = (() => {
|
|
|
1717
1872
|
},
|
|
1718
1873
|
{
|
|
1719
1874
|
id: "wood_hard",
|
|
1875
|
+
splitForm: "slivers",
|
|
1720
1876
|
density: 750,
|
|
1721
1877
|
hpPerKg: 140,
|
|
1722
1878
|
penetration: 14,
|
|
@@ -1761,6 +1917,7 @@ var GameDestruction = (() => {
|
|
|
1761
1917
|
},
|
|
1762
1918
|
{
|
|
1763
1919
|
id: "plywood",
|
|
1920
|
+
splitForm: "plies",
|
|
1764
1921
|
density: 600,
|
|
1765
1922
|
hpPerKg: 70,
|
|
1766
1923
|
penetration: 7,
|
|
@@ -1808,6 +1965,7 @@ var GameDestruction = (() => {
|
|
|
1808
1965
|
},
|
|
1809
1966
|
{
|
|
1810
1967
|
id: "mdf",
|
|
1968
|
+
splitForm: "granules",
|
|
1811
1969
|
density: 750,
|
|
1812
1970
|
hpPerKg: 45,
|
|
1813
1971
|
penetration: 6,
|
|
@@ -1974,6 +2132,7 @@ var GameDestruction = (() => {
|
|
|
1974
2132
|
floor: 60
|
|
1975
2133
|
}
|
|
1976
2134
|
},
|
|
2135
|
+
ricochetDeg: 12,
|
|
1977
2136
|
hitDebris: "chips",
|
|
1978
2137
|
breakDebris: "dust",
|
|
1979
2138
|
visual: { innerColor: 10197652 },
|
|
@@ -2005,6 +2164,7 @@ var GameDestruction = (() => {
|
|
|
2005
2164
|
0.04,
|
|
2006
2165
|
0.12
|
|
2007
2166
|
],
|
|
2167
|
+
ricochetDeg: 10,
|
|
2008
2168
|
hitDebris: "chips",
|
|
2009
2169
|
breakDebris: "dust",
|
|
2010
2170
|
visual: { innerColor: 12085567 },
|
|
@@ -2062,6 +2222,7 @@ var GameDestruction = (() => {
|
|
|
2062
2222
|
0.06,
|
|
2063
2223
|
0.3
|
|
2064
2224
|
],
|
|
2225
|
+
ricochetDeg: 15,
|
|
2065
2226
|
hitDebris: "chips",
|
|
2066
2227
|
breakDebris: "dust",
|
|
2067
2228
|
visual: { innerColor: 9407364 },
|
|
@@ -2319,6 +2480,7 @@ var GameDestruction = (() => {
|
|
|
2319
2480
|
},
|
|
2320
2481
|
{
|
|
2321
2482
|
id: "green_wood",
|
|
2483
|
+
splitForm: "slivers",
|
|
2322
2484
|
density: 650,
|
|
2323
2485
|
hpPerKg: 5,
|
|
2324
2486
|
penetration: 10,
|
|
@@ -2489,6 +2651,7 @@ var GameDestruction = (() => {
|
|
|
2489
2651
|
0.03,
|
|
2490
2652
|
0.15
|
|
2491
2653
|
],
|
|
2654
|
+
ricochetDeg: 8,
|
|
2492
2655
|
hitDebris: "chips",
|
|
2493
2656
|
breakDebris: "dust",
|
|
2494
2657
|
thermal: {
|
|
@@ -2588,6 +2751,7 @@ var GameDestruction = (() => {
|
|
|
2588
2751
|
0.02,
|
|
2589
2752
|
0.08
|
|
2590
2753
|
],
|
|
2754
|
+
ricochetDeg: 8,
|
|
2591
2755
|
hitDebris: "chips",
|
|
2592
2756
|
breakDebris: "dust",
|
|
2593
2757
|
thermal: {
|
|
@@ -2599,6 +2763,127 @@ var GameDestruction = (() => {
|
|
|
2599
2763
|
"brittle"
|
|
2600
2764
|
],
|
|
2601
2765
|
compressive: 1e5
|
|
2766
|
+
},
|
|
2767
|
+
{
|
|
2768
|
+
id: "mud",
|
|
2769
|
+
density: 1700,
|
|
2770
|
+
hpPerKg: 1,
|
|
2771
|
+
penetration: 5,
|
|
2772
|
+
ductility: 0.8,
|
|
2773
|
+
fracture: "crumble",
|
|
2774
|
+
fragmentSize: [
|
|
2775
|
+
0.02,
|
|
2776
|
+
0.08
|
|
2777
|
+
],
|
|
2778
|
+
hitDebris: "dust",
|
|
2779
|
+
breakDebris: "dust",
|
|
2780
|
+
thermal: {
|
|
2781
|
+
conductivity: 0.5,
|
|
2782
|
+
heatCapacity: 3.2
|
|
2783
|
+
},
|
|
2784
|
+
tags: [
|
|
2785
|
+
"soil",
|
|
2786
|
+
"wet"
|
|
2787
|
+
],
|
|
2788
|
+
compressive: 5e3
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
id: "clay",
|
|
2792
|
+
density: 1800,
|
|
2793
|
+
hpPerKg: 5,
|
|
2794
|
+
penetration: 7,
|
|
2795
|
+
ductility: 0.05,
|
|
2796
|
+
fracture: "crumble",
|
|
2797
|
+
fragmentSize: [
|
|
2798
|
+
0.02,
|
|
2799
|
+
0.1
|
|
2800
|
+
],
|
|
2801
|
+
hitDebris: "chips",
|
|
2802
|
+
breakDebris: "dust",
|
|
2803
|
+
thermal: {
|
|
2804
|
+
conductivity: 0.15,
|
|
2805
|
+
heatCapacity: 2.2
|
|
2806
|
+
},
|
|
2807
|
+
tags: [
|
|
2808
|
+
"soil",
|
|
2809
|
+
"brittle"
|
|
2810
|
+
],
|
|
2811
|
+
compressive: 6e4
|
|
2812
|
+
},
|
|
2813
|
+
{
|
|
2814
|
+
id: "snow",
|
|
2815
|
+
density: 300,
|
|
2816
|
+
hpPerKg: 1,
|
|
2817
|
+
penetration: 3,
|
|
2818
|
+
ductility: 0.6,
|
|
2819
|
+
fracture: "crumble",
|
|
2820
|
+
fragmentSize: [
|
|
2821
|
+
0.01,
|
|
2822
|
+
0.05
|
|
2823
|
+
],
|
|
2824
|
+
hitDebris: "dust",
|
|
2825
|
+
breakDebris: "dust",
|
|
2826
|
+
thermal: {
|
|
2827
|
+
conductivity: 0.1,
|
|
2828
|
+
heatCapacity: 2.1
|
|
2829
|
+
},
|
|
2830
|
+
tags: [
|
|
2831
|
+
"soil",
|
|
2832
|
+
"frozen"
|
|
2833
|
+
],
|
|
2834
|
+
compressive: 1e3
|
|
2835
|
+
},
|
|
2836
|
+
{
|
|
2837
|
+
id: "cobble",
|
|
2838
|
+
density: 2650,
|
|
2839
|
+
hpPerKg: 25,
|
|
2840
|
+
penetration: 25,
|
|
2841
|
+
penetrationClass: "concrete",
|
|
2842
|
+
penetrationScale: 0.9,
|
|
2843
|
+
ductility: 0.03,
|
|
2844
|
+
fracture: "crumble",
|
|
2845
|
+
fragmentSize: [
|
|
2846
|
+
0.05,
|
|
2847
|
+
0.15
|
|
2848
|
+
],
|
|
2849
|
+
ricochetDeg: 15,
|
|
2850
|
+
hitDebris: "chips",
|
|
2851
|
+
breakDebris: "chips",
|
|
2852
|
+
thermal: {
|
|
2853
|
+
conductivity: 0.2,
|
|
2854
|
+
heatCapacity: 2.5
|
|
2855
|
+
},
|
|
2856
|
+
tags: [
|
|
2857
|
+
"masonry",
|
|
2858
|
+
"brittle"
|
|
2859
|
+
],
|
|
2860
|
+
compressive: 3e5
|
|
2861
|
+
},
|
|
2862
|
+
{
|
|
2863
|
+
id: "paver",
|
|
2864
|
+
density: 2300,
|
|
2865
|
+
hpPerKg: 14,
|
|
2866
|
+
penetration: 18,
|
|
2867
|
+
penetrationClass: "concrete",
|
|
2868
|
+
penetrationScale: 1,
|
|
2869
|
+
ductility: 0.03,
|
|
2870
|
+
fracture: "crumble",
|
|
2871
|
+
fragmentSize: [
|
|
2872
|
+
0.04,
|
|
2873
|
+
0.12
|
|
2874
|
+
],
|
|
2875
|
+
ricochetDeg: 12,
|
|
2876
|
+
hitDebris: "chips",
|
|
2877
|
+
breakDebris: "chips",
|
|
2878
|
+
thermal: {
|
|
2879
|
+
conductivity: 0.15,
|
|
2880
|
+
heatCapacity: 2.5
|
|
2881
|
+
},
|
|
2882
|
+
tags: [
|
|
2883
|
+
"masonry",
|
|
2884
|
+
"brittle"
|
|
2885
|
+
],
|
|
2886
|
+
compressive: 35e4
|
|
2602
2887
|
}
|
|
2603
2888
|
];
|
|
2604
2889
|
|
|
@@ -3103,6 +3388,11 @@ var GameDestruction = (() => {
|
|
|
3103
3388
|
var BREACH_BLAST_FACTOR = 6;
|
|
3104
3389
|
var BREACH_AREA = 0.25;
|
|
3105
3390
|
var BREACH_BLAST_REACH = 0.12;
|
|
3391
|
+
var MEMBER_CUT_REACH = 0.5;
|
|
3392
|
+
var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
|
|
3393
|
+
var BLAST_EDGE_RAGGED = 0.3;
|
|
3394
|
+
var BLAST_EDGE_LOOSE = 1.35;
|
|
3395
|
+
var BLAST_EDGE_WEAR = 0.6;
|
|
3106
3396
|
var RESIDUAL_CAPACITY = 0.15;
|
|
3107
3397
|
var SEVER_BAND = 0.1;
|
|
3108
3398
|
var SEVER_ASPECT = 3;
|
|
@@ -4355,7 +4645,7 @@ var GameDestruction = (() => {
|
|
|
4355
4645
|
if (j.st.open) continue;
|
|
4356
4646
|
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
4357
4647
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
4358
|
-
j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
4648
|
+
j.st.hp -= span === void 0 ? amount : Math.min(amount, j.st.maxHp) * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
4359
4649
|
if (j.st.hp <= 0) {
|
|
4360
4650
|
j.st.hp = 0;
|
|
4361
4651
|
j.st.open = true;
|
|
@@ -4483,6 +4773,7 @@ var GameDestruction = (() => {
|
|
|
4483
4773
|
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4484
4774
|
const cut = (_b = p.rt).craters ?? (_b.craters = {});
|
|
4485
4775
|
const out = [];
|
|
4776
|
+
const wholeBefore = Object.values(worn).filter((w) => w === -1).length;
|
|
4486
4777
|
const shot = kind === "ballistic";
|
|
4487
4778
|
for (let i = 0; i < grid.length; i++) {
|
|
4488
4779
|
const cell = grid[i];
|
|
@@ -4541,6 +4832,7 @@ var GameDestruction = (() => {
|
|
|
4541
4832
|
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
4542
4833
|
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
4543
4834
|
}
|
|
4835
|
+
if (Object.values(worn).filter((w) => w === -1).length > wholeBefore) dropUnsupported(o, p, src);
|
|
4544
4836
|
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
4545
4837
|
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
4546
4838
|
if (p.structural && !isLost(p)) {
|
|
@@ -4644,10 +4936,122 @@ var GameDestruction = (() => {
|
|
|
4644
4936
|
setStage(o, p, 1, {}, src);
|
|
4645
4937
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
4646
4938
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
4939
|
+
dropUnsupported(o, p, src);
|
|
4647
4940
|
const reinforcement = p.spec.reinforcement;
|
|
4648
4941
|
if (reinforcement) exposeRebar(o, p, src);
|
|
4649
4942
|
if (p.structural) solveStructure(o);
|
|
4650
4943
|
};
|
|
4944
|
+
const blastReachOf = (p, energy) => {
|
|
4945
|
+
const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
|
|
4946
|
+
const resistance = Math.max(1, perKg * p.mat.density * p.thickness);
|
|
4947
|
+
return BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3) * clamp2(Math.cbrt(BLAST_REFERENCE_RESISTANCE / resistance), 0.3, 2);
|
|
4948
|
+
};
|
|
4949
|
+
const blastUnits = (o, p, wp, energy, dist, src) => {
|
|
4950
|
+
var _a, _b;
|
|
4951
|
+
if (!p.staged || p.st.stripped || isLost(p) || chipEnergy <= 0) return false;
|
|
4952
|
+
const reach = blastReachOf(p, energy);
|
|
4953
|
+
if (dist >= reach) return false;
|
|
4954
|
+
const radius = Math.sqrt(reach * reach - dist * dist);
|
|
4955
|
+
const [u, v] = planeAxes(p.thin);
|
|
4956
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4957
|
+
const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
|
|
4958
|
+
const grid = gridOf(p);
|
|
4959
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4960
|
+
const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
|
|
4961
|
+
const lobes = [rnd() * 2 * Math.PI, rnd() * 2 * Math.PI];
|
|
4962
|
+
const out = [];
|
|
4963
|
+
for (let i = 0; i < grid.length; i++) {
|
|
4964
|
+
const cell = grid[i];
|
|
4965
|
+
if (worn[i] === -1) continue;
|
|
4966
|
+
if (op && p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius) continue;
|
|
4967
|
+
const du = cell.center[u] - lp[u], dv = cell.center[v] - lp[v];
|
|
4968
|
+
const angle = Math.atan2(dv, du);
|
|
4969
|
+
const edge = radius * (1 + BLAST_EDGE_RAGGED * (0.5 * Math.sin(3 * angle + lobes[0]) + 0.3 * Math.sin(5 * angle + lobes[1]) + 0.4 * (rnd() - 0.5)));
|
|
4970
|
+
const d = Math.hypot(du, dv);
|
|
4971
|
+
if (d <= edge) {
|
|
4972
|
+
worn[i] = -1;
|
|
4973
|
+
out.push(i);
|
|
4974
|
+
} else if (d <= edge * BLAST_EDGE_LOOSE) {
|
|
4975
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
4976
|
+
worn[i] = Math.max(worn[i] ?? 0, perKg * mass * BLAST_EDGE_WEAR);
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
if (out.length === 0) return false;
|
|
4980
|
+
const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !(op && p.st.opening && Math.hypot(grid[i].center[u] - op[u], grid[i].center[v] - op[v]) <= p.st.opening.radius));
|
|
4981
|
+
for (const i of loose) worn[i] = -1;
|
|
4982
|
+
const blown = out.map((i) => grid[i]), fallen = loose.map((i) => grid[i]);
|
|
4983
|
+
const volume = (cells2) => cells2.reduce((sum, cell) => sum + cell.size[0] * cell.size[1] * cell.size[2], 0);
|
|
4984
|
+
const blownMass = Math.min(p.st.mass, p.mat.density * volume(blown));
|
|
4985
|
+
const fallenMass = Math.min(p.st.mass - blownMass, p.mat.density * volume(fallen));
|
|
4986
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.02 / Math.max(blownMass, 0.05)), 1, 12);
|
|
4987
|
+
const { c, q } = partXf(o, p);
|
|
4988
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
4989
|
+
const tag = `x${chipped.length}_`;
|
|
4990
|
+
const fragments = [
|
|
4991
|
+
...cellFragments(o, p, clumpCells(blown, p.thin, maxFragments), wp, null, speed, blownMass, tag, true),
|
|
4992
|
+
...fallen.length > 0 ? cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), wp, null, 0.3, fallenMass, `${tag}f`, true) : []
|
|
4993
|
+
];
|
|
4994
|
+
const cells = [...blown, ...fallen];
|
|
4995
|
+
const holes = localHolesOf(p);
|
|
4996
|
+
p.st.holes = p.st.holes.filter((_, k) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
|
|
4997
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
4998
|
+
for (const cell of cells) {
|
|
4999
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
5000
|
+
for (let k = 0; k < 3; k++) {
|
|
5001
|
+
lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
|
|
5002
|
+
hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
p.st.mass = Math.max(0, p.st.mass - blownMass - fallenMass);
|
|
5006
|
+
p.rt.removed = (p.rt.removed ?? 0) + blownMass + fallenMass;
|
|
5007
|
+
markDamaged(p);
|
|
5008
|
+
setStage(o, p, 1, {}, src);
|
|
5009
|
+
const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
5010
|
+
const units = cells.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
|
|
5011
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
|
|
5012
|
+
debris(o, p, p.mat.breakDebris, center, vec.normalize(vec.sub(center, wp), [0, 1, 0]), 14, 3, src);
|
|
5013
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
5014
|
+
if (p.structural && !isLost(p)) {
|
|
5015
|
+
solveStructure(o);
|
|
5016
|
+
collapseCheck(o, src);
|
|
5017
|
+
}
|
|
5018
|
+
return true;
|
|
5019
|
+
};
|
|
5020
|
+
const dropUnsupported = (o, p, src) => {
|
|
5021
|
+
var _a, _b;
|
|
5022
|
+
if (!p.spec.units || p.spec.units.mortar === p.spec.material || p.st.stripped || isLost(p)) return 0;
|
|
5023
|
+
const [u, v] = planeAxes(p.thin);
|
|
5024
|
+
const grid = gridOf(p);
|
|
5025
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
5026
|
+
const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
|
|
5027
|
+
const opened = (cell) => !!op && !!p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius;
|
|
5028
|
+
const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !opened(grid[i]));
|
|
5029
|
+
if (loose.length === 0) return 0;
|
|
5030
|
+
for (const i of loose) worn[i] = -1;
|
|
5031
|
+
const fallen = loose.map((i) => grid[i]);
|
|
5032
|
+
const mass = Math.min(p.st.mass, p.mat.density * fallen.reduce((sum, cell) => sum + cell.size[0] * cell.size[1] * cell.size[2], 0));
|
|
5033
|
+
const { c, q } = partXf(o, p);
|
|
5034
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
5035
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
5036
|
+
for (const cell of fallen) {
|
|
5037
|
+
for (let k = 0; k < 3; k++) {
|
|
5038
|
+
lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
|
|
5039
|
+
hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
5043
|
+
const fragments = cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), vec.add(center, [0, 1, 0]), null, 0.3, mass, `u${chipped.length}_`, true);
|
|
5044
|
+
const holes = localHolesOf(p);
|
|
5045
|
+
p.st.holes = p.st.holes.filter((_, k) => !fallen.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
|
|
5046
|
+
for (const cell of fallen) chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
5047
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
5048
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
5049
|
+
markDamaged(p);
|
|
5050
|
+
const units = fallen.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
|
|
5051
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
|
|
5052
|
+
debris(o, p, p.mat.breakDebris, center, [0, -1, 0], 6, 1, src);
|
|
5053
|
+
return loose.length;
|
|
5054
|
+
};
|
|
4651
5055
|
const stripPart = (o, p, reinforcement, src) => {
|
|
4652
5056
|
const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
|
|
4653
5057
|
p.st.stripped = true;
|
|
@@ -4714,6 +5118,7 @@ var GameDestruction = (() => {
|
|
|
4714
5118
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
4715
5119
|
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
4716
5120
|
const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
5121
|
+
const planRnd = p.mat.splitForm ? splitStream(o.id, p.spec.id, hitLocal) : rnd;
|
|
4717
5122
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
4718
5123
|
style,
|
|
4719
5124
|
dims: p.dims,
|
|
@@ -4722,8 +5127,9 @@ var GameDestruction = (() => {
|
|
|
4722
5127
|
hit: hitLocal,
|
|
4723
5128
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
4724
5129
|
maxFragments,
|
|
4725
|
-
rnd,
|
|
5130
|
+
rnd: planRnd,
|
|
4726
5131
|
sheet: p.mat.sheet,
|
|
5132
|
+
...p.mat.splitForm ? { form: p.mat.splitForm } : {},
|
|
4727
5133
|
...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
|
|
4728
5134
|
});
|
|
4729
5135
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
@@ -4862,7 +5268,8 @@ var GameDestruction = (() => {
|
|
|
4862
5268
|
const fragments = [];
|
|
4863
5269
|
for (let i = 0; i < n; i++) {
|
|
4864
5270
|
const back = !exit || i % 3 === 2;
|
|
4865
|
-
const
|
|
5271
|
+
const plies = p.mat.splitForm === "plies";
|
|
5272
|
+
const len = scale * (fibre ? 1.2 + 1.2 * rnd() : 3 + 4 * rnd()), w = scale * (0.8 + 0.8 * rnd()), t = scale * (fibre ? 0.7 + 0.6 * rnd() : plies ? 0.15 + 0.15 * rnd() : 0.4 + 0.4 * rnd());
|
|
4866
5273
|
const size = [w, w, w];
|
|
4867
5274
|
size[along] = len;
|
|
4868
5275
|
size[(along + 1) % 3] = w;
|
|
@@ -5023,7 +5430,7 @@ var GameDestruction = (() => {
|
|
|
5023
5430
|
checkIgnite(o, p, src);
|
|
5024
5431
|
}
|
|
5025
5432
|
}
|
|
5026
|
-
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
5433
|
+
wearJoints(o, p, Ein * 0.5, void 0, 0, src, false, 2 * BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3));
|
|
5027
5434
|
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
5028
5435
|
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
5029
5436
|
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
@@ -5033,7 +5440,10 @@ var GameDestruction = (() => {
|
|
|
5033
5440
|
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
5034
5441
|
res.damage += dealt;
|
|
5035
5442
|
if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
|
|
5036
|
-
|
|
5443
|
+
const breachReach = BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3);
|
|
5444
|
+
if (p.spec.units && p.spec.units.mortar !== p.spec.material) blastUnits(o, p, wp, energy, dist, src);
|
|
5445
|
+
else if (p.spec.reinforcement && p.spec.units && dist > MEMBER_CUT_REACH * breachReach) blastUnits(o, p, wp, energy, dist, src);
|
|
5446
|
+
else if (dist <= breachReach) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
5037
5447
|
return reach;
|
|
5038
5448
|
};
|
|
5039
5449
|
const blastProbe = { partsTested: 0, partsNear: 0 };
|
|
@@ -5279,6 +5689,11 @@ var GameDestruction = (() => {
|
|
|
5279
5689
|
}
|
|
5280
5690
|
return h | 0;
|
|
5281
5691
|
};
|
|
5692
|
+
const splitStream = (objectId, partId, hit) => {
|
|
5693
|
+
const codes = Array.from(`${objectId}|${partId}`, (char) => char.charCodeAt(0) / 1e3);
|
|
5694
|
+
const stream = createRng(seedOf(rng.state() / 1e3, ...codes, hit[0], hit[1], hit[2]));
|
|
5695
|
+
return () => stream.next();
|
|
5696
|
+
};
|
|
5282
5697
|
const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
|
|
5283
5698
|
const v = frag.velocity;
|
|
5284
5699
|
const speed = Math.hypot(v[0], v[1], v[2]);
|
|
@@ -6540,7 +6955,12 @@ var GameDestruction = (() => {
|
|
|
6540
6955
|
sand: "sand",
|
|
6541
6956
|
gravel: "gravel",
|
|
6542
6957
|
turf: "turf",
|
|
6543
|
-
mortar: "mortar"
|
|
6958
|
+
mortar: "mortar",
|
|
6959
|
+
mud: "mud",
|
|
6960
|
+
clay: "dry clay",
|
|
6961
|
+
snow: "snow",
|
|
6962
|
+
cobble: "cobblestone",
|
|
6963
|
+
paver: "concrete paver"
|
|
6544
6964
|
},
|
|
6545
6965
|
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
6546
6966
|
jointKinds: {
|
|
@@ -6636,7 +7056,12 @@ var GameDestruction = (() => {
|
|
|
6636
7056
|
sand: "areia",
|
|
6637
7057
|
gravel: "brita",
|
|
6638
7058
|
turf: "grama",
|
|
6639
|
-
mortar: "argamassa"
|
|
7059
|
+
mortar: "argamassa",
|
|
7060
|
+
mud: "lama",
|
|
7061
|
+
clay: "barro seco",
|
|
7062
|
+
snow: "neve",
|
|
7063
|
+
cobble: "paralelep\xEDpedo",
|
|
7064
|
+
paver: "bloquete"
|
|
6640
7065
|
},
|
|
6641
7066
|
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
6642
7067
|
jointKinds: {
|