Files
pgs/Assets/Scripts/State/QuestProgress.cs

604 lines
24 KiB
C#
Raw Normal View History

2026-02-21 16:58:22 -08:00
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestPrerequisites {
public int minimumLevel = 1;
public List<QuestID> requiredQuests;
public QuestPrerequisites(int minimumLevel, List<QuestID> requiredQuests) {
this.minimumLevel = minimumLevel;
this.requiredQuests = requiredQuests;
}
}
public class QuestRequirements {
public Hashtable requiredMonsters; // Hashtable<MonsterInfo.MonsterID, int> for #monsters required
public Hashtable requiredItems; // Hashtable<ItemSubType, int>
public List<DialogueID> requiredDialogue;
public int requiredLevel = 1;
public bool isLevelUpQuest = false;
public QuestRequirements(Hashtable requiredMonsters,
Hashtable requiredItems,
List<DialogueID> requiredDialogue = null,
int requiredLevel = 1,
bool isLevelUpQuest = false) {
this.requiredMonsters = requiredMonsters;
this.requiredItems = requiredItems;
this.requiredDialogue = requiredDialogue;
this.requiredLevel = requiredLevel;
this.isLevelUpQuest = isLevelUpQuest;
if (requiredMonsters == null) this.requiredMonsters = new Hashtable();
if (requiredItems == null) this.requiredItems = new Hashtable();
if (requiredDialogue == null) this.requiredDialogue = new List<DialogueID>();
}
}
public class QuestReward {
public int exp;
public int statPoints;
public int money;
public List<Item> items;
public QuestReward(int exp, int statPoints, int money, List<Item> items) {
this.exp = exp;
this.statPoints = statPoints;
this.money = money;
this.items = items;
if (items == null) items = new List<Item>();
}
}
public class QuestProgress {
public QuestID questID;
public QuestRequirements requirements;
public Hashtable monsterKills; // Hashtable<MonsterInfo.MonsterID, int>
public Hashtable itemProgress; // Hashtable<ItemSubType, int>
public static QuestProgress FromQuest(Quest quest) {
QuestProgress progress = new QuestProgress {
questID = quest.questID,
requirements = quest.requirements,
monsterKills = new Hashtable(),
itemProgress = new Hashtable()
};
return progress;
}
// Returns true iff you've completed the quest.
public bool CheckComplete() {
foreach (string line in WriteUp()) {
Debug.Log(line);
}
foreach (DictionaryEntry KV in requirements.requiredMonsters) {
if (!monsterKills.ContainsKey(KV.Key) || (int)monsterKills[KV.Key] < (int)KV.Value) {
return false;
}
}
foreach (DictionaryEntry KV in requirements.requiredItems) {
if (!itemProgress.ContainsKey(KV.Key) || (int)itemProgress[KV.Key] < (int)KV.Value) {
return false;
}
}
foreach (DialogueID dialogueID in requirements.requiredDialogue) {
if (!State.state.npcState.completedDialogues.Contains(dialogueID)) {
return false;
}
}
return State.state.stats.level >= requirements.requiredLevel;
}
public void RegisterMonsterKill(MonsterInfo.MonsterID monsterID) {
if (requirements.requiredMonsters.ContainsKey(monsterID)) {
if (monsterKills.ContainsKey(monsterID)) {
monsterKills[monsterID] = (int)monsterKills[monsterID] + 1;
} else {
monsterKills[monsterID] = 1;
}
}
}
public void RefreshItemProgress() {
Hashtable currentProgress = new Hashtable();
foreach (Item item in State.state.inventory.items) {
if (item != null && requirements.requiredItems.ContainsKey(item.subType)) {
if (currentProgress.ContainsKey(item.subType)) {
currentProgress[item.subType] = (int)currentProgress[item.subType] + item.count;
} else {
currentProgress[item.subType] = item.count;
}
}
}
itemProgress = currentProgress;
}
// Write up the quest progress, one requirement at a time.
public List<string> WriteUp() {
List<string> writeup = new List<string>();
if (requirements.isLevelUpQuest) {
writeup.Add("Level: " + State.state.stats.level.ToString() + " / " + requirements.requiredLevel.ToString());
}
foreach (DictionaryEntry KV in requirements.requiredMonsters) {
int numKills = 0;
if (monsterKills.ContainsKey(KV.Key)) {
numKills = (int)monsterKills[KV.Key];
}
writeup.Add("Defeat " + ((MonsterInfo)MonsterInfo.MonsterInfoMap[KV.Key]).name
+ ": " + numKills.ToString() + " / " + requirements.requiredMonsters[KV.Key].ToString());
}
foreach (DictionaryEntry KV in requirements.requiredItems) {
int numItems = 0;
if (itemProgress.ContainsKey(KV.Key)) {
numItems = (int)itemProgress[KV.Key];
}
writeup.Add("Collect " + ((ItemData)ItemData.itemData[KV.Key]).name
+ ": " + numItems.ToString() + " / " + requirements.requiredItems[KV.Key].ToString());
}
return writeup;
}
}
public class Quest {
public QuestID questID;
public string name;
public QuestPrerequisites prerequisites;
public QuestRequirements requirements;
public QuestReward reward;
public string description;
public Quest(QuestID questID,
string name,
QuestPrerequisites prerequisites,
QuestRequirements requirements,
QuestReward reward,
string description) {
this.questID = questID;
this.name = name;
this.prerequisites = prerequisites;
this.requirements = requirements;
this.reward = reward;
this.description = description;
}
}
public class QuestProgressData {
// Update on monster kill.
public Hashtable monsterToQuestID = new Hashtable(); // Hashtable<MonsterInfo.MonsterID, HashSet<QuestID>>
// Update on any item change.
public Hashtable itemToQuestID = new Hashtable(); // Hashtable<ItemSubType, HashSet<QuestID>>
public HashSet<QuestID> levelBasedQuests = new HashSet<QuestID>();
public Hashtable allQuestProgress = new Hashtable(); // Hashtable<QuestData.QuestID, QuestProgress>
public HashSet<QuestID> availableQuests = new HashSet<QuestID>();
public HashSet<QuestID> completedQuests = new HashSet<QuestID>();
public bool CheckPrerequisites(QuestID questID) {
Quest quest = (Quest)QuestInfo.quests[questID];
if (State.state.stats.level < quest.prerequisites.minimumLevel) {
return false;
}
foreach (QuestID requiredQuest in quest.prerequisites.requiredQuests) {
if (!completedQuests.Contains(requiredQuest)) {
return false;
}
}
return true;
}
public bool CheckNpcShopPrerequisites(Npc npc) {
foreach(QuestID questID in npc.utilityRequirements) {
if (!completedQuests.Contains(questID)) {
return false;
}
}
return true;
}
public bool AcceptQuest(QuestID questID) {
Quest quest = (Quest)QuestInfo.quests[questID];
if (availableQuests.Contains(questID) && CheckPrerequisites(questID)) {
availableQuests.Remove(questID);
allQuestProgress.Add(questID, QuestProgress.FromQuest(quest));
if (quest.requirements.isLevelUpQuest) {
levelBasedQuests.Add(questID);
}
foreach (DictionaryEntry KV in quest.requirements.requiredMonsters) {
if (monsterToQuestID.ContainsKey(KV.Key)) {
((HashSet<QuestID>)monsterToQuestID[KV.Key]).Add(questID);
} else {
monsterToQuestID[KV.Key] = new HashSet<QuestID>() { questID };
}
}
foreach (DictionaryEntry KV in quest.requirements.requiredItems) {
if (itemToQuestID.ContainsKey(KV.Key)) {
((HashSet<QuestID>)itemToQuestID[KV.Key]).Add(questID);
} else {
itemToQuestID[KV.Key] = new HashSet<QuestID>() { questID };
}
}
RefreshAvailableQuests();
return true;
}
return false;
}
public bool CompleteQuest(QuestID questID) {
QuestProgress progress = (QuestProgress)allQuestProgress[questID];
if (progress == null || !progress.CheckComplete()) {
return false;
}
Quest quest = (Quest)QuestInfo.quests[questID];
// Issue quest reward.
Debug.Log("Gimme!");
// Check for sufficient open slots.
int numOpenSlots = 0;
foreach (Item item in State.state.inventory.items) {
if (item == null || item.count == 0) {
numOpenSlots++;
}
}
if (numOpenSlots < quest.reward.items.Count) {
Debug.Log("You need " + (quest.reward.items.Count - numOpenSlots).ToString() + " more open slots!");
return false;
}
// Cache inventory.
Item[] inventoryCache = new Item[State.state.inventory.items.Length];
Array.Copy(State.state.inventory.items, inventoryCache, inventoryCache.Length);
HashSet<ItemSubType> itemsForQuestUpdate = new HashSet<ItemSubType>();
// Disburse items.
foreach (Item item in quest.reward.items) {
Debug.Log("Disbursing " + item.subType.ToString());
// Items remaining from what was gained >0 means that the gain failed.
if (State.state.inventory.GainItem(item, false) > 0) {
Debug.LogError("Failed to gain item " + item.data.name + "! Reverting to inventory cache.");
Array.Copy(inventoryCache, State.state.inventory.items, inventoryCache.Length);
return false;
}
itemsForQuestUpdate.Add(item.subType);
}
// Take what the quest wants (items).
Debug.Log("Here you go!");
Hashtable requiredItems = (Hashtable)quest.requirements.requiredItems.Clone();
for (int i = 0; i < State.state.inventory.items.Length; i++) {
Item item = State.state.inventory.items[i];
if (item != null && requiredItems.ContainsKey(item.subType)) {
// Drop (destroy)
Item droppedItem = State.state.inventory.DropItem(i, Mathf.Min((int)requiredItems[item.subType], item.count), false);
requiredItems[item.subType] = (int)requiredItems[item.subType] - droppedItem.count;
if ((int)requiredItems[item.subType] == 0) {
requiredItems.Remove(item.subType);
}
}
}
// Having required items remaining means something went wrong destroying the items.
if (requiredItems.Count > 0) {
string remaining = "";
foreach(DictionaryEntry KV in requiredItems) {
remaining += ((ItemData)ItemData.itemData[KV.Key]).name + ", ";
}
Debug.LogError("Failed to delete required items: " + remaining);
Array.Copy(inventoryCache, State.state.inventory.items, inventoryCache.Length);
return false;
}
// Update state.
State.state.stats.updateCache();
foreach(ItemSubType type in itemsForQuestUpdate) {
State.state.inventory.UpdateQuests(type);
}
// Remove quest from progress trackers.
foreach (DictionaryEntry KV in monsterToQuestID) {
HashSet<QuestID> quests = (HashSet<QuestID>)KV.Value;
if (quests.Contains(questID)) {
quests.Remove(questID);
}
}
foreach (DictionaryEntry KV in itemToQuestID) {
HashSet<QuestID> quests = (HashSet<QuestID>)KV.Value;
if (quests.Contains(questID)) {
quests.Remove(questID);
}
}
completedQuests.Add(questID);
allQuestProgress.Remove(questID);
levelBasedQuests.Remove(questID);
RefreshAvailableQuests();
return true;
}
public void RefreshAvailableQuests() {
foreach (QuestID questID in Enum.GetValues(typeof(QuestID))) {
if (!completedQuests.Contains(questID) &&
!allQuestProgress.ContainsKey(questID) &&
!availableQuests.Contains(questID) &&
CheckPrerequisites(questID)) {
availableQuests.Add(questID);
}
}
}
}
public enum QuestID {
QUEST_KARA_MAGIC_MUSHROOM,
QUEST_KARA_BULKY_MUSHROOM,
QUEST_KARA_LITTLE_CRITTERS,
QUEST_KARA_BANDITS,
QUEST_KARA_PLUCKING_HARPIES,
QUEST_ESTER_ALL_ABOUT_STATS,
QUEST_ESTER_ALL_ABOUT_EQUIPS,
QUEST_ESTER_PREEMPTIVE_STRIKE,
QUEST_ESTER_EMPTIVE_STRIKE,
QUEST_ESTER_TINY_LEADER,
QUEST_ESTER_LITTLE_NIGHTMARE,
QUEST_ESTER_THE_JOURNEY_BEGINS,
QUEST_LYN_GETTING_THE_GOODS,
QUEST_LYN_BEAUTIFUL_LANDSCAPES,
}
public class QuestInfo {
// Hashtable<QuestID, Quest>
public static Hashtable quests = new Hashtable() {
{ QuestID.QUEST_KARA_MAGIC_MUSHROOM,
new Quest(
QuestID.QUEST_KARA_MAGIC_MUSHROOM,
"Magic Mushrooms?",
new QuestPrerequisites(1, new List<QuestID>()),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.BROWN_MUSHROOM, 10 }
},
new Hashtable() {
{ ItemSubType.GREEN_MUSHROOM_SAMPLE, 3 }
}),
new QuestReward(50, 0, 10,
new List<Item>() {
Lottery.generateItem(ItemSubType.MUSHROOM_SOUP, 3)
}),
"Kara wants me to get a closer look at those giant mushrooms floating " +
"around the valley... I wonder what they taste like.") },
{ QuestID.QUEST_KARA_BULKY_MUSHROOM,
new Quest(
QuestID.QUEST_KARA_BULKY_MUSHROOM,
"Bulky Mushrooms",
new QuestPrerequisites(2, new List<QuestID>() {QuestID.QUEST_KARA_MAGIC_MUSHROOM}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.RED_MUSHROOM, 10 }
},
new Hashtable() {
{ ItemSubType.RED_MUSHROOM_SAMPLE, 3 }
}),
new QuestReward(80, 0, 20,
new List<Item>() {
Lottery.generateItem(ItemSubType.MUSHROOM_SOUP, 5)
}),
"Kara was roughed up by some big red mushrooms. " +
"It's time for some target practice.") },
{ QuestID.QUEST_KARA_LITTLE_CRITTERS,
new Quest(
QuestID.QUEST_KARA_LITTLE_CRITTERS,
"Little Critters",
new QuestPrerequisites(3, new List<QuestID>() {QuestID.QUEST_KARA_BULKY_MUSHROOM}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.RED_FOX, 10 },
{ MonsterInfo.MonsterID.WHITE_FOX, 10 },
},
new Hashtable() {
{ ItemSubType.FOX_FUR, 6 }
}),
new QuestReward(200, 0, 50,
new List<Item>() {
Lottery.generateItem(ItemSubType.ARCHER_TUNIC)
}),
"Kara spotted some foxes stealing crops in my field. " +
"I need to run them out of town!") },
{ QuestID.QUEST_KARA_BANDITS,
new Quest(
QuestID.QUEST_KARA_BANDITS,
"Bandits!",
new QuestPrerequisites(5, new List<QuestID>() {QuestID.QUEST_KARA_LITTLE_CRITTERS}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.HARPY_F, 10 },
{ MonsterInfo.MonsterID.HARPY_F_2, 10 },
},
new Hashtable() {
{ ItemSubType.HARPY_FEATHER, 6 }
}),
new QuestReward(400, 0, 200,
new List<Item>() {
Lottery.generateItem(ItemSubType.MUSHROOM_SOUP, 10)
}),
"Looks like we have some visitors. " +
"Aren't harpies usually pretty rare?") },
{ QuestID.QUEST_KARA_PLUCKING_HARPIES,
new Quest(
QuestID.QUEST_KARA_PLUCKING_HARPIES,
"Plucking Harpies",
new QuestPrerequisites(10, new List<QuestID>() {QuestID.QUEST_KARA_BANDITS}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.HARPY_M, 10 },
{ MonsterInfo.MonsterID.HARPY_M_2, 10 },
},
new Hashtable() {
{ ItemSubType.LARGE_HARPY_FEATHER, 10 }
}),
new QuestReward(500, 0, 500,
new List<Item>() {
Lottery.generateItem(ItemSubType.MUSHROOM_SOUP, 20)
}),
"Kara wants me to gether some Large Harpy Feathers. " +
"Weird, but alright.") },
// Ester
{ QuestID.QUEST_ESTER_ALL_ABOUT_STATS,
new Quest(
QuestID.QUEST_ESTER_ALL_ABOUT_STATS,
"All About Stats",
new QuestPrerequisites(1, new List<QuestID>() {}),
new QuestRequirements(
new Hashtable(),
new Hashtable()),
new QuestReward(50, 1, 0, new List<Item>()),
"Ester wants to teach me about latent power.") },
{ QuestID.QUEST_ESTER_ALL_ABOUT_EQUIPS,
new Quest(
QuestID.QUEST_ESTER_ALL_ABOUT_EQUIPS,
"All About Equips",
new QuestPrerequisites(2, new List<QuestID>() { QuestID.QUEST_ESTER_ALL_ABOUT_STATS }),
new QuestRequirements(
new Hashtable(),
new Hashtable()),
new QuestReward(60, 1, 0, new List<Item>()),
"Ester wants to teach me about gear! I'm all ears.") },
{ QuestID.QUEST_ESTER_PREEMPTIVE_STRIKE,
new Quest(
QuestID.QUEST_ESTER_PREEMPTIVE_STRIKE,
"Pre-emptive Strike",
new QuestPrerequisites(10, new List<QuestID>() {
QuestID.QUEST_ESTER_ALL_ABOUT_EQUIPS,
QuestID.QUEST_KARA_BANDITS
}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.HALFLING_ROGUE, 10 },
{ MonsterInfo.MonsterID.HALFLING_ARCHER, 10 },
{ MonsterInfo.MonsterID.HALFLING_SWORDWIELDER, 5 },
{ MonsterInfo.MonsterID.HARPY_F_2, 5 },
},
new Hashtable() {
{ ItemSubType.BREASTPLATE, 5 },
{ ItemSubType.FUZZY_SCARF, 5 },
{ ItemSubType.SOFT_FABRIC, 1 },
}),
new QuestReward(1000, 1, 100, new List<Item>()),
"It's time to knock some sense into those halflings on the other side of town.") },
{ QuestID.QUEST_ESTER_EMPTIVE_STRIKE,
new Quest(
QuestID.QUEST_ESTER_EMPTIVE_STRIKE,
"Emptive Strike",
new QuestPrerequisites(10, new List<QuestID>() {
QuestID.QUEST_ESTER_PREEMPTIVE_STRIKE,
}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.HALFLING_ROGUE_2, 10 },
{ MonsterInfo.MonsterID.HALFLING_ARCHER_2, 10 },
{ MonsterInfo.MonsterID.HALFLING_SWORDWIELDER_2, 5 },
{ MonsterInfo.MonsterID.HARPY_M, 5 },
{ MonsterInfo.MonsterID.HARPY_M_2, 5 },
},
new Hashtable() {
{ ItemSubType.BREASTPLATE, 5 },
{ ItemSubType.FUZZY_SCARF, 5 },
{ ItemSubType.SOFT_FABRIC, 1 },
{ ItemSubType.LARGE_HARPY_FEATHER, 1 },
}),
new QuestReward(2000, 1, 200, new List<Item>()),
"There's way more bandits than I thought! I'll push my way into their camp.") },
{ QuestID.QUEST_ESTER_TINY_LEADER,
new Quest(
QuestID.QUEST_ESTER_TINY_LEADER,
"Tiny Leader",
new QuestPrerequisites(15, new List<QuestID>() {
QuestID.QUEST_ESTER_EMPTIVE_STRIKE,
}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.HALFLING_ROGUE_3, 10 },
{ MonsterInfo.MonsterID.HALFLING_ARCHER_3, 10 },
{ MonsterInfo.MonsterID.HALFLING_SWORDWIELDER_3, 10 },
},
new Hashtable()),
new QuestReward(3000, 1, 300, new List<Item>()),
"The leader of the bandits is somewhere in the center of their camp. " +
"Maybe some underling pummelling will prepare me for the fight.") },
{ QuestID.QUEST_ESTER_LITTLE_NIGHTMARE,
new Quest(
QuestID.QUEST_ESTER_LITTLE_NIGHTMARE,
"Tiny Leader",
new QuestPrerequisites(15, new List<QuestID>() {
QuestID.QUEST_ESTER_TINY_LEADER,
}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.HALFLING_LEADER, 1 },
},
new Hashtable()),
new QuestReward(5000, 1, 500, new List<Item>()),
"It's time to take on the leader of the bandits. Maybe they'll go away " +
"after this, or at least leave the townsfolk alone.") },
{ QuestID.QUEST_ESTER_THE_JOURNEY_BEGINS,
new Quest(
QuestID.QUEST_ESTER_THE_JOURNEY_BEGINS,
"Tiny Leader",
new QuestPrerequisites(15, new List<QuestID>() {
QuestID.QUEST_ESTER_LITTLE_NIGHTMARE,
}),
new QuestRequirements(
new Hashtable() {
{ MonsterInfo.MonsterID.HALFLING_LEADER, 1 },
},
new Hashtable()),
new QuestReward(5000, 1, 500, new List<Item>()),
"It's time to head into the forest for the first time in months. " +
"I wonder how Hestus City is doing.") },
// Lyn
{ QuestID.QUEST_LYN_GETTING_THE_GOODS,
new Quest(
QuestID.QUEST_LYN_GETTING_THE_GOODS,
"Getting the Goods",
//new QuestPrerequisites(5, new List<QuestID>() { QuestID.QUEST_KARA_LITTLE_CRITTERS }),
new QuestPrerequisites(1, new List<QuestID>() { }),
new QuestRequirements(new Hashtable(), new Hashtable()),
new QuestReward(50, 0, 0, new List<Item>()),
"There's a new face around town! What's a demon doing here?") },
{ QuestID.QUEST_LYN_BEAUTIFUL_LANDSCAPES,
new Quest(
QuestID.QUEST_LYN_GETTING_THE_GOODS,
"Getting the Goods",
new QuestPrerequisites(60, new List<QuestID>() { QuestID.QUEST_LYN_GETTING_THE_GOODS }),
new QuestRequirements(new Hashtable(), new Hashtable()),
new QuestReward(0, 0, 0, new List<Item>()),
"I didn't know Lyn could paint so well!") },
};
}