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
+32 -4
View File
@@ -61,7 +61,7 @@ gameRouter.get("/load", async (context) => {
}
}
// Backfill equipmentRewards on bosses that predate the field
// Backfill equipmentRewards on bosses that predate the field (will be synced below after defaults load)
for (const boss of state.bosses) {
if (!Array.isArray(boss.equipmentRewards)) {
boss.equipmentRewards = [];
@@ -82,7 +82,7 @@ gameRouter.get("/load", async (context) => {
}
}
// Sync zoneId on quests to match current defaults
// Sync zoneId and rewards on quests to match current defaults
for (const quest of state.quests) {
const defaults = DEFAULT_QUESTS.find((d) => d.id === quest.id);
if (defaults && quest.zoneId !== defaults.zoneId) {
@@ -93,6 +93,23 @@ gameRouter.get("/load", async (context) => {
quest.zoneId = defaults?.zoneId ?? "verdant_vale";
needsBackfill = true;
}
// Sync rewards to match defaults so newly-added rewards take effect
if (defaults && JSON.stringify(quest.rewards) !== JSON.stringify(defaults.rewards)) {
quest.rewards = structuredClone(defaults.rewards);
needsBackfill = true;
}
// Retroactively apply adventurer unlocks from already-completed quests
if (quest.status === "completed") {
for (const reward of quest.rewards) {
if (reward.type === "adventurer" && reward.targetId) {
const adventurer = state.adventurers.find((a) => a.id === reward.targetId);
if (adventurer && !adventurer.unlocked) {
adventurer.unlocked = true;
needsBackfill = true;
}
}
}
}
}
for (const defaultUpgrade of DEFAULT_UPGRADES) {
@@ -149,13 +166,24 @@ gameRouter.get("/load", async (context) => {
}
}
// Backfill zoneId on bosses that predate the field
// Backfill zoneId and sync rewards on bosses to match current defaults
for (const boss of state.bosses) {
const defaults = DEFAULT_BOSSES.find((d) => d.id === boss.id);
if (!boss.zoneId) {
const defaults = DEFAULT_BOSSES.find((d) => d.id === boss.id);
boss.zoneId = defaults?.zoneId ?? "verdant_vale";
needsBackfill = true;
}
// Sync equipmentRewards and upgradeRewards to match defaults
if (defaults) {
if (JSON.stringify(boss.equipmentRewards) !== JSON.stringify(defaults.equipmentRewards)) {
boss.equipmentRewards = structuredClone(defaults.equipmentRewards);
needsBackfill = true;
}
if (JSON.stringify(boss.upgradeRewards) !== JSON.stringify(defaults.upgradeRewards)) {
boss.upgradeRewards = structuredClone(defaults.upgradeRewards);
needsBackfill = true;
}
}
}
// Merge new bosses from defaults (new zones' bosses)