/** * 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" , slot: "lowerR" }, { id: "legL", label: "RINGBRP.Loc.LegL", melee: [5, 8], ranged: [4, 6], frac: 0.42, kind: "leg" , slot: "lowerL" }, { id: "abdomen", label: "RINGBRP.Loc.Abdomen", melee: [9, 11], ranged: [7, 10], frac: 0.42, kind: "body" , slot: "body" }, { id: "chest", label: "RINGBRP.Loc.Chest", melee: [12, 12], ranged: [11, 15], frac: 0.5, kind: "vital", slot: "vital" }, { id: "armR", label: "RINGBRP.Loc.ArmR", melee: [13, 15], ranged: [16, 17], frac: 0.35, kind: "arm" , slot: "upperR" }, { id: "armL", label: "RINGBRP.Loc.ArmL", melee: [16, 18], ranged: [18, 19], frac: 0.35, kind: "arm" , slot: "upperL" }, { id: "head", label: "RINGBRP.Loc.Head", melee: [19, 20], ranged: [20, 20], frac: 0.4, kind: "head" , slot: "head" } ] }, /* A QUADRUPED — a bear, a horse, a hound, a big cat. Four legs, a barrel divided into forequarters (heart and lungs, the killing shot) and hindquarters, a neck worth aiming at, and a head that is HARDER to hit than a man's because it is carried low and swung. The standard BRP animal table, split by attack type for the same reason baseline is: a man with a spear is down among the legs, a man with a rifle is looking at a broadside. */ quadruped: { label: "RINGBRP.Species.Quadruped", locations: [ { id: "hindLegR", label: "RINGBRP.Loc.HindLegR", melee: [1, 3], ranged: [1, 2], frac: 0.42, kind: "leg" , slot: "lowerR" }, { id: "hindLegL", label: "RINGBRP.Loc.HindLegL", melee: [4, 6], ranged: [3, 4], frac: 0.42, kind: "leg" , slot: "lowerL" }, { id: "hindQtr", label: "RINGBRP.Loc.HindQtr", melee: [7, 9], ranged: [5, 8], frac: 0.42, kind: "body" , slot: "body" }, { id: "foreQtr", label: "RINGBRP.Loc.ForeQtr", melee: [10, 12], ranged: [9, 13], frac: 0.5, kind: "vital", slot: "vital" }, { id: "foreLegR", label: "RINGBRP.Loc.ForeLegR", melee: [13, 15], ranged: [14, 15], frac: 0.42, kind: "leg" , slot: "upperR" }, { id: "foreLegL", label: "RINGBRP.Loc.ForeLegL", melee: [16, 18], ranged: [16, 17], frac: 0.42, kind: "leg" , slot: "upperL" }, { id: "neck", label: "RINGBRP.Loc.Neck", melee: [19, 19], ranged: [18, 19], frac: 0.4, kind: "neck" , slot: "neck" }, { id: "head", label: "RINGBRP.Loc.Head", melee: [20, 20], ranged: [20, 20], frac: 0.4, kind: "head" , slot: "head" } ] }, /* A WINGED QUADRUPED — a dragon, a wyvern, a griffon. The quadruped, plus the two things that make it a different fight: wings you can shoot down. On the ground the wings are folded high and hard to reach; in the air they are the broadest target on the animal, which is why the ranged column gives them six faces and the melee column two. DISABLE ONE AND IT CANNOT FLY — see locationEffectsFor. */ winged: { label: "RINGBRP.Species.Winged", /* "a flyer with a hole in a wing is a quadruped" — the supporter's own tactics. Disable either wing and the creature stops being a flyer: it drops to this plan, the wings leave its hit table, and the six ranged faces they occupied go back to the body it is now standing on. Declared here rather than in the engine so a future flyer only has to say what it becomes. */ grounded: "quadruped", locations: [ { id: "hindLegR", label: "RINGBRP.Loc.HindLegR", melee: [1, 3], ranged: [1, 2], frac: 0.42, kind: "leg" , slot: "lowerR" }, { id: "hindLegL", label: "RINGBRP.Loc.HindLegL", melee: [4, 6], ranged: [3, 4], frac: 0.42, kind: "leg" , slot: "lowerL" }, { id: "hindQtr", label: "RINGBRP.Loc.HindQtr", melee: [7, 9], ranged: [5, 6], frac: 0.42, kind: "body" , slot: "body" }, { id: "foreQtr", label: "RINGBRP.Loc.ForeQtr", melee: [10, 12], ranged: [7, 9], frac: 0.5, kind: "vital", slot: "vital" }, { id: "foreLegR", label: "RINGBRP.Loc.ForeLegR", melee: [13, 14], ranged: [10, 10], frac: 0.42, kind: "leg" , slot: "upperR" }, { id: "foreLegL", label: "RINGBRP.Loc.ForeLegL", melee: [15, 16], ranged: [11, 11], frac: 0.42, kind: "leg" , slot: "upperL" }, { id: "wingR", label: "RINGBRP.Loc.WingR", melee: [17, 17], ranged: [12, 14], frac: 0.35, kind: "wing" , slot: "wingR", bare: true }, { id: "wingL", label: "RINGBRP.Loc.WingL", melee: [18, 18], ranged: [15, 17], frac: 0.35, kind: "wing" , slot: "wingL", bare: true }, { id: "neck", label: "RINGBRP.Loc.Neck", melee: [19, 19], ranged: [18, 19], frac: 0.4, kind: "neck" , slot: "neck" }, { id: "head", label: "RINGBRP.Loc.Head", melee: [20, 20], ranged: [20, 20], frac: 0.4, kind: "head" , slot: "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" , slot: "lower" }, { id: "limbBrace", label: "RINGBRP.Loc.BraceLimb", melee: [4, 8], ranged: [4, 7], frac: 0.42, kind: "brace" , slot: "brace" }, { id: "lowTrunk", label: "RINGBRP.Loc.LowerTrunk", melee: [9, 12], ranged: [8, 12], frac: 0.42, kind: "body" , slot: "body" }, { id: "upTrunk", label: "RINGBRP.Loc.UpperTrunk", melee: [13, 15], ranged: [13, 15], frac: 0.42, kind: "body" , slot: "vital" }, { id: "limbGrasp", label: "RINGBRP.Loc.GraspLimb", melee: [16, 18], ranged: [16, 17], frac: 0.35, kind: "grasp" , slot: "upper" }, { id: "ridge", label: "RINGBRP.Loc.Ridge", melee: [19, 20], ranged: [18, 20], frac: 0.5, kind: "ridge" , slot: "head" } ] }, /* 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" , slot: "body" }, { id: "bodyB", label: "RINGBRP.Loc.BodyB", melee: [5, 8], ranged: [5, 8], frac: 0.28, kind: "unit" , slot: "vital" }, { id: "bodyC", label: "RINGBRP.Loc.BodyC", melee: [9, 12], ranged: [9, 12], frac: 0.28, kind: "unit" , slot: "head" }, { id: "bodyD", label: "RINGBRP.Loc.BodyD", melee: [13, 16], ranged: [13, 16], frac: 0.28, kind: "unit" , slot: "lower" }, { id: "bodyE", label: "RINGBRP.Loc.BodyE", melee: [17, 20], ranged: [17, 20], frac: 0.28, kind: "unit" , slot: "upper" } ] } }; /** * 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 } } }, /* Side-on, because a quadruped seen from the front tells you nothing about which end you hit. Landscape viewBox — the partial reads it from here, so a body plan that is wider than it is tall simply draws that way. */ quadruped: { viewBox: "0 0 208 132", parts: { hindQtr: { t: "rect", x: 34, y: 42, w: 58, h: 44, rx: 14 }, foreQtr: { t: "rect", x: 88, y: 38, w: 62, h: 50, rx: 14 }, neck: { t: "path", d: "M142 44 L176 24 L188 40 L154 62 Z" }, head: { t: "ellipse", cx: 186, cy: 28, rx: 19, ry: 14 }, hindLegR: { t: "rect", x: 40, y: 84, w: 13, h: 44, rx: 6 }, hindLegL: { t: "rect", x: 60, y: 84, w: 13, h: 44, rx: 6 }, foreLegR: { t: "rect", x: 104, y: 86, w: 13, h: 42, rx: 6 }, foreLegL: { t: "rect", x: 124, y: 86, w: 13, h: 42, rx: 6 } } }, /* The quadruped with both wings drawn spread rather than folded, because the table's ranged column assumes a thing in the air. */ winged: { viewBox: "0 0 208 132", parts: { wingR: { t: "path", d: "M96 40 L44 6 L18 26 L74 52 Z" }, wingL: { t: "path", d: "M100 44 L58 92 L82 116 L114 62 Z" }, hindQtr: { t: "rect", x: 34, y: 44, w: 56, h: 40, rx: 13 }, foreQtr: { t: "rect", x: 86, y: 40, w: 60, h: 46, rx: 13 }, neck: { t: "path", d: "M138 46 L172 26 L184 42 L150 64 Z" }, head: { t: "ellipse", cx: 184, cy: 30, rx: 18, ry: 13 }, hindLegR: { t: "rect", x: 42, y: 82, w: 12, h: 40, rx: 6 }, hindLegL: { t: "rect", x: 60, y: 82, w: 12, h: 40, rx: 6 }, foreLegR: { t: "rect", x: 102, y: 84, w: 12, h: 38, rx: 6 }, foreLegL: { t: "rect", x: 120, y: 84, w: 12, h: 38, rx: 6 } } }, /* 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 } } } };