THE DEFECT. locationFor had ONE caller in the engine, hitLocationRoll, and hitLocationRoll was called from fall, burn, detonate and burstAttack. Falling, fire, explosions and automatic fire. Nothing else. rollWeaponDamage rolled dice, printed a number and returned it, so the chat Damage button, the character sheet's damage button and the item sheet's all stopped there. A grenade knew which arm it took off. A sword did not. Every individual piece was already correct - the tables, per-location armour and coverage, disable and destroy thresholds, Major Wound, dying, death, the figure on the sheet. They simply were not joined up, which is why this reads as a wiring change rather than a new subsystem. - rollWeaponDamage now carries through to hitLocationRoll: it rolls the location, applies that location's armour, applies the wound, and handles the rest through the machinery that already owned it. Nothing here re-implements any of that - locationModeFor picks the melee or ranged column from the weapon's class. The two columns have always differed and nothing was choosing between them - with no target it does exactly what it did before and SAYS SO on a card, rather than failing quietly - burstAttack opts out with locate:false - it rolls its own location per round, and letting both fire would have applied every round of a burst twice BODY PLANS. Two new tables: quadruped (four legs, fore/hindquarters, neck, a head that is hard to reach) and winged (that, plus wings). Forequarters are the vital. DISABLE EITHER WING AND IT CANNOT FLY, which is the fight a party can win. Barghest, Kelpie and Church grim were all species "baseline" - a black dog, a horse and a grave-hound, each hit-located with two arms and a chest. Fixed. Added "The supporter", a winged heraldic beast, so the new table is exercised by real content instead of existing unused. Body plan is a NEW FIELD, not a species. A bear is not a playable people with characteristic dice and talents, so creature specs take bodyPlan and the playable species list is untouched. THE NPC SHEET had no hit locations at all - 102 lines against the character sheet's 627 - while every NPC in the game had them derived and wired to consequences. It now carries the body-plan selector, the figure, the per-location table, the condition and a GROUNDED flag. The sheet class already extended the character sheet, so it already HAD the context; the template never used it. FOUND IN PASSING, by the new guard: kind "body" - the abdomen, the trunk, the hindquarters - did nothing whatever. Its printed effect has always promised "-30% to all Physical actions and bleeding 1 hit point per round until First Aid" and the code delivered none of it, on every body plan including the humanoid default. It now bleeds 1, or 2 destroyed, exactly as bodyX says. NEW GUARD: check-anatomy. Every body plan must cover 1-20 exactly once in BOTH modes, every location must be drawn and every drawn shape must be a location, and every kind must be one locationEffectsFor acts on, has effect text for, and has a destruction outcome. Verified it fails on a d20 gap, a shapeless limb and an unhandled kind before wiring it in. Ninth guard. VERIFIED IN A RUNNING FOUNDRY, not just unit-tested: melee bar reads the Melee column and the rifle reads Ranged on the same creature; forequarters graded as a vital hit; a maxed wing set flightLost and the sheet said GROUNDED; the real chat Damage button rolled 1D20 15, found the right foreleg, applied 12 through armour, disabled the leg and took the dog from 16 to 4; and with nothing targeted nothing was touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
142 lines
8.5 KiB
Handlebars
142 lines
8.5 KiB
Handlebars
<form class="ringbrp-sheet rb-npc" autocomplete="off">
|
|
<header class="rb-header">
|
|
<img class="rb-portrait" src="{{actor.img}}" data-edit="img" title="{{actor.name}}"/>
|
|
<div class="rb-headline">
|
|
<input class="rb-name" name="name" type="text" value="{{actor.name}}"/>
|
|
<input class="rb-role" name="system.role" type="text" value="{{sys.role}}" placeholder="{{localize 'RINGBRP.Sheet.Role'}}"/>
|
|
<div class="rb-origin">
|
|
<span class="rb-style-fixed">{{styleLabel}}</span>
|
|
</div>
|
|
</div>
|
|
<div class="rb-vitals">
|
|
<div class="rb-hp"><span class="rb-vital-label">{{localize "RINGBRP.Sheet.HP"}}</span>
|
|
<input type="number" name="system.hp.value" value="{{sys.hp.value}}"/><span class="rb-slash">/</span><span class="rb-max">{{sys.hp.max}}</span></div>
|
|
<div class="rb-mw"><span class="rb-vital-label">{{localize "RINGBRP.Sheet.MajorWound"}}</span><span class="rb-big">{{sys.majorWound.value}}</span></div>
|
|
<div class="rb-armour"><span class="rb-vital-label">{{localize "RINGBRP.Sheet.Armour"}}</span>
|
|
<input type="number" name="system.naturalArmour.value" value="{{sys.naturalArmour.value}}"/></div>
|
|
{{!-- The body plan. Every NPC has always HAD hit locations; nothing on this
|
|
sheet ever let a GM choose which body they belonged to, so every bear and
|
|
every dragon in the game was quietly a man with two arms. --}}
|
|
<div class="rb-species"><span class="rb-vital-label">{{localize "RINGBRP.Sheet.BodyPlan"}}</span>
|
|
<select name="system.speciesProfile" class="rb-species-select">
|
|
{{#each RINGBRP.locationTables as |t key|}}
|
|
<option value="{{key}}" {{#if (rbEq ../sys.speciesProfile key)}}selected{{/if}}>{{localize t.label}}</option>
|
|
{{/each}}
|
|
</select></div>
|
|
</div>
|
|
</header>
|
|
|
|
<section class="rb-characteristics">
|
|
{{#each sys.characteristics as |c key|}}
|
|
<div class="rb-char" data-characteristic="{{key}}">
|
|
<a class="rb-char-label" data-action="roll-characteristic">{{localize (rbConcat "RINGBRP.Char." key)}}</a>
|
|
<input type="number" name="system.characteristics.{{key}}.value" value="{{c.value}}"/>
|
|
<a class="rb-char-x5" data-action="roll-characteristic">{{rbTimes5 c.value}}</a></div>
|
|
{{/each}}
|
|
</section>
|
|
|
|
<section class="rb-derived">
|
|
<div class="rb-derived-group"><span class="rb-dl">{{localize "RINGBRP.Sheet.DamageMod"}}</span><span class="rb-dv">{{sys.damageModifier}}</span></div>
|
|
<div class="rb-derived-group"><span class="rb-dl">{{localize "RINGBRP.Sheet.MOV"}}</span><span class="rb-dv">{{sys.mov}}</span></div>
|
|
<div class="rb-derived-group"><span class="rb-dl">{{localize "RINGBRP.Sheet.Condition"}}</span><span class="rb-dv rb-cond-{{sys.condition}}">{{localize (rbConcat "RINGBRP.Condition." sys.condition)}}</span></div>
|
|
{{#if sys.wounded.flightLost}}<div class="rb-derived-group rb-flag-bad">{{localize "RINGBRP.Sheet.Grounded"}}</div>{{/if}}
|
|
</section>
|
|
|
|
<nav class="sheet-tabs rb-tabs" data-group="primary">
|
|
<a class="item" data-tab="skills">{{localize "RINGBRP.Tab.Skills"}}</a>
|
|
<a class="item" data-tab="combat">{{localize "RINGBRP.Tab.Combat"}}</a>
|
|
<a class="item" data-tab="bio">{{localize "RINGBRP.Tab.Tactics"}}</a>
|
|
</nav>
|
|
|
|
<section class="sheet-body rb-body">
|
|
<div class="tab" data-group="primary" data-tab="skills">
|
|
{{#each RINGBRP.categories as |cat key|}}
|
|
{{! Handlebars cannot read a property off a subexpression — `(lookup a b).length`
|
|
is a parse error, and a parse error in a template is a sheet that never opens.
|
|
#if already treats an empty array as falsy, so the length was never needed. }}
|
|
{{#if (lookup ../skillsByCategory key)}}
|
|
<div class="rb-cat">
|
|
<h3 class="rb-cat-head">{{localize cat.label}}<span class="rb-catbonus">+{{lookup ../sys.categoryBonus key}}</span>
|
|
<a class="rb-add" data-action="item-create" data-type="skill"><i class="fas fa-plus"></i></a></h3>
|
|
<ol class="rb-skills">
|
|
{{#each (lookup ../skillsByCategory key) as |sk|}}
|
|
<li class="rb-skill" data-item-id="{{sk.id}}">
|
|
<a class="rb-skill-name" data-action="roll-skill">{{sk.name}}</a>
|
|
<span class="rb-skill-val">{{sk.system.value}}%</span>
|
|
<a data-action="item-edit"><i class="fas fa-edit"></i></a>
|
|
<a data-action="item-delete"><i class="fas fa-trash"></i></a>
|
|
</li>
|
|
{{/each}}
|
|
</ol>
|
|
</div>
|
|
{{/if}}
|
|
{{/each}}
|
|
<a class="rb-add-wide" data-action="item-create" data-type="skill"><i class="fas fa-plus"></i> {{localize "RINGBRP.Sheet.AddSkill"}}</a>
|
|
</div>
|
|
|
|
<div class="tab" data-group="primary" data-tab="combat">
|
|
{{!-- Hit locations. These were derived for every NPC in the system and drawn
|
|
nowhere, so a GM running a bear could not see that its foreleg was gone. --}}
|
|
<section class="rb-npc-anatomy">
|
|
<div class="rb-npc-figure">{{> "systems/ringbrp/templates/partials/body-figure.hbs" map=bodyMap label=speciesLabel svgClass="rb-body-npc"}}</div>
|
|
<table class="rb-table rb-loctable rb-loctable-compact">
|
|
<thead><tr>
|
|
<th>{{localize "RINGBRP.Loc.Header"}}</th>
|
|
<th>{{localize "RINGBRP.Loc.Melee"}}</th>
|
|
<th>{{localize "RINGBRP.Loc.Ranged"}}</th>
|
|
<th>{{localize "RINGBRP.Sheet.Armour"}}</th>
|
|
<th>{{localize "RINGBRP.Sheet.HP"}}</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{{#each sys.locations as |l|}}
|
|
<tr class="rb-locrow {{#if l.destroyed}}rb-loc-row-destroyed{{else if l.disabled}}rb-loc-row-disabled{{/if}}" data-loc="{{l.id}}">
|
|
<td class="rb-loc-cell">{{localize l.label}}
|
|
{{#if l.destroyed}}<span class="rb-loc-flag rb-loc-flag-x">{{localize "RINGBRP.Loc.Destroyed"}}</span>
|
|
{{else if l.disabled}}<span class="rb-loc-flag">{{localize "RINGBRP.Loc.Disabled"}}</span>{{/if}}</td>
|
|
<td class="rb-dim">{{l.melee}}</td>
|
|
<td class="rb-dim">{{l.ranged}}</td>
|
|
<td>{{l.armour}}</td>
|
|
<td><input type="number" class="rb-loc-taken" name="system.locationDamage.{{l.id}}" value="{{l.taken}}"/><span class="rb-slash">/</span>{{l.max}}</td>
|
|
</tr>
|
|
{{/each}}
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<h3 class="rb-cat-head">{{localize "RINGBRP.Tab.Weapons"}}
|
|
<a class="rb-add" data-action="item-create" data-type="weapon"><i class="fas fa-plus"></i></a></h3>
|
|
<table class="rb-table">
|
|
<thead><tr><th>{{localize "RINGBRP.Sheet.Weapon"}}</th><th>{{localize "RINGBRP.Sheet.Damage"}}</th><th>{{localize "RINGBRP.Sheet.Range"}}</th><th>{{localize "RINGBRP.Sheet.Ammo"}}</th><th></th></tr></thead>
|
|
<tbody>
|
|
{{#each weapons as |w|}}
|
|
<tr data-item-id="{{w.id}}">
|
|
<td><a data-action="roll-attack" class="rb-strong">{{w.name}}</a></td>
|
|
<td><a data-action="roll-damage">{{#if (rbEq w.system.damage.mode "byBand")}}{{w.system.damage.short}}/{{w.system.damage.medium}}/{{w.system.damage.long}}{{else}}{{w.system.damage.formula}}{{/if}}</a></td>
|
|
<td>{{w.system.rangeSummary}}</td>
|
|
<td>{{#if w.system.ammo.capacity}}{{w.system.ammo.loaded}}/{{w.system.ammo.capacity}}{{/if}}</td>
|
|
<td class="rb-actions"><a data-action="item-edit"><i class="fas fa-edit"></i></a><a data-action="item-delete"><i class="fas fa-trash"></i></a></td>
|
|
</tr>
|
|
{{/each}}
|
|
</tbody>
|
|
</table>
|
|
<ol class="rb-list">
|
|
{{#each armours as |a|}}
|
|
<li data-item-id="{{a.id}}">
|
|
<a class="rb-equip {{#if a.system.equipped}}on{{/if}}" data-action="toggle-equipped"><i class="fas fa-shield-halved"></i></a>
|
|
<span class="rb-strong">{{a.name}}</span><span class="rb-dim">{{a.system.points}}</span>
|
|
<a data-action="item-edit"><i class="fas fa-edit"></i></a><a data-action="item-delete"><i class="fas fa-trash"></i></a>
|
|
</li>
|
|
{{/each}}
|
|
</ol>
|
|
<a class="rb-add-wide" data-action="item-create" data-type="armour"><i class="fas fa-plus"></i> {{localize "RINGBRP.Tab.Armour"}}</a>
|
|
</div>
|
|
|
|
<div class="tab" data-group="primary" data-tab="bio">
|
|
<label class="rb-block">{{localize "RINGBRP.Sheet.Tactics"}}</label>
|
|
<textarea name="system.tactics" rows="6">{{sys.tactics}}</textarea>
|
|
<label class="rb-block">{{localize "RINGBRP.Sheet.Notes"}}</label>
|
|
{{editor notesHTML target="system.notes" button=true editable=editable engine="prosemirror"}}
|
|
</div>
|
|
</section>
|
|
</form>
|