Wire up talent HP bonus with per-level scaling (e.g. Grit +2 HP and +1/level)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0bd83e5f92
commit
f552a475c1
7 changed files with 42 additions and 11 deletions
|
|
@ -3,6 +3,7 @@ import HpBar from "./HpBar";
|
||||||
import StatBlock from "./StatBlock";
|
import StatBlock from "./StatBlock";
|
||||||
import styles from "./CharacterCard.module.css";
|
import styles from "./CharacterCard.module.css";
|
||||||
import { calculateAC } from "../utils/derived-ac";
|
import { calculateAC } from "../utils/derived-ac";
|
||||||
|
import { getTalentHpBonus } from "../utils/talent-effects";
|
||||||
|
|
||||||
interface CharacterCardProps {
|
interface CharacterCardProps {
|
||||||
character: Character;
|
character: Character;
|
||||||
|
|
@ -38,7 +39,7 @@ export default function CharacterCard({
|
||||||
<div className={styles.hpAcRow} onClick={(e) => e.stopPropagation()}>
|
<div className={styles.hpAcRow} onClick={(e) => e.stopPropagation()}>
|
||||||
<HpBar
|
<HpBar
|
||||||
current={character.hp_current}
|
current={character.hp_current}
|
||||||
max={character.hp_max}
|
max={character.hp_max + getTalentHpBonus(character)}
|
||||||
onChange={(hp) => onHpChange(character.id, hp)}
|
onChange={(hp) => onHpChange(character.id, hp)}
|
||||||
/>
|
/>
|
||||||
<div className={styles.ac}>
|
<div className={styles.ac}>
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,12 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hpBonus {
|
||||||
|
color: #4caf50;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
margin-left: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
.xpThreshold {
|
.xpThreshold {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import type { Character, GameItem } from "../types";
|
import type { Character, GameItem } from "../types";
|
||||||
import { calculateAC } from "../utils/derived-ac";
|
import { calculateAC } from "../utils/derived-ac";
|
||||||
|
import { getTalentHpBonus } from "../utils/talent-effects";
|
||||||
import AcDisplay from "./AcDisplay";
|
import AcDisplay from "./AcDisplay";
|
||||||
import InlineNumber from "./InlineNumber";
|
import InlineNumber from "./InlineNumber";
|
||||||
import StatsPanel from "./StatsPanel";
|
import StatsPanel from "./StatsPanel";
|
||||||
|
|
@ -51,6 +52,8 @@ export default function CharacterSheet({
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
||||||
const acBreakdown = calculateAC(character);
|
const acBreakdown = calculateAC(character);
|
||||||
|
const hpBonus = getTalentHpBonus(character);
|
||||||
|
const effectiveHpMax = character.hp_max + hpBonus;
|
||||||
const xpThreshold = character.level * 10;
|
const xpThreshold = character.level * 10;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -122,14 +125,24 @@ export default function CharacterSheet({
|
||||||
/>
|
/>
|
||||||
<span className={styles.hpSlash}>/</span>
|
<span className={styles.hpSlash}>/</span>
|
||||||
{mode === "edit" ? (
|
{mode === "edit" ? (
|
||||||
<InlineNumber
|
<>
|
||||||
value={character.hp_max}
|
<InlineNumber
|
||||||
onChange={(hp) => onUpdate(character.id, { hp_max: hp })}
|
value={character.hp_max}
|
||||||
className={styles.hpMax}
|
onChange={(hp) => onUpdate(character.id, { hp_max: hp })}
|
||||||
min={0}
|
className={styles.hpMax}
|
||||||
/>
|
min={0}
|
||||||
|
/>
|
||||||
|
{hpBonus > 0 && (
|
||||||
|
<span className={styles.hpBonus}>(+{hpBonus})</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.hpMax}>{character.hp_max}</span>
|
<span className={styles.hpMax}>
|
||||||
|
{effectiveHpMax}
|
||||||
|
{hpBonus > 0 && (
|
||||||
|
<span className={styles.hpBonus}> (+{hpBonus})</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export function getTalentGearSlotsBonus(character: Character): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get HP bonus from talents (e.g. Grit +2).
|
* Get HP bonus from talents (e.g. Grit +2 and +1 per level).
|
||||||
*/
|
*/
|
||||||
export function getTalentHpBonus(character: Character): number {
|
export function getTalentHpBonus(character: Character): number {
|
||||||
let bonus = 0;
|
let bonus = 0;
|
||||||
|
|
@ -81,6 +81,9 @@ export function getTalentHpBonus(character: Character): number {
|
||||||
if (typeof talent.effect.hp_bonus === "number") {
|
if (typeof talent.effect.hp_bonus === "number") {
|
||||||
bonus += talent.effect.hp_bonus;
|
bonus += talent.effect.hp_bonus;
|
||||||
}
|
}
|
||||||
|
if (typeof talent.effect.hp_per_level === "number") {
|
||||||
|
bonus += talent.effect.hp_per_level * character.level;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return bonus;
|
return bonus;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,4 +155,12 @@ if (talentCount === 0) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Migration: update Grit talent to include hp_per_level ---
|
||||||
|
db.prepare(
|
||||||
|
`UPDATE game_talents SET effect = '{"hp_bonus":2,"hp_per_level":1}' WHERE name = 'Grit' AND effect = '{"hp_bonus":2}'`,
|
||||||
|
).run();
|
||||||
|
db.prepare(
|
||||||
|
`UPDATE character_talents SET effect = '{"hp_bonus":2,"hp_per_level":1}' WHERE name = 'Grit' AND effect = '{"hp_bonus":2}'`,
|
||||||
|
).run();
|
||||||
|
|
||||||
export default db;
|
export default db;
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ export function seedDevData() {
|
||||||
brynnId,
|
brynnId,
|
||||||
"Grit",
|
"Grit",
|
||||||
"+2 HP and +1 HP each level",
|
"+2 HP and +1 HP each level",
|
||||||
'{"hp_bonus":2}',
|
'{"hp_bonus":2,"hp_per_level":1}',
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export const SEED_TALENTS: SeedTalent[] = [
|
||||||
name: "Grit",
|
name: "Grit",
|
||||||
source: "Fighter",
|
source: "Fighter",
|
||||||
description: "Gain +2 HP and +1 HP each level",
|
description: "Gain +2 HP and +1 HP each level",
|
||||||
effect: { hp_bonus: 2 },
|
effect: { hp_bonus: 2, hp_per_level: 1 },
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- Priest ---
|
// --- Priest ---
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue