summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-06-17 11:59:46 +0200
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-06-17 11:59:46 +0200
commitf79ea07b18b0a83594b86df0714caccfbd9b20a0 (patch)
tree3b30322818e8c156a319c2cfcf017d88d28b4e9f
parent75ce4e40dc1e44c48647e618c2b129af0299a8fc (diff)
downloadmana-f79ea07b18b0a83594b86df0714caccfbd9b20a0.tar.gz
mana-f79ea07b18b0a83594b86df0714caccfbd9b20a0.tar.bz2
mana-f79ea07b18b0a83594b86df0714caccfbd9b20a0.tar.xz
mana-f79ea07b18b0a83594b86df0714caccfbd9b20a0.zip
Add quest database support and parsing utilities
- Introduce QuestDB with quest/effect structures and XML parsing - Register questdb in CMake and settings manager - Add fromString overload for std::vector<int> parsing
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/resources/questdb.cpp111
-rw-r--r--src/resources/questdb.h77
-rw-r--r--src/resources/settingsmanager.cpp6
-rw-r--r--src/utils/stringutils.cpp26
-rw-r--r--src/utils/stringutils.h2
6 files changed, 224 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4a6a93db..6e3f78bc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -346,6 +346,8 @@ set(SRCS
resources/music.h
resources/npcdb.cpp
resources/npcdb.h
+ resources/questdb.cpp
+ resources/questdb.h
resources/resource.cpp
resources/resource.h
resources/resourcemanager.cpp
diff --git a/src/resources/questdb.cpp b/src/resources/questdb.cpp
new file mode 100644
index 00000000..207389e7
--- /dev/null
+++ b/src/resources/questdb.cpp
@@ -0,0 +1,111 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2025 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/questdb.h"
+#include "log.h"
+
+#include <unordered_map>
+
+namespace QuestDB {
+
+// The quests are stored in a map using their variable ID as the key
+static std::unordered_map<int, Quest> quests;
+
+void readQuestVarNode(XML::Node node, const std::string &filename)
+{
+ int varId = 0;
+ if (!node.attribute("id", varId))
+ return;
+
+ Quest &quest = quests[varId];
+
+ for (auto child : node.children())
+ {
+ if (child.name() == "effect")
+ {
+ QuestEffect &effect = quest.effects.emplace_back();
+ child.attribute("map", effect.map);
+ child.attribute("npc", effect.npc);
+ child.attribute("effect", effect.effect);
+ child.attribute("value", effect.values);
+
+ if (effect.map.empty() || effect.npc == 0 || effect.effect == 0 || effect.values.empty())
+ {
+ logger->log("Warning: effect node for var %d is missing required attributes", varId);
+ }
+ }
+ else if (child.name() == "quest")
+ {
+ QuestState &state = quest.states.emplace_back();
+ child.attribute("name", state.name);
+ child.attribute("group", state.group);
+ child.attribute("incomplete", state.incomplete);
+ child.attribute("complete", state.complete);
+
+ if (state.incomplete.empty() && state.complete.empty())
+ {
+ logger->log("Warning: quest node for var %d ('%s') has neither 'complete' nor 'incomplete' values",
+ varId, state.name.c_str());
+ continue;
+ }
+
+ for (auto questChild : child.children())
+ {
+ QuestRowType rowType;
+ std::string_view tag = questChild.name();
+ if (tag == "text")
+ rowType = QuestRowType::Text;
+ else if (tag == "name")
+ rowType = QuestRowType::Name;
+ else if (tag == "reward")
+ rowType = QuestRowType::Reward;
+ else if (tag == "questgiver" || tag == "giver")
+ rowType = QuestRowType::Giver;
+ else if (tag == "coordinates")
+ rowType = QuestRowType::Coordinates;
+ else if (tag == "npc")
+ rowType = QuestRowType::NPC;
+ else
+ {
+ logger->log("Warning: unknown quest row type '%s' for var %d ('%s')",
+ tag.data(), varId, state.name.c_str());
+ continue;
+ }
+
+ QuestRow &row = state.rows.emplace_back(rowType);
+ row.text = questChild.textContent();
+ }
+ }
+ }
+}
+
+void unload()
+{
+ quests.clear();
+}
+
+const Quest &get(int var)
+{
+ static Quest emptyQuest;
+ auto it = quests.find(var);
+ return it == quests.end() ? emptyQuest : it->second;
+}
+
+} // namespace QuestDB
diff --git a/src/resources/questdb.h b/src/resources/questdb.h
new file mode 100644
index 00000000..5c03a255
--- /dev/null
+++ b/src/resources/questdb.h
@@ -0,0 +1,77 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2025 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "utils/xml.h"
+
+#include <string>
+#include <vector>
+
+struct QuestEffect
+{
+ std::vector<int> values; // Quest variable values to which the effect applies
+ std::string map; // Map name the NPC is located on
+ int npc = 0; // NPC ID
+ int effect = 0; // Effect ID
+};
+
+enum class QuestRowType
+{
+ Text,
+ Name,
+ Reward,
+ Giver,
+ Coordinates,
+ NPC
+};
+
+struct QuestRow
+{
+ QuestRow(QuestRowType type)
+ : type(type)
+ {}
+
+ QuestRowType type;
+ std::string text;
+};
+
+struct QuestState
+{
+ std::string name; // Name of the quest in this state
+ std::string group; // Group name of the quest in this state
+ std::vector<int> incomplete; // Quest variable values for this state (quest incomplete)
+ std::vector<int> complete; // Quest variable values for this state (quest complete)
+ std::vector<QuestRow> rows; // Rows of text in the Quests window for this state
+};
+
+struct Quest
+{
+ std::vector<QuestEffect> effects;
+ std::vector<QuestState> states;
+};
+
+namespace QuestDB
+{
+ void readQuestVarNode(XML::Node node, const std::string &filename);
+ void unload();
+
+ const Quest &get(int var);
+};
diff --git a/src/resources/settingsmanager.cpp b/src/resources/settingsmanager.cpp
index 9323d4d1..3630f64f 100644
--- a/src/resources/settingsmanager.cpp
+++ b/src/resources/settingsmanager.cpp
@@ -27,6 +27,7 @@
#include "resources/monsterdb.h"
#include "resources/npcdb.h"
#include "resources/abilitydb.h"
+#include "resources/questdb.h"
#include "resources/statuseffectdb.h"
#include "net/net.h"
@@ -96,6 +97,7 @@ namespace SettingsManager
NPCDB::unload();
AbilityDB::unload();
MonsterDB::unload();
+ QuestDB::unload();
if (itemDb)
itemDb->unload();
hairDB.unload();
@@ -221,6 +223,10 @@ namespace SettingsManager
{
NPCDB::readNPCNode(childNode, filename);
}
+ else if (childNode.name() == "var")
+ {
+ QuestDB::readQuestVarNode(childNode, filename);
+ }
else if (childNode.name() == "emote")
{
EmoteDB::readEmoteNode(childNode, filename);
diff --git a/src/utils/stringutils.cpp b/src/utils/stringutils.cpp
index 05b6982c..7b05b365 100644
--- a/src/utils/stringutils.cpp
+++ b/src/utils/stringutils.cpp
@@ -152,6 +152,32 @@ bool getBoolFromString(std::string text, bool def)
return def;
}
+// Overload for std::vector<int> to parse comma-separated integers
+void fromString(const char *str, std::vector<int> &value)
+{
+ value.clear();
+
+ const char *p = str;
+ while (*p)
+ {
+ while (*p == ' ' || *p == ',')
+ ++p; // skip spaces and commas
+ if (!*p)
+ break;
+ char *end = nullptr;
+ int v = static_cast<int>(strtol(p, &end, 10));
+ if (end != p)
+ {
+ value.push_back(v);
+ p = end;
+ }
+ else
+ {
+ ++p; // skip invalid character to avoid infinite loop
+ }
+ }
+}
+
std::string autocomplete(const std::vector<std::string> &candidates,
std::string base)
{
diff --git a/src/utils/stringutils.h b/src/utils/stringutils.h
index 53c36bc6..8dd4454a 100644
--- a/src/utils/stringutils.h
+++ b/src/utils/stringutils.h
@@ -193,6 +193,8 @@ inline void fromString(const char *str, bool &value)
value = getBoolFromString(str);
}
+void fromString(const char *str, std::vector<int> &value);
+
template<typename T>
struct FromString<T, std::enable_if_t<std::is_enum_v<T>>>
{