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
@@ -35,6 +35,7 @@ interface EquipmentCardProps {
gold: number;
essence: number;
crystals: number;
dropBossName?: string | undefined;
}
const costLabel = (cost: { gold: number; essence: number; crystals: number }): string => {
@@ -45,7 +46,7 @@ const costLabel = (cost: { gold: number; essence: number; crystals: number }): s
return parts.join(" ");
};
const EquipmentCard = ({ item, gold, essence, crystals }: EquipmentCardProps): React.JSX.Element => {
const EquipmentCard = ({ item, gold, essence, crystals, dropBossName }: EquipmentCardProps): React.JSX.Element => {
const { equipItem, buyEquipment } = useGame();
const canAfford = item.cost
@@ -67,7 +68,11 @@ const EquipmentCard = ({ item, gold, essence, crystals }: EquipmentCardProps): R
)}
</div>
<div className="equipment-action">
{!item.owned && !item.cost && <span className="equipment-locked">πŸ”’ Boss drop</span>}
{!item.owned && !item.cost && (
<span className="equipment-locked">
{dropBossName ? `βš”οΈ Drop: ${dropBossName}` : "πŸ”’ Boss drop"}
</span>
)}
{!item.owned && item.cost && (
<button
className="equip-button"
@@ -109,6 +114,13 @@ export const EquipmentPanel = (): React.JSX.Element => {
const equipment = state.equipment ?? [];
const unownedCount = equipment.filter((e) => !e.owned).length;
const equipmentDropSources = new Map<string, string>();
for (const boss of state.bosses) {
for (const equipmentId of (boss.equipmentRewards ?? [])) {
equipmentDropSources.set(equipmentId, boss.name);
}
}
return (
<section className="panel equipment-panel">
<div className="panel-header">
@@ -138,6 +150,7 @@ export const EquipmentPanel = (): React.JSX.Element => {
gold={state.resources.gold}
essence={state.resources.essence}
crystals={state.resources.crystals}
dropBossName={equipmentDropSources.get(item.id)}
/>
))}
{items.length === 0 && (