summaryrefslogblamecommitdiff
path: root/src/localplayer.cpp
blob: 92224c03a2789911fcc20ab07c2cd2f21aab8cbc (plain) (tree)
1
2
3
4
5
6
7
8
  
                   
                                                            
                                                
  
                                         
  
                                                                        



                                                                        
                                                                   




                                                                     
                                                                         
   

                        
 
                   
                          
                  
                      
                  
                 
                
                     
                       

                  
                    
                         
 
                                
 
                            
                            
                             
                    
                             
                              
 
                               
                                  
 
                          
                              
 
                                
 
                                    
 
                                              
                                       
 
                                     
 

                                       

                       
                                                   
 

                                      



                           

                         



                         
                                            

                                                 
 
                       
                           
     
                              
         
                                                 

                                                     
                               
                                
                                          
                                                        

                                                      

                                  
         
                       
     
 
                        
 
                                         

                                                 
 

                

                                                          
         












                                                                                
 



                                        
                                        
                 
                                                                
                     












                                                                              
                                              
                                               
                     
                 
                                        

                                          




                      
         
     



                                                                               
                                
     
 
                   

 
                                                        


                       
                             
                           

     
                                       

 







                                       
 

                                                            
                                               

               














                                                                                

                                            
 
                                              

                                         

                                     



































                                                                            









                                                                          
             



                                                                  
                           
                                       
                           
             

                                                      
                                                                   



                                                                             




                             








                                                                          
             


                                                              
                           
                                       
                           



                                                                   

                                                                  




                             








                                                                              
             




                                                               
                           

                                         



                                                                   

                                                                            




                             








                                                                              
             



                                                              
                           

                                          



                                                                   

                                                                            










                                                   

                                                                         








                                                                      


                                                                          






                                                                         
                                                           


                                                             



                                                                    




                                                                              

                                                                   








                                                   
                                                                              








                                                                     

                                                                          






                                                                        
                                                           


                                                             


                                                                         




                                                                              

                                                                   








                                                   
                                                                              








                                                                     

                                                                          






                                                                      
                                                           


                                                             


                                                                          




                                                                              

                                                                        








                                                   

                                                            








                                                                        


                                                                         





                                                                         
                                                           


                                                             



                                                                    




                                                                              

                                                                        






                                                                 
                                                 
 

                      
 

                                                    
 


                                       
                            
                                                     
     
                               
     


                                                         
     

 
                                                                 
 
                                           
                                        
 


                 
                                             
 

                                   
 

                                                                                  

                                 



                                                      
                                                                        




                   

                                         


               

                       
                                                                   
     
                                              



                                                         
                                


        
                         
                                                             
                             
     

 
                                     



                   

                                          
                                                            
               
 
               
                                    
 
                          
               
 
                                    

                                
     
        
     

                               
     
 
                               
                
     
                            

                            
 
                                                              
                                    
 
                     
 




                                
                                                            
                                  

 
                                              
 


               














                                                                        
                                                                     
                                     
     
                                    









                                                                         
 

                                                               
                                         
                                                                      
     

 

                                        

                                                     





                                                                                
 
                                                                          


                                                                    
                                         







                                                       

                       
                      

                                                   
                               
     

                          
                             
     
                      


     
                                                 
 


                      
                                          
     
                                                                       

                                            












                       
                  

 

                                                
                        
     
                        
 
                                                                     
                         

                                                                            


                         


                                                              

                
 

                             
                              
               
                                
 
                            
                    
     

                                           


                        
                                                     

 
                                    
 
                              
               
                                
 
                                            

 

                                                  
                              
               
 


                                              
 
                                                         

               








                                                        
                                      
     
                             

                          
 
                                   
 
                                
 
                      
 


                                                                              

                                     
     
        
     
                                                                         
     
 
                                                     



                              

                

                              
                           
     
                         
                       

 

                                                                
 
             
     












                                                                         

                                                 
                                 
         
                                                              

                                       
                                                                


         
     
                                                  
         

                                                                        
                                                                
                                                                             
                                                                          

         
                                                              

                                       
                            



                                                             
         


     
                                           
 
                                                            
                   
     
                             
     
                                                            
     
                                            
                                                                              
                   
         
                                                    
                                           
                                                     
         
     

 
                                                             
 


                             



                                                    
                                       



                                              


                
                         
 
                      
                          

                          
                     
                                          
 
 
                                                                          
 
                                                     
 
 
                                                                   
 
                                             
     
                                                



                                                          
                                        

                                 
                                  

         
                                                 
     
                                                      


                                          








                                                                              
                                  



                                                                 
                    
                 
                                           
                 


                                                                


             
                                             
     
                                                            





                                                           

                                 
 











                                                                          
                          




































                                                                               
                                                             
     
                                       

     
/*
 *  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 <http://www.gnu.org/licenses/>.
 */

#include "localplayer.h"

#include "client.h"
#include "configuration.h"
#include "event.h"
#include "flooritem.h"
#include "guild.h"
#include "item.h"
#include "map.h"
#include "particle.h"
#include "playerinfo.h"
#include "sound.h"

#include "gui/gui.h"
#include "gui/okdialog.h"

#include "gui/widgets/chattab.h"

#include "net/chathandler.h"
#include "net/gamehandler.h"
#include "net/guildhandler.h"
#include "net/net.h"
#include "net/partyhandler.h"
#include "net/playerhandler.h"

#include "resources/iteminfo.h"
#include "resources/userpalette.h"

#include "utils/gettext.h"
#include "utils/stringutils.h"

const int AWAY_LIMIT_TIMER = 60;

LocalPlayer *local_player = nullptr;

LocalPlayer::LocalPlayer(int id, int subtype):
    Being(id, PLAYER, subtype, nullptr)
{
    listen(Event::AttributesChannel);

    mAwayListener = new AwayListener();

    mUpdateName = true;

    setShowName(config.getValue("showownname", 1));

    listen(Event::ConfigChannel);
    listen(Event::ActorSpriteChannel);
}

LocalPlayer::~LocalPlayer()
{
    delete mAwayDialog;
    delete mAwayListener;
}

void LocalPlayer::logic()
{
    // Actions are allowed at 5.5 per second
    if (get_elapsed_time(mLastActionTime) >= 182)
        mLastActionTime = -1;

    // Show XP messages
    if (!mMessages.empty())
    {
        if (mMessageTime == 0)
        {
            MessagePair info = mMessages.front();

            particleEngine->addTextRiseFadeOutEffect(
                    info.first,
                    getPixelX(),
                    getPixelY() - 32 - 16,
                    &userPalette->getColor(info.second),
                    gui->getInfoParticleFont(), true);

            mMessages.pop_front();
            mMessageTime = 30;
        }
        mMessageTime--;
    }

    PlayerInfo::logic();

    // Targeting allowed 4 times a second
    if (get_elapsed_time(mLastTargetTime) >= 250)
        mLastTargetTime = -1;

    if (mTarget)
    {
        ActorSprite::Type targetType = mTarget->getType();
        switch (targetType)
        {
            case ActorSprite::NPC:
                mTarget->setTargetType(
                    withinRange(mTarget,
                                Net::getGameHandler()->getNpcTalkRange()) ?
                                    TCT_IN_RANGE : TCT_NORMAL);
                break;
            case ActorSprite::MONSTER:
            case ActorSprite::PLAYER:
            {
                // Dealing with attacks
                bool withinAttackRange = withinRange(mTarget, getAttackRange());
                mTarget->setTargetType(withinAttackRange ?
                                       TCT_IN_RANGE : TCT_NORMAL);

                if (!mTarget->isAlive())
                {
                    stopAttack();
                }
                else if (mGoingToTarget)
                {
                    if (!withinAttackRange && getPath().empty())
                    {
                        setDestination(mTarget->getPosition());
                    }
                    else if (withinAttackRange)
                    {
                        // Truncate the path to terminate at the next node.
                        // This permits to avoid a walking glitch in tile path
                        // mode.
                        if (!mPath.empty())
                        {
                            pathSetByMouse();
                            setDestination(mPath.front());
                        }

                        mKeepAttacking = true;
                        mGoingToTarget = false;
                    }
                }
                else if (mKeepAttacking)
                {
                    attack(mTarget, true);
                }
                break;
            }
            default:
                break;
        }
    }
    else if (mPickUpTarget
        && withinRange(mPickUpTarget, Net::getGameHandler()->getPickupRange()))
    {
        Net::getPlayerHandler()->pickUp(mPickUpTarget);
        mPickUpTarget = nullptr;
    }

    Being::logic();
}

void LocalPlayer::setAction(Action action, int attackId)
{
    if (action == DEAD)
    {
        mLastTargetTime = -1;
        setTarget(nullptr);
    }

    Being::setAction(action, attackId);
}

void LocalPlayer::setGMLevel(int level)
{
    mGMLevel = level;

    if (level > 0)
        setGM(true);
}


Position LocalPlayer::getNextWalkPosition(unsigned char dir)
{
    // Compute where the next tile will be set.
    int dx = 0;
    int dy = 0;
    if (dir & Being::UP)
        dy--;
    if (dir & Being::DOWN)
        dy++;
    if (dir & Being::LEFT)
        dx--;
    if (dir & Being::RIGHT)
        dx++;

    Vector pos = getPosition();

    // If no map or no direction is given, give back the current player position
    if (!mMap || (!dx && !dy))
        return Position((int)pos.x, (int)pos.y);

    const int tileW = mMap->getTileWidth();
    const int tileH = mMap->getTileHeight();

    // Get the current tile pos and its offset
    const int tileX = (int)pos.x / tileW;
    const int tileY = (int)pos.y / tileH;
    int offsetX = (int)pos.x % tileW;
    int offsetY = (int)pos.y % tileH;

    // Get the walkability of every surrounding tiles.
    bool wTopLeft = mMap->getWalk(tileX - 1, tileY - 1, getWalkMask());
    bool wTop = mMap->getWalk(tileX, tileY - 1, getWalkMask());
    bool wTopRight = mMap->getWalk(tileX + 1, tileY - 1, getWalkMask());
    bool wLeft = mMap->getWalk(tileX - 1, tileY, getWalkMask());
    bool wRight = mMap->getWalk(tileX + 1, tileY, getWalkMask());
    bool wBottomLeft = mMap->getWalk(tileX - 1, tileY + 1, getWalkMask());
    bool wBottom = mMap->getWalk(tileX, tileY + 1, getWalkMask());
    bool wBottomRight = mMap->getWalk(tileX + 1, tileY + 1, getWalkMask());

    // Make diagonals unwalkable when both straight directions are blocking
    if (!wTop)
    {
        if (!wRight)
            wTopRight = false;
        if (!wLeft)
            wTopLeft = false;
    }
    if (!wBottom)
    {
        if (!wRight)
            wBottomRight = false;
        if (!wLeft)
            wBottomLeft = false;
    }

    // We'll make tests for each desired direction

    // Handle diagonal cases by setting the way back to a straight direction
    // when necessary.
    if (dx && dy)
    {
        // Going top-right
        if (dx > 0 && dy < 0)
        {
            // Choose a straight direction when diagonal target is blocked
            if (!wTop && wRight)
                dy = 0;
            else if (wTop && !wRight)
                dx = 0;
            else if (!wTop && !wRight)
                return Position(tileX * tileW + tileW
                                - getCollisionRadius(),
                                tileY * tileH + getCollisionRadius());
            else if (!wTopRight)
            {
                // Both straight direction are walkable
                // Go right when below the corner
                if (offsetY >=
                    (offsetX / tileH - (offsetX / tileW * tileH)))
                    dy = 0;
                else // Go up otherwise
                    dx = 0;
            }
            else // The top-right diagonal is walkable
            {
                return mMap->checkNodeOffsets(getCollisionRadius(),
                                                getWalkMask(),
                                                Position((int)pos.x + tileW,
                                                        (int)pos.y - tileH));
            }
        }

        // Going top-left
        if (dx < 0 && dy < 0)
        {
            // Choose a straight direction when diagonal target is blocked
            if (!wTop && wLeft)
                dy = 0;
            else if (wTop && !wLeft)
                dx = 0;
            else if (!wTop && !wLeft)
                return Position(tileX * tileW + getCollisionRadius(),
                                tileY * tileH + getCollisionRadius());
            else if (!wTopLeft)
            {
                // Go left when below the corner
                if (offsetY >= (offsetX / mMap->getTileWidth()
                    * mMap->getTileHeight()))
                    dy = 0;
                else // Go up otherwise
                    dx = 0;
            }
            else // The diagonal is walkable
                return mMap->checkNodeOffsets(getCollisionRadius(),
                                              getWalkMask(),
                                    Position((int)pos.x - tileW,
                                             (int)pos.y - tileH));
        }

        // Going bottom-left
        if (dx < 0 && dy > 0)
        {
            // Choose a straight direction when diagonal target is blocked
            if (!wBottom && wLeft)
                dy = 0;
            else if (wBottom && !wLeft)
                dx = 0;
            else if (!wBottom && !wLeft)
                return Position(tileX * tileW + getCollisionRadius(),
                                tileY * tileH + tileH - getCollisionRadius());
            else if (!wBottomLeft)
            {
                // Both straight direction are walkable
                // Go down when below the corner
                if (offsetY >= (offsetX / mMap->getTileHeight()
                    - (offsetX / mMap->getTileWidth()
                        * mMap->getTileHeight()) ))
                    dx = 0;
                else // Go left otherwise
                    dy = 0;
            }
            else // The diagonal is walkable
                return mMap->checkNodeOffsets(getCollisionRadius(),
                                              getWalkMask(),
                                              Position((int)pos.x - tileW,
                                                       (int)pos.y + tileH));
        }

        // Going bottom-right
        if (dx > 0 && dy > 0)
        {
            // Choose a straight direction when diagonal target is blocked
            if (!wBottom && wRight)
                dy = 0;
            else if (wBottom && !wRight)
                dx = 0;
            else if (!wBottom && !wRight)
                return Position(tileX * tileW + tileW - getCollisionRadius(),
                                tileY * tileH + tileH - getCollisionRadius());
            else if (!wBottomRight)
            {
                // Both straight direction are walkable
                // Go down when below the corner
                if (offsetY >= (offsetX / mMap->getTileWidth()
                    * mMap->getTileHeight()))
                    dx = 0;
                else // Go right otherwise
                    dy = 0;
            }
            else // The diagonal is walkable
                return mMap->checkNodeOffsets(getCollisionRadius(),
                                              getWalkMask(),
                                              Position((int)pos.x + tileW,
                                                       (int)pos.y + tileH));
        }

    } // End of diagonal cases

    // Straight directions
    // Right direction
    if (dx > 0 && !dy)
    {
        // If the straight destination is blocked,
        // Make the player go the closest possible.
        if (!wRight)
            return Position(tileX * tileW + tileW - getCollisionRadius(),
                            (int)pos.y);
        else
        {
            if (!wTopRight)
            {
                // If we're going to collide with the top-right corner
                if (offsetY - getCollisionRadius() < 0)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW
                                    + tileW - getCollisionRadius(),
                                    tileY * tileH + getCollisionRadius());

                }
            }

            if (!wBottomRight)
            {
                // If we're going to collide with the bottom-right corner
                if (offsetY + getCollisionRadius() > tileH)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW
                                    + tileW - getCollisionRadius(),
                                    tileY * tileH
                                    + tileH - getCollisionRadius());

                }
            }
            // If the way is clear, step up one checked tile ahead.
            return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
                                       Position((int)pos.x + tileW,
                                                (int)pos.y));
        }
    }

    // Left direction
    if (dx < 0 && !dy)
    {
        // If the straight destination is blocked,
        // Make the player go the closest possible.
        if (!wLeft)
            return Position(tileX * tileW + getCollisionRadius(), (int)pos.y);
        else
        {
            if (!wTopLeft)
            {
                // If we're going to collide with the top-left corner
                if (offsetY - getCollisionRadius() < 0)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW + getCollisionRadius(),
                                    tileY * tileH + getCollisionRadius());

                }
            }

            if (!wBottomLeft)
            {
                // If we're going to collide with the bottom-left corner
                if (offsetY + getCollisionRadius() > tileH)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW + getCollisionRadius(),
                                    tileY * tileH
                                    + tileH - getCollisionRadius());

                }
            }
            // If the way is clear, step up one checked tile ahead.
            return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
                                       Position((int)pos.x - tileW,
                                                (int)pos.y));
        }
    }

    // Up direction
    if (!dx && dy < 0)
    {
        // If the straight destination is blocked,
        // Make the player go the closest possible.
        if (!wTop)
            return Position((int)pos.x, tileY * tileH + getCollisionRadius());
        else
        {
            if (!wTopLeft)
            {
                // If we're going to collide with the top-left corner
                if (offsetX - getCollisionRadius() < 0)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW + getCollisionRadius(),
                                    tileY * tileH + getCollisionRadius());

                }
            }

            if (!wTopRight)
            {
                // If we're going to collide with the top-right corner
                if (offsetX + getCollisionRadius() > tileW)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW
                                    + tileW - getCollisionRadius(),
                                    tileY * tileH + getCollisionRadius());

                }
            }
            // If the way is clear, step up one checked tile ahead.
            return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
                                          Position((int)pos.x,
                                                   (int)pos.y - tileH));
        }
    }

    // Down direction
    if (!dx && dy > 0)
    {
        // If the straight destination is blocked,
        // Make the player go the closest possible.
        if (!wBottom)
            return Position((int)pos.x, tileY * tileH
                            + tileH - getCollisionRadius());
        else
        {
            if (!wBottomLeft)
            {
                // If we're going to collide with the bottom-left corner
                if (offsetX - getCollisionRadius() < 0)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW + getCollisionRadius(),
                                    tileY * tileH
                                    + tileH - getCollisionRadius());
                }
            }

            if (!wBottomRight)
            {
                // If we're going to collide with the bottom-right corner
                if (offsetX + getCollisionRadius() > tileW)
                {
                    // We make the player corrects its offset
                    // before going further
                    return Position(tileX * tileW
                                    + tileW - getCollisionRadius(),
                                    tileY * tileH
                                    + tileH - getCollisionRadius());

                }
            }
            // If the way is clear, step up one checked tile ahead.
            return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
                                          Position((int)pos.x,
                                                   (int)pos.y + tileH));
        }
    }

    // Return the current position if everything else has failed.
    return Position((int)pos.x, (int)pos.y);
}

void LocalPlayer::nextTile(unsigned char dir = 0)
{
    if (!mMap || !dir)
        return;

    const Vector &pos = getPosition();
    Position destination = getNextWalkPosition(dir);

    if ((int)pos.x != destination.x
        || (int)pos.y != destination.y)
    {
        lookAt(destination);
        setDestination(destination.x, destination.y);
    }
    else if (dir != mDirection)
    {
        // If the being can't move, just change direction
        Net::getPlayerHandler()->setDirection(dir);
        setDirection(dir);
    }
}

bool LocalPlayer::checkInviteRights(const std::string &guildName)
{
    if (Guild *guild = getGuild(guildName))
        return guild->getInviteRights();

    return false;
}

void LocalPlayer::inviteToGuild(Being *being)
{
    if (being->getType() != PLAYER)
        return;

    // TODO: Allow user to choose which guild to invite being to
    // For now, just invite to the first guild you have permissions to invite with
    auto itr = mGuilds.begin();
    auto itr_end = mGuilds.end();
    for (; itr != itr_end; ++itr)
    {
        if (checkInviteRights(itr->second->getName()))
        {
            Net::getGuildHandler()->invite(itr->second->getId(), being);
            return;
        }
    }
}

void LocalPlayer::pickUp(FloorItem *item)
{
    if (!item)
        return;

    cancelGoToTarget();

    if (withinRange(item, Net::getGameHandler()->getPickupRange()))
    {
        Net::getPlayerHandler()->pickUp(item);
        // We found it, so set the player direction to it
        // if the player does not move
        if (getDestination() == getPosition())
            lookAt(item->getPosition());
        mPickUpTarget = nullptr;
    }
    else
    {
        pathSetByMouse();
        setDestination(item->getPixelX(), item->getPixelY());
        mPickUpTarget = item;
    }
}

Being *LocalPlayer::getTarget() const
{
    return mTarget;
}

void LocalPlayer::setTarget(Being *target)
{
    if ((mLastTargetTime != -1 || target == this) && target)
        return;

    if (target)
        mLastTargetTime = tick_time;

    if (target == mTarget)
        return;

    if (target || mAction == ATTACK)
    {
        mTargetTime = tick_time;
    }
    else
    {
        mKeepAttacking = false;
        mTargetTime = -1;
    }

    Being *oldTarget = nullptr;
    if (mTarget)
    {
        mTarget->untarget();
        oldTarget = mTarget;
    }

    if (mTarget && mTarget->getType() == ActorSprite::MONSTER)
        mTarget->setShowName(false);

    mTarget = target;

    if (oldTarget)
        oldTarget->updateName();
    if (mTarget)
        mTarget->updateName();

    if (target && target->getType() == ActorSprite::MONSTER)
        target->setShowName(true);
}

void LocalPlayer::setDestination(int x, int y)
{
    if (!mMap)
        return;

    int srcX = x;
    int srcY = y;
    int dstX = (int)mDest.x;
    int dstY = (int)mDest.y;
    int tileWidth = mMap->getTileWidth();
    int tileHeight = mMap->getTileHeight();
    if (!Net::getPlayerHandler()->usePixelPrecision())
    {
        // For tile-based clients, we accept positions on the same tile.
        srcX = srcX / tileWidth;
        srcY = srcY / tileHeight;
        dstX = dstX / tileWidth;
        dstY = dstY / tileHeight;
    }

    // Only send a new message to the server when destination changes
    if (srcX != dstX || srcY != dstY)
    {
        Being::setDestination(x, y);
        // Note: Being::setDestination() updates mDest, so we get the new
        // destination.
        dstX = (int)mDest.x;
        dstY = (int)mDest.y;

        if (!Net::getPlayerHandler()->usePixelPrecision())
        {
            dstX = dstX / tileWidth;
            dstY = dstY / tileHeight;
        }

        // If the destination given to being class is accepted,
        // we inform the Server.
        if (srcX == dstX && srcY == dstY)
            Net::getPlayerHandler()->setDestination(x, y, mDirection);
    }
}

void LocalPlayer::setWalkingDir(int dir)
{
    // This function is called by Game::handleInput()

    // Don't compute a new path before the last one set by keyboard is finished.
    // This permits to avoid movement glitches and server spamming.
    const Vector &pos = getPosition();
    const Vector &dest = getDestination();
    if (!isPathSetByMouse() && (pos.x != dest.x || pos.y != dest.y))
        return;

    // If the player is pressing a key, and its different from what he has
    // been pressing, stop (do not send this stop to the server) and
    // start in the new direction
    if (dir && (dir != getWalkingDir()))
        local_player->stopWalking(false);

    // Else, he is not pressing a key,
    // and the current path hasn't been sent by mouse,
    // then let the path die (1/2 tile after that.)
    // This permit to avoid desyncs with other clients.
    else if (!dir)
        return;

    cancelGoToTarget();

    mWalkingDir = dir;

    // If we're not already walking, start walking.
    if (mAction != MOVE && dir)
    {
        startWalking(dir);
    }
    else if (mAction == MOVE)
    {
        nextTile(dir);
    }
}

void LocalPlayer::startWalking(unsigned char dir)
{
    if (!mMap || !dir)
        return;

    if (mAction == MOVE && !mPath.empty())
    {
        // Just finish the current action, otherwise we get out of sync
        const Vector &pos = getPosition();
        Being::setDestination(pos.x, pos.y);
        return;
    }

    int dx = 0, dy = 0;
    if (dir & UP)
        dy--;
    if (dir & DOWN)
        dy++;
    if (dir & LEFT)
        dx--;
    if (dir & RIGHT)
        dx++;

    nextTile(dir);
}

void LocalPlayer::stopWalking(bool sendToServer)
{
    if (mAction == MOVE)
    {
        mWalkingDir = 0;

        setDestination((int) getPosition().x, (int) getPosition().y);
        if (sendToServer)
             Net::getPlayerHandler()->setDestination((int) getPosition().x,
                                                     (int) getPosition().y);
        setAction(STAND);
    }

    // No path set anymore, so we reset the path by mouse flag
    mPathSetByMouse = false;

    clearPath();
}

void LocalPlayer::toggleSit()
{
    if (mLastActionTime != -1)
        return;
    mLastActionTime = tick_time;

    Being::Action newAction;
    switch (mAction)
    {
        case STAND: newAction = SIT; break;
        case SIT: newAction = STAND; break;
        default: return;
    }

    Net::getPlayerHandler()->changeAction(newAction);
}

void LocalPlayer::emote(int emoteId)
{
    if (mLastActionTime != -1)
        return;
    mLastActionTime = tick_time;

    Net::getPlayerHandler()->emote(emoteId);
}

void LocalPlayer::attack(Being *target, bool keep)
{
    if (mLastActionTime != -1)
        return;

    // Can only attack when standing still
    if (mAction != STAND && mAction != ATTACK)
        return;

    if (!target || target->getType() == ActorSprite::NPC)
        return;

    // Can't attack more times than its attack speed
    static int lastAttackTime = 0;
    if (get_elapsed_time(lastAttackTime) < mAttackSpeed)
        return;

    lastAttackTime = tick_time;

    mKeepAttacking = keep;

    if (mTarget != target || !mTarget)
    {
        mLastTargetTime = -1;
        setTarget(target);
    }

    lookAt(mTarget->getPosition());

    mLastActionTime = tick_time;

    setAction(ATTACK);

    if (mEquippedWeapon)
    {
        std::string soundFile = mEquippedWeapon->getSound(EQUIP_EVENT_STRIKE);
        if (!soundFile.empty())
            sound.playSfx(soundFile);
    }
    else
    {
        sound.playSfx(paths.getValue("attackSfxFile", "fist-swish.ogg"));
    }

    Net::getPlayerHandler()->attack(target->getId());
}

void LocalPlayer::stopAttack()
{
    if (mTarget)
    {
        if (mAction == ATTACK)
            setAction(STAND);
        setTarget(nullptr);
    }
    mLastTargetTime = -1;
    cancelGoToTarget();
}

void LocalPlayer::pickedUp(const ItemInfo &itemInfo, int amount,
                           unsigned char fail)
{
    if (fail)
    {
        const char* msg;
        switch (fail)
        {
            case PICKUP_BAD_ITEM:
                msg = N_("Tried to pick up nonexistent item."); break;
            case PICKUP_TOO_HEAVY: msg = N_("Item is too heavy."); break;
            case PICKUP_TOO_FAR: msg = N_("Item is too far away"); break;
            case PICKUP_INV_FULL: msg = N_("Inventory is full."); break;
            case PICKUP_STACK_FULL: msg = N_("Stack is too big."); break;
            case PICKUP_DROP_STEAL:
                msg = N_("Item belongs to someone else."); break;
            default: msg = N_("Unknown problem picking up item."); break;
        }
        if (config.getValue("showpickupchat", 1))
        {
            SERVER_NOTICE(_(msg))
        }
        if (mMap && config.getBoolValue("showpickupparticle"))
        {
            // Show pickup notification
            addMessageToQueue(_(msg), UserPalette::PICKUP_INFO);
        }
    }
    else
    {
        if (config.getBoolValue("showpickupchat"))
        {
            // TRANSLATORS: This sentence may be translated differently
            // for different grammatical numbers (singular, plural, ...)
            SERVER_NOTICE(strprintf(ngettext("You picked up %d "
                    "[@@%d|%s@@].", "You picked up %d [@@%d|%s@@].", amount),
                    amount, itemInfo.getId(), itemInfo.getName().c_str()))
        }

        if (mMap && config.getBoolValue("showpickupparticle"))
        {
            // Show pickup notification
            std::string msg;
            if (amount > 1)
                msg = strprintf("%i ", amount);
            msg += itemInfo.getName();
            addMessageToQueue(msg, UserPalette::PICKUP_INFO);
        }
    }
}

void LocalPlayer::setAttackRange(int range)
{
    // When the range is more than the minimal, we accept it
    if (range > -1)
    {
        mAttackRange = range;
    }
    else if (Net::getNetworkType() == ServerType::TMWATHENA)
    {
        // TODO: Fix this to be more generic
        Item *weapon = PlayerInfo::getEquipment(TmwAthena::EQUIP_FIGHT1_SLOT);
        if (weapon)
        {
            const ItemInfo info = weapon->getInfo();
            if (info.getAttackRange() > -1)
                mAttackRange = info.getAttackRange();
        }
    }
}

bool LocalPlayer::withinRange(Actor *target, int range) const
{
    if (!target || range < 0)
        return false;

    const Vector &targetPos = target->getPosition();
    const Vector &pos = getPosition();
    const int dx = abs(targetPos.x - pos.x);
    const int dy = abs(targetPos.y - pos.y);
    return !(dx > range || dy > range);
}

void LocalPlayer::setGotoTarget(Being *target)
{
    if (!target)
        return;

    mLastTargetTime = -1;

    setTarget(target);
    mGoingToTarget = true;
    mKeepAttacking = true;

    pathSetByMouse();
    setDestination(target->getPosition());
}

void LocalPlayer::addMessageToQueue(const std::string &message, int color)
{
    mMessages.push_back(MessagePair(message, color));
}

void LocalPlayer::event(Event::Channel channel, const Event &event)
{
    if (channel == Event::ActorSpriteChannel)
    {
        if (event.getType() == Event::Destroyed)
        {
            ActorSprite *actor = event.getActor("source");

            if (mPickUpTarget == actor)
                mPickUpTarget = nullptr;

            if (mTarget == actor)
                mTarget = nullptr;
        }
    }
    else if (channel == Event::AttributesChannel)
    {
        if (event.getType() == Event::UpdateAttribute)
        {
            if (event.getInt("id") == EXP)
            {
                int change = 0;
                int oldXp = event.getInt("oldValue");
                int newXp = event.getInt("newValue");

                // When the new XP is lower than the old one,
                // it means that a new level has been reached.
                // Thus, the xp difference can only be obtained
                // with the exp needed for the next level.
                // The new XP value is then the XP obtained for the new level.
                if (newXp < oldXp)
                {
                    change = PlayerInfo::getAttribute(EXP_NEEDED)
                        - oldXp + newXp;
                }
                else
                {
                    change = newXp - oldXp;
                }

                if (change > 0)
                    addMessageToQueue(toString(change) + " xp");
            }
        }
    }
    else if (channel == Event::ConfigChannel)
    {
        if (event.getType() == Event::ConfigOptionChanged &&
            event.getString("option") == "showownname")
        {
            setShowName(config.getValue("showownname", 1));
        }

    }

    Being::event(channel, event);
}

void LocalPlayer::changeAwayMode()
{
    mAwayMode = !mAwayMode;
    mAfkTime = 0;
    if (mAwayMode)
    {
        mAwayDialog = new OkDialog(_("Away"),
                config.getValue("afkMessage", "I am away from keyboard"));
        mAwayDialog->addActionListener(mAwayListener);
    }

    mAwayDialog = nullptr;
}

void LocalPlayer::setAway(const std::string &message)
{
    if (!message.empty())
        config.setValue("afkMessage", message);
    changeAwayMode();
}

void LocalPlayer::afkRespond(ChatTab *tab, const std::string &nick)
{
    if (mAwayMode)
    {
        if (mAfkTime == 0
            || cur_time < mAfkTime
            || cur_time - mAfkTime > AWAY_LIMIT_TIMER)
        {
            std::string msg = "*AFK*: "
                    + config.getValue("afkMessage", "I am away from keyboard");

            Net::getChatHandler()->privateMessage(nick, msg);
            if (!tab)
            {
                localChatTab->chatLog(getName() + " : " + msg,
                                      ACT_WHISPER, false);
            }
            else
            {
                tab->chatLog(getName(), msg);
            }
            mAfkTime = cur_time;
        }
    }
}

void AwayListener::action(const gcn::ActionEvent &event)
{
    if (event.getId() == "ok" && local_player->getAwayMode())
    {
        local_player->changeAwayMode();
    }
}