RINGBRP.locationTables and RINGBRP.bodyArt lived inside ringbrp.mjs, which no tool can import — it touches game, Hooks and foundry at load. So nothing outside Foundry could ask how many hit locations a Cadence has, or what shape a Vesh is, which is exactly what art generated from a statblock needs to know. Moved byte for byte to anatomy.mjs and assigned back, the same move rules.mjs got when the simulator needed the combat rules. One definition, two readers. check-rules matched those tables with a regex against ringbrp.mjs and went blind the moment they moved — it reported "could not read locationTables" and the species check silently stopped running. It imports the value now. A guard that parses source text breaks when the source is refactored; one that imports it cannot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
6.0 KiB
JavaScript
110 lines
6.0 KiB
JavaScript
/**
|
|
* Species anatomy: where a creature can be hit, and what it looks like.
|
|
*
|
|
* This lived inside ringbrp.mjs, where only Foundry could reach it. Node cannot import
|
|
* the engine — it touches `game`, `Hooks` and `foundry` at load — so any tool that wants
|
|
* to know a Cadence has five bodies and no head had no way to ask. tools/make-portraits
|
|
* and tools/mj-queue both need exactly that, for the same reason rules.mjs exists: art
|
|
* generated from the creature's own geometry CANNOT contradict its hit-location table,
|
|
* because they are the same numbers.
|
|
*
|
|
* Moved byte for byte. ringbrp.mjs now assigns RINGBRP.locationTables and RINGBRP.bodyArt
|
|
* from here, so the engine and the tools read one definition. There is no second copy.
|
|
*/
|
|
|
|
/* Location fractions are of TOTAL hit points. They were sized against the old
|
|
26-point pool; halving HP left a leg at 4 against a sidearm doing 4-6 through
|
|
armour, so 64% of hits crippled the limb they struck. Raised so a sidearm
|
|
cripples ~40% of the time and a rifle ~55%. */
|
|
export const LOCATION_TABLES = {
|
|
baseline: {
|
|
label: "RINGBRP.Species.Baseline",
|
|
locations: [
|
|
{ id: "legR", label: "RINGBRP.Loc.LegR", melee: [1, 4], ranged: [1, 3], frac: 0.42, kind: "leg" },
|
|
{ id: "legL", label: "RINGBRP.Loc.LegL", melee: [5, 8], ranged: [4, 6], frac: 0.42, kind: "leg" },
|
|
{ id: "abdomen", label: "RINGBRP.Loc.Abdomen", melee: [9, 11], ranged: [7, 10], frac: 0.42, kind: "body" },
|
|
{ id: "chest", label: "RINGBRP.Loc.Chest", melee: [12, 12], ranged: [11, 15], frac: 0.5, kind: "vital"},
|
|
{ id: "armR", label: "RINGBRP.Loc.ArmR", melee: [13, 15], ranged: [16, 17], frac: 0.35, kind: "arm" },
|
|
{ id: "armL", label: "RINGBRP.Loc.ArmL", melee: [16, 18], ranged: [18, 19], frac: 0.35, kind: "arm" },
|
|
{ id: "head", label: "RINGBRP.Loc.Head", melee: [19, 20], ranged: [20, 20], frac: 0.4, kind: "head" }
|
|
]
|
|
},
|
|
/* A VESH is bilaterally ASYMMETRIC: one long grasping limb, one short bracing
|
|
limb, and no head at all — the senses run along a dorsal ridge. There is no
|
|
single killing shot to the skull because there is no skull. The ridge is the
|
|
vital, and it is a long target rather than a small one. */
|
|
vesh: {
|
|
label: "RINGBRP.Species.Vesh",
|
|
locations: [
|
|
{ id: "footBrace", label: "RINGBRP.Loc.BraceFoot", melee: [1, 3], ranged: [1, 3], frac: 0.42, kind: "leg" },
|
|
{ id: "limbBrace", label: "RINGBRP.Loc.BraceLimb", melee: [4, 8], ranged: [4, 7], frac: 0.42, kind: "brace" },
|
|
{ id: "lowTrunk", label: "RINGBRP.Loc.LowerTrunk", melee: [9, 12], ranged: [8, 12], frac: 0.42, kind: "body" },
|
|
{ id: "upTrunk", label: "RINGBRP.Loc.UpperTrunk", melee: [13, 15], ranged: [13, 15], frac: 0.42, kind: "body" },
|
|
{ id: "limbGrasp", label: "RINGBRP.Loc.GraspLimb", melee: [16, 18], ranged: [16, 17], frac: 0.35, kind: "grasp" },
|
|
{ id: "ridge", label: "RINGBRP.Loc.Ridge", melee: [19, 20], ranged: [18, 20], frac: 0.5, kind: "ridge" }
|
|
]
|
|
},
|
|
/* A CADENCE is not one body. It is four to six small bodies that together are
|
|
one person, and the location roll picks which body you hit. Killing a body
|
|
does not kill the person — it reduces them. There is no vital and no head:
|
|
every body carries an equal share, and the person degrades as they are
|
|
whittled down. This is why shooting a Cadence feels like nothing is working. */
|
|
cadence: {
|
|
label: "RINGBRP.Species.Cadence",
|
|
locations: [
|
|
{ id: "bodyA", label: "RINGBRP.Loc.BodyA", melee: [1, 4], ranged: [1, 4], frac: 0.28, kind: "unit" },
|
|
{ id: "bodyB", label: "RINGBRP.Loc.BodyB", melee: [5, 8], ranged: [5, 8], frac: 0.28, kind: "unit" },
|
|
{ id: "bodyC", label: "RINGBRP.Loc.BodyC", melee: [9, 12], ranged: [9, 12], frac: 0.28, kind: "unit" },
|
|
{ id: "bodyD", label: "RINGBRP.Loc.BodyD", melee: [13, 16], ranged: [13, 16], frac: 0.28, kind: "unit" },
|
|
{ id: "bodyE", label: "RINGBRP.Loc.BodyE", melee: [17, 20], ranged: [17, 20], frac: 0.28, kind: "unit" }
|
|
]
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The figure drawn on the combat tab. One entry per species profile, keyed by the
|
|
* same location ids as the table above, so a species that has a location gets a
|
|
* shape and one that does not simply has none — no separate list to keep in step.
|
|
*
|
|
* Deliberately blocky: these are readable at sheet size and honest about the
|
|
* anatomy. A Vesh is asymmetric with no head; a Cadence is five separate bodies.
|
|
*/
|
|
export const BODY_ART = {
|
|
baseline: {
|
|
viewBox: "0 0 120 196",
|
|
parts: {
|
|
head: { t: "ellipse", cx: 60, cy: 21, rx: 15, ry: 17 },
|
|
chest: { t: "rect", x: 40, y: 42, w: 40, h: 40, rx: 9 },
|
|
abdomen: { t: "rect", x: 44, y: 84, w: 32, h: 30, rx: 8 },
|
|
armR: { t: "rect", x: 18, y: 45, w: 17, h: 64, rx: 8 },
|
|
armL: { t: "rect", x: 85, y: 45, w: 17, h: 64, rx: 8 },
|
|
legR: { t: "rect", x: 43, y: 117, w: 15, h: 72, rx: 7 },
|
|
legL: { t: "rect", x: 62, y: 117, w: 15, h: 72, rx: 7 }
|
|
}
|
|
},
|
|
/* One long grasping limb, one short bracing limb, a trunk that leans, and the
|
|
sensory ridge running down the back instead of a head. */
|
|
vesh: {
|
|
viewBox: "0 0 120 196",
|
|
parts: {
|
|
ridge: { t: "path", d: "M44 18 L70 14 L76 96 L52 100 Z" },
|
|
upTrunk: { t: "path", d: "M40 34 L70 30 L74 84 L44 88 Z" },
|
|
lowTrunk: { t: "path", d: "M44 90 L74 86 L78 132 L48 136 Z" },
|
|
limbGrasp: { t: "path", d: "M74 36 L96 44 L104 148 L86 150 L82 60 Z" },
|
|
limbBrace: { t: "rect", x: 20, y: 52, w: 16, h: 48, rx: 8 },
|
|
footBrace: { t: "path", d: "M46 136 L78 132 L84 188 L58 190 Z" }
|
|
}
|
|
},
|
|
/* Five bodies, one person. No vital, no head, every body an equal share. */
|
|
cadence: {
|
|
viewBox: "0 0 120 196",
|
|
parts: {
|
|
bodyA: { t: "ellipse", cx: 34, cy: 52, rx: 20, ry: 24 },
|
|
bodyB: { t: "ellipse", cx: 86, cy: 44, rx: 20, ry: 24 },
|
|
bodyC: { t: "ellipse", cx: 60, cy: 100, rx: 20, ry: 24 },
|
|
bodyD: { t: "ellipse", cx: 28, cy: 148, rx: 20, ry: 24 },
|
|
bodyE: { t: "ellipse", cx: 88, cy: 152, rx: 20, ry: 24 }
|
|
}
|
|
}
|
|
};
|