diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-06-17 11:59:46 +0200 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-06-17 11:59:46 +0200 |
commit | f79ea07b18b0a83594b86df0714caccfbd9b20a0 (patch) | |
tree | 3b30322818e8c156a319c2cfcf017d88d28b4e9f | |
parent | 75ce4e40dc1e44c48647e618c2b129af0299a8fc (diff) | |
download | mana-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.txt | 2 | ||||
-rw-r--r-- | src/resources/questdb.cpp | 111 | ||||
-rw-r--r-- | src/resources/questdb.h | 77 | ||||
-rw-r--r-- | src/resources/settingsmanager.cpp | 6 | ||||
-rw-r--r-- | src/utils/stringutils.cpp | 26 | ||||
-rw-r--r-- | src/utils/stringutils.h | 2 |
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>>> { |