Fix crit glow: only the weapon that scored nat 20 shows crit indicator, not all weapons
This commit is contained in:
parent
b0fa709767
commit
859ac64868
5 changed files with 26 additions and 21 deletions
|
|
@ -9,7 +9,7 @@ interface AttackBlockProps {
|
|||
characterName?: string;
|
||||
characterColor?: string;
|
||||
mode?: "view" | "edit";
|
||||
isCrit?: boolean;
|
||||
critKeys?: Set<string>;
|
||||
}
|
||||
|
||||
export default function AttackBlock({
|
||||
|
|
@ -19,7 +19,7 @@ export default function AttackBlock({
|
|||
characterName,
|
||||
characterColor,
|
||||
mode,
|
||||
isCrit,
|
||||
critKeys,
|
||||
}: AttackBlockProps) {
|
||||
const weapons = attacks.filter((a) => !a.isTalent);
|
||||
const talents = attacks.filter((a) => a.isTalent);
|
||||
|
|
@ -65,13 +65,13 @@ export default function AttackBlock({
|
|||
type="attack"
|
||||
dice={atk.damage}
|
||||
label={`${atk.name} damage`}
|
||||
icon={isCrit ? "💥" : "💥"}
|
||||
icon="💥"
|
||||
title={
|
||||
isCrit
|
||||
critKeys?.has(`${characterId}:${atk.name}`)
|
||||
? `CRIT damage: double ${atk.damage}`
|
||||
: `Damage: ${atk.damage} (Shift: crit = double dice)`
|
||||
}
|
||||
forceCrit={isCrit}
|
||||
forceCrit={critKeys?.has(`${characterId}:${atk.name}`)}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import styles from "./CharacterDetail.module.css";
|
|||
interface CharacterDetailProps {
|
||||
character: Character;
|
||||
campaignId: number;
|
||||
isCrit?: boolean;
|
||||
critKeys?: Set<string>;
|
||||
onUpdate: (id: number, data: Partial<Character>) => void;
|
||||
onStatChange: (characterId: number, statName: string, value: number) => void;
|
||||
onAddGearFromItem: (characterId: number, item: GameItem) => void;
|
||||
|
|
@ -32,7 +32,7 @@ interface CharacterDetailProps {
|
|||
export default function CharacterDetail({
|
||||
character,
|
||||
campaignId,
|
||||
isCrit,
|
||||
critKeys,
|
||||
onUpdate,
|
||||
onStatChange,
|
||||
onAddGearFromItem,
|
||||
|
|
@ -64,7 +64,7 @@ export default function CharacterDetail({
|
|||
character={character}
|
||||
mode={mode}
|
||||
campaignId={campaignId}
|
||||
isCrit={isCrit}
|
||||
critKeys={critKeys}
|
||||
onUpdate={onUpdate}
|
||||
onStatChange={onStatChange}
|
||||
onAddGearFromItem={onAddGearFromItem}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface CharacterSheetProps {
|
|||
character: Character;
|
||||
mode: "view" | "edit";
|
||||
campaignId: number;
|
||||
isCrit?: boolean;
|
||||
critKeys?: Set<string>;
|
||||
onUpdate: (id: number, data: Partial<Character>) => void;
|
||||
onStatChange: (characterId: number, statName: string, value: number) => void;
|
||||
onAddGearFromItem: (characterId: number, item: GameItem) => void;
|
||||
|
|
@ -38,7 +38,7 @@ export default function CharacterSheet({
|
|||
character,
|
||||
mode,
|
||||
campaignId,
|
||||
isCrit,
|
||||
critKeys,
|
||||
onUpdate,
|
||||
onStatChange,
|
||||
onAddGearFromItem,
|
||||
|
|
@ -165,7 +165,7 @@ export default function CharacterSheet({
|
|||
character={character}
|
||||
mode={mode}
|
||||
campaignId={campaignId}
|
||||
isCrit={isCrit}
|
||||
critKeys={critKeys}
|
||||
onStatChange={onStatChange}
|
||||
/>
|
||||
<InfoPanel
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ interface StatsPanelProps {
|
|||
character: Character;
|
||||
mode: "view" | "edit";
|
||||
campaignId: number;
|
||||
isCrit?: boolean;
|
||||
critKeys?: Set<string>;
|
||||
onStatChange: (characterId: number, statName: string, value: number) => void;
|
||||
}
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export default function StatsPanel({
|
|||
character,
|
||||
mode,
|
||||
campaignId,
|
||||
isCrit,
|
||||
critKeys,
|
||||
onStatChange,
|
||||
}: StatsPanelProps) {
|
||||
const attacks = generateAttacks(character);
|
||||
|
|
@ -43,7 +43,7 @@ export default function StatsPanel({
|
|||
characterName={character.name}
|
||||
characterColor={character.color}
|
||||
mode={mode}
|
||||
isCrit={isCrit}
|
||||
critKeys={critKeys}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default function CampaignView() {
|
|||
});
|
||||
const [rolls, setRolls] = useState<RollResult[]>([]);
|
||||
const [freshIds, setFreshIds] = useState<Set<number>>(new Set());
|
||||
const [critCharIds, setCritCharIds] = useState<Set<number>>(new Set());
|
||||
const [critKeys, setCritKeys] = useState<Set<string>>(new Set());
|
||||
|
||||
// Fetch characters and join socket room
|
||||
useEffect(() => {
|
||||
|
|
@ -172,15 +172,20 @@ export default function CampaignView() {
|
|||
});
|
||||
}, 2000);
|
||||
|
||||
// Track crits: if this is a nat20 attack, mark character for crit damage
|
||||
// Track crits by character+weapon key (e.g. "1:SHORTSWORD")
|
||||
if (roll.nat20 && roll.character_id && roll.label.includes("attack")) {
|
||||
setCritCharIds((prev) => new Set(prev).add(roll.character_id!));
|
||||
const weaponName = roll.label.replace(" attack", "");
|
||||
const key = `${roll.character_id}:${weaponName}`;
|
||||
setCritKeys((prev) => new Set(prev).add(key));
|
||||
}
|
||||
// If this is a damage roll, clear the crit flag for that character
|
||||
if (roll.character_id && roll.label.includes("damage")) {
|
||||
setCritCharIds((prev) => {
|
||||
const weaponName = roll.label
|
||||
.replace(" damage", "")
|
||||
.replace(" (CRIT)", "");
|
||||
const key = `${roll.character_id}:${weaponName}`;
|
||||
setCritKeys((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(roll.character_id!);
|
||||
next.delete(key);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
|
@ -316,7 +321,7 @@ export default function CampaignView() {
|
|||
<CharacterDetail
|
||||
character={selectedCharacter}
|
||||
campaignId={campaignId}
|
||||
isCrit={critCharIds.has(selectedCharacter.id)}
|
||||
critKeys={critKeys}
|
||||
onUpdate={handleUpdate}
|
||||
onStatChange={handleStatChange}
|
||||
onAddGearFromItem={handleAddGearFromItem}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue