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
+1 -1
View File
@@ -49,7 +49,7 @@ export const DEFAULT_BOSSES: Boss[] = [
essenceReward: 400,
crystalReward: 20,
upgradeRewards: ["archmage_1"],
equipmentRewards: ["hide_armour", "rune_stone"],
equipmentRewards: ["hide_armour"],
prestigeRequirement: 0,
zoneId: "verdant_vale",
},
+11 -1
View File
@@ -8,7 +8,10 @@ export const DEFAULT_QUESTS: Quest[] = [
description: "Every legend begins somewhere. Send your first adventurer into the field.",
status: "available",
durationSeconds: 60,
rewards: [{ type: "gold", amount: 500 }],
rewards: [
{ type: "gold", amount: 500 },
{ type: "adventurer", targetId: "militia" },
],
prerequisiteIds: [],
zoneId: "verdant_vale",
},
@@ -21,6 +24,7 @@ export const DEFAULT_QUESTS: Quest[] = [
rewards: [
{ type: "gold", amount: 2_000 },
{ type: "essence", amount: 5 },
{ type: "adventurer", targetId: "apprentice" },
],
prerequisiteIds: ["first_steps"],
zoneId: "verdant_vale",
@@ -34,6 +38,7 @@ export const DEFAULT_QUESTS: Quest[] = [
rewards: [
{ type: "crystals", amount: 10 },
{ type: "upgrade", targetId: "global_1" },
{ type: "adventurer", targetId: "scout" },
],
prerequisiteIds: ["goblin_camp"],
zoneId: "verdant_vale",
@@ -47,6 +52,7 @@ export const DEFAULT_QUESTS: Quest[] = [
rewards: [
{ type: "essence", amount: 50 },
{ type: "upgrade", targetId: "click_2" },
{ type: "adventurer", targetId: "acolyte" },
],
prerequisiteIds: ["haunted_mine"],
zoneId: "verdant_vale",
@@ -63,6 +69,7 @@ export const DEFAULT_QUESTS: Quest[] = [
{ type: "gold", amount: 15_000 },
{ type: "essence", amount: 20 },
{ type: "upgrade", targetId: "cleric_1" },
{ type: "adventurer", targetId: "ranger" },
],
prerequisiteIds: [],
zoneId: "shattered_ruins",
@@ -78,6 +85,7 @@ export const DEFAULT_QUESTS: Quest[] = [
{ type: "gold", amount: 80_000 },
{ type: "essence", amount: 120 },
{ type: "upgrade", targetId: "scout_1" },
{ type: "adventurer", targetId: "knight" },
],
prerequisiteIds: ["necromancer_tower"],
zoneId: "shattered_ruins",
@@ -93,6 +101,7 @@ export const DEFAULT_QUESTS: Quest[] = [
{ type: "essence", amount: 300 },
{ type: "crystals", amount: 30 },
{ type: "upgrade", targetId: "mage_1" },
{ type: "adventurer", targetId: "archmage" },
],
prerequisiteIds: ["crumbling_fortress"],
zoneId: "shattered_ruins",
@@ -107,6 +116,7 @@ export const DEFAULT_QUESTS: Quest[] = [
rewards: [
{ type: "gold", amount: 500_000 },
{ type: "crystals", amount: 50 },
{ type: "adventurer", targetId: "paladin" },
{ type: "adventurer", targetId: "dragon_rider" },
],
prerequisiteIds: ["cursed_library"],
+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)