summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-02-22 01:09:07 +0100
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-02-25 18:28:11 +0100
commit63e2712b2ad2c398160d399bdd9e454417c2654b (patch)
tree8190b702ee57e33051646b71431301e9be79d1fb /src
parentcca8b9c59a83d865ed1c64cef4f5c098f0c78bf9 (diff)
downloadmana-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.txt2
-rw-r--r--src/being.cpp68
-rw-r--r--src/being.h37
-rw-r--r--src/event.h1
-rw-r--r--src/gui/ministatuswindow.cpp19
-rw-r--r--src/localplayer.cpp16
-rw-r--r--src/localplayer.h3
-rw-r--r--src/net/tmwa/beinghandler.cpp80
-rw-r--r--src/resources/settingsmanager.cpp22
-rw-r--r--src/resources/statuseffectdb.cpp94
-rw-r--r--src/resources/statuseffectdb.h67
-rw-r--r--src/statuseffect.cpp156
-rw-r--r--src/statuseffect.h98
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;
};