summaryrefslogblamecommitdiff
path: root/src/actorsprite.cpp
blob: ab220d451d0f3c9beb33537a6e20e08ee0c6c493 (plain) (tree)
1
2
3

                   
                                                



















                                                                         
                          
                  






                            


                            
                               
                                      
                            
 

                  

                                  

                                                      
                                 
 





                                                          
                              
  


                           
                    
 
                                
 
                                           
                                  
                                   
                                             

 










                                               

                                                                          
                                   
                                   

                          


                                                                      
                                                  
     
 




                                                                             
 










                                                                      





                                                 
                                               
         
                                                                                           
                                                               
                                                       


         
                                    
                      

                                        
 
                              
                                             

 

                              

 













                                                                        
                                                      
 



                                                                      






                           
                                                   




                                                                        
                                     














                                                            
                                                                  


                                                            
                           













































                                                                              
                                                                       












                                                                            
                                                                     






















                                                                      
                             
     
                                 

                                         
                                                 

     





                                                                          
                             
     
                                               

                                              
                                                 

     





































                                                                               

                                                                        
                                                       





                                                 



                                                                          


                                                                     

                                                                     


                                                     

                                                             
 
                                            


















                                                                              


                        


                 
                       

                  



                          


                
                           
                   

 

                                    







                                                                               

                          
                                             
     
                                                  
         



                                                            






                                                              
                                             
     
                                                  

                                            

                                                         












                                                                             





                                                                         
                               


                                                              
                                                                    

                                                           

     
                                                    



                                                     
/*
 *  The Mana Client
 *  Copyright (C) 2010-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 <http://www.gnu.org/licenses/>.
 */

#include "actorsprite.h"

#include "client.h"
#include "configuration.h"
#include "event.h"
#include "imagesprite.h"
#include "localplayer.h"
#include "log.h"
#include "simpleanimation.h"
#include "sound.h"
#include "statuseffect.h"

#include "net/net.h"

#include "resources/image.h"
#include "resources/imageset.h"
#include "resources/resourcemanager.h"
#include "resources/theme.h"

#include <cassert>

#define EFFECTS_FILE "effects.xml"

ImageSet *ActorSprite::targetCursorImages[2][NUM_TC];
SimpleAnimation *ActorSprite::targetCursor[2][NUM_TC];
bool ActorSprite::loaded = false;

ActorSprite::ActorSprite(int id):
    mId(id),
    mStunMode(0),
    mStatusParticleEffects(&mStunParticleEffects, false),
    mChildParticleEffects(&mStatusParticleEffects, false),
    mMustResetParticles(false),
    mUsedTargetCursor(nullptr)
{}

ActorSprite::~ActorSprite()
{
    setMap(nullptr);

    mUsedTargetCursor = nullptr;

    // Notify listeners of the destruction.
    Event event(Event::Destroyed);
    event.setActor("source", this);
    event.trigger(Event::ActorSpriteChannel);
}

int ActorSprite::getDrawOrder() const
{
    int drawOrder = Actor::getDrawOrder();

    // See note at ActorSprite::draw
    if (mMap)
        drawOrder += mMap->getTileHeight() / 2;

    return drawOrder;
}

bool ActorSprite::draw(Graphics *graphics, int offsetX, int offsetY) const
{
    int px = getPixelX() + offsetX;
    int py = getPixelY() + offsetY;

    if (mUsedTargetCursor)
    {
        mUsedTargetCursor->reset();
        mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK);
        mUsedTargetCursor->draw(graphics, px, py);
    }

    // This is makes sure that actors positioned on the center of a tile have
    // their sprite aligned to the bottom of that tile, mainly to maintain
    // compatibility with older clients.
    if (mMap)
        py += mMap->getTileHeight() / 2;

    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
    update(tick_time * MILLISECONDS_IN_A_TICK);

    // Restart status/particle effects, if needed
    if (mMustResetParticles)
    {
        mMustResetParticles = false;
        for (int statusEffect : mStatusEffects)
        {
            const StatusEffect *effect = StatusEffect::getStatusEffect(statusEffect, true);
            if (effect && effect->particleEffectIsPersistent())
                updateStatusEffect(statusEffect, true);
        }
    }

    // See note at ActorSprite::draw
    float py = mPos.y;
    if (mMap)
        py += mMap->getTileHeight() / 2;

    // Update particle effects
    mChildParticleEffects.moveTo(mPos.x, py);
}

void ActorSprite::actorLogic()
{
}

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::setTargetType(TargetCursorType type)
{
    if (type == TCT_NONE)
        untarget();
    else
        mUsedTargetCursor = targetCursor[type][getTargetCursorSize()];
}

struct EffectDescription {
    std::string mGFXEffect;
    std::string mSFXEffect;
};

static EffectDescription *default_effect = nullptr;
static std::map<int, EffectDescription *> effects;
static bool effects_initialized = false;

static EffectDescription *getEffectDescription(xmlNodePtr node, int *id)
{
    auto *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 "effects"))
        {
            logger->log("Error loading being effects file: "
                    EFFECTS_FILE);
            return nullptr;
        }

        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_t 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() == local_player->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)
{
    if (this == local_player)
    {
        Event event(Event::Stun);
        event.setInt("oldMode", oldMode);
        event.setInt("newMode", newMode);
        event.trigger(Event::ActorSpriteChannel);
    }

    handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1);
    handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1);
}

void ActorSprite::updateStatusEffect(int index, bool newStatus)
{
    if (this == local_player)
    {
        Event event(Event::UpdateStatusEffect);
        event.setInt("index", index);
        event.setBool("newStatus", newStatus);
        event.trigger(Event::ActorSpriteChannel);
    }

    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 = paths.getStringValue("sprites") + it->sprite;
        int variant = it->variant;
        addSprite(AnimatedSprite::load(file, variant));
    }

    // Ensure that something is shown, if desired
    if (size() == 0 && forceDisplay)
    {
        if (display.image.empty())
        {
            addSprite(AnimatedSprite::load(paths.getStringValue("sprites")
                + paths.getStringValue("spriteErrorFile")));
        }
        else
        {
            ResourceManager *resman = ResourceManager::getInstance();
            std::string imagePath = paths.getStringValue("itemIcons")
                                    + display.image;
            Image *img = resman->getImage(imagePath);

            if (!img)
                img = Theme::getImageFromTheme(
                    paths.getStringValue("unknownItemFile"));

            addSprite(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;
}

void ActorSprite::load()
{
    if (loaded)
        unload();

    initTargetCursor();

    loaded = true;
}

void ActorSprite::unload()
{
    if (!loaded)
        return;

    cleanupTargetCursors();
    loaded = false;
}

void ActorSprite::initTargetCursor()
{
    static const std::string targetCursor = "graphics/target-cursor-%s-%s.png";
    static const char * const cursorTypeStr[NUM_TCT] = {
        "normal",
        "in-range"
    };
    static const int targetWidths[NUM_TC] = { 44, 62, 82 };
    static const int targetHeights[NUM_TC] = { 35, 44, 60 };
    static const char * const cursorSizeStr[NUM_TC] = { "s", "m", "l" };

    // Load target cursors
    for (int size = 0; size < NUM_TC; size++)
    {
        for (int type = 0; type < NUM_TCT; type++)
        {
            loadTargetCursor(strprintf(targetCursor.c_str(),
                                       cursorTypeStr[type],
                                       cursorSizeStr[size]),
                             targetWidths[size],
                             targetHeights[size], type, size);
        }
    }
}

void ActorSprite::cleanupTargetCursors()
{
    for (int size = 0; size < NUM_TC; size++)
    {
        for (int type = 0; type < NUM_TCT; type++)
        {
            delete targetCursor[type][size];
            if (targetCursorImages[type][size])
                targetCursorImages[type][size]->decRef();
        }
    }
}

void ActorSprite::loadTargetCursor(const std::string &filename,
                                   int width, int height, int type, int size)
{
    assert(size > -1);
    assert(size < 3);

    ResourceManager *resman = ResourceManager::getInstance();
    ImageSet *currentImageSet = resman->getImageSet(filename, width, height);

    if (!currentImageSet)
    {
        logger->log("Error loading target cursor: %s", filename.c_str());
        return;
    }

    auto *anim = new Animation;

    for (unsigned int i = 0; i < currentImageSet->size(); ++i)
    {
        anim->addFrame(currentImageSet->get(i), DEFAULT_FRAME_DELAY,
                      -(currentImageSet->getWidth() / 2),
                      -(currentImageSet->getHeight() / 2));
    }

    auto *currentCursor = new SimpleAnimation(anim);

    targetCursorImages[type][size] = currentImageSet;
    targetCursor[type][size] = currentCursor;
}