From 264be2108c51837fa92085f6c839e66aebbcfc9e Mon Sep 17 00:00:00 2001 From: Thorbjørn Lindeijer Date: Sun, 28 Jan 2024 20:56:41 +0100 Subject: Added support for map/layer mask A custom "Mask" property on a layer or a "foregroundXmask" property on a map can now be used in combination with the SMSG_MAP_MASK to dynamically disable certain map layers from the server. Feature previously seen on ManaPlus and implemented for Mana client for compatibility. Also added a ResourceRef class for automating the Resource reference counting. Closes #44 --- src/map.cpp | 72 ++++++++++++++++++++++-------------------- src/map.h | 7 ++++ src/net/tmwa/network.cpp | 9 ++++-- src/net/tmwa/network.h | 1 + src/net/tmwa/playerhandler.cpp | 10 ++++++ src/net/tmwa/protocol.h | 2 ++ src/properties.h | 21 ++++++++++++ src/resources/ambientlayer.cpp | 16 +++------- src/resources/ambientlayer.h | 27 +++++++--------- src/resources/mapreader.cpp | 20 ++++++++++++ src/resources/resource.h | 68 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 188 insertions(+), 65 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index 76ddc56a..1ba62fcd 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -255,61 +255,46 @@ void Map::initializeAmbientLayers() { ResourceManager *resman = ResourceManager::getInstance(); + auto addAmbientLayer = [=](const std::string &name, std::list &list) + { + if (Image *img = resman->getImage(getProperty(name + "image"))) + { + auto ambientLayer = new AmbientLayer(img); + ambientLayer->mParallax = getFloatProperty(name + "parallax"); + ambientLayer->mSpeedX = getFloatProperty(name + "scrollX"); + ambientLayer->mSpeedY = getFloatProperty(name + "scrollY"); + ambientLayer->mMask = getIntProperty(name + "mask", 1); + + list.push_back(ambientLayer); + + // The AmbientLayer takes control over the image. + img->decRef(); + } + }; + // search for "foreground*" or "overlay*" (old term) in map properties for (int i = 0; /* terminated by a break */; i++) { - std::string name; if (hasProperty("foreground" + toString(i) + "image")) { - name = "foreground" + toString(i); + addAmbientLayer("foreground" + toString(i), mForegrounds); } else if (hasProperty("overlay" + toString(i) + "image")) { - name = "overlay" + toString(i); + addAmbientLayer("overlay" + toString(i), mForegrounds); } else { break; // the FOR loop } - - Image *img = resman->getImage(getProperty(name + "image")); - const float speedX = getFloatProperty(name + "scrollX"); - const float speedY = getFloatProperty(name + "scrollY"); - const float parallax = getFloatProperty(name + "parallax"); - const bool keepRatio = getBoolProperty(name + "keepratio"); - - if (img) - { - mForegrounds.push_back( - new AmbientLayer(img, parallax, speedX, speedY, keepRatio)); - - // The AmbientLayer takes control over the image. - img->decRef(); - } } - // search for "background*" in map properties for (int i = 0; hasProperty("background" + toString(i) + "image"); i++) { - const std::string name = "background" + toString(i); - - Image *img = resman->getImage(getProperty(name + "image")); - const float speedX = getFloatProperty(name + "scrollX"); - const float speedY = getFloatProperty(name + "scrollY"); - const float parallax = getFloatProperty(name + "parallax"); - const bool keepRatio = getBoolProperty(name + "keepratio"); - - if (img) - { - mBackgrounds.push_back( - new AmbientLayer(img, parallax, speedX, speedY, keepRatio)); - - // The AmbientLayer takes control over the image. - img->decRef(); - } + addAmbientLayer("background" + toString(i), mBackgrounds); } } @@ -387,6 +372,9 @@ void Map::draw(Graphics *graphics, int scrollX, int scrollY) { for (; layeri != mLayers.end() && !overFringe; ++layeri) { + if (((*layeri)->getMask() & mMask) == 0) + continue; + if ((*layeri)->isFringeLayer() && (mDebugFlags & DEBUG_SPECIAL2)) overFringe = true; @@ -503,10 +491,16 @@ void Map::updateAmbientLayers(float scrollX, float scrollY) std::list::iterator i; for (i = mBackgrounds.begin(); i != mBackgrounds.end(); i++) { + if (((*i)->mMask & mMask) == 0) + continue; + (*i)->update(timePassed, dx, dy); } for (i = mForegrounds.begin(); i != mForegrounds.end(); i++) { + if (((*i)->mMask & mMask) == 0) + continue; + (*i)->update(timePassed, dx, dy); } mLastScrollX = scrollX; @@ -541,6 +535,9 @@ void Map::drawAmbientLayers(Graphics *graphics, LayerType type, for (auto i = layers->begin(); i != layers->end(); i++) { + if (((*i)->mMask & mMask) == 0) + continue; + (*i)->draw(graphics, graphics->getWidth(), graphics->getHeight()); // Detail 1: only one overlay, higher: all overlays @@ -1041,3 +1038,8 @@ TileAnimation *Map::getAnimationForGid(int gid) const auto i = mTileAnimations.find(gid); return (i == mTileAnimations.end()) ? NULL : i->second; } + +void Map::setMask(int mask) +{ + mMask = mask; +} diff --git a/src/map.h b/src/map.h index 2900b4d1..410ae16e 100644 --- a/src/map.h +++ b/src/map.h @@ -131,9 +131,13 @@ class MapLayer int getTileDrawWidth(int x1, int y1, int endX, int &width) const; + void setMask(int mask) { mMask = mask; } + int getMask() const { return mMask; } + private: int mX, mY; int mWidth, mHeight; + int mMask = 1; bool mIsFringeLayer; /**< Whether the actors are drawn. */ Image **mTiles; Map *mMap; /** The mother map pointer */ @@ -335,6 +339,8 @@ class Map : public Properties */ TileAnimation *getAnimationForGid(int gid) const; + void setMask(int mask); + protected: friend class Actor; @@ -415,6 +421,7 @@ class Map : public Properties std::map mTileAnimations; + int mMask = 1; }; #endif diff --git a/src/net/tmwa/network.cpp b/src/net/tmwa/network.cpp index c1ba615f..d0a5dc62 100644 --- a/src/net/tmwa/network.cpp +++ b/src/net/tmwa/network.cpp @@ -40,7 +40,7 @@ // indicator for a variable-length packet const uint16_t VAR = 1; -uint16_t packet_lengths[0x220] = { +uint16_t packet_lengths[] = { 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -82,9 +82,12 @@ uint16_t packet_lengths[0x220] = { VAR,VAR, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56,VAR, 4, 5, 10, // #0x0200 26, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 19, 10, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2,VAR, 16, 0, 8,VAR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + VAR,122,VAR,VAR,VAR,VAR, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +static const int packet_lengths_size + = static_cast(sizeof(packet_lengths) / sizeof(uint16_t)); const unsigned int BUFFER_SIZE = 65536; namespace TmwAthena { @@ -287,7 +290,7 @@ bool Network::messageReady() // TODO don't hard-code this single case if (msgId == SMSG_SERVER_VERSION_RESPONSE) len = 10; - else if (msgId < 0x220) + else if (msgId < packet_lengths_size) len = packet_lengths[msgId]; if (len == VAR) diff --git a/src/net/tmwa/network.h b/src/net/tmwa/network.h index 97c7970f..4df44b75 100644 --- a/src/net/tmwa/network.h +++ b/src/net/tmwa/network.h @@ -41,6 +41,7 @@ * the protocol accordingly. */ #define CLIENT_PROTOCOL_VERSION 1 +// 10 -> 11: SMSG_MAP_MASK DONE namespace TmwAthena { diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp index 6ef669e4..5c57b8aa 100644 --- a/src/net/tmwa/playerhandler.cpp +++ b/src/net/tmwa/playerhandler.cpp @@ -152,6 +152,7 @@ PlayerHandler::PlayerHandler() SMSG_PLAYER_STAT_UPDATE_5, SMSG_PLAYER_STAT_UPDATE_6, SMSG_PLAYER_ARROW_MESSAGE, + SMSG_MAP_MASK, 0 }; handledMessages = _messages; @@ -512,6 +513,15 @@ void PlayerHandler::handleMessage(MessageIn &msg) } } break; + + case SMSG_MAP_MASK: + { + const int mask = msg.readInt32(); + msg.readInt32(); // unused + if (Map *map = Game::instance()->getCurrentMap()) + map->setMask(mask); + } + break; } } diff --git a/src/net/tmwa/protocol.h b/src/net/tmwa/protocol.h index 768d6061..e9ea84e8 100644 --- a/src/net/tmwa/protocol.h +++ b/src/net/tmwa/protocol.h @@ -213,6 +213,8 @@ static const int STORAGE_OFFSET = 1; #define SMSG_MVP 0x010c +#define SMSG_MAP_MASK 0x0226 + /********************************** * Packets from client to server * **********************************/ diff --git a/src/properties.h b/src/properties.h index 40be8d7f..84b6d184 100644 --- a/src/properties.h +++ b/src/properties.h @@ -70,6 +70,27 @@ class Properties return ret; } + /** + * Gets a map property as an int. + * + * @param name The name of the property. + * @param def Default value, 0 by default. + * @return the value of the given property or the given default when it + * doesn't exist. + */ + float getIntProperty(const std::string &name, int def = 0) const + { + auto i = mProperties.find(name); + int ret = def; + if (i != mProperties.end()) + { + std::stringstream ss; + ss.str(i->second); + ss >> ret; + } + return ret; + } + /** * Gets a map property as a boolean. * diff --git a/src/resources/ambientlayer.cpp b/src/resources/ambientlayer.cpp index 301927f8..b71378cb 100644 --- a/src/resources/ambientlayer.cpp +++ b/src/resources/ambientlayer.cpp @@ -25,20 +25,12 @@ #include "resources/image.h" #include "resources/resourcemanager.h" -AmbientLayer::AmbientLayer(Image *img, float parallax, - float speedX, float speedY, bool keepRatio): - mImage(img), mParallax(parallax), - mPosX(0), mPosY(0), - mSpeedX(speedX), mSpeedY(speedY), - mKeepRatio(keepRatio) -{ - mImage->incRef(); -} +AmbientLayer::AmbientLayer(Image *img) + : mImage(img) +{} AmbientLayer::~AmbientLayer() -{ - mImage->decRef(); -} +{} void AmbientLayer::update(int timePassed, float dx, float dy) { diff --git a/src/resources/ambientlayer.h b/src/resources/ambientlayer.h index 9ccf5194..def2090b 100644 --- a/src/resources/ambientlayer.h +++ b/src/resources/ambientlayer.h @@ -21,6 +21,8 @@ #ifndef RESOURCES_AMBIENTOVERLAY_H #define RESOURCES_AMBIENTOVERLAY_H +#include "resource.h" + class Graphics; class Image; @@ -31,29 +33,24 @@ class AmbientLayer * Constructor. * * @param img the image this overlay displays - * @param parallax scroll factor based on camera position - * @param speedX scrolling speed in x-direction - * @param speedY scrolling speed in y-direction - * @param keepRatio rescale the image to keep - * the same ratio than in 800x600 resolution mode. */ - AmbientLayer(Image *img, float parallax, - float speedX, float speedY, bool keepRatio = false); - + AmbientLayer(Image *img); ~AmbientLayer(); void update(int timePassed, float dx, float dy); void draw(Graphics *graphics, int x, int y); + float mParallax = 0; /**< Scroll factor based on camera position. */ + float mSpeedX = 0; /**< Scrolling speed in X direction. */ + float mSpeedY = 0; /**< Scrolling speed in Y direction. */ + int mMask = 1; + bool mKeepRatio = false; /**< Keep overlay ratio on every resolution like in 800x600 */ + private: - Image *mImage; - float mParallax; - float mPosX; /**< Current layer X position. */ - float mPosY; /**< Current layer Y position. */ - float mSpeedX; /**< Scrolling speed in X direction. */ - float mSpeedY; /**< Scrolling speed in Y direction. */ - bool mKeepRatio; /**< Keep overlay ratio on every resolution */ + ResourceRef mImage; + float mPosX = 0; /**< Current layer X position. */ + float mPosY = 0; /**< Current layer Y position. */ }; #endif diff --git a/src/resources/mapreader.cpp b/src/resources/mapreader.cpp index af41da12..aba9f85d 100644 --- a/src/resources/mapreader.cpp +++ b/src/resources/mapreader.cpp @@ -276,6 +276,26 @@ void MapReader::readLayer(xmlNodePtr node, Map *map) // Load the tile data for_each_xml_child_node(childNode, node) { + if (xmlStrEqual(childNode->name, BAD_CAST "properties")) + { + for_each_xml_child_node(prop, childNode) + { + if (!xmlStrEqual(prop->name, BAD_CAST "property")) + continue; + + const std::string pname = XML::getProperty(prop, "name", ""); + const std::string value = XML::getProperty(prop, "value", ""); + + // TODO: Consider supporting "Hidden", "Version" and "NotVersion" + + if (pname == "Mask") + { + layer->setMask(atoi(value.c_str())); + } + } + continue; + } + if (!xmlStrEqual(childNode->name, BAD_CAST "data")) continue; diff --git a/src/resources/resource.h b/src/resources/resource.h index af688eb0..988b78a5 100644 --- a/src/resources/resource.h +++ b/src/resources/resource.h @@ -69,4 +69,72 @@ class Resource unsigned mRefCount; /**< Reference count. */ }; +/** + * Automatically counting Resource reference. + */ +template +class ResourceRef +{ +public: + // Allow implicit construction from RESOURCE * + ResourceRef(RESOURCE *resource = nullptr) + : mResource(resource) + { + if (mResource) + mResource->incRef(); + } + + // Copy constructor + ResourceRef(const ResourceRef &other) + : mResource(other.mResource) + { + if (mResource) + mResource->incRef(); + } + + // Move constructor + ResourceRef(ResourceRef &&other) + : mResource(other.mResource) + { + other.mResource = nullptr; + } + + // Destructor + ~ResourceRef() + { + if (mResource) + mResource->decRef(); + } + + // Assignment operator + ResourceRef &operator=(const ResourceRef &other) + { + if (this != &other) + { + if (mResource) + mResource->decRef(); + + mResource = other.mResource; + + if (mResource) + mResource->incRef(); + } + return *this; + } + + // Allow dereferencing + RESOURCE *operator->() const + { return mResource; } + + RESOURCE *get() const + { return mResource; } + + // Allow implicit conversion to RESOURCE * + operator RESOURCE *() const + { return mResource; } + +private: + RESOURCE *mResource; +}; + #endif -- cgit v1.2.3-70-g09d2