diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | src/being.cpp | 146 | ||||
-rw-r--r-- | src/being.h | 2 | ||||
-rw-r--r-- | src/compoundsprite.cpp | 11 | ||||
-rw-r--r-- | src/compoundsprite.h | 2 | ||||
-rw-r--r-- | src/resources/itemdb.cpp | 104 | ||||
-rw-r--r-- | src/resources/itemdb.h | 7 | ||||
-rw-r--r-- | src/resources/iteminfo.h | 28 | ||||
-rw-r--r-- | src/utils/stringutils.h | 5 |
9 files changed, 267 insertions, 39 deletions
@@ -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); |