feat: display unlock conditions on all locked items

All locked items now show players exactly what they need to do next:
- Adventurers: "πŸ“œ Complete: [Quest Name]"
- Upgrades: "βš”οΈ Defeat: [Boss]" or "πŸ“œ Complete: [Quest]"
- Equipment (boss drops): "βš”οΈ Drop: [Boss Name]" instead of generic label
- Bosses: "βš”οΈ Defeat: [Previous Boss] first" or zone gate boss
- Quests: "πŸ“œ Complete: [Prerequisite Quest]"

Also wires adventurer unlocks through quests (militia through dragon_rider
had no unlock path), retroactively applies rewards on existing saves,
syncs boss reward arrays from defaults on load, and removes invalid
rune_stone reference from Forest Giant.
This commit is contained in:
2026-03-06 15:08:08 -08:00
committed by Naomi Carrigan
parent 42db6e1991
commit c5ea59ffb4
9 changed files with 148 additions and 13 deletions
@@ -10,6 +10,7 @@ interface BossCardProps {
prestigeCount: number;
onChallenge: (bossId: string) => void;
isChallenging: boolean;
unlockHint?: string | undefined;
}
const BossCard = ({
@@ -17,6 +18,7 @@ const BossCard = ({
prestigeCount,
onChallenge,
isChallenging,
unlockHint,
}: BossCardProps): React.JSX.Element => {
const hpPercent = (boss.currentHp / boss.maxHp) * 100;
const isPrestigeLocked = boss.prestigeRequirement > prestigeCount;
@@ -33,6 +35,9 @@ const BossCard = ({
πŸ”’ Requires Prestige {boss.prestigeRequirement}
</p>
)}
{!isPrestigeLocked && boss.status === "locked" && unlockHint && (
<p className="unlock-hint">{unlockHint}</p>
)}
</div>
{boss.status !== "locked" && boss.status !== "defeated" && (
@@ -146,6 +151,26 @@ export const BossPanel = (): React.JSX.Element => {
? zoneBosses
: zoneBosses.filter((b) => b.status !== "locked");
const bossUnlockHints = new Map<string, string>();
for (const zone of zones) {
const allZoneBosses = state.bosses.filter((b) => b.zoneId === zone.id);
for (let i = 0; i < allZoneBosses.length; i++) {
const boss = allZoneBosses[i];
if (!boss || boss.status !== "locked") continue;
if (i === 0 && zone.unlockBossId) {
const gateBoss = state.bosses.find((b) => b.id === zone.unlockBossId);
if (gateBoss) {
bossUnlockHints.set(boss.id, `βš”οΈ Defeat: ${gateBoss.name}`);
}
} else if (i > 0) {
const prevBoss = allZoneBosses[i - 1];
if (prevBoss) {
bossUnlockHints.set(boss.id, `βš”οΈ Defeat: ${prevBoss.name} first`);
}
}
}
}
return (
<section className="panel boss-panel">
<div className="panel-header">
@@ -181,6 +206,7 @@ export const BossPanel = (): React.JSX.Element => {
boss={boss}
isChallenging={challengingBossId === boss.id}
prestigeCount={state.prestige.count}
unlockHint={bossUnlockHints.get(boss.id)}
onChallenge={(id) => {
void handleChallenge(id);
}}