@adatechnology/game-destruction 0.1.1 → 0.3.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 -3
- package/dist/index.d.ts +22 -1
- package/dist/index.global.js +55 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +55 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -80,6 +80,9 @@ makes a through hole, through 5 cm concrete it only cracks and lodges. Grazing h
|
|
|
80
80
|
| `fire` | `temperature`, `radius`, `duration` | heats towards the flame temperature and ignites at the material's ignition point |
|
|
81
81
|
| `heat` | same | heat without flame (cutting torch, hot plate); melts what it can reach |
|
|
82
82
|
|
|
83
|
+
Brittle materials that `shatter` (glass, ceramic) take blunt and ballistic damage multiplied by `fragility(ductility, thickness)` (x1 from 2 cm up, to x118
|
|
84
|
+
for 3 mm glass), so a 9 mm shatters a window pane in one shot while a 5 cm block of glass takes several.
|
|
85
|
+
|
|
83
86
|
`hit(objectId, hit)` finds the part along `direction`; `hitPart` targets a known part; `shoot` and `raycast` work in world space.
|
|
84
87
|
|
|
85
88
|
## Fire, melting and leaks
|
|
@@ -125,13 +128,20 @@ without any `structure` behave exactly as before.
|
|
|
125
128
|
horizontal length for walls. A 0.3 m concrete column carries 40 t, a 0.2 m brick wall 8 t per metre, a 0.1 x 0.2 m softwood beam 2 t.
|
|
126
129
|
- **Joints** carry load when `bears` is true (default when both parts have a role). `bears: 'a_on_b'` means part `a` rests on `b`: load flows only
|
|
127
130
|
that way. A `partition` is supported but supports nothing, so breaking it never drops anything.
|
|
128
|
-
- **Overload**: above stress 1 a part
|
|
131
|
+
- **Overload**: above stress 1 a part loses `min(0.6, 0.12 + 0.8 * (stress - 1))` of its `maxHp` per second (stress 1.02 gives way in ~7 s, 1.1 in ~5 s,
|
|
132
|
+
1.5 in ~2 s, anything above 1.7 in ~1.7 s; deterministic), emits `stress` (once per 0.5 s) and cascades; brittle
|
|
129
133
|
parts `crack` from stress 0.8. Metal never fractures, so overloaded steel only reports stress.
|
|
130
134
|
- **Collapse**: a part with no support path to an anchored part falls: it is cut from the supported pieces into its own group (index 1 or more) and
|
|
131
|
-
one `collapse` event lists the group
|
|
135
|
+
one `collapse` event lists the group, and every part in it that is not already lost gets `PartState.status === 'collapsed'` (kept through
|
|
136
|
+
`serialize`/`load`, cleared by `reset`; a part that later breaks becomes `broken`). Non-structural parts that hang only on collapsed parts fall with them. Make the group its own object with
|
|
132
137
|
`splitGroup`.
|
|
133
138
|
- `buildFrame({ spanX, spanZ, columnsX, columnsZ, height, floors, walls, roof, columnCapacity, ... })` returns a test structure: columns on
|
|
134
139
|
foundations (`rebar`), beams, a slab per floor, optional brick walls (`mortar`) with a door opening, and a roof that rests (`resting`) on the top beams.
|
|
140
|
+
- `sizeStructure(spec, { safety?, redundancy? })` returns a copy of the spec with `structure.capacity` set from the loads the world itself solves
|
|
141
|
+
for the intact spec, so every structural part rests at stress `safety` (default 0.65). With `redundancy` (e.g. 0.97) the capacities are also
|
|
142
|
+
raised where needed so that losing any single `column` keeps every other part at or below that stress: the structure stands with one column
|
|
143
|
+
gone and, without help, falls with two neighbours gone. Raw material capacities are rarely a sensible design (a 54 t roof on 20 cm columns is
|
|
144
|
+
already over 1 at rest); size the structure instead of hand-tuning. Parts without `structure` or with role `none` are untouched.
|
|
135
145
|
|
|
136
146
|
## Contracts: implement your own or use the default world
|
|
137
147
|
|
|
@@ -182,7 +192,7 @@ world continues with the same events as the original. The loading world must use
|
|
|
182
192
|
`raycast`, `shoot`, `disassemble`, `workableJoints`, `nearestJoint`, `extinguish`, `setLiters`, `update`, `state`, `part`, `reset`, `on`,
|
|
183
193
|
`register`, `serialize`, `load`, `time`. `state()` and `part()` return live views; do not mutate them.
|
|
184
194
|
|
|
185
|
-
Also exported: `buildFrame`, `partCapacity`, `checkDestructible`, `checkHit`, `IMPACT_CONTRACT`, `MATERIALS`, `JOINT_DEFAULTS`, `JOINT_BY_MATERIALS`, `inferJoints`, `guessMaterial`, `partVolume`, `partMass`, `partThickness`, the
|
|
195
|
+
Also exported: `buildFrame`, `sizeStructure`, `partCapacity`, `checkDestructible`, `checkHit`, `IMPACT_CONTRACT`, `MATERIALS`, `JOINT_DEFAULTS`, `JOINT_BY_MATERIALS`, `inferJoints`, `guessMaterial`, `partVolume`, `partMass`, `partThickness`, the
|
|
186
196
|
helpers `vec`, `quat`, `quatFromEuler`, `rotateVec`, `toLocal`, `toWorld`, the locale (`locales`, `resolveStrings`, `DestructionStrings`) and all types.
|
|
187
197
|
|
|
188
198
|
The locale table has `en` and `pt-BR` strings for material names, part statuses, joint kinds, tools, damage types, event labels and fracture styles:
|
package/dist/index.d.ts
CHANGED
|
@@ -96,6 +96,18 @@ interface DestructionOptions {
|
|
|
96
96
|
/** Distance at which a burning part heats parts of other objects, in metres (default 0.6). */
|
|
97
97
|
fireSpread?: number;
|
|
98
98
|
}
|
|
99
|
+
interface SizeStructureOptions {
|
|
100
|
+
/** Stress every structural part has at rest after sizing (`load / capacity`), 0..1 (default 0.65). */
|
|
101
|
+
safety?: number;
|
|
102
|
+
/**
|
|
103
|
+
* Redundancy target: when set, the capacity of a part is raised (never lowered) until losing any single `column` leaves every other
|
|
104
|
+
* structural part at this stress or below, e.g. 0.97 (an N-1 design: the building stands with one column gone). Omit to skip the check.
|
|
105
|
+
* Should be above `safety` and, to let the structure fall when a second column goes, below 1.
|
|
106
|
+
*/
|
|
107
|
+
redundancy?: number;
|
|
108
|
+
/** Extra or overriding materials, the same list given to `createDestruction`. */
|
|
109
|
+
materials?: MaterialDef[];
|
|
110
|
+
}
|
|
99
111
|
interface AddOptions {
|
|
100
112
|
/** Object id (default a generated `obj_1`, `obj_2`...). */
|
|
101
113
|
id?: string;
|
|
@@ -299,6 +311,15 @@ interface FrameOptions {
|
|
|
299
311
|
*/
|
|
300
312
|
declare const buildFrame: (o?: FrameOptions) => DestructibleSpec;
|
|
301
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Sizes the load capacity of a structure the way an engineer would, from the loads the world itself computes. The intact spec goes through the
|
|
316
|
+
* same load solver `createDestruction` uses, and each structural part (`structure.role` other than `none`) gets `structure.capacity = load / safety`,
|
|
317
|
+
* so its at-rest stress is exactly `safety`. With `redundancy` set, every `column` is also removed in turn and the capacity of the parts that
|
|
318
|
+
* would carry more is raised so they stay at or below `redundancy`. Parts without `structure`, with role `none`, or with no support path to an
|
|
319
|
+
* anchored part are left as they are. The spec is not modified; a copy is returned.
|
|
320
|
+
*/
|
|
321
|
+
declare const sizeStructure: (spec: DestructibleSpec, options?: SizeStructureOptions) => DestructibleSpec;
|
|
322
|
+
|
|
302
323
|
/** Small vector helpers (arrays of three numbers). Pure functions; they allocate a new array. */
|
|
303
324
|
declare const vec: {
|
|
304
325
|
add: (a: Vec3, b: Vec3) => Vec3;
|
|
@@ -366,4 +387,4 @@ declare const checkDestructible: (impl: DestructibleImpl, options?: CheckOptions
|
|
|
366
387
|
problems: string[];
|
|
367
388
|
};
|
|
368
389
|
|
|
369
|
-
export { type AddOptions, type CheckOptions, type DestructionLocale, type DestructionOptions, type DestructionSnapshot, type DestructionStrings, type DestructionWorld, type FrameOptions, JOINT_BY_MATERIALS, JOINT_DEFAULTS, MATERIALS, type MaterialDef, type MaterialThermal, type ObjectRuntime, type PartRuntime, buildFrame, checkDestructible, createDestruction, fragility, guessMaterial, inferJoints, locales, partCapacity, partMass, partThickness, partVolume, quat, quatFromEuler, resolveStrings, rotateVec, toLocal, toWorld, vec };
|
|
390
|
+
export { type AddOptions, type CheckOptions, type DestructionLocale, type DestructionOptions, type DestructionSnapshot, type DestructionStrings, type DestructionWorld, type FrameOptions, JOINT_BY_MATERIALS, JOINT_DEFAULTS, MATERIALS, type MaterialDef, type MaterialThermal, type ObjectRuntime, type PartRuntime, type SizeStructureOptions, buildFrame, checkDestructible, createDestruction, fragility, guessMaterial, inferJoints, locales, partCapacity, partMass, partThickness, partVolume, quat, quatFromEuler, resolveStrings, rotateVec, sizeStructure, toLocal, toWorld, vec };
|
package/dist/index.global.js
CHANGED
|
@@ -41,6 +41,7 @@ var GameDestruction = (() => {
|
|
|
41
41
|
quatFromEuler: () => quatFromEuler,
|
|
42
42
|
resolveStrings: () => resolveStrings,
|
|
43
43
|
rotateVec: () => rotateVec,
|
|
44
|
+
sizeStructure: () => sizeStructure,
|
|
44
45
|
toLocal: () => toLocal,
|
|
45
46
|
toWorld: () => toWorld,
|
|
46
47
|
vec: () => vec
|
|
@@ -1456,9 +1457,12 @@ var GameDestruction = (() => {
|
|
|
1456
1457
|
var DMG_PER_J = 0.02;
|
|
1457
1458
|
var BLAST_COUPLING = 0.1;
|
|
1458
1459
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1460
|
+
var CUT_SLICE = 1;
|
|
1461
|
+
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
1459
1462
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
1460
1463
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
1461
1464
|
var clone = (v) => structuredClone(v);
|
|
1465
|
+
var overloadRate = (stress) => Math.min(0.6, 0.12 + 0.8 * (stress - 1));
|
|
1462
1466
|
var updateBound = (o) => {
|
|
1463
1467
|
if (!o.parts.length) return;
|
|
1464
1468
|
const c = [0, 0, 0];
|
|
@@ -1608,6 +1612,7 @@ var GameDestruction = (() => {
|
|
|
1608
1612
|
fuel0,
|
|
1609
1613
|
area: shapeArea(ps.shape),
|
|
1610
1614
|
thickness: partThickness(ps),
|
|
1615
|
+
cutFactor: cutFactorOf(ps.shape),
|
|
1611
1616
|
dims: shapeDims(ps.shape),
|
|
1612
1617
|
bound: shapeBound(ps.shape),
|
|
1613
1618
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
@@ -1768,7 +1773,10 @@ var GameDestruction = (() => {
|
|
|
1768
1773
|
for (const g of o.st.groups) {
|
|
1769
1774
|
const ps = g.map((id) => o.byId.get(id)).filter((p) => !isLost(p) && !o.collapsed.has(p.spec.id));
|
|
1770
1775
|
if (ps.length === 0 || g.some((id) => o.byId.get(id).anchored) || !ps.some((p) => p.structural)) continue;
|
|
1771
|
-
for (const p of ps)
|
|
1776
|
+
for (const p of ps) {
|
|
1777
|
+
o.collapsed.add(p.spec.id);
|
|
1778
|
+
p.st.status = "collapsed";
|
|
1779
|
+
}
|
|
1772
1780
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
|
|
1773
1781
|
}
|
|
1774
1782
|
}
|
|
@@ -2068,7 +2076,7 @@ var GameDestruction = (() => {
|
|
|
2068
2076
|
const effM = p.thickness / Math.max(sin, 0.3);
|
|
2069
2077
|
const thr = mat.penetration * effM * 1e3;
|
|
2070
2078
|
const penetrated = E > thr;
|
|
2071
|
-
const scale = mat.damageScale?.ballistic ?? 1;
|
|
2079
|
+
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2072
2080
|
const id = p.spec.id;
|
|
2073
2081
|
if (!penetrated && mat.ricochetDeg && angle < mat.ricochetDeg) {
|
|
2074
2082
|
const r = vec.sub(d, vec.scale(n, 2 * vec.dot(d, n)));
|
|
@@ -2129,7 +2137,7 @@ var GameDestruction = (() => {
|
|
|
2129
2137
|
if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
2130
2138
|
else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2131
2139
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2132
|
-
return applyDamage(o, p, Ec * DMG_PER_J * scale, kind, point, d, E * 0.1, 12, src);
|
|
2140
|
+
return applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
2133
2141
|
};
|
|
2134
2142
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2135
2143
|
const start = cur.length;
|
|
@@ -2418,7 +2426,7 @@ var GameDestruction = (() => {
|
|
|
2418
2426
|
p.rt.stressT = 0.5;
|
|
2419
2427
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
2420
2428
|
}
|
|
2421
|
-
applyDamage(o, p, (stress
|
|
2429
|
+
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
2422
2430
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
2423
2431
|
p.rt.stressT = 0.5;
|
|
2424
2432
|
const { c, q } = partXf(o, p);
|
|
@@ -2880,6 +2888,47 @@ var GameDestruction = (() => {
|
|
|
2880
2888
|
return { name: "frame", parts, joints, tags: ["structure"] };
|
|
2881
2889
|
};
|
|
2882
2890
|
|
|
2891
|
+
// src/size.ts
|
|
2892
|
+
var sizeStructure = (spec, options = {}) => {
|
|
2893
|
+
const safety = options.safety ?? 0.65;
|
|
2894
|
+
const redundancy = options.redundancy;
|
|
2895
|
+
const loads = (s) => {
|
|
2896
|
+
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
2897
|
+
const id = w.add(s);
|
|
2898
|
+
return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0 }]));
|
|
2899
|
+
};
|
|
2900
|
+
const out = structuredClone(spec);
|
|
2901
|
+
const base = loads(out);
|
|
2902
|
+
const need = /* @__PURE__ */ new Map();
|
|
2903
|
+
for (const p of out.parts) {
|
|
2904
|
+
if (!p.structure || p.structure.role === "none") continue;
|
|
2905
|
+
const b = base.get(p.id);
|
|
2906
|
+
if (!b || b.stress <= 0) continue;
|
|
2907
|
+
need.set(p.id, b.load / safety);
|
|
2908
|
+
}
|
|
2909
|
+
if (redundancy !== void 0) {
|
|
2910
|
+
for (const col of out.parts) {
|
|
2911
|
+
if (col.structure?.role !== "column") continue;
|
|
2912
|
+
const reduced = {
|
|
2913
|
+
...out,
|
|
2914
|
+
parts: out.parts.filter((p) => p.id !== col.id),
|
|
2915
|
+
joints: (out.joints ?? []).filter((j) => j.a !== col.id && j.b !== col.id)
|
|
2916
|
+
};
|
|
2917
|
+
if (!out.joints) delete reduced.joints;
|
|
2918
|
+
const l = loads(reduced);
|
|
2919
|
+
for (const [id, n] of need) {
|
|
2920
|
+
const r = l.get(id);
|
|
2921
|
+
if (r && r.stress > 0) need.set(id, Math.max(n, r.load / redundancy));
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
for (const p of out.parts) {
|
|
2926
|
+
const n = need.get(p.id);
|
|
2927
|
+
if (n !== void 0) p.structure = { ...p.structure, capacity: n };
|
|
2928
|
+
}
|
|
2929
|
+
return out;
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2883
2932
|
// src/locale.ts
|
|
2884
2933
|
var locales = {
|
|
2885
2934
|
en: {
|
|
@@ -2912,7 +2961,7 @@ var GameDestruction = (() => {
|
|
|
2912
2961
|
wax: "wax",
|
|
2913
2962
|
soil: "soil"
|
|
2914
2963
|
},
|
|
2915
|
-
partStatus: { intact: "intact", damaged: "damaged", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
2964
|
+
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
2916
2965
|
jointKinds: {
|
|
2917
2966
|
screw: "screw",
|
|
2918
2967
|
bolt: "nut and bolt",
|
|
@@ -2993,7 +3042,7 @@ var GameDestruction = (() => {
|
|
|
2993
3042
|
wax: "cera",
|
|
2994
3043
|
soil: "terra"
|
|
2995
3044
|
},
|
|
2996
|
-
partStatus: { intact: "intacto", damaged: "danificado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
3045
|
+
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
2997
3046
|
jointKinds: {
|
|
2998
3047
|
screw: "parafuso",
|
|
2999
3048
|
bolt: "porca e parafuso",
|