summaryrefslogtreecommitdiff
path: root/src/actorsprite.cpp
diff options
context:
space:
mode:
authorJared Adams <jaxad0127@gmail.com>2010-05-02 14:43:40 -0600
committerJared Adams <jaxad0127@gmail.com>2010-05-06 22:21:57 -0600
commit9baedc27191c82bbf1fedee2a7e738bc5b267c0e (patch)
treef0986c6839d1e79b402d5d4ec36e4994741955ea /src/actorsprite.cpp
parent844e9a7a72faca6a212e788a3adc45e17f41dca6 (diff)
downloadmana-9baedc27191c82bbf1fedee2a7e738bc5b267c0e.tar.gz
mana-9baedc27191c82bbf1fedee2a7e738bc5b267c0e.tar.bz2
mana-9baedc27191c82bbf1fedee2a7e738bc5b267c0e.tar.xz
mana-9baedc27191c82bbf1fedee2a7e738bc5b267c0e.zip
Add support for floor item sprites
This commit adds a sprite hierarchy (Sprite->ImageSprite,AnimatedSprite,CompundSprite; CompoundSprite,Actor->ActorSprite;ActorSprite->Being,FloorItem) to collect common functionailty into new base classes which will make other Mantis tickets easier to do. Also allows monsters to use particle effects. Reviewed-by: Bertram
Diffstat (limited to 'src/actorsprite.cpp')
-rw-r--r--src/actorsprite.cpp326
1 files changed, 326 insertions, 0 deletions
diff --git a/src/actorsprite.cpp b/src/actorsprite.cpp
new file mode 100644
index 00000000..ad703bc2
--- /dev/null
+++ b/src/actorsprite.cpp
@@ -0,0 +1,326 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "actorsprite.h"
+
+#include "client.h"
+#include "effectmanager.h"
+#include "imagesprite.h"
+#include "localplayer.h"
+#include "log.h"
+#include "simpleanimation.h"
+#include "sound.h"
+#include "statuseffect.h"
+
+#include "gui/theme.h"
+
+#include "net/net.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#define EFFECTS_FILE "effects.xml"
+
+ActorSprite::ActorSprite(int id):
+ mId(id),
+ mStunMode(0),
+ mStatusParticleEffects(&mStunParticleEffects, false),
+ mChildParticleEffects(&mStatusParticleEffects, false),
+ mMustResetParticles(false),
+ mUsedTargetCursor(NULL)
+{
+ //
+}
+
+ActorSprite::~ActorSprite()
+{
+ setMap(NULL);
+
+ mUsedTargetCursor = NULL;
+
+ if (player_node && player_node->getTarget() == this)
+ player_node->setTarget(NULL);
+}
+
+bool ActorSprite::draw(Graphics *graphics, int offsetX, int offsetY) const
+{
+ // TODO: Eventually, we probably should fix all sprite offsets so that
+ // these translations aren't necessary anymore. The sprites know
+ // best where their base point should be.
+ const int px = getPixelX() + offsetX - 16;
+ // Temporary fix to the Y offset.
+ const int py = getPixelY() + offsetY -
+ ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32);
+
+ if (mUsedTargetCursor)
+ mUsedTargetCursor->draw(graphics, px, py);
+
+ return drawSpriteAt(graphics, px, py);
+}
+
+bool ActorSprite::drawSpriteAt(Graphics *graphics, int x, int y) const
+{
+ return CompoundSprite::draw(graphics, x, y);
+}
+
+void ActorSprite::logic()
+{
+ // Update sprite animations
+ if (mUsedTargetCursor)
+ mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK);
+
+ update(tick_time * MILLISECONDS_IN_A_TICK);
+
+ // Restart status/particle effects, if needed
+ if (mMustResetParticles)
+ {
+ mMustResetParticles = false;
+ for (std::set<int>::iterator it = mStatusEffects.begin();
+ it != mStatusEffects.end(); it++)
+ {
+ const StatusEffect *effect = StatusEffect::getStatusEffect(*it, true);
+ if (effect && effect->particleEffectIsPersistent())
+ updateStatusEffect(*it, true);
+ }
+ }
+
+ // Update particle effects
+ mChildParticleEffects.moveTo(mPos.x, mPos.y);
+}
+
+void ActorSprite::setMap(Map* map)
+{
+ Actor::setMap(map);
+
+ // Clear particle effect list because child particles became invalid
+ mChildParticleEffects.clear();
+ mMustResetParticles = true; // Reset status particles on next redraw
+}
+
+void ActorSprite::controlParticle(Particle *particle)
+{
+ mChildParticleEffects.addLocally(particle);
+}
+
+void ActorSprite::setTargetAnimation(SimpleAnimation *animation)
+{
+ mUsedTargetCursor = animation;
+ mUsedTargetCursor->reset();
+}
+
+struct EffectDescription {
+ std::string mGFXEffect;
+ std::string mSFXEffect;
+};
+
+static EffectDescription *default_effect = NULL;
+static std::map<int, EffectDescription *> effects;
+static bool effects_initialized = false;
+
+static EffectDescription *getEffectDescription(xmlNodePtr node, int *id)
+{
+ EffectDescription *ed = new EffectDescription;
+
+ *id = atoi(XML::getProperty(node, "id", "-1").c_str());
+ ed->mSFXEffect = XML::getProperty(node, "audio", "");
+ ed->mGFXEffect = XML::getProperty(node, "particle", "");
+
+ return ed;
+}
+
+static EffectDescription *getEffectDescription(int effectId)
+{
+ if (!effects_initialized)
+ {
+ XML::Document doc(EFFECTS_FILE);
+ xmlNodePtr root = doc.rootNode();
+
+ if (!root || !xmlStrEqual(root->name, BAD_CAST "being-effects"))
+ {
+ logger->log("Error loading being effects file: "
+ EFFECTS_FILE);
+ return NULL;
+ }
+
+ for_each_xml_child_node(node, root)
+ {
+ int id;
+
+ if (xmlStrEqual(node->name, BAD_CAST "effect"))
+ {
+ EffectDescription *EffectDescription =
+ getEffectDescription(node, &id);
+ effects[id] = EffectDescription;
+ }
+ else if (xmlStrEqual(node->name, BAD_CAST "default"))
+ {
+ EffectDescription *effectDescription =
+ getEffectDescription(node, &id);
+
+ if (default_effect)
+ delete default_effect;
+
+ default_effect = effectDescription;
+ }
+ }
+
+ effects_initialized = true;
+ } // done initializing
+
+ EffectDescription *ed = effects[effectId];
+
+ return ed ? ed : default_effect;
+}
+
+void ActorSprite::setStatusEffect(int index, bool active)
+{
+ const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end();
+
+ if (active != wasActive)
+ {
+ updateStatusEffect(index, active);
+ if (active)
+ mStatusEffects.insert(index);
+ else
+ mStatusEffects.erase(index);
+ }
+}
+
+void ActorSprite::setStatusEffectBlock(int offset, Uint16 newEffects)
+{
+ for (int i = 0; i < STATUS_EFFECTS; i++)
+ {
+ int index = StatusEffect::blockEffectIndexToEffectIndex(offset + i);
+
+ if (index != -1)
+ setStatusEffect(index, (newEffects & (1 << i)) > 0);
+ }
+}
+
+void ActorSprite::internalTriggerEffect(int effectId, bool sfx, bool gfx)
+{
+ logger->log("Special effect #%d on %s", effectId,
+ getId() == player_node->getId() ? "self" : "other");
+
+ EffectDescription *ed = getEffectDescription(effectId);
+
+ if (!ed)
+ {
+ logger->log("Unknown special effect and no default recorded");
+ return;
+ }
+
+ if (gfx && !ed->mGFXEffect.empty())
+ {
+ Particle *selfFX;
+
+ selfFX = particleEngine->addEffect(ed->mGFXEffect, 0, 0);
+ controlParticle(selfFX);
+ }
+
+ if (sfx && !ed->mSFXEffect.empty())
+ sound.playSfx(ed->mSFXEffect);
+}
+
+void ActorSprite::updateStunMode(int oldMode, int newMode)
+{
+ handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1);
+ handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1);
+}
+
+void ActorSprite::updateStatusEffect(int index, bool newStatus)
+{
+ handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index);
+}
+
+void ActorSprite::handleStatusEffect(StatusEffect *effect, int effectId)
+{
+ if (!effect)
+ return;
+
+ // TODO: Find out how this is meant to be used
+ // (SpriteAction != Being::Action)
+ //SpriteAction action = effect->getAction();
+ //if (action != ACTION_INVALID)
+ // setAction(action);
+
+ Particle *particle = effect->getParticle();
+
+ if (effectId >= 0)
+ {
+ mStatusParticleEffects.setLocally(effectId, particle);
+ }
+ else
+ {
+ mStunParticleEffects.clearLocally();
+ if (particle)
+ mStunParticleEffects.addLocally(particle);
+ }
+}
+
+void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display,
+ bool forceDisplay)
+{
+ clear();
+
+ SpriteRefs it, it_end;
+
+ for (it = display.sprites.begin(), it_end = display.sprites.end();
+ it != it_end; it++)
+ {
+ std::string file = "graphics/sprites/" + (*it)->sprite;
+ int variant = (*it)->variant;
+ push_back(AnimatedSprite::load(file, variant));
+ }
+
+ // Ensure that something is shown, if desired
+ if (size() == 0 && forceDisplay)
+ {
+ if (display.image.empty())
+ push_back(AnimatedSprite::load("graphics/sprites/error.xml"));
+ else
+ {
+ ResourceManager *resman = ResourceManager::getInstance();
+ std::string imagePath = "graphics/items/" + display.image;
+ Image *img = resman->getImage(imagePath);
+
+ if (!img)
+ img = Theme::getImageFromTheme("unknown-item.png");
+
+ push_back(new ImageSprite(img));
+ }
+ }
+
+ mChildParticleEffects.clear();
+
+ //setup particle effects
+ if (Particle::enabled)
+ {
+ std::list<std::string>::const_iterator it, it_end;
+ for (it = display.particles.begin(), it_end = display.particles.end();
+ it != it_end; it++)
+ {
+ Particle *p = particleEngine->addEffect(*it, 0, 0);
+ controlParticle(p);
+ }
+ }
+
+ mMustResetParticles = true;
+}