diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-02-22 01:09:07 +0100 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-02-25 18:28:11 +0100 |
commit | 63e2712b2ad2c398160d399bdd9e454417c2654b (patch) | |
tree | 8190b702ee57e33051646b71431301e9be79d1fb /src | |
parent | cca8b9c59a83d865ed1c64cef4f5c098f0c78bf9 (diff) | |
download | mana-63e2712b2ad2c398160d399bdd9e454417c2654b.tar.gz mana-63e2712b2ad2c398160d399bdd9e454417c2654b.tar.bz2 mana-63e2712b2ad2c398160d399bdd9e454417c2654b.tar.xz mana-63e2712b2ad2c398160d399bdd9e454417c2654b.zip |
Added support for option, opt1, opt2, opt3 status effect fields
Supporting these fields is necessary to correctly identify status
effects sent by tmwAthena.
This is a manual port of ac4e40a1408ad4d6fbcfce9d2bc6a0bc187ea5a4,
542d2ba78d84e0fa051e0620ccab5fb3a0c711e3 and
8800940bb4b94f6dab7dcf80bf0abc3e3b09e35f from M+.
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/being.cpp | 68 | ||||
-rw-r--r-- | src/being.h | 37 | ||||
-rw-r--r-- | src/event.h | 1 | ||||
-rw-r--r-- | src/gui/ministatuswindow.cpp | 19 | ||||
-rw-r--r-- | src/localplayer.cpp | 16 | ||||
-rw-r--r-- | src/localplayer.h | 3 | ||||
-rw-r--r-- | src/net/tmwa/beinghandler.cpp | 80 | ||||
-rw-r--r-- | src/resources/settingsmanager.cpp | 22 | ||||
-rw-r--r-- | src/resources/statuseffectdb.cpp | 94 | ||||
-rw-r--r-- | src/resources/statuseffectdb.h | 67 | ||||
-rw-r--r-- | src/statuseffect.cpp | 156 | ||||
-rw-r--r-- | src/statuseffect.h | 98 |
13 files changed, 320 insertions, 343 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2319610c..36d1a9fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -358,6 +358,8 @@ set(SRCS resources/specialdb.h resources/spritedef.h resources/spritedef.cpp + resources/statuseffectdb.cpp + resources/statuseffectdb.h resources/theme.cpp resources/theme.h resources/userpalette.cpp diff --git a/src/being.cpp b/src/being.cpp index ae3f13de..4a74bfd8 100644 --- a/src/being.cpp +++ b/src/being.cpp @@ -54,6 +54,7 @@ #include "resources/iteminfo.h" #include "resources/monsterdb.h" #include "resources/npcdb.h" +#include "resources/statuseffectdb.h" #include "resources/theme.h" #include "resources/userpalette.h" @@ -78,6 +79,7 @@ Being::Being(int id, Type type, int subtype, Map *map) Being::~Being() { + mStatusParticleEffects.clearLocally(); delete mSpeechBubble; delete mDispName; delete mText; @@ -596,64 +598,33 @@ void Being::fireMissile(Being *victim, const std::string &particle) } } -void Being::setStatusEffect(int index, bool active) +void Being::setStatusEffect(int id, bool active) { - const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end(); + const auto it = mStatusEffects.find(id); + const bool wasActive = it != mStatusEffects.end(); if (active != wasActive) { - updateStatusEffect(index, active); if (active) - mStatusEffects.insert(index); + mStatusEffects.insert(id); else - mStatusEffects.erase(index); - } -} + mStatusEffects.erase(it); -void Being::setStatusEffectBlock(int offset, uint16_t newEffects) -{ - for (int i = 0; i < STATUS_EFFECTS; i++) - { - int index = StatusEffect::blockEffectIndexToEffectIndex(offset + i); - - if (index != -1) - setStatusEffect(index, (newEffects & (1 << i)) > 0); + updateStatusEffect(id, active); } } -void Being::updateStunMode(int oldMode, int newMode) -{ - handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1); - handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1); -} - -void Being::updateStatusEffect(int index, bool newStatus) -{ - handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index); -} - -void Being::handleStatusEffect(StatusEffect *effect, int effectId) +void Being::updateStatusEffect(int id, bool newStatus) { + auto effect = StatusEffectDB::getStatusEffect(id); if (!effect) return; - // TODO: Find out how this is meant to be used - // (SpriteAction != Being::Action) - //SpriteAction action = effect->getAction(); - //if (action != ACTION_INVALID) - // setAction(action); + Particle *particle = effect->getParticle(newStatus); - Particle *particle = effect->getParticle(); - - if (effectId >= 0) + if (id >= 0) { - mStatusParticleEffects.setLocally(effectId, particle); - } - else - { - mStunParticleEffects.clearLocally(); - if (particle) - mStunParticleEffects.addLocally(particle); + mStatusParticleEffects.setLocally(id, particle); } } @@ -854,8 +825,8 @@ void Being::logic() // Restart status/particle effects, if needed for (int statusEffect : mStatusEffects) { - const StatusEffect *effect = StatusEffect::getStatusEffect(statusEffect, true); - if (effect && effect->particleEffectIsPersistent()) + const StatusEffect *effect = StatusEffectDB::getStatusEffect(statusEffect); + if (effect && effect->persistentParticleEffect) updateStatusEffect(statusEffect, true); } } @@ -863,6 +834,14 @@ void Being::logic() if (mAction != DEAD && !mSpeedPixelsPerSecond.isNull()) { updateMovement(); + + // See note at ActorSprite::draw + float py = mPos.y; + if (mMap) + py += mMap->getTileHeight() / 2; + + // Update particle effects + mStatusParticleEffects.moveTo(mPos.x, py); } ActorSprite::logic(); @@ -1431,7 +1410,6 @@ void Being::setMap(Map *map) for (auto &spriteState : mSpriteStates) spriteState.particles.clear(); - mStunParticleEffects.clearLocally(); mStatusParticleEffects.clearLocally(); mRestoreParticlesOnLogic = true; diff --git a/src/being.h b/src/being.h index aaf4c459..609953ce 100644 --- a/src/being.h +++ b/src/being.h @@ -380,26 +380,7 @@ class Being : public ActorSprite, public EventListener */ void fireMissile(Being *target, const std::string &particle); - /** - * Sets the being's stun mode. If zero, the being is `normal', - * otherwise it is `stunned' in some fashion. - */ - void setStunMode(int stunMode) - { - if (mStunMode != stunMode) - updateStunMode(mStunMode, stunMode); - mStunMode = stunMode; - } - - void setStatusEffect(int index, bool active); - - /** - * A status effect block is a 16 bit mask of status effects. We assign - * each such flag a block ID of offset + bitnr. - * - * These are NOT the same as the status effect indices. - */ - void setStatusEffectBlock(int offset, uint16_t flags); + void setStatusEffect(int id, bool active); /** * Returns the path this being is following. An empty path is returned @@ -497,12 +478,6 @@ class Being : public ActorSprite, public EventListener virtual void pathFinished() {} /** - * Notify self that the stun mode has been updated. Invoked by - * setStunMode if something changed. - */ - virtual void updateStunMode(int oldMode, int newMode); - - /** * Notify self that a status effect has flipped. * The new flag is passed. */ @@ -554,21 +529,11 @@ class Being : public ActorSprite, public EventListener bool mIsGM = false; private: - /** - * Handle an update to a status or stun effect - * - * \param effect The StatusEffect to effect - * \param effectId -1 for stun, otherwise the effect index - */ - void handleStatusEffect(StatusEffect *effect, int effectId); - void updateMovement(); Type mType = UNKNOWN; - uint16_t mStunMode = 0; /**< Stun mode; zero if not stunned */ std::set<int> mStatusEffects; /**< set of active status effects */ - ParticleList mStunParticleEffects; ParticleVector mStatusParticleEffects; /** Speech Bubble components */ diff --git a/src/event.h b/src/event.h index a148cef1..612d77cb 100644 --- a/src/event.h +++ b/src/event.h @@ -97,7 +97,6 @@ public: StateChange, StorageCount, StringInput, - Stun, UpdateAttribute, UpdateStat, UpdateStatusEffect, diff --git a/src/gui/ministatuswindow.cpp b/src/gui/ministatuswindow.cpp index 08656f78..1218fbf3 100644 --- a/src/gui/ministatuswindow.cpp +++ b/src/gui/ministatuswindow.cpp @@ -39,6 +39,7 @@ #include "net/tmwa/protocol.h" +#include "resources/statuseffectdb.h" #include "resources/theme.h" #include "utils/gettext.h" @@ -154,21 +155,21 @@ void MiniStatusWindow::event(Event::Channel channel, const Event &event) { if (event.getType() == Event::UpdateStatusEffect) { - int index = event.getInt("index"); - bool newStatus = event.getBool("newStatus"); + const int id = event.getInt("index"); + const bool newStatus = event.getBool("newStatus"); - if (auto effect = StatusEffect::getStatusEffect(index, newStatus)) + if (auto effect = StatusEffectDB::getStatusEffect(id)) { - effect->deliverMessage(); - effect->playSFX(); + effect->deliverMessage(newStatus); + effect->playSfx(newStatus); - Sprite *sprite = effect->getIcon(); + Sprite *sprite = newStatus ? effect->getIconSprite() : nullptr; if (!sprite) { // delete sprite, if necessary for (unsigned int i = 0; i < mStatusEffectIcons.size();) - if (mStatusEffectIcons[i] == index) + if (mStatusEffectIcons[i] == id) { mStatusEffectIcons.erase(mStatusEffectIcons.begin() + i); @@ -184,7 +185,7 @@ void MiniStatusWindow::event(Event::Channel channel, const Event &event) for (unsigned int i = 0; i < mStatusEffectIcons.size(); i++) - if (mStatusEffectIcons[i] == index) + if (mStatusEffectIcons[i] == id) { setIcon(i, sprite); found = true; @@ -195,7 +196,7 @@ void MiniStatusWindow::event(Event::Channel channel, const Event &event) { // add new int offset = mStatusEffectIcons.size(); setIcon(offset, sprite); - mStatusEffectIcons.push_back(index); + mStatusEffectIcons.push_back(id); } } } diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 0813d7fc..d7375c55 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -1018,24 +1018,14 @@ void LocalPlayer::event(Event::Channel channel, const Event &event) Being::event(channel, event); } -void LocalPlayer::updateStunMode(int oldMode, int newMode) -{ - Event event(Event::Stun); - event.setInt("oldMode", oldMode); - event.setInt("newMode", newMode); - event.trigger(Event::ActorSpriteChannel); - - Being::updateStunMode(oldMode, newMode); -} - -void LocalPlayer::updateStatusEffect(int index, bool newStatus) +void LocalPlayer::updateStatusEffect(int id, bool newStatus) { Event event(Event::UpdateStatusEffect); - event.setInt("index", index); + event.setInt("index", id); event.setBool("newStatus", newStatus); event.trigger(Event::ActorSpriteChannel); - Being::updateStatusEffect(index, newStatus); + Being::updateStatusEffect(id, newStatus); } void LocalPlayer::changeAwayMode() diff --git a/src/localplayer.h b/src/localplayer.h index 7d358f1c..ba4011c1 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -184,8 +184,7 @@ class LocalPlayer final : public Being void event(Event::Channel channel, const Event &event) override; protected: - void updateStunMode(int oldMode, int newMode) override; - void updateStatusEffect(int index, bool newStatus) override; + void updateStatusEffect(int id, bool newStatus) override; /** Make the character starts to walk. */ void startWalking(unsigned char dir); diff --git a/src/net/tmwa/beinghandler.cpp b/src/net/tmwa/beinghandler.cpp index ba983542..78685dfd 100644 --- a/src/net/tmwa/beinghandler.cpp +++ b/src/net/tmwa/beinghandler.cpp @@ -40,6 +40,7 @@ #include "resources/emotedb.h" #include "resources/hairdb.h" +#include "resources/statuseffectdb.h" #include <cmath> @@ -168,6 +169,42 @@ static void handlePosMessage(Map *map, Being *dstBeing, Uint16 x, Uint16 y, } } +static void applyStatusEffectsByOption1(Being *being, + const StatusEffectDB::OptionsMap &map, + uint16_t option) +{ + for (auto &[opt, id] : map) + being->setStatusEffect(id, option == opt); +} + +static void applyStatusEffectsByOption(Being *being, + const StatusEffectDB::OptionsMap &map, + uint16_t option) +{ + for (auto &[opt, id] : map) + { + const bool enabled = (option & opt) != 0; + being->setStatusEffect(id, enabled); + } +} + +/** + * Maps flags or indexes to their corresponding status effect index and + * updates the state of the given being. This is tmwAthena-specific. + */ +static void applyStatusEffects(Being *being, + uint16_t opt0, + uint16_t opt1, + uint16_t opt2, + std::optional<uint16_t> opt3 = {}) +{ + applyStatusEffectsByOption(being, StatusEffectDB::opt0ToIdMap(), opt0); + applyStatusEffectsByOption1(being, StatusEffectDB::opt1ToIdMap(), opt1); + applyStatusEffectsByOption(being, StatusEffectDB::opt2ToIdMap(), opt2); + if (opt3) + applyStatusEffectsByOption(being, StatusEffectDB::opt3ToIdMap(), *opt3); +} + void BeingHandler::handleMessage(MessageIn &msg) { if (!actorSpriteManager) @@ -181,8 +218,10 @@ void BeingHandler::handleMessage(MessageIn &msg) Uint16 weapon, shield; Uint16 gmstatus; int param1; - int stunMode; - Uint32 statusEffects; + uint16_t opt0; + uint16_t opt1; + uint16_t opt2; + uint16_t opt3; int type, guild; Uint16 status; Being *srcBeing, *dstBeing; @@ -198,9 +237,9 @@ void BeingHandler::handleMessage(MessageIn &msg) // Information about a being in range id = msg.readInt32(); speed = (float)msg.readInt16(); - stunMode = msg.readInt16(); // opt1 - statusEffects = msg.readInt16(); // opt2 - statusEffects |= ((Uint32)msg.readInt16()) << 16; // option + opt1 = msg.readInt16(); + opt2 = msg.readInt16(); + opt0 = msg.readInt16(); job = msg.readInt16(); // class dstBeing = actorSpriteManager->findBeing(id); @@ -258,7 +297,7 @@ void BeingHandler::handleMessage(MessageIn &msg) } msg.readInt16(); // guild emblem msg.readInt16(); // manner - dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 + opt3 = msg.readInt16(); msg.readInt8(); // karma gender = msg.readInt8(); @@ -296,9 +335,7 @@ void BeingHandler::handleMessage(MessageIn &msg) msg.readInt8(); // unknown msg.readInt8(); // unknown / sit - dstBeing->setStunMode(stunMode); - dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); - dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + applyStatusEffects(dstBeing, opt0, opt1, opt2, opt3); break; case SMSG_BEING_SPAWN: @@ -530,10 +567,9 @@ void BeingHandler::handleMessage(MessageIn &msg) // An update about a player, potentially including movement. id = msg.readInt32(); speed = msg.readInt16(); - stunMode = msg.readInt16(); // opt1; Aethyra use this as cape - statusEffects = msg.readInt16(); // opt2; Aethyra use this as misc1 - statusEffects |= ((Uint32) msg.readInt16()) - << 16; // status.options; Aethyra uses this as misc2 + opt1 = msg.readInt16(); + opt2 = msg.readInt16(); + opt0 = msg.readInt16(); job = msg.readInt16(); dstBeing = actorSpriteManager->findBeing(id); @@ -576,7 +612,7 @@ void BeingHandler::handleMessage(MessageIn &msg) msg.readInt32(); // guild msg.readInt16(); // emblem msg.readInt16(); // manner - dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 + opt3 = msg.readInt16(); msg.readInt8(); // karma dstBeing->setGender(msg.readInt8() == 0 ? Gender::FEMALE : Gender::MALE); @@ -633,9 +669,7 @@ void BeingHandler::handleMessage(MessageIn &msg) msg.readInt8(); // Lv msg.readInt8(); // unused - dstBeing->setStunMode(stunMode); - dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); - dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + applyStatusEffects(dstBeing, opt0, opt1, opt2, opt3); break; case SMSG_PLAYER_STOP: @@ -680,14 +714,12 @@ void BeingHandler::handleMessage(MessageIn &msg) if (!dstBeing) break; - stunMode = msg.readInt16(); - statusEffects = msg.readInt16(); - statusEffects |= ((Uint32) msg.readInt16()) << 16; - msg.readInt8(); // Unused? + opt1 = msg.readInt16(); + opt2 = msg.readInt16(); + opt0 = msg.readInt16(); + msg.readInt8(); // zero - dstBeing->setStunMode(stunMode); - dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff); - dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); + applyStatusEffects(dstBeing, opt0, opt1, opt2); break; case SMSG_BEING_STATUS_CHANGE: diff --git a/src/resources/settingsmanager.cpp b/src/resources/settingsmanager.cpp index 8966f976..5314b4c8 100644 --- a/src/resources/settingsmanager.cpp +++ b/src/resources/settingsmanager.cpp @@ -20,22 +20,24 @@ #include "resources/settingsmanager.h" -#include "configuration.h" #include "resources/attributes.h" +#include "resources/emotedb.h" #include "resources/hairdb.h" #include "resources/itemdb.h" #include "resources/monsterdb.h" -#include "resources/specialdb.h" #include "resources/npcdb.h" -#include "resources/emotedb.h" -#include "statuseffect.h" -#include "units.h" +#include "resources/specialdb.h" +#include "resources/statuseffectdb.h" #include "net/net.h" #include "utils/xml.h" #include "utils/path.h" + +#include "configuration.h" #include "log.h" +#include "statuseffect.h" +#include "units.h" namespace SettingsManager { @@ -55,7 +57,7 @@ namespace SettingsManager SpecialDB::init(); NPCDB::init(); EmoteDB::init(); - StatusEffect::init(); + StatusEffectDB::init(); Units::init(); // load stuff from settings @@ -79,7 +81,7 @@ namespace SettingsManager SpecialDB::checkStatus(); NPCDB::checkStatus(); EmoteDB::checkStatus(); - StatusEffect::checkStatus(); + StatusEffectDB::checkStatus(); Units::checkStatus(); if (Net::getNetworkType() == ServerType::MANASERV) @@ -90,7 +92,7 @@ namespace SettingsManager void unload() { - StatusEffect::unload(); + StatusEffectDB::unload(); EmoteDB::unload(); NPCDB::unload(); SpecialDB::unload(); @@ -225,9 +227,9 @@ namespace SettingsManager { EmoteDB::readEmoteNode(childNode, filename); } - else if (childNode.name() == "status-effect" || childNode.name() == "stun-effect") + else if (childNode.name() == "status-effect") { - StatusEffect::readStatusEffectNode(childNode, filename); + StatusEffectDB::readStatusEffectNode(childNode, filename); } else if (childNode.name() == "unit") { diff --git a/src/resources/statuseffectdb.cpp b/src/resources/statuseffectdb.cpp new file mode 100644 index 00000000..1bfff5e1 --- /dev/null +++ b/src/resources/statuseffectdb.cpp @@ -0,0 +1,94 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-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 "statuseffectdb.h" + +bool StatusEffectDB::mLoaded = false; +std::map<int, StatusEffect> StatusEffectDB::mStatusEffects; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt0ToIdMap; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt1ToIdMap; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt2ToIdMap; +StatusEffectDB::OptionsMap StatusEffectDB::mOpt3ToIdMap; + + +const StatusEffect *StatusEffectDB::getStatusEffect(int id) +{ + auto it = mStatusEffects.find(id); + if (it == mStatusEffects.end()) + return nullptr; + return &it->second; +} + +void StatusEffectDB::init() +{ + if (mLoaded) + unload(); +} + +void StatusEffectDB::readStatusEffectNode(XML::Node node, const std::string &/* filename */) +{ + const int id = node.getProperty("id", -1); + + const int opt0 = node.getProperty("option", 0); + const int opt1 = node.getProperty("opt1", 0); + const int opt2 = node.getProperty("opt2", 0); + const int opt3 = node.getProperty("opt3", 0); + if (opt0 != 0 && opt0 <= UINT16_MAX) + mOpt0ToIdMap[opt0] = id; + if (opt1 != 0 && opt1 <= UINT16_MAX) + mOpt1ToIdMap[opt1] = id; + if (opt2 != 0 && opt2 <= UINT16_MAX) + mOpt2ToIdMap[opt2] = id; + if (opt3 != 0 && opt3 <= UINT16_MAX) + mOpt3ToIdMap[opt3] = id; + + auto &effect = mStatusEffects[id]; + + node.attribute("start-message", effect.start.message); + node.attribute("start-audio", effect.start.sfx); + node.attribute("start-particle", effect.start.particleEffect); + + // For now we don't support separate particle effect for "already applied" + // status effects. + if (effect.start.particleEffect.empty()) + node.attribute("particle", effect.start.particleEffect); + + node.attribute("end-message", effect.end.message); + node.attribute("end-audio", effect.end.sfx); + node.attribute("end-particle", effect.end.particleEffect); + + node.attribute("icon", effect.icon); + node.attribute("persistent-particle-effect", effect.persistentParticleEffect); +} + +void StatusEffectDB::checkStatus() +{ + mLoaded = true; +} + +void StatusEffectDB::unload() +{ + if (!mLoaded) + return; + + mStatusEffects.clear(); + mLoaded = false; +} diff --git a/src/resources/statuseffectdb.h b/src/resources/statuseffectdb.h new file mode 100644 index 00000000..d1f1a6bf --- /dev/null +++ b/src/resources/statuseffectdb.h @@ -0,0 +1,67 @@ +/* + * The Mana Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-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/>. + */ + +#ifndef STATUSEFFECTDB_H +#define STATUSEFFECTDB_H + +#include "statuseffect.h" +#include "utils/xml.h" + +#include <cstdint> +#include <map> + +class StatusEffectDB +{ +public: + /** + * Retrieves a status effect. + * + * \param id ID of the status effect. + */ + static const StatusEffect *getStatusEffect(int id); + + using OptionsMap = std::map<uint16_t, int>; + + /** + * These map flags or indexes to their corresponding status effect ID. + * This is tmwAthena-specific. + */ + static const OptionsMap &opt0ToIdMap() { return mOpt0ToIdMap; } + static const OptionsMap &opt1ToIdMap() { return mOpt1ToIdMap; } + static const OptionsMap &opt2ToIdMap() { return mOpt2ToIdMap; } + static const OptionsMap &opt3ToIdMap() { return mOpt3ToIdMap; } + + static void init(); + static void readStatusEffectNode(XML::Node node, const std::string &filename); + static void checkStatus(); + static void unload(); + +private: + static bool mLoaded; + + static std::map<int, StatusEffect> mStatusEffects; + static OptionsMap mOpt0ToIdMap; + static OptionsMap mOpt1ToIdMap; + static OptionsMap mOpt2ToIdMap; + static OptionsMap mOpt3ToIdMap; +}; + +#endif // STATUSEFFECTDB_H diff --git a/src/statuseffect.cpp b/src/statuseffect.cpp index 9c13d037..7cb035bd 100644 --- a/src/statuseffect.cpp +++ b/src/statuseffect.cpp @@ -1,7 +1,7 @@ /* * The Mana Client * Copyright (C) 2008-2009 The Mana World Development Team - * Copyright (C) 2009-2013 The Mana Developers + * Copyright (C) 2009-2025 The Mana Developers * * This file is part of The Mana Client. * @@ -22,144 +22,50 @@ #include "statuseffect.h" #include "event.h" +#include "particle.h" #include "sound.h" #include "configuration.h" -#include <map> - -#define STATUS_EFFECTS_FILE "status-effects.xml" - -bool StatusEffect::mLoaded = false; - -StatusEffect::StatusEffect() = default; -StatusEffect::~StatusEffect() = default; - -void StatusEffect::playSFX() +/** + * Plays the sound effect associated with this status effect, if possible. + */ +void StatusEffect::playSfx(bool enabled) const { - if (!mSFXEffect.empty()) - sound.playSfx(mSFXEffect); + auto &sfx = enabled ? start.sfx : end.sfx; + if (!sfx.empty()) + sound.playSfx(sfx); } -void StatusEffect::deliverMessage() +/** + * Delivers the chat message associated with this status effect, if + * possible. + */ +void StatusEffect::deliverMessage(bool enabled) const { - if (!mMessage.empty()) - serverNotice(mMessage); + auto &message = enabled ? start.message : end.message; + if (!message.empty()) + serverNotice(message); } -Particle *StatusEffect::getParticle() const +/** + * Creates the particle effect associated with this status effect, if + * possible. + */ +Particle *StatusEffect::getParticle(bool enabled) const { - if (mParticleEffect.empty()) + auto &particleEffect = enabled ? start.particleEffect : end.particleEffect; + if (particleEffect.empty()) return nullptr; - return particleEngine->addEffect(mParticleEffect, 0, 0); + return particleEngine->addEffect(particleEffect, 0, 0); } -Sprite *StatusEffect::getIcon() const +/** + * Retrieves the status icon for this effect, if applicable. + */ +Sprite *StatusEffect::getIconSprite() const { - if (mIcon.empty()) + if (icon.empty()) return nullptr; - return Sprite::load(paths.getStringValue("sprites") + mIcon); -} - -std::string StatusEffect::getAction() const -{ - if (mAction.empty()) - return SpriteAction::INVALID; - return mAction; -} - - -// -- initialisation and static parts -- - - -typedef std::map<int, StatusEffect *> status_effect_map[2]; - -static status_effect_map statusEffects; -static status_effect_map stunEffects; -static std::map<int, int> blockEffectIndexMap; - -int StatusEffect::blockEffectIndexToEffectIndex(int blockIndex) -{ - if (blockEffectIndexMap.find(blockIndex) == blockEffectIndexMap.end()) - return -1; - return blockEffectIndexMap[blockIndex]; -} - -StatusEffect *StatusEffect::getStatusEffect(int index, bool enabling) -{ - return statusEffects[enabling][index]; -} - -StatusEffect *StatusEffect::getStunEffect(int index, bool enabling) -{ - return stunEffects[enabling][index]; -} - -void StatusEffect::init() -{ - if (mLoaded) - unload(); -} - -void StatusEffect::readStatusEffectNode(XML::Node node, const std::string &filename) -{ - status_effect_map *the_map = nullptr; - int index = atoi(node.getProperty("id", "-1").c_str()); - if (node.name() == "status-effect") - { - the_map = &statusEffects; - int block_index = atoi(node.getProperty("block-id", "-1").c_str()); - - if (index >= 0 && block_index >= 0) - blockEffectIndexMap[block_index] = index; - } - else if (node.name() == "stun-effect") - the_map = &stunEffects; - - if (the_map) - { - auto *startEffect = new StatusEffect; - auto *endEffect = new StatusEffect; - - startEffect->mMessage = node.getProperty("start-message", ""); - startEffect->mSFXEffect = node.getProperty("start-audio", ""); - startEffect->mParticleEffect = node.getProperty("start-particle", ""); - startEffect->mIcon = node.getProperty("icon", ""); - startEffect->mAction = node.getProperty("action", ""); - startEffect->mPersistentParticleEffect = (node.getProperty("persistent-particle-effect", "no")) != "no"; - - endEffect->mMessage = node.getProperty("end-message", ""); - endEffect->mSFXEffect = node.getProperty("end-audio", ""); - endEffect->mParticleEffect = node.getProperty("end-particle", ""); - - (*the_map)[1][index] = startEffect; - (*the_map)[0][index] = endEffect; - } - -} - -void StatusEffect::checkStatus() -{ - mLoaded = true; -} - -void unloadMap(std::map<int, StatusEffect *> map) -{ - for (auto &[_, effect] : map) - delete effect; - - map.clear(); -} - -void StatusEffect::unload() -{ - if (!mLoaded) - return; - - unloadMap(statusEffects[0]); - unloadMap(statusEffects[1]); - unloadMap(stunEffects[0]); - unloadMap(stunEffects[1]); - - mLoaded = false; + return Sprite::load(paths.getStringValue("sprites") + icon); } diff --git a/src/statuseffect.h b/src/statuseffect.h index 62961db9..fce52603 100644 --- a/src/statuseffect.h +++ b/src/statuseffect.h @@ -1,7 +1,7 @@ /* * The Mana Client * Copyright (C) 2008-2009 The Mana World Development Team - * Copyright (C) 2009-2013 The Mana Developers + * Copyright (C) 2009-2025 The Mana Developers * * This file is part of The Mana Client. * @@ -21,93 +21,35 @@ #pragma once -#include "particle.h" -#include "sprite.h" +#include <string> -#include "utils/xml.h" +class Particle; +class Sprite; class StatusEffect { public: - StatusEffect(); - ~StatusEffect(); + struct Event + { + std::string message; + std::string sfx; + std::string particleEffect; + }; - /** - * Plays the sound effect associated with this status effect, if possible. - */ - void playSFX(); - - /** - * Delivers the chat message associated with this status effect, if - * possible. - */ - void deliverMessage(); - - /** - * Creates the particle effect associated with this status effect, if - * possible. - */ - Particle *getParticle() const; - - /** - * Retrieves the status icon for this effect, if applicable - */ - Sprite *getIcon() const; - - /** - * Retrieves an action to perform, or SpriteAction::INVALID - */ - std::string getAction() const; + Event start; + Event end; + std::string icon; /** * Determines whether the particle effect should be restarted when the - * being changes maps + * being changes maps. */ - bool particleEffectIsPersistent() const { return mPersistentParticleEffect; } - - - /** - * Retrieves a status effect. - * - * \param index Index of the status effect. - * \param enabling Whether to retrieve the activating effect (true) or - * the deactivating effect (false). - */ - static StatusEffect *getStatusEffect(int index, bool enabling); - - /** - * Retrieves a stun effect. - * - * \param index Index of the stun effect. - * \param enabling Whether to retrieve the activating effect (true) or - * the deactivating effect (false). - */ - static StatusEffect *getStunEffect(int index, bool enabling); - - /** - * Maps a block effect index to its corresponding effect index. Block - * effect indices are used for opt2/opt3/status.option blocks; their - * mapping to regular effect indices is handled in the config file. - * - * Returns -1 on failure. - */ - static int blockEffectIndexToEffectIndex(int blocKIndex); - - static void init(); - - static void readStatusEffectNode(XML::Node node, const std::string &filename); - - static void checkStatus(); - - static void unload(); + bool persistentParticleEffect = false; -private: - static bool mLoaded; + StatusEffect() = default; - std::string mMessage; - std::string mSFXEffect; - std::string mParticleEffect; - std::string mIcon; - std::string mAction; - bool mPersistentParticleEffect = false; + void playSfx(bool enabled) const; + void deliverMessage(bool enabled) const; + Particle *getParticle(bool enabled) const; + Sprite *getIconSprite() const; }; |