summaryrefslogtreecommitdiff
path: root/src/resources/sprite
diff options
context:
space:
mode:
Diffstat (limited to 'src/resources/sprite')
-rw-r--r--src/resources/sprite/spritedef.cpp591
-rw-r--r--src/resources/sprite/spritedef.h159
2 files changed, 750 insertions, 0 deletions
diff --git a/src/resources/sprite/spritedef.cpp b/src/resources/sprite/spritedef.cpp
new file mode 100644
index 000000000..cb0fc777c
--- /dev/null
+++ b/src/resources/sprite/spritedef.cpp
@@ -0,0 +1,591 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2015 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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 "resources/sprite/spritedef.h"
+
+#include "configuration.h"
+#include "logger.h"
+#include "settings.h"
+
+#include "const/resources/map/map.h"
+
+#include "resources/action.h"
+#include "resources/animation.h"
+#include "resources/dye.h"
+#include "resources/imageset.h"
+#include "resources/resourcemanager.h"
+#include "resources/spriteaction.h"
+#include "resources/spritereference.h"
+
+#include "debug.h"
+
+SpriteReference *SpriteReference::Empty = nullptr;
+
+const Action *SpriteDef::getAction(const std::string &action,
+ const unsigned num) const
+{
+ Actions::const_iterator i = mActions.find(num);
+ if (i == mActions.end() && num != 100)
+ i = mActions.find(100);
+
+ if (i == mActions.end() || !(*i).second)
+ return nullptr;
+
+ const ActionMap *const actMap = (*i).second;
+ if (!actMap)
+ return nullptr;
+ const ActionMap::const_iterator it = actMap->find(action);
+
+ if (it == actMap->end())
+ {
+ logger->log("Warning: no action \"%s\" defined!", action.c_str());
+ return nullptr;
+ }
+
+ return (*it).second;
+}
+
+unsigned SpriteDef::findNumber(const unsigned num) const
+{
+ unsigned min = 101;
+ FOR_EACH (Actions::const_iterator, it, mActions)
+ {
+ const unsigned n = (*it).first;
+ if (n >= num && n < min)
+ min = n;
+ }
+ if (min == 101)
+ return 0;
+ return min;
+}
+
+SpriteDef *SpriteDef::load(const std::string &animationFile,
+ const int variant, const bool prot)
+{
+ BLOCK_START("SpriteDef::load")
+ const size_t pos = animationFile.find('|');
+ std::string palettes;
+ if (pos != std::string::npos)
+ palettes = animationFile.substr(pos + 1);
+
+ XML::Document doc(animationFile.substr(0, pos),
+ UseResman_true,
+ SkipError_false);
+ XmlNodePtrConst rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlNameEqual(rootNode, "sprite"))
+ {
+ logger->log("Error, failed to parse %s", animationFile.c_str());
+
+ const std::string errorFile = paths.getStringValue("sprites").append(
+ paths.getStringValue("spriteErrorFile"));
+ BLOCK_END("SpriteDef::load")
+ if (animationFile != errorFile)
+ return load(errorFile, 0, prot);
+ else
+ return nullptr;
+ }
+
+ SpriteDef *const def = new SpriteDef;
+ def->mProcessedFiles.insert(animationFile);
+ def->loadSprite(rootNode, variant, palettes);
+ def->substituteActions();
+ if (settings.fixDeadAnimation)
+ def->fixDeadAction();
+ if (prot)
+ {
+ def->incRef();
+ def->setProtected(true);
+ }
+ BLOCK_END("SpriteDef::load")
+ return def;
+}
+
+void SpriteDef::fixDeadAction()
+{
+ FOR_EACH (ActionsIter, it, mActions)
+ {
+ ActionMap *const d = (*it).second;
+ if (!d)
+ continue;
+ const ActionMap::iterator i = d->find(SpriteAction::DEAD);
+ const ActionMap::iterator i2 = d->find(SpriteAction::STAND);
+ // search dead action and check what it not same with stand action
+ if (i != d->end() && i->second && i->second != i2->second)
+ (i->second)->setLastFrameDelay(0);
+ }
+}
+
+void SpriteDef::substituteAction(const std::string &restrict complete,
+ const std::string &restrict with)
+{
+ FOR_EACH (ActionsConstIter, it, mActions)
+ {
+ ActionMap *const d = (*it).second;
+ if (!d)
+ continue;
+ if (d->find(complete) == d->end())
+ {
+ const ActionMap::iterator i = d->find(with);
+ if (i != d->end())
+ (*d)[complete] = i->second;
+ }
+ }
+}
+
+void SpriteDef::substituteActions()
+{
+ substituteAction(SpriteAction::STAND, SpriteAction::DEFAULT);
+ substituteAction(SpriteAction::MOVE, SpriteAction::STAND);
+ substituteAction(SpriteAction::ATTACK, SpriteAction::STAND);
+ substituteAction(SpriteAction::CAST, SpriteAction::ATTACK);
+ substituteAction(SpriteAction::SIT, SpriteAction::STAND);
+ substituteAction(SpriteAction::SITTOP, SpriteAction::SIT);
+ substituteAction(SpriteAction::DEAD, SpriteAction::STAND);
+ substituteAction(SpriteAction::SPAWN, SpriteAction::STAND);
+ substituteAction(SpriteAction::FLY, SpriteAction::MOVE);
+ substituteAction(SpriteAction::SWIM, SpriteAction::MOVE);
+ substituteAction(SpriteAction::RIDE, SpriteAction::MOVE);
+ substituteAction(SpriteAction::STANDSKY, SpriteAction::STAND);
+ substituteAction(SpriteAction::STANDWATER, SpriteAction::STAND);
+ substituteAction(SpriteAction::STANDRIDE, SpriteAction::STAND);
+ substituteAction(SpriteAction::SITSKY, SpriteAction::SIT);
+ substituteAction(SpriteAction::SITWATER, SpriteAction::SIT);
+ substituteAction(SpriteAction::SITRIDE, SpriteAction::SIT);
+ substituteAction(SpriteAction::ATTACKSKY, SpriteAction::ATTACK);
+ substituteAction(SpriteAction::ATTACKWATER, SpriteAction::ATTACK);
+ substituteAction(SpriteAction::ATTACKRIDE, SpriteAction::ATTACK);
+ substituteAction(SpriteAction::CASTSKY, SpriteAction::CAST);
+ substituteAction(SpriteAction::CASTWATER, SpriteAction::CAST);
+ substituteAction(SpriteAction::CASTRIDE, SpriteAction::CAST);
+ substituteAction(SpriteAction::SPAWNSKY, SpriteAction::SPAWN);
+ substituteAction(SpriteAction::SPAWNWATER, SpriteAction::SPAWN);
+ substituteAction(SpriteAction::SPAWNRIDE, SpriteAction::SPAWN);
+ substituteAction(SpriteAction::DEADSKY, SpriteAction::DEAD);
+ substituteAction(SpriteAction::DEADWATER, SpriteAction::DEAD);
+ substituteAction(SpriteAction::DEADRIDE, SpriteAction::DEAD);
+}
+
+void SpriteDef::loadSprite(const XmlNodePtr spriteNode,
+ const int variant,
+ const std::string &palettes)
+{
+ BLOCK_START("SpriteDef::loadSprite")
+ if (!spriteNode)
+ {
+ BLOCK_END("SpriteDef::loadSprite")
+ return;
+ }
+ // Get the variant
+ const int variantCount = XML::getProperty(spriteNode, "variants", 0);
+ int variant_offset = 0;
+
+ if (variantCount > 0 && variant < variantCount)
+ {
+ variant_offset =
+ variant * XML::getProperty(spriteNode, "variant_offset", 0);
+ }
+
+ for_each_xml_child_node(node, spriteNode)
+ {
+ if (xmlNameEqual(node, "imageset"))
+ loadImageSet(node, palettes);
+ else if (xmlNameEqual(node, "action"))
+ loadAction(node, variant_offset);
+ else if (xmlNameEqual(node, "include"))
+ includeSprite(node, variant);
+ }
+ BLOCK_END("SpriteDef::loadSprite")
+}
+
+void SpriteDef::loadImageSet(const XmlNodePtr node,
+ const std::string &palettes)
+{
+ const std::string name = XML::getProperty(node, "name", "");
+
+ // We don't allow redefining image sets. This way, an included sprite
+ // definition will use the already loaded image set with the same name.
+ if (mImageSets.find(name) != mImageSets.end())
+ return;
+
+ const int width = XML::getProperty(node, "width", 0);
+ const int height = XML::getProperty(node, "height", 0);
+ std::string imageSrc = XML::getProperty(node, "src", "");
+ Dye::instantiate(imageSrc, palettes);
+
+ ImageSet *const imageSet = resourceManager->getImageSet(imageSrc,
+ width, height);
+
+ if (!imageSet)
+ {
+ logger->log1("Couldn't load imageset!");
+ return;
+ }
+
+ imageSet->setOffsetX(XML::getProperty(node, "offsetX", 0));
+ imageSet->setOffsetY(XML::getProperty(node, "offsetY", 0));
+ mImageSets[name] = imageSet;
+}
+
+void SpriteDef::loadAction(const XmlNodePtr node,
+ const int variant_offset)
+{
+ if (!node)
+ return;
+
+ const std::string actionName = XML::getProperty(node, "name", "");
+ const std::string imageSetName = XML::getProperty(node, "imageset", "");
+ const unsigned hp = XML::getProperty(node, "hp", 100);
+
+ const ImageSetIterator si = mImageSets.find(imageSetName);
+ if (si == mImageSets.end())
+ {
+ logger->log("Warning: imageset \"%s\" not defined in %s",
+ imageSetName.c_str(), getIdPath().c_str());
+ return;
+ }
+ const ImageSet *const imageSet = si->second;
+
+ if (actionName == SpriteAction::INVALID)
+ {
+ logger->log("Warning: Unknown action \"%s\" defined in %s",
+ actionName.c_str(), getIdPath().c_str());
+ return;
+ }
+ Action *const action = new Action;
+ action->setNumber(hp);
+ addAction(hp, actionName, action);
+
+ // dirty hack to fix bad resources in tmw server
+ if (actionName == "attack_stab")
+ addAction(hp, "attack", action);
+
+ // When first action set it as default direction
+ const Actions::const_iterator i = mActions.find(hp);
+ if ((*i).second->size() == 1)
+ addAction(hp, SpriteAction::DEFAULT, action);
+
+ // Load animations
+ for_each_xml_child_node(animationNode, node)
+ {
+ if (xmlNameEqual(animationNode, "animation"))
+ loadAnimation(animationNode, action, imageSet, variant_offset);
+ }
+}
+
+void SpriteDef::loadAnimation(const XmlNodePtr animationNode,
+ Action *const action,
+ const ImageSet *const imageSet,
+ const int variant_offset) const
+{
+ if (!action || !imageSet || !animationNode)
+ return;
+
+ const std::string directionName =
+ XML::getProperty(animationNode, "direction", "");
+ const SpriteDirection::Type directionType
+ = makeSpriteDirection(directionName);
+
+ if (directionType == SpriteDirection::INVALID)
+ {
+ logger->log("Warning: Unknown direction \"%s\" used in %s",
+ directionName.c_str(), getIdPath().c_str());
+ return;
+ }
+
+ Animation *const animation = new Animation;
+ action->setAnimation(directionType, animation);
+
+ // Get animation frames
+ for_each_xml_child_node(frameNode, animationNode)
+ {
+ const int delay = XML::getIntProperty(
+ frameNode, "delay", 0, 0, 100000);
+ const int offsetX = XML::getProperty(frameNode, "offsetX", 0)
+ + imageSet->getOffsetX() - imageSet->getWidth() / 2
+ + mapTileSize / 2;
+ const int offsetY = XML::getProperty(frameNode, "offsetY", 0)
+ + imageSet->getOffsetY() - imageSet->getHeight() + mapTileSize;
+ const int rand = XML::getIntProperty(frameNode, "rand", 100, 0, 100);
+
+ if (xmlNameEqual(frameNode, "frame"))
+ {
+ const int index = XML::getProperty(frameNode, "index", -1);
+
+ if (index < 0)
+ {
+ logger->log1("No valid value for 'index'");
+ continue;
+ }
+
+ Image *const img = imageSet->get(index + variant_offset);
+
+ if (!img)
+ {
+ logger->log("No image at index %d", index + variant_offset);
+ continue;
+ }
+
+ animation->addFrame(img, delay, offsetX, offsetY, rand);
+ }
+ else if (xmlNameEqual(frameNode, "sequence"))
+ {
+ const int start = XML::getProperty(frameNode, "start", -1);
+ const int end = XML::getProperty(frameNode, "end", -1);
+ const std::string value = XML::getProperty(frameNode, "value", "");
+ const int repeat = XML::getIntProperty(
+ frameNode, "repeat", 1, 0, 100);
+
+ if (repeat < 1)
+ {
+ logger->log1("No valid value for 'repeat'");
+ continue;
+ }
+
+ if (value.empty())
+ {
+ if (addSequence(start, end, delay, offsetX, offsetY,
+ variant_offset, repeat, rand, imageSet, animation))
+ {
+ continue;
+ }
+ }
+ else
+ {
+ StringVect vals;
+ splitToStringVector(vals, value, ',');
+ FOR_EACH (StringVectCIter, it, vals)
+ {
+ const std::string str = *it;
+ const size_t idx = str.find("-");
+ if (str == "p")
+ {
+ animation->addPause(delay, rand);
+ }
+ else if (idx != std::string::npos)
+ {
+ const int v1 = atoi(str.substr(0, idx).c_str());
+ const int v2 = atoi(str.substr(idx + 1).c_str());
+ addSequence(v1, v2, delay, offsetX, offsetY,
+ variant_offset, repeat, rand, imageSet, animation);
+ }
+ else
+ {
+ Image *const img = imageSet->get(atoi(
+ str.c_str()) + variant_offset);
+ if (img)
+ {
+ animation->addFrame(img, delay,
+ offsetX, offsetY, rand);
+ }
+ }
+ }
+ }
+ }
+ else if (xmlNameEqual(frameNode, "pause"))
+ {
+ animation->addPause(delay, rand);
+ }
+ else if (xmlNameEqual(frameNode, "end"))
+ {
+ animation->addTerminator(rand);
+ }
+ else if (xmlNameEqual(frameNode, "jump"))
+ {
+ animation->addJump(XML::getProperty(
+ frameNode, "action", ""), rand);
+ }
+ else if (xmlNameEqual(frameNode, "label"))
+ {
+ const std::string name = XML::getProperty(frameNode, "name", "");
+ if (!name.empty())
+ animation->addLabel(name);
+ }
+ else if (xmlNameEqual(frameNode, "goto"))
+ {
+ const std::string name = XML::getProperty(frameNode, "label", "");
+ if (!name.empty())
+ animation->addGoto(name, rand);
+ }
+ } // for frameNode
+}
+
+void SpriteDef::includeSprite(const XmlNodePtr includeNode, const int variant)
+{
+ std::string filename = XML::getProperty(includeNode, "file", "");
+
+ if (filename.empty())
+ return;
+ filename = paths.getStringValue("sprites").append(filename);
+
+ if (mProcessedFiles.find(filename) != mProcessedFiles.end())
+ {
+ logger->log("Error, Tried to include %s which already is included.",
+ filename.c_str());
+ return;
+ }
+ mProcessedFiles.insert(filename);
+
+ XML::Document doc(filename, UseResman_true, SkipError_false);
+ const XmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlNameEqual(rootNode, "sprite"))
+ {
+ logger->log("Error, no sprite root node in %s", filename.c_str());
+ return;
+ }
+
+ loadSprite(rootNode, variant);
+}
+
+SpriteDef::~SpriteDef()
+{
+ // Actions are shared, so ensure they are deleted only once.
+ std::set<Action*> actions;
+ FOR_EACH (Actions::iterator, i, mActions)
+ {
+ FOR_EACHP (ActionMap::iterator, it, (*i).second)
+ actions.insert(it->second);
+ delete (*i).second;
+ }
+
+ FOR_EACH (std::set<Action*>::const_iterator, i, actions)
+ delete *i;
+
+ mActions.clear();
+
+ FOR_EACH (ImageSetIterator, i, mImageSets)
+ {
+ if (i->second)
+ {
+ i->second->decRef();
+ i->second = nullptr;
+ }
+ }
+ mImageSets.clear();
+}
+
+SpriteDirection::Type SpriteDef::makeSpriteDirection(const std::string
+ &direction)
+{
+ if (direction.empty() || direction == "default")
+ return SpriteDirection::DEFAULT;
+ else if (direction == "up")
+ return SpriteDirection::UP;
+ else if (direction == "left")
+ return SpriteDirection::LEFT;
+ else if (direction == "right")
+ return SpriteDirection::RIGHT;
+ else if (direction == "down")
+ return SpriteDirection::DOWN;
+ else if (direction == "upleft")
+ return SpriteDirection::UPLEFT;
+ else if (direction == "upright")
+ return SpriteDirection::UPRIGHT;
+ else if (direction == "downleft")
+ return SpriteDirection::DOWNLEFT;
+ else if (direction == "downright")
+ return SpriteDirection::DOWNRIGHT;
+ else
+ return SpriteDirection::INVALID;
+}
+
+void SpriteDef::addAction(const unsigned hp, const std::string &name,
+ Action *const action)
+{
+ const Actions::const_iterator i = mActions.find(hp);
+ if (i == mActions.end())
+ mActions[hp] = new ActionMap();
+
+ (*mActions[hp])[name] = action;
+}
+
+bool SpriteDef::addSequence(const int start,
+ const int end,
+ const int delay,
+ const int offsetX,
+ const int offsetY,
+ const int variant_offset,
+ int repeat,
+ const int rand,
+ const ImageSet *const imageSet,
+ Animation *const animation)
+{
+ if (!imageSet || !animation)
+ return true;
+
+ if (start < 0 || end < 0)
+ {
+ logger->log1("No valid value for 'start' or 'end'");
+ return true;
+ }
+
+ if (start <= end)
+ {
+ while (repeat > 0)
+ {
+ int pos = start;
+ while (end >= pos)
+ {
+ Image *const img = imageSet->get(pos + variant_offset);
+
+ if (!img)
+ {
+ logger->log("No image at index %d",
+ pos + variant_offset);
+ pos ++;
+ continue;
+ }
+
+ animation->addFrame(img, delay,
+ offsetX, offsetY, rand);
+ pos ++;
+ }
+ repeat --;
+ }
+ }
+ else
+ {
+ while (repeat > 0)
+ {
+ int pos = start;
+ while (end <= pos)
+ {
+ Image *const img = imageSet->get(pos + variant_offset);
+
+ if (!img)
+ {
+ logger->log("No image at index %d",
+ pos + variant_offset);
+ pos ++;
+ continue;
+ }
+
+ animation->addFrame(img, delay,
+ offsetX, offsetY, rand);
+ pos --;
+ }
+ repeat --;
+ }
+ }
+ return false;
+}
diff --git a/src/resources/sprite/spritedef.h b/src/resources/sprite/spritedef.h
new file mode 100644
index 000000000..02ce98ff9
--- /dev/null
+++ b/src/resources/sprite/spritedef.h
@@ -0,0 +1,159 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2015 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus 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 RESOURCES_SPRITE_SPRITEDEF_H
+#define RESOURCES_SPRITE_SPRITEDEF_H
+
+#include "resources/resource.h"
+
+#include "resources/spritedirection.h"
+
+#include "utils/xml.h"
+
+#include <map>
+#include <set>
+
+class Action;
+class Animation;
+class ImageSet;
+
+/**
+ * Defines a class to load an animation.
+ */
+class SpriteDef final : public Resource
+{
+ public:
+ A_DELETE_COPY(SpriteDef)
+
+ /**
+ * Loads a sprite definition file.
+ */
+ static SpriteDef *load(const std::string &file,
+ const int variant,
+ const bool prot) A_WARN_UNUSED;
+
+ /**
+ * Returns the specified action.
+ */
+ const Action *getAction(const std::string &action,
+ const unsigned num) const A_WARN_UNUSED;
+
+ unsigned findNumber(const unsigned num) const A_WARN_UNUSED;
+
+ /**
+ * Converts a string into a SpriteDirection enum.
+ */
+ static SpriteDirection::Type
+ makeSpriteDirection(const std::string &direction) A_WARN_UNUSED;
+
+ void addAction(const unsigned hp, const std::string &name,
+ Action *const action);
+
+ static bool addSequence(const int start,
+ const int end,
+ const int delay,
+ const int offsetX,
+ const int offsetY,
+ const int variant_offset,
+ int repeat,
+ const int rand,
+ const ImageSet *const imageSet,
+ Animation *const animation);
+
+ private:
+ /**
+ * Constructor.
+ */
+ SpriteDef() :
+ Resource(),
+ mImageSets(),
+ mActions(),
+ mProcessedFiles()
+ { }
+
+ /**
+ * Destructor.
+ */
+ ~SpriteDef();
+
+ /**
+ * Loads a sprite element.
+ */
+ void loadSprite(const XmlNodePtr spriteNode,
+ const int variant,
+ const std::string &palettes = "");
+
+ /**
+ * Loads an imageset element.
+ */
+ void loadImageSet(const XmlNodePtr node,
+ const std::string &palettes);
+
+ /**
+ * Loads an action element.
+ */
+ void loadAction(const XmlNodePtr node,
+ const int variant_offset);
+
+ /**
+ * Loads an animation element.
+ */
+ void loadAnimation(const XmlNodePtr animationNode,
+ Action *const action,
+ const ImageSet *const imageSet,
+ const int variant_offset) const;
+
+ /**
+ * Include another sprite into this one.
+ */
+ void includeSprite(const XmlNodePtr includeNode, const int variant);
+
+ /**
+ * Complete missing actions by copying existing ones.
+ */
+ void substituteActions();
+
+ /**
+ * Fix bad timeout in last dead action frame
+ */
+ void fixDeadAction();
+
+ /**
+ * When there are no animations defined for the action "complete", its
+ * animations become a copy of those of the action "with".
+ */
+ void substituteAction(const std::string &restrict complete,
+ const std::string &restrict with);
+
+ typedef std::map<std::string, ImageSet*> ImageSets;
+ typedef ImageSets::iterator ImageSetIterator;
+ typedef std::map<std::string, Action*> ActionMap;
+ typedef std::map<unsigned, ActionMap*> Actions;
+ typedef Actions::const_iterator ActionsConstIter;
+ typedef Actions::iterator ActionsIter;
+
+ ImageSets mImageSets;
+ Actions mActions;
+ std::set<std::string> mProcessedFiles;
+};
+
+#endif // RESOURCES_SPRITE_SPRITEDEF_H