Add manual target override to skill check

Allow setting an explicit target number to roll against instead of the
computed Per+training+mod. Works with no sheet skill selected (e.g. a
GM-set 'roll vs 43'). Empty field keeps the auto-computed behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 12:22:28 +02:00
parent c4c622c1b7
commit d3305b957e

View File

@@ -466,6 +466,7 @@ function DeathwatchRoller({ authedPlayer }) {
const [skillRollName, setSkillRollName] = useState('')
const [skillRollModifier, setSkillRollModifier] = useState(0)
const [manualSkillRoll, setManualSkillRoll] = useState('')
const [skillTargetOverride, setSkillTargetOverride] = useState('')
// Load enemies from database API
async function loadEnemiesFromAPI() {
@@ -1200,6 +1201,11 @@ function DeathwatchRoller({ authedPlayer }) {
const selectedSkillBase = characteristicValue(playerCharacteristics, selectedSkillCharacteristic, 45)
const selectedSkillTrainingBonus = skillTrainingBonus(selectedSkill)
const selectedSkillTarget = Math.max(1, Math.min(100, selectedSkillBase + selectedSkillTrainingBonus + (Number(skillRollModifier) || 0)))
const skillTargetOverrideNum = parseInt(String(skillTargetOverride).trim(), 10)
const hasSkillTargetOverride = String(skillTargetOverride).trim() !== '' && Number.isFinite(skillTargetOverrideNum)
const effectiveSkillTarget = hasSkillTargetOverride
? Math.max(1, Math.min(100, skillTargetOverrideNum))
: selectedSkillTarget
const woundPct = maxWounds>0 ? Math.max(0, Math.min(100, (curWounds/maxWounds)*100)) : 0
const status = curWounds<=0 ? 'Dead' : (curWounds<maxWounds ? 'Wounded' : 'Alive')
@@ -1226,8 +1232,8 @@ function DeathwatchRoller({ authedPlayer }) {
function rollSkillCheck() {
setError('')
setInfo('')
if (!skillRollName) {
setError('No skill selected')
if (!skillRollName && !hasSkillTargetOverride) {
setError('Select a skill or enter a target number')
return
}
let roll
@@ -1241,25 +1247,27 @@ function DeathwatchRoller({ authedPlayer }) {
} else {
roll = d100()
}
const dg = degrees(selectedSkillTarget, roll)
const label = skillRollName || `Target ${effectiveSkillTarget}`
const dg = degrees(effectiveSkillTarget, roll)
const res = {
id: uid(),
ts: Date.now(),
rollKind: 'skill',
skillName: skillRollName,
characteristic: selectedSkillCharacteristic,
base: selectedSkillBase,
training: skillTrainingLabel(selectedSkill),
skillName: label,
characteristic: skillRollName ? selectedSkillCharacteristic : null,
base: skillRollName ? selectedSkillBase : null,
training: skillRollName ? skillTrainingLabel(selectedSkill) : null,
modifier: Number(skillRollModifier) || 0,
target: selectedSkillTarget,
target: effectiveSkillTarget,
targetOverride: hasSkillTargetOverride,
attackRoll: roll,
success: dg.success,
dos: dg.dos,
dof: dg.dof,
using: selectedSkillCharacteristic,
using: skillRollName ? selectedSkillCharacteristic : null,
}
addRollResult(res)
setInfo(`${skillRollName}: ${dg.success ? `Success (${dg.dos} DoS)` : `Fail (${dg.dof} DoF)`}`)
setInfo(`${label}: ${dg.success ? `Success (${dg.dos} DoS)` : `Fail (${dg.dof} DoF)`}`)
}
return (
@@ -1470,9 +1478,9 @@ function DeathwatchRoller({ authedPlayer }) {
<div className="font-semibold">Skill Check</div>
<div className="text-xs opacity-70">Uses this player sheet's skills and characteristics.</div>
</div>
<button onClick={rollSkillCheck} disabled={skillOptions.length === 0} className="rounded-xl px-4 py-2 bg-emerald-700 hover:bg-emerald-600 disabled:opacity-40">Roll Skill</button>
<button onClick={rollSkillCheck} disabled={skillOptions.length === 0 && !hasSkillTargetOverride} className="rounded-xl px-4 py-2 bg-emerald-700 hover:bg-emerald-600 disabled:opacity-40">Roll Skill</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 items-end">
<div className="grid grid-cols-1 md:grid-cols-5 gap-3 items-end">
<div className="md:col-span-2">
<label className="text-xs uppercase opacity-70">Skill</label>
<select className="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2" value={skillRollName} onChange={e=>setSkillRollName(e.target.value)} disabled={skillOptions.length === 0}>
@@ -1484,7 +1492,11 @@ function DeathwatchRoller({ authedPlayer }) {
</div>
<div>
<label className="text-xs uppercase opacity-70">Modifier</label>
<input className="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2" type="number" value={skillRollModifier} onChange={e=>setSkillRollModifier(parseInt(e.target.value||'0', 10))} />
<input className="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2" type="number" value={skillRollModifier} onChange={e=>setSkillRollModifier(parseInt(e.target.value||'0', 10))} disabled={hasSkillTargetOverride} />
</div>
<div>
<label className="text-xs uppercase opacity-70">Target</label>
<input className="w-full rounded-xl border border-slate-600 bg-slate-800 px-3 py-2" type="number" min="1" max="100" placeholder="auto" value={skillTargetOverride} onChange={e=>setSkillTargetOverride(e.target.value)} />
</div>
<div>
<label className="text-xs uppercase opacity-70">Manual d100</label>
@@ -1492,8 +1504,10 @@ function DeathwatchRoller({ authedPlayer }) {
</div>
</div>
<div className="text-sm opacity-85">
Target <span className="font-semibold">{selectedSkillTarget}</span>
{skillRollName && (
Target <span className="font-semibold">{effectiveSkillTarget}</span>
{hasSkillTargetOverride ? (
<span> = manual target</span>
) : skillRollName && (
<span> = {selectedSkillCharacteristic} {selectedSkillBase} {selectedSkillTrainingBonus >= 0 ? '+' : ''}{selectedSkillTrainingBonus} training {Number(skillRollModifier) ? `${Number(skillRollModifier) > 0 ? '+' : ''}${Number(skillRollModifier)} mod` : '+0 mod'}</span>
)}
</div>