/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2012 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 .
*/
#include "resources/spritedef.h"
#include "log.h"
#include "sprite.h"
#include "resources/action.h"
#include "resources/animation.h"
#include "resources/dye.h"
#include "resources/image.h"
#include "resources/resourcemanager.h"
#include "configuration.h"
#include "utils/xml.h"
#include
std::set processedFiles;
Action *SpriteDef::getAction(const std::string &action) const
{
auto i = mActions.find(action);
if (i == mActions.end())
{
logger->log("Warning: no action \"%s\" defined!", action.c_str());
return nullptr;
}
return i->second;
}
SpriteDef *SpriteDef::load(const std::string &animationFile, int variant)
{
std::string::size_type pos = animationFile.find('|');
std::string palettes;
if (pos != std::string::npos)
palettes = animationFile.substr(pos + 1);
processedFiles.clear();
processedFiles.insert(animationFile);
XML::Document doc(animationFile.substr(0, pos));
XML::Node rootNode = doc.rootNode();
if (!rootNode || rootNode.name() != "sprite")
{
logger->log("Error, failed to parse %s", animationFile.c_str());
std::string errorFile = paths.getStringValue("sprites")
+ paths.getStringValue("spriteErrorFile");
if (animationFile != errorFile)
{
return load(errorFile, 0);
}
return nullptr;
}
auto *def = new SpriteDef;
def->loadSprite(rootNode, variant, palettes);
def->substituteActions();
return def;
}
void SpriteDef::substituteAction(std::string complete, std::string with)
{
if (mActions.find(complete) == mActions.end())
{
auto i = mActions.find(with);
if (i != mActions.end())
{
mActions[complete] = i->second;
}
}
}
void SpriteDef::substituteActions()
{
substituteAction(SpriteAction::STAND, SpriteAction::DEFAULT);
substituteAction(SpriteAction::MOVE, SpriteAction::STAND);
substituteAction(SpriteAction::ATTACK, SpriteAction::STAND);
substituteAction(SpriteAction::CAST_MAGIC, SpriteAction::ATTACK);
substituteAction(SpriteAction::USE_ITEM, SpriteAction::CAST_MAGIC);
substituteAction(SpriteAction::SIT, SpriteAction::STAND);
substituteAction(SpriteAction::SLEEP, SpriteAction::SIT);
substituteAction(SpriteAction::HURT, SpriteAction::STAND);
substituteAction(SpriteAction::DEAD, SpriteAction::HURT);
}
void SpriteDef::loadSprite(XML::Node spriteNode, int variant,
const std::string &palettes)
{
// Get the variant
const int variantCount = spriteNode.getProperty("variants", 0);
int variant_offset = 0;
if (variantCount > 0 && variant < variantCount)
{
variant_offset =
variant * spriteNode.getProperty("variant_offset", 0);
}
for (auto node : spriteNode.children())
{
if (node.name() == "imageset")
{
loadImageSet(node, palettes);
}
else if (node.name() == "action")
{
loadAction(node, variant_offset);
}
else if (node.name() == "include")
{
includeSprite(node);
}
}
}
void SpriteDef::loadImageSet(XML::Node node, const std::string &palettes)
{
const std::string name = node.getProperty("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 = node.getProperty("width", 0);
const int height = node.getProperty("height", 0);
std::string imageSrc = node.getProperty("src", "");
Dye::instantiate(imageSrc, palettes);
ResourceManager *resman = ResourceManager::getInstance();
auto imageSet = resman->getImageSet(imageSrc, width, height);
if (!imageSet)
{
logger->error(strprintf("Couldn't load imageset (%s)!",
imageSrc.c_str()));
}
imageSet->setOffsetX(node.getProperty("offsetX", 0));
imageSet->setOffsetY(node.getProperty("offsetY", 0));
mImageSets[name] = imageSet;
}
void SpriteDef::loadAction(XML::Node node, int variant_offset)
{
const std::string actionName = node.getProperty("name", "");
const std::string imageSetName = node.getProperty("imageset", "");
auto si = mImageSets.find(imageSetName);
if (si == mImageSets.end())
{
logger->log("Warning: imageset \"%s\" not defined in %s",
imageSetName.c_str(), getIdPath().c_str());
return;
}
ImageSet *imageSet = si->second;
if (actionName == SpriteAction::INVALID)
{
logger->log("Warning: Unknown action \"%s\" defined in %s",
actionName.c_str(), getIdPath().c_str());
return;
}
auto *action = new Action;
mActions[actionName] = action;
// When first action set it as default direction
if (mActions.size() == 1)
{
mActions[SpriteAction::DEFAULT] = action;
}
// Load animations
for (auto animationNode : node.children())
{
if (animationNode.name() == "animation")
{
loadAnimation(animationNode, action, imageSet, variant_offset);
}
}
}
void SpriteDef::loadAnimation(XML::Node animationNode,
Action *action, ImageSet *imageSet,
int variant_offset)
{
const std::string directionName =
animationNode.getProperty("direction", "");
const SpriteDirection directionType = makeSpriteDirection(directionName);
if (directionType == DIRECTION_INVALID)
{
logger->log("Warning: Unknown direction \"%s\" used in %s",
directionName.c_str(), getIdPath().c_str());
return;
}
auto *animation = new Animation;
action->setAnimation(directionType, animation);
// Get animation frames
for (auto frameNode : animationNode.children())
{
const int delay = frameNode.getProperty("delay",
DEFAULT_FRAME_DELAY);
int offsetX = frameNode.getProperty("offsetX", 0) +
imageSet->getOffsetX();
int offsetY = frameNode.getProperty("offsetY", 0) +
imageSet->getOffsetY();
if (frameNode.name() == "frame")
{
const int index = frameNode.getProperty("index", -1);
if (index < 0)
{
logger->log("No valid value for 'index'");
continue;
}
Image *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);
}
else if (frameNode.name() == "sequence")
{
int start = frameNode.getProperty("start", -1);
const int end = frameNode.getProperty("end", -1);
if (start < 0 || end < 0)
{
logger->log("No valid value for 'start' or 'end'");
continue;
}
while (end >= start)
{
Image *img = imageSet->get(start + variant_offset);
if (!img)
{
logger->log("No image at index %d", start + variant_offset);
break;
}
animation->addFrame(img, delay, offsetX, offsetY);
start++;
}
}
else if (frameNode.name() == "end")
{
animation->addTerminator();
}
} // for frameNode
}
void SpriteDef::includeSprite(XML::Node includeNode)
{
std::string filename = includeNode.getProperty("file", "");
if (filename.empty())
return;
filename = paths.getStringValue("sprites") + filename;
if (processedFiles.find(filename) != processedFiles.end())
{
logger->log("Error, Tried to include %s which already is included.",
filename.c_str());
return;
}
processedFiles.insert(filename);
XML::Document doc(filename);
XML::Node rootNode = doc.rootNode();
if (!rootNode || rootNode.name() != "sprite")
{
logger->log("Error, no sprite root node in %s", filename.c_str());
return;
}
loadSprite(rootNode, 0);
}
SpriteDef::~SpriteDef()
{
// Actions are shared, so ensure they are deleted only once.
std::set actions;
for (const auto &action : mActions)
{
actions.insert(action.second);
}
for (auto action : actions)
{
delete action;
}
}
SpriteDirection SpriteDef::makeSpriteDirection(const std::string &direction)
{
if (direction.empty() || direction == "default")
return DIRECTION_DEFAULT;
if (direction == "up")
return DIRECTION_UP;
if (direction == "left")
return DIRECTION_LEFT;
if (direction == "right")
return DIRECTION_RIGHT;
if (direction == "down")
return DIRECTION_DOWN;
return DIRECTION_INVALID;
}