feat: wire up stuck-weapon follow-up for impale/slash specials
Add POST /api/combat/stuck-weapon-removal endpoint supporting three removal types: attacker (weapon skill roll), self (STR+CON roll), and first-aid (First Aid skill roll). After a special hit with damage through, the attack result UI shows a stuck-weapon card with buttons for each removal option.
This commit is contained in:
+56
-2
@@ -574,6 +574,45 @@ function combatantOptions(selectedId) {
|
||||
return combatants().map((c) => `<option value="${c.id}" ${c.id === selectedId ? 'selected' : ''}>${escapeHtml(c.name)}</option>`).join('');
|
||||
}
|
||||
|
||||
function renderStuckWeaponFollowUp({ attackerCombatantId, defenderCombatantId, weaponName, kind }) {
|
||||
const wrap = el(`<div class="card" style="margin-top:0.75rem"></div>`);
|
||||
wrap.appendChild(el(`<p><strong>Weapon stuck (${kind}).</strong> Choose removal attempt:</p>`));
|
||||
const removalResult = el(`<div></div>`);
|
||||
|
||||
async function doRemoval(removalType, extra = {}) {
|
||||
try {
|
||||
const res = await api('POST', '/api/combat/stuck-weapon-removal', {
|
||||
attackerCombatantId, defenderCombatantId, weaponName, kind, removalType, ...extra,
|
||||
});
|
||||
state.combat = res.combatState;
|
||||
const r = res.result;
|
||||
const outcome = r.weaponBreaks ? 'Weapon breaks!' : r.success ? 'Weapon removed successfully.' : 'Weapon stays stuck.';
|
||||
removalResult.innerHTML = `<p>${escapeHtml(outcome)}</p>`;
|
||||
await loadLog();
|
||||
renderSidebar();
|
||||
renderLog();
|
||||
} catch (err) {
|
||||
removalResult.innerHTML = `<p class="error-message">${escapeHtml(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
const attackerBtn = el(`<button class="button">Attacker removes weapon</button>`);
|
||||
attackerBtn.addEventListener('click', () => doRemoval('attacker'));
|
||||
|
||||
const selfBtn = el(`<button class="button">Target removes from self</button>`);
|
||||
selfBtn.addEventListener('click', () => doRemoval('self'));
|
||||
|
||||
const faSkill = el(`<input type="number" placeholder="First Aid %" style="width:7rem">`);
|
||||
const faBtn = el(`<button class="button">Remove with First Aid</button>`);
|
||||
faBtn.addEventListener('click', () => doRemoval('first-aid', { firstAidSkillPercent: Number(faSkill.value) || 0 }));
|
||||
|
||||
const btnRow = el(`<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;margin-top:0.5rem"></div>`);
|
||||
btnRow.append(attackerBtn, selfBtn, faSkill, faBtn);
|
||||
wrap.appendChild(btnRow);
|
||||
wrap.appendChild(removalResult);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function renderCombatMain() {
|
||||
const wrap = el(`<div></div>`);
|
||||
if (combatants().length < 1) {
|
||||
@@ -632,12 +671,27 @@ function renderCombatMain() {
|
||||
try {
|
||||
const res = await api('POST', '/api/combat/attack', body);
|
||||
state.combat = res.combatState;
|
||||
resultBox.innerHTML = `
|
||||
resultBox.innerHTML = '';
|
||||
const summary = el(`<div>
|
||||
<p>${tierTag(res.result.attackCheck.tier)} roll ${res.result.attackCheck.roll} vs ${res.result.effectiveSkillPercent}% (base ${res.result.effectiveSkillPercent - res.result.modifierTotal}${res.result.modifierTotal ? `, modifiers ${res.result.modifierTotal > 0 ? '+' : ''}${res.result.modifierTotal}` : ''})</p>
|
||||
${res.result.hitLocationRoll ? `<p>Hit location: ${res.result.hitLocationRoll.location}</p>` : ''}
|
||||
${res.result.damageThrough != null ? `<p>Damage through: ${res.result.damageThrough}</p>` : ''}
|
||||
${res.result.fumble ? `<p>Fumble: ${res.result.fumble.results.map((r) => r.effect).join('; ')}</p>` : ''}
|
||||
`;
|
||||
</div>`);
|
||||
resultBox.appendChild(summary);
|
||||
|
||||
const special = res.result.damageResult?.special;
|
||||
const weaponStuck = (special === 'impale' || special === 'slash') && res.result.damageThrough > 0;
|
||||
if (weaponStuck) {
|
||||
const stuckBox = renderStuckWeaponFollowUp({
|
||||
attackerCombatantId: body.attackerCombatantId,
|
||||
defenderCombatantId: body.defenderCombatantId,
|
||||
weaponName: body.weaponName,
|
||||
kind: special,
|
||||
});
|
||||
resultBox.appendChild(stuckBox);
|
||||
}
|
||||
|
||||
await loadLog();
|
||||
renderSidebar();
|
||||
renderLog();
|
||||
|
||||
Reference in New Issue
Block a user