Skills: +-5 buttons, advanced skills use 50% of the attribute (creation.advanced_attr_percent)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
Claude
2026-10-22 14:37:52 +00:00
parent 3e67aba75c
commit a8c9740a05
7 changed files with 14 additions and 6 deletions
+4 -2
View File
@@ -314,9 +314,11 @@ export default function Creator({ rules: baseRules, user, loadRequest, clearLoad
<button className="ghost small-btn" title={`Lock again: you get ${c.unlockCost} points back, its points and proficiency picks are returned`}
onClick={() => setCh((x) => ({ ...x, unlocked: x.unlocked.filter((id) => id !== s.id), skill_points: { ...x.skill_points, [s.id]: 0 }, proficiency_picks: { ...x.proficiency_picks, [s.id]: 0 } }))}>Lock</button>
)}
<button onClick={() => setPts(s.points - 1)} aria-label={`${s.name} minus`}>−</button>
<button className="step5" onClick={() => setPts(s.points - 5)} disabled={s.points <= 0} aria-label={`${s.name} minus 5`}>−5</button>
<button onClick={() => setPts(s.points - 1)} disabled={s.points <= 0} aria-label={`${s.name} minus`}>−</button>
<span className="val">{s.points}</span>
<button onClick={() => setPts(s.points + 1)} disabled={s.points >= s.max || c.skillLeft <= 0} aria-label={`${s.name} plus`}>+</button>
<button onClick={() => setPts(Math.min(s.points + 1, s.max, s.points + c.skillLeft))} disabled={s.points >= s.max || c.skillLeft <= 0} aria-label={`${s.name} plus`}>+</button>
<button className="step5" onClick={() => setPts(Math.min(s.points + 5, s.max, s.points + c.skillLeft))} disabled={s.points >= s.max || c.skillLeft <= 0} aria-label={`${s.name} plus 5`}>+5</button>
<Tip text={explainSkill(s, ctx)}><span className="mod">{s.total}</span></Tip><span className="small muted" title="Most points you can add"> max {s.max}</span>
</div>
)}
+2 -1
View File
@@ -124,8 +124,9 @@ export function compute(ch, rules) {
const pips = profPip + freePips
const prof = [0, cr.proficiency_bonus, cr.expertise_bonus][pips]
const best = Math.max(...s.attrs.map((a) => attrs[a])) // with several attributes the higher one counts
const base = s.type === 'advanced' ? Math.floor((best * (cr.advanced_attr_percent ?? 100)) / 100) : best // advanced skills only use part of the attribute
const unlocked = ch.unlocked.includes(s.id)
return { ...s, points, prof, profPip, freePips, pips, unlocked, total: best + points + prof,
return { ...s, points, prof, profPip, freePips, pips, unlocked, base, total: base + points + prof,
usable: s.type === 'basic' || unlocked || profPip > 0, // advanced skills must be unlocked (a profession proficiency does it for free)
free: s.type === 'advanced' && profPip > 0, // unlocked by the profession, costs nothing
max: Math.floor(best / cr.skill_max_divisor) }
+1 -1
View File
@@ -29,7 +29,7 @@ export function explainSkill(s, { ch, rules, c }) {
const list = s.attrs.map((id) => `${attrShort(rules, id)} ${c.attrs[id]}`).join(', ')
const training = s.pips === 2 ? `Expertise +${s.prof}` : s.pips === 1 ? `Proficient +${s.prof}` : 'no training +0'
const lines = [
`Target number = best attribute ${best} (${s.attrs.length > 1 ? `higher of ${list}` : list}) + ${s.points} skill points + ${training} = ${s.total}`,
`Target number = ${s.type === 'advanced' && s.base !== best ? `${cr.advanced_attr_percent}% of the best attribute (${best}) = ${s.base}` : `best attribute ${best}`} (${s.attrs.length > 1 ? `higher of ${list}` : list}) + ${s.points} skill points + ${training} = ${s.total}`,
`Most points you can put in: best attribute ÷ ${cr.skill_max_divisor}, rounded down = ${s.max}`,
]
if (s.type === 'advanced') {
+1
View File
@@ -384,3 +384,4 @@ body:has(.lobbychat) main { padding: .5rem calc(var(--chatw) + .5rem) 0 .5rem; }
.tablelog { position: absolute; left: .6rem; bottom: .5rem; display: grid; gap: .25rem; max-width: 70%; }
.tablelog .trayroll { padding: .2rem .6rem; border-radius: 8px; font-size: .85rem; background: color-mix(in srgb, var(--panel) 88%, transparent); border: 1px solid var(--line); border-top: 3px solid var(--line); box-shadow: none; }
@media (prefers-reduced-motion: reduce) { .thrown { animation: none; } }
.stepper .step5 { font-size: .75rem; padding: .25rem .4rem; min-width: 2.2rem; }