diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-06-17 15:35:55 +0200 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-06-23 16:57:49 +0200 |
commit | e2042f2b6684e21b39214ce422212720b4374e1b (patch) | |
tree | ca10aa99e61891a906c6e07c6561c74bbca3095c | |
parent | f79ea07b18b0a83594b86df0714caccfbd9b20a0 (diff) | |
download | mana-e2042f2b6684e21b39214ce422212720b4374e1b.tar.gz mana-e2042f2b6684e21b39214ce422212720b4374e1b.tar.bz2 mana-e2042f2b6684e21b39214ce422212720b4374e1b.tar.xz mana-e2042f2b6684e21b39214ce422212720b4374e1b.zip |
Apply quest effects to NPCs
The `TmwAthena::PlayerHandler` now handles the quest variables and
applies the active quest effects to NPCs. They are updated when
variables change or the map changes.
-rw-r--r-- | src/net/tmwa/beinghandler.cpp | 8 | ||||
-rw-r--r-- | src/net/tmwa/playerhandler.cpp | 80 | ||||
-rw-r--r-- | src/net/tmwa/playerhandler.h | 16 | ||||
-rw-r--r-- | src/resources/questdb.cpp | 39 | ||||
-rw-r--r-- | src/resources/questdb.h | 41 |
5 files changed, 177 insertions, 7 deletions
diff --git a/src/net/tmwa/beinghandler.cpp b/src/net/tmwa/beinghandler.cpp index c5979e9f..c7debd5e 100644 --- a/src/net/tmwa/beinghandler.cpp +++ b/src/net/tmwa/beinghandler.cpp @@ -33,9 +33,9 @@ #include "playerrelations.h" #include "net/net.h" -#include "net/playerhandler.h" #include "net/tmwa/messagein.h" #include "net/tmwa/messageout.h" +#include "net/tmwa/playerhandler.h" #include "net/tmwa/protocol.h" #include "resources/emotedb.h" @@ -107,6 +107,12 @@ static Being *createBeing(int id, short job) outMsg.writeInt32(id); } + if (type == ActorSprite::NPC) + { + auto playerHandler = static_cast<TmwAthena::PlayerHandler*>(Net::getPlayerHandler()); + playerHandler->applyQuestStatusEffects(being); + } + return being; } diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp index f6f6ef41..14477e7b 100644 --- a/src/net/tmwa/playerhandler.cpp +++ b/src/net/tmwa/playerhandler.cpp @@ -21,6 +21,8 @@ #include "net/tmwa/playerhandler.h" +#include "actorspritemanager.h" +#include "being.h" #include "client.h" #include "configuration.h" #include "game.h" @@ -152,10 +154,14 @@ PlayerHandler::PlayerHandler() SMSG_PLAYER_STAT_UPDATE_6, SMSG_PLAYER_ARROW_MESSAGE, SMSG_MAP_MASK, + SMSG_QUEST_SET_VAR, + SMSG_QUEST_PLAYER_VARS, 0 }; handledMessages = _messages; playerHandler = this; + + listen(Event::GameChannel); } void PlayerHandler::handleMessage(MessageIn &msg) @@ -514,6 +520,30 @@ void PlayerHandler::handleMessage(MessageIn &msg) map->setMask(mask); } break; + + case SMSG_QUEST_SET_VAR: + { + int variable = msg.readInt16(); + int value = msg.readInt32(); + mQuestVars.set(variable, value); + updateQuestStatusEffects(); + break; + } + + case SMSG_QUEST_PLAYER_VARS: + { + msg.readInt16(); // length + mQuestVars.clear(); + unsigned int count = (msg.getLength() - 4) / 6; + for (unsigned int i = 0; i < count; ++i) + { + int variable = msg.readInt16(); + int value = msg.readInt32(); + mQuestVars.set(variable, value); + } + updateQuestStatusEffects(); + break; + } } } @@ -664,4 +694,54 @@ Vector PlayerHandler::getPixelsPerSecondMoveSpeed(const Vector &speed, Map *map) return pixelsPerSecond; } +void PlayerHandler::event(Event::Channel channel, const Event &event) +{ + if (channel == Event::GameChannel) + { + if (event.getType() == Event::MapLoaded) + { + updateQuestStatusEffects(); + } + } +} + +void PlayerHandler::applyQuestStatusEffects(Being *npc) +{ + const auto npcId = npc->getSubType(); + const auto effect = mActiveQuestEffects.get(npcId); + if (effect != 0) + npc->setStatusEffect(effect, true); +} + +void PlayerHandler::updateQuestStatusEffects() +{ + auto game = Game::instance(); + if (!game) + return; + + const auto ¤tMapName = game->getCurrentMapName(); + auto updatedQuestEffects = QuestDB::getActiveEffects(mQuestVars, currentMapName); + + // Loop over all NPCs, disabling no longer active effects and enabling new ones + for (auto actor : actorSpriteManager->getAll()) { + if (actor->getType() != ActorSprite::NPC) + continue; + + auto *npc = static_cast<Being *>(actor); + const auto npcId = npc->getSubType(); + const auto oldEffect = mActiveQuestEffects.get(npcId); + const auto newEffect = updatedQuestEffects.get(npcId); + + if (oldEffect != newEffect) + { + if (oldEffect != 0) + npc->setStatusEffect(oldEffect, false); + if (newEffect != 0) + npc->setStatusEffect(newEffect, true); + } + } + + std::swap(mActiveQuestEffects, updatedQuestEffects); +} + } // namespace TmwAthena diff --git a/src/net/tmwa/playerhandler.h b/src/net/tmwa/playerhandler.h index f1a67e94..6d2da1af 100644 --- a/src/net/tmwa/playerhandler.h +++ b/src/net/tmwa/playerhandler.h @@ -26,9 +26,12 @@ #include "net/tmwa/messagehandler.h" +#include "resources/questdb.h" + namespace TmwAthena { -class PlayerHandler final : public MessageHandler, public Net::PlayerHandler +class PlayerHandler final : public MessageHandler, public Net::PlayerHandler, + public EventListener { public: PlayerHandler(); @@ -63,6 +66,17 @@ class PlayerHandler final : public MessageHandler, public Net::PlayerHandler bool usePixelPrecision() override { return false; } + + // EventListener + void event(Event::Channel channel, const Event &event) override; + + void applyQuestStatusEffects(Being *npc); + + private: + void updateQuestStatusEffects(); + + QuestVars mQuestVars; + QuestEffectMap mActiveQuestEffects; }; } // namespace TmwAthena diff --git a/src/resources/questdb.cpp b/src/resources/questdb.cpp index 207389e7..13bc2112 100644 --- a/src/resources/questdb.cpp +++ b/src/resources/questdb.cpp @@ -21,7 +21,9 @@ #include "resources/questdb.h" #include "log.h" +#include <algorithm> #include <unordered_map> +#include <utility> namespace QuestDB { @@ -42,11 +44,11 @@ void readQuestVarNode(XML::Node node, const std::string &filename) { QuestEffect &effect = quest.effects.emplace_back(); child.attribute("map", effect.map); - child.attribute("npc", effect.npc); - child.attribute("effect", effect.effect); + child.attribute("npc", effect.npcId); + child.attribute("effect", effect.statusEffectId); child.attribute("value", effect.values); - if (effect.map.empty() || effect.npc == 0 || effect.effect == 0 || effect.values.empty()) + if (effect.map.empty() || effect.npcId == 0 || effect.statusEffectId == 0 || effect.values.empty()) { logger->log("Warning: effect node for var %d is missing required attributes", varId); } @@ -108,4 +110,35 @@ const Quest &get(int var) return it == quests.end() ? emptyQuest : it->second; } +// In quests, the map name may include the file extension. This is discouraged +// but supported for compatibility. +static std::string_view baseName(const std::string &fileName) +{ + auto pos = fileName.find_last_of('.'); + return pos == std::string::npos ? fileName : std::string_view(fileName.data(), pos); +} + +QuestEffectMap getActiveEffects(const QuestVars &questVars, + const std::string &mapName) +{ + QuestEffectMap activeEffects; + + for (auto &[var, quest] : quests) + { + auto value = questVars.get(var); + + for (auto &effect : quest.effects) + { + if (baseName(effect.map) != mapName) + continue; + if (std::find(effect.values.begin(), effect.values.end(), value) == effect.values.end()) + continue; + + activeEffects.set(effect.npcId, effect.statusEffectId); + } + } + + return activeEffects; +} + } // namespace QuestDB diff --git a/src/resources/questdb.h b/src/resources/questdb.h index 5c03a255..23817b81 100644 --- a/src/resources/questdb.h +++ b/src/resources/questdb.h @@ -22,17 +22,51 @@ #include "utils/xml.h" +#include <map> #include <string> #include <vector> +/** + * A map that returns a default value for non-existent keys. + */ +template<typename Key, typename Value, Value def = Value()> +class MapWithDefault +{ +public: + void set(Key key, Value value) + { + mVars[key] = value; + } + + Value get(Key key) const + { + auto it = mVars.find(key); + return it != mVars.end() ? it->second : def; + } + + void clear() + { + mVars.clear(); + } + +private: + std::map<Key, Value> mVars; +}; + 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 + int npcId = 0; + int statusEffectId = 0; }; +// Map of quest variables, from variable ID to value +using QuestVars = MapWithDefault<int, int>; + +// Map of quest effects, from NPC ID to status effect ID +using QuestEffectMap = MapWithDefault<int, int>; + enum class QuestRowType { Text, @@ -74,4 +108,7 @@ namespace QuestDB void unload(); const Quest &get(int var); + + QuestEffectMap getActiveEffects(const QuestVars &questVars, + const std::string &mapName); }; |