Attack card: humanoid and quadruped damage buttons
The single Roll damage button becomes two, with person and paw icons. Each rolls damage and then a hit location. A targeted token still uses its own anatomy; with no target the blow is placed on the chosen body plan and shown, with nothing applied. Both buttons disable after either is used. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
197bdf9fb0
commit
1e0deb2ef0
@@ -468,6 +468,7 @@
|
|||||||
"RINGBRP.Loc.Header": "Hit location",
|
"RINGBRP.Loc.Header": "Hit location",
|
||||||
"RINGBRP.Loc.Grounded": "{name} comes down. A flyer with a hole in a wing is a ground animal — {plan} now, and it fights as one for the rest of the scene.",
|
"RINGBRP.Loc.Grounded": "{name} comes down. A flyer with a hole in a wing is a ground animal — {plan} now, and it fights as one for the rest of the scene.",
|
||||||
"RINGBRP.Loc.NoTarget": "No target — damage rolled, but nothing was hit. Target a token and roll damage again to place the blow.",
|
"RINGBRP.Loc.NoTarget": "No target — damage rolled, but nothing was hit. Target a token and roll damage again to place the blow.",
|
||||||
|
"RINGBRP.Loc.NoTargetPlaced": "No target: the blow is placed but nothing is applied. Target a token to take the damage off.",
|
||||||
"RINGBRP.Loc.RollHint": "Roll 1D20 for a hit location on this anatomy",
|
"RINGBRP.Loc.RollHint": "Roll 1D20 for a hit location on this anatomy",
|
||||||
"RINGBRP.Tab.Anatomy": "Anatomy",
|
"RINGBRP.Tab.Anatomy": "Anatomy",
|
||||||
"RINGBRP.Sheet.LocationNote": "A blow meeting a location's maximum disables it; twice that destroys it. Damage also comes off general hit points, so the two readings always agree.",
|
"RINGBRP.Sheet.LocationNote": "A blow meeting a location's maximum disables it; twice that destroys it. Damage also comes off general hit points, so the two readings always agree.",
|
||||||
@@ -735,6 +736,10 @@
|
|||||||
"RINGBRP.React.ReadinessSkill": "from training",
|
"RINGBRP.React.ReadinessSkill": "from training",
|
||||||
"RINGBRP.React.ReadinessInt": "from wits",
|
"RINGBRP.React.ReadinessInt": "from wits",
|
||||||
"RINGBRP.Chat.RollDamage": "Roll damage",
|
"RINGBRP.Chat.RollDamage": "Roll damage",
|
||||||
|
"RINGBRP.Chat.RollDamageHumanoid": "Humanoid",
|
||||||
|
"RINGBRP.Chat.RollDamageQuadruped": "Quadruped",
|
||||||
|
"RINGBRP.Chat.DamageHumanoidHint": "Roll damage, then a hit location on a humanoid. A targeted token uses its own anatomy.",
|
||||||
|
"RINGBRP.Chat.DamageQuadrupedHint": "Roll damage, then a hit location on a quadruped. A targeted token uses its own anatomy.",
|
||||||
"RINGBRP.Chat.DamageByBand": "by range band",
|
"RINGBRP.Chat.DamageByBand": "by range band",
|
||||||
"RINGBRP.Chat.DamageCritical": "maximum damage, armour ignored",
|
"RINGBRP.Chat.DamageCritical": "maximum damage, armour ignored",
|
||||||
"RINGBRP.Chat.DamageSpecial": "armour counts half",
|
"RINGBRP.Chat.DamageSpecial": "armour counts half",
|
||||||
|
|||||||
+22
-3
@@ -5950,7 +5950,8 @@ function damageTargetFor(explicit = null) {
|
|||||||
* token and the blow lands somewhere.
|
* token and the blow lands somewhere.
|
||||||
*/
|
*/
|
||||||
export async function rollWeaponDamage({ actor = null, weapon, level = "success", band = null,
|
export async function rollWeaponDamage({ actor = null, weapon, level = "success", band = null,
|
||||||
bonus = "", target = null, locate = true } = {}) {
|
bonus = "", target = null, locate = true,
|
||||||
|
plan = null } = {}) {
|
||||||
if (!weapon) return null;
|
if (!weapon) return null;
|
||||||
const d = weapon.system?.damage ?? {};
|
const d = weapon.system?.damage ?? {};
|
||||||
let formula = d.formula || "1d6";
|
let formula = d.formula || "1d6";
|
||||||
@@ -6004,6 +6005,21 @@ export async function rollWeaponDamage({ actor = null, weapon, level = "success"
|
|||||||
if (victim) {
|
if (victim) {
|
||||||
await hitLocationRoll({ target: victim, damage: roll.total,
|
await hitLocationRoll({ target: victim, damage: roll.total,
|
||||||
mode: locationModeFor(weapon), level });
|
mode: locationModeFor(weapon), level });
|
||||||
|
} else if (plan) {
|
||||||
|
// The attack card's humanoid / quadruped buttons. With nobody targeted the blow
|
||||||
|
// is still placed on the body plan the player chose, so the GM can narrate it,
|
||||||
|
// but nothing is applied. A target always uses its own sheet's anatomy instead,
|
||||||
|
// because its location hit points are keyed to that table.
|
||||||
|
const mode = locationModeFor(weapon);
|
||||||
|
const r = await new Roll("1d20").evaluate();
|
||||||
|
const loc = locationFor(r.total, plan, mode);
|
||||||
|
const content = await rbRenderTemplate("systems/ringbrp/templates/chat-location.hbs", {
|
||||||
|
roll: r.total, mode, preview: true,
|
||||||
|
locationLabel: game.i18n.localize(loc.label),
|
||||||
|
speciesLabel: game.i18n.localize(RINGBRP.locationTables[plan]?.label ?? ""),
|
||||||
|
damage: roll.total
|
||||||
|
});
|
||||||
|
await r.toMessage({ speaker: ChatMessage.getSpeaker({ actor: actor ?? undefined }), content });
|
||||||
} else {
|
} else {
|
||||||
// Say so rather than failing quietly. A GM who meant to target somebody needs
|
// Say so rather than failing quietly. A GM who meant to target somebody needs
|
||||||
// to know the blow went nowhere, and one who did not is told how to make it.
|
// to know the blow went nowhere, and one who did not is told how to make it.
|
||||||
@@ -6490,8 +6506,11 @@ async function _onChatDamage(ev) {
|
|||||||
: game.actors.get(f.actorId);
|
: game.actors.get(f.actorId);
|
||||||
const weapon = actor?.items?.get(f.weaponId);
|
const weapon = actor?.items?.get(f.weaponId);
|
||||||
if (!weapon) return ui.notifications?.warn(game.i18n.localize("RINGBRP.Notify.NoWeapon"));
|
if (!weapon) return ui.notifications?.warn(game.i18n.localize("RINGBRP.Notify.NoWeapon"));
|
||||||
btn.disabled = true; // one damage roll per hit
|
// One damage roll per hit, whichever body plan was chosen.
|
||||||
await rollWeaponDamage({ actor, weapon, level: f.level });
|
btn.closest(".rb-chat-damage")?.querySelectorAll("[data-action='chat-damage']")
|
||||||
|
.forEach(b => { b.disabled = true; });
|
||||||
|
const plan = btn.dataset.plan in RINGBRP.locationTables ? btn.dataset.plan : null;
|
||||||
|
await rollWeaponDamage({ actor, weapon, level: f.level, plan });
|
||||||
}
|
}
|
||||||
|
|
||||||
// v13 renamed this and warns on every message if the old name is registered, so
|
// v13 renamed this and warns on every message if the old name is registered, so
|
||||||
|
|||||||
@@ -1324,6 +1324,7 @@
|
|||||||
}
|
}
|
||||||
.rb-dmg-btn:hover:not(:disabled) { background: var(--rb-yellow); color: var(--rb-panel); }
|
.rb-dmg-btn:hover:not(:disabled) { background: var(--rb-yellow); color: var(--rb-panel); }
|
||||||
.rb-dmg-btn:disabled { opacity: 0.45; cursor: default; }
|
.rb-dmg-btn:disabled { opacity: 0.45; cursor: default; }
|
||||||
|
.rb-dmg-btn i { margin-right: 4px; }
|
||||||
.rb-dmg-note { font-size: 0.75rem; font-style: italic; color: var(--rb-muted, #9fb0d9); }
|
.rb-dmg-note { font-size: 0.75rem; font-style: italic; color: var(--rb-muted, #9fb0d9); }
|
||||||
|
|
||||||
/* Carried but not worn — the ARMOUR box says why it reads zero */
|
/* Carried but not worn — the ARMOUR box says why it reads zero */
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
<div class="rb-chat-result">
|
<div class="rb-chat-result">
|
||||||
<span class="rb-loc-name">{{locationLabel}}</span>
|
<span class="rb-loc-name">{{locationLabel}}</span>
|
||||||
<span class="rb-loc-dmg">{{damage}}</span>
|
<span class="rb-loc-dmg">{{damage}}</span>
|
||||||
<span class="rb-loc-of">{{taken}} / {{max}}</span>
|
{{#unless preview}}<span class="rb-loc-of">{{taken}} / {{max}}</span>{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
{{#if effect}}<p class="rb-loc-effect">{{effect}}</p>{{/if}}
|
{{#if effect}}<p class="rb-loc-effect">{{effect}}</p>{{/if}}
|
||||||
|
{{#if preview}}<p class="rb-loc-effect">{{localize "RINGBRP.Loc.NoTargetPlaced"}}</p>{{/if}}
|
||||||
{{#if majorWound}}<p class="rb-wound-loss">{{localize "RINGBRP.Wound.Header"}}</p>{{/if}}
|
{{#if majorWound}}<p class="rb-wound-loss">{{localize "RINGBRP.Wound.Header"}}</p>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,8 +22,15 @@
|
|||||||
</div>
|
</div>
|
||||||
{{#if damage}}
|
{{#if damage}}
|
||||||
<div class="rb-chat-damage">
|
<div class="rb-chat-damage">
|
||||||
<button type="button" class="rb-dmg-btn" data-action="chat-damage">
|
<button type="button" class="rb-dmg-btn" data-action="chat-damage" data-plan="baseline"
|
||||||
{{localize "RINGBRP.Chat.RollDamage"}} <strong>{{damage.label}}</strong>
|
title="{{localize "RINGBRP.Chat.DamageHumanoidHint"}}">
|
||||||
|
<i class="fa-solid fa-person" aria-hidden="true"></i>
|
||||||
|
{{localize "RINGBRP.Chat.RollDamageHumanoid"}} <strong>{{damage.label}}</strong>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="rb-dmg-btn" data-action="chat-damage" data-plan="quadruped"
|
||||||
|
title="{{localize "RINGBRP.Chat.DamageQuadrupedHint"}}">
|
||||||
|
<i class="fa-solid fa-paw" aria-hidden="true"></i>
|
||||||
|
{{localize "RINGBRP.Chat.RollDamageQuadruped"}} <strong>{{damage.label}}</strong>
|
||||||
</button>
|
</button>
|
||||||
{{#if damage.critical}}<span class="rb-dmg-note">{{localize "RINGBRP.Chat.DamageCritical"}}</span>{{/if}}
|
{{#if damage.critical}}<span class="rb-dmg-note">{{localize "RINGBRP.Chat.DamageCritical"}}</span>{{/if}}
|
||||||
{{#if damage.special}}<span class="rb-dmg-note">{{localize "RINGBRP.Chat.DamageSpecial"}}</span>{{/if}}
|
{{#if damage.special}}<span class="rb-dmg-note">{{localize "RINGBRP.Chat.DamageSpecial"}}</span>{{/if}}
|
||||||
|
|||||||
Reference in New Issue
Block a user