@adatechnology/game-destruction 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -340,6 +340,45 @@ var frame = (ax, ay, az, angle = 0) => {
340
340
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
341
341
  };
342
342
  var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
343
+ var splinterBox = (inp, box, limit = inp.maxFragments) => {
344
+ const { rnd } = inp;
345
+ const dims = box.s;
346
+ const [fmin, fmax] = inp.fragmentSize;
347
+ const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
348
+ const [a, b] = [0, 1, 2].filter((i) => i !== g);
349
+ const w = Math.max(fmin * 1.5, 0.01);
350
+ const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
351
+ const maxLen = fmax * 4;
352
+ const cells = bsp(box, c, [a, b], rnd);
353
+ const pieces = [];
354
+ for (const cell of cells) {
355
+ const extra = rnd() < 0.4 ? 1 : 0;
356
+ const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
357
+ for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
358
+ }
359
+ 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] }));
360
+ };
361
+ var severAt = (inp, root, at, gap) => {
362
+ const dims = root.s;
363
+ const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
364
+ const half = dims[L] / 2;
365
+ const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
366
+ const out = [];
367
+ const stub = (from, to) => {
368
+ if (to - from < 0.02) return;
369
+ const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
370
+ c[L] = (from + to) / 2;
371
+ sz[L] = to - from;
372
+ out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
373
+ };
374
+ stub(-half, lo);
375
+ stub(hi, half);
376
+ const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
377
+ band.c[L] = (lo + hi) / 2;
378
+ band.s[L] = Math.max(hi - lo, 0.01);
379
+ for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
380
+ return out;
381
+ };
343
382
  var tearStack = (inp, root, t, u, v, n, sheet) => {
344
383
  const { dims, rnd, maxFragments } = inp;
345
384
  const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
@@ -378,23 +417,8 @@ var planFragments = (inp) => {
378
417
  return out;
379
418
  }
380
419
  if (style === "splinter") {
381
- const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
382
- const [a, b] = [0, 1, 2].filter((i) => i !== g);
383
- const w = Math.max(fmin * 1.5, 0.01);
384
- const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
385
- const maxLen = fmax * 4;
386
- const cells = bsp(root, c, [a, b], rnd);
387
- const pieces = [];
388
- for (const cell of cells) {
389
- let list = [cell];
390
- const extra = rnd() < 0.4 ? 1 : 0;
391
- const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
392
- list = bsp(cell, k, [g], rnd);
393
- for (const p of list) pieces.push(p);
394
- }
395
- for (const p of pieces.slice(0, maxFragments)) {
396
- out.push({ 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] });
397
- }
420
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
421
+ for (const p of splinterBox(inp, root)) out.push(p);
398
422
  return out;
399
423
  }
400
424
  if (style === "tear") {
@@ -2457,9 +2481,14 @@ var BREACH_BLAST_FACTOR = 6;
2457
2481
  var BREACH_AREA = 0.25;
2458
2482
  var BREACH_BLAST_REACH = 0.12;
2459
2483
  var RESIDUAL_CAPACITY = 0.15;
2484
+ var SEVER_BAND = 0.1;
2485
+ var SEVER_ASPECT = 3;
2486
+ var SEVER_BALLISTIC = 2;
2460
2487
  var SUPPORT_TOLERANCE = 0.05;
2461
2488
  var UNIT_FRAGMENT_FACTOR = 4;
2462
2489
  var BREACH_GRID_CAP = 4096;
2490
+ var CHIP_ENERGY = 200;
2491
+ var CHIP_SPLASH = 0.5;
2463
2492
  var CUT_SLICE = 1;
2464
2493
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
2465
2494
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -2503,6 +2532,7 @@ var createDestruction = (options = {}) => {
2503
2532
  let windCfg = windOpts ? resolveWind(windOpts) : null;
2504
2533
  const ignitionRules = resolveIgnitionRules(options.ignition);
2505
2534
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
2535
+ const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
2506
2536
  const rng = createRng(options.seed ?? 1);
2507
2537
  const rnd = () => rng.next();
2508
2538
  let time = 0;
@@ -2876,41 +2906,68 @@ var createDestruction = (options = {}) => {
2876
2906
  markDamaged(p);
2877
2907
  if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
2878
2908
  };
2909
+ const freeSpan = (p, supports) => {
2910
+ if (supports.length === 0) return Infinity;
2911
+ const d = p.dims;
2912
+ const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
2913
+ const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
2914
+ let far = 0;
2915
+ for (let i = 0; i <= 16; i++) {
2916
+ const x = (i / 16 - 0.5) * d[L];
2917
+ let near = Infinity;
2918
+ for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
2919
+ far = Math.max(far, near);
2920
+ }
2921
+ return 2 * far;
2922
+ };
2879
2923
  const solveStructure = (o) => {
2880
2924
  o.maxStress = 0;
2881
2925
  if (!o.hasStructure) return;
2882
2926
  const parts = o.parts;
2883
2927
  const n = parts.length;
2884
2928
  const dist = new Array(n).fill(-1);
2885
- const queue = [];
2886
2929
  const ok = (i) => parts[i].structural && !isLost(parts[i]);
2887
2930
  const isPartition = (i) => parts[i].spec.structure?.role === "partition";
2888
- for (let i = 0; i < n; i++) if (ok(i) && parts[i].anchored) {
2889
- dist[i] = 0;
2890
- queue.push(i);
2891
- }
2892
- for (let h = 0; h < queue.length; h++) {
2893
- const s = queue[h];
2894
- if (isPartition(s)) continue;
2895
- for (const j of o.byPart.get(parts[s].spec.id)) {
2896
- if (j.st.open || !j.bears) continue;
2897
- const r = j.ia === s ? j.ib : j.ia;
2898
- if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
2899
- dist[r] = dist[s] + 1;
2900
- queue.push(r);
2931
+ const out = /* @__PURE__ */ new Set();
2932
+ let queue = [];
2933
+ let lowerOf = /* @__PURE__ */ new Map();
2934
+ for (; ; ) {
2935
+ dist.fill(-1);
2936
+ queue = [];
2937
+ for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
2938
+ dist[i] = 0;
2939
+ queue.push(i);
2901
2940
  }
2902
- }
2903
- const incoming = new Array(n).fill(0);
2904
- const lowerOf = /* @__PURE__ */ new Map();
2905
- for (const i of queue) {
2906
- const lower = [];
2907
- for (const j of o.byPart.get(parts[i].spec.id)) {
2908
- if (j.st.open || !j.bears) continue;
2909
- const k = restsOn(o, j, i);
2910
- if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
2941
+ for (let h = 0; h < queue.length; h++) {
2942
+ const s = queue[h];
2943
+ if (isPartition(s)) continue;
2944
+ for (const j of o.byPart.get(parts[s].spec.id)) {
2945
+ if (j.st.open || !j.bears) continue;
2946
+ const r = j.ia === s ? j.ib : j.ia;
2947
+ if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
2948
+ dist[r] = dist[s] + 1;
2949
+ queue.push(r);
2950
+ }
2911
2951
  }
2912
- lowerOf.set(i, lower);
2952
+ lowerOf = /* @__PURE__ */ new Map();
2953
+ let dropped = false;
2954
+ for (const i of queue) {
2955
+ const lower = [];
2956
+ for (const j of o.byPart.get(parts[i].spec.id)) {
2957
+ if (j.st.open || !j.bears) continue;
2958
+ const k = restsOn(o, j, i);
2959
+ if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
2960
+ }
2961
+ lowerOf.set(i, lower);
2962
+ const max = parts[i].spec.structure?.maxSpan;
2963
+ if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
2964
+ out.add(i);
2965
+ dropped = true;
2966
+ }
2967
+ }
2968
+ if (!dropped) break;
2913
2969
  }
2970
+ const incoming = new Array(n).fill(0);
2914
2971
  const bending = new Array(n).fill(0);
2915
2972
  const through = parts.map(() => []);
2916
2973
  const pass = (k, at, mass) => {
@@ -3422,6 +3479,7 @@ var createDestruction = (options = {}) => {
3422
3479
  const r = rayPart(o, p, origin, d);
3423
3480
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
3424
3481
  if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
3482
+ if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
3425
3483
  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 };
3426
3484
  }
3427
3485
  }
@@ -3685,6 +3743,62 @@ var createDestruction = (options = {}) => {
3685
3743
  if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
3686
3744
  openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
3687
3745
  };
3746
+ const unitGrids = /* @__PURE__ */ new WeakMap();
3747
+ const gridOf = (p) => {
3748
+ let grid = unitGrids.get(p);
3749
+ if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP));
3750
+ return grid;
3751
+ };
3752
+ const insideChip = (p, objectPoint) => {
3753
+ const cells = p.st.chipped;
3754
+ if (!cells?.length) return false;
3755
+ const [u, v] = planeAxes(p.thin);
3756
+ const lp = toLocal(p.pos, p.q, objectPoint);
3757
+ return cells.some((c) => Math.abs(lp[u] - c.center[u]) <= c.size[u] / 2 && Math.abs(lp[v] - c.center[v]) <= c.size[v] / 2);
3758
+ };
3759
+ const chipUnits = (o, p, wp, energy, dir, radius, src) => {
3760
+ var _a, _b;
3761
+ if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
3762
+ const [u, v] = planeAxes(p.thin);
3763
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
3764
+ const grid = gridOf(p);
3765
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
3766
+ const out = [];
3767
+ for (let i = 0; i < grid.length; i++) {
3768
+ const cell = grid[i];
3769
+ if (worn[i] === -1) continue;
3770
+ const gap = Math.hypot(Math.max(0, Math.abs(lp[u] - cell.center[u]) - cell.size[u] / 2), Math.max(0, Math.abs(lp[v] - cell.center[v]) - cell.size[v] / 2));
3771
+ if (gap > radius) continue;
3772
+ const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
3773
+ const total = (worn[i] ?? 0) + energy * share;
3774
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
3775
+ if (total < chipEnergy * mass) {
3776
+ worn[i] = total;
3777
+ continue;
3778
+ }
3779
+ worn[i] = -1;
3780
+ out.push(cell);
3781
+ }
3782
+ if (out.length === 0) return;
3783
+ const { c, q } = partXf(o, p);
3784
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
3785
+ const holes = localHolesOf(p);
3786
+ for (const cell of out) {
3787
+ const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
3788
+ const center = toWorld(c, q, cell.center);
3789
+ const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
3790
+ const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
3791
+ p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
3792
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
3793
+ p.st.mass = Math.max(0, p.st.mass - mass);
3794
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
3795
+ markDamaged(p);
3796
+ setStage(o, p, 1, {}, src);
3797
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
3798
+ debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
3799
+ }
3800
+ for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
3801
+ };
3688
3802
  const openBreach = (o, p, center, total, energy, dir, src) => {
3689
3803
  const [u, v] = planeAxes(p.thin);
3690
3804
  const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
@@ -3696,7 +3810,8 @@ var createDestruction = (options = {}) => {
3696
3810
  failPart(o, p, worldCenter, dir, energy, 12, src);
3697
3811
  return;
3698
3812
  }
3699
- const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
3813
+ const worn = p.rt.chips;
3814
+ const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
3700
3815
  const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
3701
3816
  const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
3702
3817
  const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
@@ -3780,6 +3895,8 @@ var createDestruction = (options = {}) => {
3780
3895
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
3781
3896
  const hitLocal = toLocal(c, q, hitW);
3782
3897
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
3898
+ const axis = style === "splinter" ? severAxis(p) : -1;
3899
+ const cutAt = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
3783
3900
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
3784
3901
  style,
3785
3902
  dims: p.dims,
@@ -3789,7 +3906,8 @@ var createDestruction = (options = {}) => {
3789
3906
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
3790
3907
  maxFragments,
3791
3908
  rnd,
3792
- sheet: p.mat.sheet
3909
+ sheet: p.mat.sheet,
3910
+ ...cutAt !== void 0 ? { cut: { at: cutAt, gap: SEVER_BAND } } : {}
3793
3911
  });
3794
3912
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
3795
3913
  const mass = p.st.mass;
@@ -3848,10 +3966,33 @@ var createDestruction = (options = {}) => {
3848
3966
  emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
3849
3967
  };
3850
3968
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
3969
+ const severAxis = (p) => {
3970
+ if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
3971
+ const d = p.dims;
3972
+ const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
3973
+ return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
3974
+ };
3975
+ const bandOf = (o, p, axis, origin) => {
3976
+ const { c, q } = partXf(o, p);
3977
+ const along = toLocal(c, q, origin)[axis];
3978
+ const L = p.dims[axis];
3979
+ return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
3980
+ };
3851
3981
  const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
3982
+ var _a;
3852
3983
  if (isLost(p) || amount <= 0) return 0;
3853
3984
  const prev = p.st.hp;
3854
3985
  p.st.hp -= amount;
3986
+ const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
3987
+ if (axis >= 0 && p.st.hp > 0) {
3988
+ const band = bandOf(o, p, axis, origin);
3989
+ const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
3990
+ cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
3991
+ if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
3992
+ p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
3993
+ p.st.hp = 0;
3994
+ }
3995
+ }
3855
3996
  markDamaged(p);
3856
3997
  const trigger = CHAIN_TRIGGER_OF[type];
3857
3998
  const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
@@ -3890,7 +4031,7 @@ var createDestruction = (options = {}) => {
3890
4031
  const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
3891
4032
  const mat = p.mat;
3892
4033
  const piercing = round.piercing ?? 1;
3893
- if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
4034
+ if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry)) || insideChip(p, objLocal(o, entry))) {
3894
4035
  return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
3895
4036
  }
3896
4037
  const sin = Math.min(1, Math.abs(vec.dot(d, n)));
@@ -3949,6 +4090,7 @@ var createDestruction = (options = {}) => {
3949
4090
  debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
3950
4091
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
3951
4092
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
4093
+ chipUnits(o, p, entry, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
3952
4094
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
3953
4095
  };
3954
4096
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -3976,6 +4118,7 @@ var createDestruction = (options = {}) => {
3976
4118
  const scale = mat.damageScale?.[kind] ?? 1;
3977
4119
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
3978
4120
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
4121
+ if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
3979
4122
  return done;
3980
4123
  };
3981
4124
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
@@ -5467,6 +5610,7 @@ var locales = {
5467
5610
  sway: "sway",
5468
5611
  spall: "plaster came off",
5469
5612
  stage: "damage stage",
5613
+ chip: "brick knocked out",
5470
5614
  expose: "rebar bare",
5471
5615
  body: "body",
5472
5616
  blast: "blast",
@@ -5557,6 +5701,7 @@ var locales = {
5557
5701
  sway: "balan\xE7o",
5558
5702
  spall: "reboco soltou",
5559
5703
  stage: "est\xE1gio de dano",
5704
+ chip: "tijolo arrancado",
5560
5705
  expose: "vergalh\xE3o \xE0 mostra",
5561
5706
  body: "corpo",
5562
5707
  blast: "explos\xE3o",