summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--src/being.cpp146
-rw-r--r--src/being.h2
-rw-r--r--src/compoundsprite.cpp11
-rw-r--r--src/compoundsprite.h2
-rw-r--r--src/resources/itemdb.cpp104
-rw-r--r--src/resources/itemdb.h7
-rw-r--r--src/resources/iteminfo.h28
-rw-r--r--src/utils/stringutils.h5
9 files changed, 267 insertions, 39 deletions
diff --git a/NEWS b/NEWS
index 802e57cd..ac74291c 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,7 @@
- Added support for reading most client-data settings from settings.xml
- Added support for XML includes, both absolute and relative
- Added support for map/layer mask
+- Added support for sprite replacements
- Added support for particle effects on equipment
- Added support for hit/miss sounds on equipment for all players
- Added online player list to Social window
diff --git a/src/being.cpp b/src/being.cpp
index 56ed65f7..aec9deb0 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -762,6 +762,7 @@ void Being::setDirection(Uint8 direction)
dir = DIRECTION_LEFT;
mSpriteDirection = dir;
+ updateSprites();
CompoundSprite::setDirection(dir);
}
@@ -1099,6 +1100,110 @@ void Being::updateColors()
}
}
+/**
+ * Updates the visible sprite IDs of the being, taking into account the item
+ * replacements.
+ */
+void Being::updateSprites()
+{
+ // hack for allow different logic in dead player
+ const int direction = mAction == DEAD ? DIRECTION_DEAD : mSpriteDirection;
+
+ // Get the current item IDs
+ std::vector<int> itemIDs(mSpriteStates.size());
+ for (size_t i = 0; i < mSpriteStates.size(); i++)
+ itemIDs[i] = mSpriteStates[i].id;
+
+ // Apply the replacements
+ for (auto &spriteState : mSpriteStates)
+ {
+ if (!spriteState.id)
+ continue;
+
+ auto &itemInfo = itemDb->get(spriteState.id);
+ for (const auto &replacement : itemInfo.replacements)
+ {
+ if (replacement.direction != DIRECTION_ALL && replacement.direction != direction)
+ continue;
+
+ if (replacement.sprite == SPRITE_ALL)
+ {
+ if (replacement.items.empty())
+ {
+ itemIDs.assign(itemIDs.size(), 0);
+ }
+ else
+ {
+ for (int &id : itemIDs)
+ {
+ for (auto &item : replacement.items)
+ if (!item.from || id == item.from)
+ id = item.to;
+ }
+ }
+ }
+ else if (replacement.sprite < itemIDs.size())
+ {
+ int &id = itemIDs[replacement.sprite];
+
+ if (replacement.items.empty())
+ {
+ id = 0;
+ }
+ else
+ {
+ for (auto &item : replacement.items)
+ if (!item.from || id == item.from)
+ id = item.to;
+ }
+ }
+ }
+ }
+
+ // Set the new sprites
+ bool newSpriteSet = false;
+
+ for (size_t i = 0; i < mSpriteStates.size(); i++)
+ {
+ auto &spriteState = mSpriteStates[i];
+ if (spriteState.visibleId == itemIDs[i])
+ continue;
+
+ spriteState.visibleId = itemIDs[i];
+
+ if (spriteState.visibleId == 0)
+ {
+ CompoundSprite::setSprite(i, nullptr);
+ }
+ else
+ {
+ newSpriteSet = true;
+
+ auto &itemInfo = itemDb->get(spriteState.visibleId);
+ std::string filename = itemInfo.getSprite(mGender, mSubType);
+ AnimatedSprite *equipmentSprite = nullptr;
+
+ if (!filename.empty())
+ {
+ if (!spriteState.color.empty())
+ filename += "|" + spriteState.color;
+
+ equipmentSprite = AnimatedSprite::load(
+ paths.getStringValue("sprites") + filename);
+
+ if (equipmentSprite)
+ equipmentSprite->setDirection(getSpriteDirection());
+ }
+
+ CompoundSprite::setSprite(i, equipmentSprite);
+ }
+ }
+
+ // Make sure any new sprites are set to the correct action
+ if (newSpriteSet)
+ setAction(mAction);
+}
+
void Being::setSprite(unsigned slot, int id, const std::string &color,
bool isWeapon)
{
@@ -1114,43 +1219,32 @@ void Being::setSprite(unsigned slot, int id, const std::string &color,
if (spriteState.id != id)
removeSpriteParticles(spriteState);
+ // Clear the current sprite when the color changes
+ if (spriteState.color != color)
+ {
+ spriteState.visibleId = 0;
+ CompoundSprite::setSprite(slot, nullptr);
+ }
+
spriteState.id = id;
spriteState.color = color;
if (id == 0) // id = 0 means unequip
{
- removeSprite(slot);
-
if (isWeapon)
mEquippedWeapon = nullptr;
}
else
{
auto &itemInfo = itemDb->get(id);
- std::string filename = itemInfo.getSprite(mGender, mSubType);
- AnimatedSprite *equipmentSprite = nullptr;
-
- if (!filename.empty())
- {
- if (!color.empty())
- filename += "|" + color;
-
- equipmentSprite = AnimatedSprite::load(
- paths.getStringValue("sprites") + filename);
- }
-
- if (equipmentSprite)
- equipmentSprite->setDirection(getSpriteDirection());
-
- CompoundSprite::setSprite(slot, equipmentSprite);
addSpriteParticles(spriteState, itemInfo.display);
if (isWeapon)
mEquippedWeapon = &itemInfo;
-
- setAction(mAction);
}
+
+ updateSprites();
}
void Being::setSpriteID(unsigned slot, int id)
@@ -1183,14 +1277,18 @@ void Being::setGender(Gender gender)
{
mGender = gender;
- // Reload all subsprites
+ // Reset all sprites to force reload with the correct gender
for (size_t i = 0; i < mSpriteStates.size(); i++)
{
- auto &sprite = mSpriteStates[i];
- if (sprite.id != 0)
- setSprite(i, sprite.id, sprite.color);
+ auto &spriteState = mSpriteStates[i];
+ if (spriteState.visibleId)
+ {
+ CompoundSprite::setSprite(i, nullptr);
+ spriteState.visibleId = 0;
+ }
}
+ updateSprites();
updateName();
}
}
diff --git a/src/being.h b/src/being.h
index abe056c0..0f32e60f 100644
--- a/src/being.h
+++ b/src/being.h
@@ -442,6 +442,7 @@ class Being : public ActorSprite, public EventListener
protected:
struct SpriteState {
int id = 0;
+ int visibleId = 0;
std::string color;
std::vector<Particle*> particles;
};
@@ -464,6 +465,7 @@ class Being : public ActorSprite, public EventListener
void restoreAllSpriteParticles();
void updateColors();
+ void updateSprites();
/**
* Gets the advised Y chat text position.
diff --git a/src/compoundsprite.cpp b/src/compoundsprite.cpp
index 2e39fb47..24a166a8 100644
--- a/src/compoundsprite.cpp
+++ b/src/compoundsprite.cpp
@@ -158,17 +158,6 @@ void CompoundSprite::setSprite(int layer, Sprite *sprite)
mNeedsRedraw = true;
}
-void CompoundSprite::removeSprite(int layer)
-{
- // Skip if it won't change anything
- if (!mSprites.at(layer))
- return;
-
- delete mSprites.at(layer);
- mSprites.at(layer) = nullptr;
- mNeedsRedraw = true;
-}
-
void CompoundSprite::clear()
{
// Skip if it won't change anything
diff --git a/src/compoundsprite.h b/src/compoundsprite.h
index bb5dddc8..17e2dd6c 100644
--- a/src/compoundsprite.h
+++ b/src/compoundsprite.h
@@ -77,8 +77,6 @@ public:
Sprite *getSprite(int layer) const
{ return mSprites.at(layer); }
- void removeSprite(int layer);
-
void clear();
void ensureSize(size_t layerCount);
diff --git a/src/resources/itemdb.cpp b/src/resources/itemdb.cpp
index 68ddcd75..a6e65c71 100644
--- a/src/resources/itemdb.cpp
+++ b/src/resources/itemdb.cpp
@@ -21,6 +21,7 @@
#include "resources/itemdb.h"
+#include "configuration.h"
#include "log.h"
#include "resources/hairdb.h"
@@ -29,10 +30,11 @@
#include "utils/dtor.h"
#include "utils/gettext.h"
#include "utils/stringutils.h"
-#include "configuration.h"
+#include "net/tmwa/protocol.h"
#include <cassert>
+#include <string_view>
void setStatsList(std::list<ItemStat> stats)
{
@@ -60,6 +62,62 @@ static ItemType itemTypeFromString(const std::string &name, int id = 0)
return ITEM_UNUSABLE;
}
+static uint8_t spriteFromString(std::string_view name)
+{
+ if (name.empty())
+ return SPRITE_ALL;
+ if (name == "race" || name == "type")
+ return TmwAthena::SPRITE_BASE;
+ if (name == "shoes" || name == "boot" || name == "boots")
+ return TmwAthena::SPRITE_SHOE;
+ if (name == "bottomclothes" || name == "bottom" || name == "pants")
+ return TmwAthena::SPRITE_BOTTOMCLOTHES;
+ if (name == "topclothes" || name == "top" || name == "torso" || name == "body")
+ return TmwAthena::SPRITE_TOPCLOTHES;
+ if (name == "misc1")
+ return TmwAthena::SPRITE_MISC1;
+ if (name == "misc2" || name == "scarf" || name == "scarfs")
+ return TmwAthena::SPRITE_MISC2;
+ if (name == "hair")
+ return TmwAthena::SPRITE_HAIR;
+ if (name == "hat" || name == "hats")
+ return TmwAthena::SPRITE_HAT;
+ if (name == "wings")
+ return TmwAthena::SPRITE_CAPE;
+ if (name == "glove" || name == "gloves")
+ return TmwAthena::SPRITE_GLOVES;
+ if (name == "weapon" || name == "weapons")
+ return TmwAthena::SPRITE_WEAPON;
+ if (name == "shield" || name == "shields")
+ return TmwAthena::SPRITE_SHIELD;
+ if (name == "amulet" || name == "amulets")
+ return 12;
+ if (name == "ring" || name == "rings")
+ return 13;
+
+ return SPRITE_UNKNOWN;
+}
+
+static uint8_t directionFromString(std::string_view name)
+{
+ if (name.empty())
+ return DIRECTION_ALL;
+ if (name == "down" || name == "downall")
+ return DIRECTION_DOWN;
+ if (name == "left")
+ return DIRECTION_LEFT;
+ if (name == "up" || name == "upall")
+ return DIRECTION_UP;
+ if (name == "right")
+ return DIRECTION_RIGHT;
+
+ // hack for died action.
+ if (name == "died")
+ return DIRECTION_DEAD;
+
+ return DIRECTION_UNKNOWN;
+}
+
void ItemDB::loadEmptyItemDefinition()
{
mUnknown->name = _("Unknown item");
@@ -166,6 +224,46 @@ void ItemDB::loadFloorSprite(SpriteDisplay &display, XML::Node floorNode)
}
}
+void ItemDB::loadReplacement(ItemInfo &info, XML::Node replaceNode)
+{
+ std::string_view spriteString;
+ std::string_view directionString;
+
+ replaceNode.attribute("sprite", spriteString);
+ replaceNode.attribute("direction", directionString);
+
+ const uint8_t sprite = spriteFromString(spriteString);
+ const uint8_t direction = directionFromString(directionString);
+
+ if (sprite == SPRITE_UNKNOWN)
+ {
+ logger->log("ItemDB: Invalid sprite name '%s' in replace tag",
+ spriteString.data());
+ return;
+ }
+
+ if (direction == DIRECTION_UNKNOWN)
+ {
+ logger->log("ItemDB: Invalid direction name '%s' in replace tag",
+ directionString.data());
+ return;
+ }
+
+ Replacement &replace = info.replacements.emplace_back();
+ replace.sprite = sprite;
+ replace.direction = direction;
+
+ for (auto child : replaceNode.children())
+ {
+ if (child.name() == "item")
+ {
+ Replacement::Item &item = replace.items.emplace_back();
+ child.attribute("from", item.from);
+ child.attribute("to", item.to);
+ }
+ }
+}
+
void ItemDB::unload()
{
logger->log("Unloading item database...");
@@ -228,6 +326,10 @@ void ItemDB::loadCommonRef(ItemInfo &itemInfo, XML::Node node, const std::string
{
loadFloorSprite(itemInfo.display, itemChild);
}
+ else if (itemChild.name() == "replace")
+ {
+ loadReplacement(itemInfo, itemChild);
+ }
}
}
diff --git a/src/resources/itemdb.h b/src/resources/itemdb.h
index 8359452c..69620122 100644
--- a/src/resources/itemdb.h
+++ b/src/resources/itemdb.h
@@ -133,13 +133,18 @@ class ItemDB
/**
* Loads the sound references contained in a <sound> tag.
*/
- void loadSoundRef(ItemInfo &itemInfo, XML::Node node);
+ void loadSoundRef(ItemInfo &itemInfo, XML::Node node);
/**
* Loads the floor item references contained in a <floor> tag.
*/
void loadFloorSprite(SpriteDisplay &display, XML::Node node);
+ /**
+ * Loads the <replace> tag.
+ */
+ void loadReplacement(ItemInfo &info, XML::Node replaceNode);
+
// Items database
std::map<int, ItemInfo *> mItemInfos;
std::map<std::string, ItemInfo *> mNamedItemInfos;
diff --git a/src/resources/iteminfo.h b/src/resources/iteminfo.h
index 42c65c80..4f9ab10a 100644
--- a/src/resources/iteminfo.h
+++ b/src/resources/iteminfo.h
@@ -67,6 +67,32 @@ namespace ManaServ {
class ManaServItemDB;
}
+enum ReplacementDirection : uint8_t
+{
+ DIRECTION_ALL = DIRECTION_DEFAULT,
+ DIRECTION_DEAD = DIRECTION_INVALID,
+ DIRECTION_UNKNOWN,
+};
+
+enum ReplacementSprite : uint8_t
+{
+ SPRITE_UNKNOWN = 254,
+ SPRITE_ALL = 255,
+};
+
+struct Replacement
+{
+ struct Item
+ {
+ int from = 0; // ID to replace (0: any)
+ int to = 0; // Replace with this ID (0: remove)
+ };
+
+ uint8_t sprite = SPRITE_ALL; // sprite slot to replace
+ uint8_t direction = DIRECTION_ALL; // direction in which to replace
+ std::vector<Item> items; // specific items to replace (empty: remove)
+};
+
/**
* Defines a class for storing generic item infos.
*/
@@ -109,6 +135,8 @@ public:
ItemType type = ITEM_UNUSABLE; /**< Item type. */
+ std::vector<Replacement> replacements;
+
const std::string &getSprite(Gender gender, int race) const;
const std::string &getSound(EquipmentSoundEvent event) const;
diff --git a/src/utils/stringutils.h b/src/utils/stringutils.h
index 8781a9c4..ca54803d 100644
--- a/src/utils/stringutils.h
+++ b/src/utils/stringutils.h
@@ -149,6 +149,11 @@ inline void fromString(const char *str, std::string &value)
value = str;
}
+inline void fromString(const char *str, std::string_view &value)
+{
+ value = str;
+}
+
inline void fromString(const char *str, int &value)
{
value = atoi(str);