summaryrefslogblamecommitdiff
path: root/src/actorsprite.cpp
blob: af98a0fbc8658d384f41e554e64157d58475c891 (plain) (tree)
1
2
3
4
5
6
  
                       
                                           
                                                    
  
                                             















                                                                         
 
                                
                   
                          


                          
                            
                         





                         


                                      

                             

                  
                                                      




                                                      
                                        
                     
            




                                                          
                              




                           
                    
 
                                

                                                        
                                        

                                           
                                                                       
     
                               
                                                 


     

                                                                  
 
                                      




                                                                          
                       

                                                                    


                                              











                                                                      

                                                              





                                                
                                     






                                                 
                                                                    
         
                                            







                                                               
                                   





                              
                                        







                                                                        
                                                           



                                               
                                                            






                                                                      
                              




                           
                                                   


                                                  

                                                                     
 


                       
                                                        







                                                            
                                                                  



                                        
                                               
 
                                                          
         
                                                             
                           





                                           
                                             
             
                                                            
                                                    
                                                
             
                                                   
             
                                                            










                                                    




                                                                             



                                    
                                                                     












                                                                              

                                                                 
 
                                                 
     

                                                                      





                                                                

                                                                           
 
                                    

               





                                                                
                                                                       
 
                        




                                                                       
                                       
                                                                         

                                       
                                             

 
                                                                      




                                                                          
                                                                           



                                                                               

                                                                
 
                







                                                  
                                                     













                                                                  

                                                             
                                                              


            
                                              
     

                     

                                                                        
 
                                           
                                                              


                                                 
                                


                                  
                                                  

                                                           


            
                                                                           




                                  

                                                                         

                          

                                                                         

                          
                                                      
 





                                                                   

                              







                                            
                                                          
         
                                                                      


















                               
                            





                           
                                                                             



                                               

                                                                      



                                           
                                             







                                       


     
                                             









                                    




                                    


                                                                          





                                                           


                                                                        












                                                           
                                                   



                                                         
                                                         





                                                               

                                                                     
 
                                                      

               
                                                                  
                                 






                                                                         
                                          
 
                                                        
     
                                                   


                                                      

     
                                                                     



                                        
                                           






                                                     
/*
 *  The ManaPlus Client
 *  Copyright (C) 2010  The Mana Developers
 *  Copyright (C) 2011-2013  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 "actorsprite.h"

#include "actorspritelistener.h"
#include "client.h"
#include "configuration.h"
#include "effectmanager.h"
#include "imagesprite.h"
#include "localplayer.h"
#include "simpleanimation.h"
#include "soundmanager.h"
#include "statuseffect.h"

#include "gui/theme.h"

#include "net/net.h"

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

#include "utils/checkutils.h"

#include "debug.h"

static const char *const EFFECTS_FILE = "effects.xml";

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

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

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

    mUsedTargetCursor = nullptr;

    if (player_node && player_node->getTarget() == this)
        player_node->setTarget(nullptr);

    // Notify listeners of the destruction.
    FOR_EACH (ActorSpriteListenerIterator, iter, mActorSpriteListeners)
    {
        if (reportFalse(*iter))
            (*iter)->actorSpriteDestroyed(*this);
    }
}

bool ActorSprite::draw(Graphics *const graphics,
                       const int offsetX, const int offsetY) const
{
    FUNC_BLOCK("ActorSprite::draw", 1)
    // 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.
#ifdef MANASERV_SUPPORT
    const int py = getPixelY() + offsetY -
        ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32);
#else
    const int py = getPixelY() + offsetY - 32;
#endif

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

    return drawSpriteAt(graphics, px, py);
}

bool ActorSprite::drawSpriteAt(Graphics *const graphics,
                               const int x, const int y) const
{
    return CompoundSprite::draw(graphics, x, y);
}

void ActorSprite::logic()
{
    BLOCK_START("ActorSprite::logic")
    // Update sprite animations
    update(tick_time * MILLISECONDS_IN_A_TICK);

    // Restart status/particle effects, if needed
    if (mMustResetParticles)
    {
        mMustResetParticles = false;
        FOR_EACH (std::set<int>::const_iterator, it, mStatusEffects)
        {
            const StatusEffect *const effect
                = StatusEffect::getStatusEffect(*it, true);
            if (effect && effect->particleEffectIsPersistent())
                updateStatusEffect(*it, true);
        }
    }

    // Update particle effects
    mChildParticleEffects.moveTo(mPos.x, mPos.y);
    BLOCK_END("ActorSprite::logic")
}

void ActorSprite::actorLogic()
{
}

void ActorSprite::setMap(Map *const 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 *const particle)
{
    mChildParticleEffects.addLocally(particle);
}

void ActorSprite::setTargetType(const TargetCursorType type)
{
    if (type == TCT_NONE)
        untarget();
    else
        mUsedTargetCursor = targetCursor[type][getTargetCursorSize()];
}

struct EffectDescription final
{
    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 const node,
                                               int *const id)
{
    if (!id)
        return nullptr;

    EffectDescription *const 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(const int effectId)
{
    if (!effects_initialized)
    {
        XML::Document doc(EFFECTS_FILE);
        const XmlNodePtr root = doc.rootNode();

        if (!root || !xmlNameEqual(root, "being-effects"))
        {
            logger->log1("Error loading being effects file");
            return nullptr;
        }

        for_each_xml_child_node(node, root)
        {
            int id;

            if (xmlNameEqual(node, "effect"))
            {
                EffectDescription *const effectDescription =
                    getEffectDescription(node, &id);
                effects[id] = effectDescription;
            }
            else if (xmlNameEqual(node, "default"))
            {
                EffectDescription *const effectDescription =
                    getEffectDescription(node, &id);

                delete default_effect;

                default_effect = effectDescription;
            }
        }

        effects_initialized = true;
    } // done initializing

    std::map<int, EffectDescription *>::iterator it = effects.find(effectId);
    if (it == effects.end())
        return default_effect;

    EffectDescription *const ed = (*it).second;

    return ed ? ed : default_effect;
}

void ActorSprite::setStatusEffect(const int index, const 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(const int offset,
                                       const uint16_t newEffects)
{
    for (unsigned i = 0; i < STATUS_EFFECTS; i++)
    {
        const int index = StatusEffect::blockEffectIndexToEffectIndex(
            offset + i);

        if (index != -1)
            setStatusEffect(index, (newEffects & (1 << i)) > 0);
    }
}

void ActorSprite::internalTriggerEffect(const int effectId, const bool sfx,
                                        const bool gfx)
{
    if (reportTrue(!particleEngine))
        return;

    if (player_node)
    {
        logger->log("Special effect #%d on %s", effectId,
            getId() == player_node->getId() ? "self" : "other");
    }

    const EffectDescription *const ed = getEffectDescription(effectId);

    if (reportTrue(!ed))
    {
        logger->log1("Unknown special effect and no default recorded");
        return;
    }

    if (gfx && !ed->mGFXEffect.empty())
        controlParticle(particleEngine->addEffect(ed->mGFXEffect, 0, 0));

    if (sfx && !ed->mSFXEffect.empty())
        soundManager.playSfx(ed->mSFXEffect);
}

void ActorSprite::updateStunMode(const int oldMode, const int newMode)
{
    handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1);
    handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1);
}

void ActorSprite::updateStatusEffect(const int index, const bool newStatus)
{
    handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index);
}

void ActorSprite::handleStatusEffect(StatusEffect *const effect,
                                     const 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 *const particle = effect->getParticle();

    if (effectId >= 0)
    {
        mStatusParticleEffects.setLocally(effectId, particle);
    }
    else
    {
        mStunParticleEffects.clearLocally();
        if (particle)
            mStunParticleEffects.addLocally(particle);
    }
}

void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display,
                                     const bool forceDisplay,
                                     const int imageType,
                                     const std::string &color)
{
    clear();

    FOR_EACH (SpriteRefs, it, display.sprites)
    {
        if (!*it)
            continue;
        const std::string file = paths.getStringValue("sprites").append(
            combineDye2((*it)->sprite, color));

        const int variant = (*it)->variant;
        addSprite(AnimatedSprite::delayedLoad(file, variant));
    }

    // Ensure that something is shown, if desired
    if (empty() && forceDisplay)
    {
        if (display.image.empty())
        {
            addSprite(AnimatedSprite::delayedLoad(
                paths.getStringValue("sprites").append(
                paths.getStringValue("spriteErrorFile"))));
        }
        else
        {
            ResourceManager *const resman = ResourceManager::getInstance();
            std::string imagePath;
            switch (imageType)
            {
                case 0:
                default:
                    imagePath = paths.getStringValue("itemIcons").append(
                        display.image);
                    break;
                case 1:
                    imagePath = paths.getStringValue("itemIcons").append(
                        display.floor);
                    break;
            }
            imagePath = combineDye2(imagePath, color);

            Image *img = resman->getImage(imagePath);

            if (!img)
                img = Theme::getImageFromTheme("unknown-item.png");

            addSprite(new ImageSprite(img));
            if (img)
                img->decRef();
        }
    }

    mChildParticleEffects.clear();

    //setup particle effects
    if (Particle::enabled && particleEngine)
    {
        FOR_EACH (StringVectCIter, itr, display.particles)
        {
            Particle *const p = particleEngine->addEffect(*itr, 0, 0);
            controlParticle(p);
        }
    }

    mMustResetParticles = true;
}

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

    initTargetCursor();

    loaded = true;
}

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

    cleanupTargetCursors();
    loaded = false;
}

void ActorSprite::addActorSpriteListener(ActorSpriteListener *const listener)
{
    mActorSpriteListeners.push_front(listener);
}

void ActorSprite::removeActorSpriteListener(ActorSpriteListener *const
                                            listener)
{
    mActorSpriteListeners.remove(listener);
}

static const char *cursorType(const int type)
{
    switch (type)
    {
        case ActorSprite::TCT_IN_RANGE:
            return "in-range";
        default:
        case ActorSprite::TCT_NORMAL:
            return "normal";
    }
}

static const char *cursorSize(const int size)
{
    switch (size)
    {
        case ActorSprite::TC_LARGE:
            return "l";
        case ActorSprite::TC_MEDIUM:
            return "m";
        default:
        case ActorSprite::TC_SMALL:
            return "s";
    }
}

void ActorSprite::initTargetCursor()
{
    static const std::string targetCursorFile = "target-cursor-%s-%s.png";
    static const int targetWidths[NUM_TC] = {44, 62, 82};
    static const int targetHeights[NUM_TC] = {35, 44, 60};

    // Load target cursors
    for (int size = TC_SMALL; size < NUM_TC; size++)
    {
        for (int type = TCT_NORMAL; type < NUM_TCT; type++)
        {
            loadTargetCursor(strprintf(targetCursorFile.c_str(),
                cursorType(type), cursorSize(size)), targetWidths[size],
                targetHeights[size], type, size);
        }
    }
}

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

void ActorSprite::loadTargetCursor(const std::string &filename,
                                   const int width, const int height,
                                   const int type, const int size)
{
    if (reportTrue(size < TC_SMALL || size >= NUM_TC))
        return;

    ImageSet *const currentImageSet = Theme::getImageSetFromTheme(
        filename, width, height);

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

    Animation *const anim = new Animation;

    for (size_t i = 0; i < currentImageSet->size(); ++i)
    {
        anim->addFrame(currentImageSet->get(i), 75,
            (16 - (currentImageSet->getWidth() / 2)),
            (16 - (currentImageSet->getHeight() / 2)),
            100);
    }

    SimpleAnimation *const currentCursor = new SimpleAnimation(anim);

    if (targetCursor[type][size])
    {
        delete targetCursor[type][size];
        targetCursor[type][size] = nullptr;
        if (targetCursorImages[type][size])
            targetCursorImages[type][size]->decRef();
    }

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