/*
 *  The ManaPlus Client
 *  Copyright (C) 2004-2009  The Mana World Development Team
 *  Copyright (C) 2009-2010  The Mana Developers
 *  Copyright (C) 2011-2015  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 "actormanager.h"

#include "game.h"
#include "configuration.h"
#include "settings.h"

#include "being/playerinfo.h"
#include "being/playerrelations.h"

#include "gui/viewport.h"

#include "gui/widgets/tabs/chat/chattab.h"

#include "gui/windows/chatwindow.h"
#include "gui/windows/equipmentwindow.h"
#include "gui/windows/socialwindow.h"
#include "gui/windows/questswindow.h"

#include "input/inputmanager.h"

#include "utils/checkutils.h"
#include "utils/mathutils.h"
#include "utils/gettext.h"

#include "net/beinghandler.h"
#include "net/charserverhandler.h"
#include "net/packetlimiter.h"
#include "net/playerhandler.h"
#include "net/serverfeatures.h"

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

#include "resources/map/map.h"

#include "resources/db/itemdb.h"

#include <algorithm>

#include "debug.h"

#define for_actors for (ActorSpritesConstIterator it = mActors.begin(), \
    it_end = mActors.end() ; it != it_end; ++it)

#define for_actorsm for (ActorSpritesIterator it = mActors.begin(), \
    it_end = mActors.end() ; it != it_end; ++it)

ActorManager *actorManager = nullptr;

class FindBeingFunctor final
{
    public:
        bool operator() (const ActorSprite *const actor) const
        {
            if (!actor
                || actor->getType() == ActorType::FloorItem
                || actor->getType() == ActorType::Portal)
            {
                return false;
            }
            const Being *const b = static_cast<const Being* const>(actor);

            const unsigned other_y = y
                + ((b->getType() == ActorType::Npc) ? 1 : 0);
            const Vector &pos = b->getPosition();
            return (static_cast<unsigned>(pos.x) / mapTileSize == x &&
                (static_cast<unsigned>(pos.y) / mapTileSize == y
                || static_cast<unsigned>(pos.y) / mapTileSize == other_y) &&
                b->isAlive() && (type == ActorType::Unknown
                || b->getType() == type));
        }

        uint16_t x, y;
        ActorTypeT type;
} beingActorFinder;

class FindBeingEqualFunctor final
{
    public:
        bool operator() (const Being *const being) const
        {
            if (!being || !findBeing)
                return false;
            return being->getId() == findBeing->getId();
        }

        Being *findBeing;
} beingEqualActorFinder;

class SortBeingFunctor final
{
    public:
        bool operator() (const Being *const being1,
                         const Being *const being2) const
        {
            if (!being1 || !being2)
                return false;

            if (priorityBeings)
            {
                int w1 = defaultPriorityIndex;
                int w2 = defaultPriorityIndex;
                const StringIntMapCIter it1 = priorityBeings->find(
                    being1->getName());
                const StringIntMapCIter it2 = priorityBeings->find(
                    being2->getName());
                if (it1 != priorityBeings->end())
                    w1 = (*it1).second;
                if (it2 != priorityBeings->end())
                    w2 = (*it2).second;

                if (w1 != w2)
                    return w1 < w2;
            }
            if (being1->getDistance() != being2->getDistance())
            {
                if (specialDistance && being1->getDistance() <= 2
                    && being2->getDistance() <= attackRange
                    && being2->getDistance() > 2)
                {
                    return false;
                }
                else if (specialDistance && being2->getDistance() <= 2
                         && being1->getDistance() <= attackRange
                         && being1->getDistance() > 2)
                {
                    return true;
                }
                return being1->getDistance() < being2->getDistance();
            }

            const int d1 = abs(being1->getTileX() - x)
                + abs(being1->getTileY() - y);
            const int d2 = abs(being2->getTileX() - x)
                + abs(being2->getTileY() - y);

            if (d1 != d2)
                return d1 < d2;
            if (attackBeings)
            {
                int w1 = defaultAttackIndex;
                int w2 = defaultAttackIndex;
                const StringIntMapCIter it1 = attackBeings->find(
                    being1->getName());
                const StringIntMapCIter it2 = attackBeings->find(
                    being2->getName());
                if (it1 != attackBeings->end())
                    w1 = (*it1).second;
                if (it2 != attackBeings->end())
                    w2 = (*it2).second;

                if (w1 != w2)
                    return w1 < w2;
            }

            return (being1->getName() < being2->getName());
        }
        StringIntMap *attackBeings;
        StringIntMap *priorityBeings;
        int x;
        int y;
        int defaultAttackIndex;
        int defaultPriorityIndex;
        int attackRange;
        bool specialDistance;
} beingActorSorter;

ActorManager::ActorManager() :
    mActors(),
    mDeleteActors(),
    mBlockedBeings(),
    mMap(nullptr),
    mSpellHeal1(serverConfig.getValue("spellHeal1", "#lum")),
    mSpellHeal2(serverConfig.getValue("spellHeal2", "#inma")),
    mSpellItenplz(serverConfig.getValue("spellItenplz", "#itenplz")),
    mTargetDeadPlayers(config.getBoolValue("targetDeadPlayers")),
    mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")),
    mCyclePlayers(config.getBoolValue("cyclePlayers")),
    mCycleMonsters(config.getBoolValue("cycleMonsters")),
    mCycleNPC(config.getBoolValue("cycleNPC")),
    mExtMouseTargeting(config.getBoolValue("extMouseTargeting")),
    mPriorityAttackMobs(),
    mPriorityAttackMobsSet(),
    mPriorityAttackMobsMap(),
    mAttackMobs(),
    mAttackMobsSet(),
    mAttackMobsMap(),
    mIgnoreAttackMobs(),
    mIgnoreAttackMobsSet(),
    mPickupItems(),
    mPickupItemsSet(),
    mPickupItemsMap(),
    mIgnorePickupItems(),
    mIgnorePickupItemsSet()
{
    config.addListener("targetDeadPlayers", this);
    config.addListener("targetOnlyReachable", this);
    config.addListener("cyclePlayers", this);
    config.addListener("cycleMonsters", this);
    config.addListener("cycleNPC", this);
    config.addListener("extMouseTargeting", this);
    config.addListener("showBadges", this);

    loadAttackList();
}

ActorManager::~ActorManager()
{
    config.removeListeners(this);
    CHECKLISTENERS
    storeAttackList();
    clear();
}

void ActorManager::setMap(Map *const map)
{
    mMap = map;

    if (localPlayer)
        localPlayer->setMap(map);
}

void ActorManager::setPlayer(LocalPlayer *const player)
{
    localPlayer = player;
    mActors.insert(player);
    if (socialWindow)
        socialWindow->updateAttackFilter();
    if (socialWindow)
        socialWindow->updatePickupFilter();
}

Being *ActorManager::createBeing(const BeingId id,
                                 const ActorTypeT type,
                                 const BeingTypeId subtype)
{
    Being *const being = new Being(id, type, subtype, mMap);

    mActors.insert(being);
    if (type == ActorType::Player
#ifdef EATHENA_SUPPORT
        || type == ActorType::Mercenary
        || type == ActorType::Pet
#endif
        || type == ActorType::Npc)
    {
        being->updateFromCache();
        if (beingHandler)
            beingHandler->requestNameById(id);
        if (localPlayer)
            localPlayer->checkNewName(being);
    }
    else if (type == ActorType::Monster)
    {
        if (serverFeatures && serverFeatures->haveMonsterName())
            beingHandler->requestNameById(id);
    }
    else if (type == ActorType::Portal)
    {
        if (beingHandler &&
            serverFeatures &&
            serverFeatures->haveServerWarpNames())
        {
            beingHandler->requestNameById(id);
        }
    }
    if (type == ActorType::Player)
    {
        if (socialWindow)
            socialWindow->updateActiveList();
    }
    else if (type == ActorType::Npc)
    {
        if (questsWindow)
            questsWindow->addEffect(being);
    }
    return being;
}

FloorItem *ActorManager::createItem(const BeingId id,
                                    const int itemId,
                                    const int x, const int y,
                                    const int itemType,
                                    const int amount,
                                    const int refine,
                                    const ItemColor color,
                                    const Identified identified,
                                    const Damaged damaged,
                                    const int subX, const int subY,
                                    const int *const cards)
{
    FloorItem *const floorItem = new FloorItem(id,
        itemId,
        x, y,
        itemType,
        amount,
        refine,
        color,
        identified,
        damaged,
        cards);
    floorItem->postInit(mMap, subX, subY);

    if (!checkForPickup(floorItem))
        floorItem->disableHightlight();
    mActors.insert(floorItem);
    return floorItem;
}

void ActorManager::destroy(ActorSprite *const actor)
{
    if (!actor || actor == localPlayer)
        return;

    mDeleteActors.insert(actor);
}

void ActorManager::erase(ActorSprite *const actor)
{
    if (!actor || actor == localPlayer)
        return;

    mActors.erase(actor);
}

void ActorManager::undelete(const ActorSprite *const actor)
{
    if (!actor || actor == localPlayer)
        return;

    FOR_EACH (ActorSpritesConstIterator, it, mDeleteActors)
    {
        if (*it == actor)
        {
            mDeleteActors.erase(*it);
            return;
        }
    }
}

Being *ActorManager::findBeing(const BeingId id) const
{
    for_actors
    {
        ActorSprite *const actor = *it;
        if (actor->getId() == id &&
            actor->getType() != ActorType::FloorItem)
        {
            return static_cast<Being*>(actor);
        }
    }

    return nullptr;
}

Being *ActorManager::findBeing(const int x, const int y,
                               const ActorTypeT type) const
{
    beingActorFinder.x = static_cast<uint16_t>(x);
    beingActorFinder.y = static_cast<uint16_t>(y);
    beingActorFinder.type = type;

    const ActorSpritesConstIterator it = std::find_if(
        mActors.begin(), mActors.end(), beingActorFinder);

    return (it == mActors.end()) ? nullptr : static_cast<Being*>(*it);
}

Being *ActorManager::findBeingByPixel(const int x, const int y,
                                      const AllPlayers allPlayers) const
{
    if (!mMap)
        return nullptr;

    const bool targetDead = mTargetDeadPlayers;
    const bool modActive = inputManager.isActionActive(
        InputAction::STOP_ATTACK);

    if (mExtMouseTargeting)
    {
        Being *tempBeing = nullptr;
        bool noBeing(false);

        for_actorsm
        {
            if (!*it)
                continue;

            if ((*it)->getType() == ActorType::Portal)
                continue;

            if ((*it)->getType() == ActorType::FloorItem)
            {
                if (!noBeing)
                {
                    const FloorItem *const floor
                        = static_cast<const FloorItem*>(*it);
                    const int px = floor->getPixelX();
                    const int py = floor->getPixelY();
                    if ((px - mapTileSize     <= x) &&
                        (px + mapTileSize     >  x) &&
                        (py - mapTileSize * 2 <= y) &&
                        (py + mapTileSize / 2 >  y))
                    {
                        noBeing = true;
                    }
                }
                continue;
            }

            Being *const being = static_cast<Being*>(*it);

            if (being->getInfo() && !(being->getInfo()->isTargetSelection()
                || modActive))
            {
                continue;
            }

            if ((being->mAction != BeingAction::DEAD
                || (targetDead && being->getType() == ActorType::Player))
                && (allPlayers == AllPlayers_true || being != localPlayer))
            {
                const int px = being->getPixelX();
                const int py = being->getPixelY();
                if ((px - mapTileSize / 2 <= x) &&
                    (px + mapTileSize / 2 >  x) &&
                    (py - mapTileSize     <= y) &&
                    (py                   >  y))
                {
                    return being;
                }
                else if (!noBeing &&
                         (px - mapTileSize     <= x) &&
                         (px + mapTileSize     >  x) &&
                         (py - mapTileSize * 2 <= y) &&
                         (py + mapTileSize / 2 >  y))
                {
                    if (tempBeing)
                        noBeing = true;
                    else
                        tempBeing = being;
                }
            }
        }

        if (noBeing)
            return nullptr;
        return tempBeing;
    }
    else
    {
        for_actorsm
        {
            if (!*it)
                continue;

            if ((*it)->getType() == ActorType::Portal ||
                (*it)->getType() == ActorType::FloorItem)
            {
                continue;
            }

            Being *const being = static_cast<Being*>(*it);

            if (being->getInfo() && !(being->getInfo()->isTargetSelection()
                || modActive))
            {
                continue;
            }

            const int px = being->getPixelX();
            const int py = being->getPixelY();
            if ((px - mapTileSize / 2 <= x) &&
                (px + mapTileSize / 2 >  x) &&
                (py - mapTileSize     <= y) &&
                (py                   >  y))
            {
                return being;
            }
        }
        return nullptr;
    }
}

void ActorManager::findBeingsByPixel(std::vector<ActorSprite*> &beings,
                                     const int x, const int y,
                                     const AllPlayers allPlayers) const
{
    if (!mMap)
        return;

    const int xtol = mapTileSize / 2;
    const int uptol = mapTileSize;
    const bool modActive = inputManager.isActionActive(
        InputAction::STOP_ATTACK);

    for_actors
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::Portal)
            continue;

        const Being *const being = dynamic_cast<const Being*>(*it);

        if (being && being->getInfo()
            && !(being->getInfo()->isTargetSelection() || modActive))
        {
            continue;
        }

        ActorSprite *const actor = *it;

        if ((being &&
            (being->isAlive() ||
            (mTargetDeadPlayers &&
            being->getType() == ActorType::Player)) &&
            (allPlayers == AllPlayers_true ||
            being != localPlayer)) ||
            actor->getType() == ActorType::FloorItem)
        {
            if ((actor->getPixelX() - xtol <= x) &&
                (actor->getPixelX() + xtol > x) &&
                (actor->getPixelY() - uptol <= y) &&
                (actor->getPixelY() > y))
            {
                beings.push_back(actor);
            }
        }
    }
}

Being *ActorManager::findPortalByTile(const int x, const int y) const
{
    if (!mMap)
        return nullptr;

    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getType() != ActorType::Portal)
            continue;

        Being *const being = static_cast<Being*>(*it);

        if (being->getTileX() == x && being->getTileY() == y)
            return being;
    }

    return nullptr;
}

FloorItem *ActorManager::findItem(const BeingId id) const
{
    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getId() == id &&
            (*it)->getType() == ActorType::FloorItem)
        {
            return static_cast<FloorItem*>(*it);
        }
    }

    return nullptr;
}

FloorItem *ActorManager::findItem(const int x, const int y) const
{
    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getTileX() == x && (*it)->getTileY() == y &&
            (*it)->getType() == ActorType::FloorItem)
        {
            return static_cast<FloorItem*>(*it);
        }
    }

    return nullptr;
}

bool ActorManager::pickUpAll(const int x1, const int y1,
                             const int x2, const int y2,
                             const bool serverBuggy)
{
    if (!localPlayer)
        return false;

    bool finded(false);
    const bool allowAll = mPickupItemsSet.find("") != mPickupItemsSet.end();
    if (!serverBuggy)
    {
        for_actorsm
        {
            if (!*it)
                continue;

            if ((*it)->getType() == ActorType::FloorItem
                && ((*it)->getTileX() >= x1 && (*it)->getTileX() <= x2)
                && ((*it)->getTileY() >= y1 && (*it)->getTileY() <= y2))
            {
                FloorItem *const item = static_cast<FloorItem*>(*it);
                if (allowAll)
                {
                    if (mIgnorePickupItemsSet.find(item->getName())
                        == mIgnorePickupItemsSet.end())
                    {
                        if (localPlayer->pickUp(item))
                            finded = true;
                    }
                }
                else
                {
                    if (mPickupItemsSet.find(item->getName())
                        != mPickupItemsSet.end())
                    {
                        if (localPlayer->pickUp(item))
                            finded = true;
                    }
                }
            }
        }
    }
    else if (PacketLimiter::checkPackets(PacketType::PACKET_PICKUP))
    {
        FloorItem *item = nullptr;
        unsigned cnt = 65535;
        for_actorsm
        {
            if (!*it)
                continue;

            if ((*it)->getType() == ActorType::FloorItem
                && ((*it)->getTileX() >= x1 && (*it)->getTileX() <= x2)
                && ((*it)->getTileY() >= y1 && (*it)->getTileY() <= y2))
            {
                FloorItem *const tempItem = static_cast<FloorItem*>(*it);
                if (tempItem->getPickupCount() < cnt)
                {
                    if (allowAll)
                    {
                        if (mIgnorePickupItemsSet.find(tempItem->getName())
                            == mIgnorePickupItemsSet.end())
                        {
                            item = tempItem;
                            cnt = item->getPickupCount();
                            if (cnt == 0)
                            {
                                item->incrementPickup();
                                localPlayer->pickUp(item);
                                return true;
                            }
                        }
                    }
                    else
                    {
                        if (mPickupItemsSet.find(tempItem->getName())
                            != mPickupItemsSet.end())
                        {
                            item = tempItem;
                            cnt = item->getPickupCount();
                            if (cnt == 0)
                            {
                                item->incrementPickup();
                                localPlayer->pickUp(item);
                                return true;
                            }
                        }
                    }
                }
            }
        }
        if (item && localPlayer->pickUp(item))
            finded = true;
    }
    return finded;
}

bool ActorManager::pickUpNearest(const int x, const int y,
                                 int maxdist) const
{
    if (!localPlayer)
        return false;

    maxdist = maxdist * maxdist;
    FloorItem *closestItem = nullptr;
    int dist = 0;
    const bool allowAll = mPickupItemsSet.find("") != mPickupItemsSet.end();

    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem)
        {
            FloorItem *const item = static_cast<FloorItem*>(*it);

            const int d = (item->getTileX() - x) * (item->getTileX() - x)
                + (item->getTileY() - y) * (item->getTileY() - y);

            if ((d < dist || !closestItem) && (!mTargetOnlyReachable
                || localPlayer->isReachable(item->getTileX(),
                item->getTileY(), false)))
            {
                if (allowAll)
                {
                    if (mIgnorePickupItemsSet.find(item->getName())
                        == mIgnorePickupItemsSet.end())
                    {
                        dist = d;
                        closestItem = item;
                    }
                }
                else
                {
                    if (mPickupItemsSet.find(item->getName())
                        != mPickupItemsSet.end())
                    {
                        dist = d;
                        closestItem = item;
                    }
                }
            }
        }
    }
    if (closestItem && dist <= maxdist)
        return localPlayer->pickUp(closestItem);

    return false;
}

Being *ActorManager::findBeingByName(const std::string &name,
                                     const ActorTypeT type) const
{
    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem
            || (*it)->getType() == ActorType::Portal)
        {
            continue;
        }

        Being *const being = static_cast<Being*>(*it);
        if (being->getName() == name &&
            (type == ActorType::Unknown || type == being->getType()))
        {
            return being;
        }
    }
    return nullptr;
}

Being *ActorManager::findNearestByName(const std::string &name,
                                       const ActorTypeT &type) const
{
    if (!localPlayer)
        return nullptr;

    int dist = 0;
    Being* closestBeing = nullptr;
    int x, y;

    x = localPlayer->getTileX();
    y = localPlayer->getTileY();

    for_actorsm
    {
        if (reportTrue(!*it))
            continue;

        if ((*it)->getType() == ActorType::FloorItem
            || (*it)->getType() == ActorType::Portal)
        {
            continue;
        }

        Being *const being = static_cast<Being*>(*it);

        if (being && being->getName() == name &&
            (type == ActorType::Unknown || type == being->getType()))
        {
            if (being->getType() == ActorType::Player)
            {
                return being;
            }
            else
            {
                const int d = (being->getTileX() - x) * (being->getTileX() - x)
                    + (being->getTileY() - y) * (being->getTileY() - y);

                if (validateBeing(nullptr, being, type, nullptr, 50)
                    && (d < dist || closestBeing == nullptr))
                {
                    dist = d;
                    closestBeing = being;
                }
            }
        }
    }
    return closestBeing;
}

const ActorSprites &ActorManager::getAll() const
{
    return mActors;
}

void ActorManager::logic()
{
    BLOCK_START("ActorManager::logic")
    for_actors
    {
        if (*it)
            (*it)->logic();
    }

    if (mDeleteActors.empty())
    {
        BLOCK_END("ActorManager::logic")
        return;
    }

    BLOCK_START("ActorManager::logic 1")
    FOR_EACH (ActorSpritesConstIterator, it, mDeleteActors)
    {
        const ActorSprite *const actor = *it;
        const ActorTypeT &type = actor->getType();
        if (type == ActorType::Player)
        {
            const Being *const being = static_cast<const Being*>(actor);
            being->addToCache();
            if (beingEquipmentWindow)
                beingEquipmentWindow->resetBeing(being);
        }
        if (localPlayer)
        {
            if (localPlayer->getTarget() == actor)
                localPlayer->setTarget(nullptr);
            if (localPlayer->getPickUpTarget() == actor)
                localPlayer->unSetPickUpTarget();
        }
        if (viewport)
            viewport->clearHover(*it);
    }

    FOR_EACH (ActorSpritesConstIterator, it, mDeleteActors)
    {
        ActorSprite *actor = *it;
        mActors.erase(actor);
        delete actor;
    }

    mDeleteActors.clear();
    BLOCK_END("ActorManager::logic 1")
    BLOCK_END("ActorManager::logic")
}

void ActorManager::clear()
{
    if (beingEquipmentWindow)
        beingEquipmentWindow->setBeing(nullptr);

    if (localPlayer)
    {
        localPlayer->setTarget(nullptr);
        localPlayer->unSetPickUpTarget();
        mActors.erase(localPlayer);
    }

    for_actors
        delete *it;
    mActors.clear();
    mDeleteActors.clear();

    if (localPlayer)
        mActors.insert(localPlayer);
}

Being *ActorManager::findNearestPvpPlayer() const
{
    if (!localPlayer)
        return nullptr;

    // don't attack players
    if (settings.pvpAttackType == 3)
        return nullptr;

    const Game *const game = Game::instance();
    if (!game)
        return nullptr;

    const Map *const map = game->getCurrentMap();
    if (!map)
        return nullptr;

    const int mapPvpMode = map->getPvpMode();
    Being *target = nullptr;
    int minDistSquared = 20000;

    for_actors
    {
        if ((*it)->getType() != ActorType::Player)
            continue;

        Being *const being = static_cast<Being*>(*it);

        if (!being || !being->isAlive() ||
            localPlayer == being)
        {
            continue;
        }

        const int teamId = being->getTeamId();
        // this condition is very TMW-specific
        if (!(mapPvpMode || teamId))
            continue;

        if (!localPlayer->checAttackPermissions(being))
            continue;

        const int dx = being->getTileX() - localPlayer->getTileX();
        const int dy = being->getTileY() - localPlayer->getTileY();
        const int distSquared = dx * dx + dy * dy;
        if (distSquared < minDistSquared)
        {
            minDistSquared = distSquared;
            target = being;
        }
    }

    return target;
}


Being *ActorManager::findNearestLivingBeing(const int x, const int y,
                                            const int maxTileDist,
                                            const ActorTypeT type,
                                            const Being *const excluded) const
{
    const int maxDist = maxTileDist * mapTileSize;

    return findNearestLivingBeing(nullptr, maxDist,
        type,
        x, y,
        excluded,
        AllowSort_true);
}

Being *ActorManager::findNearestLivingBeing(const Being *const aroundBeing,
                                            const int maxDist,
                                            const ActorTypeT type,
                                            const AllowSort allowSort) const
{
    if (!aroundBeing)
        return nullptr;

    return findNearestLivingBeing(aroundBeing,
        maxDist,
        type,
        aroundBeing->getTileX(),
        aroundBeing->getTileY(),
        aroundBeing,
        allowSort);
}

Being *ActorManager::findNearestLivingBeing(const Being *const aroundBeing,
                                            int maxDist,
                                            const ActorTypeT &type,
                                            const int x, const int y,
                                            const Being *const excluded,
                                            const AllowSort allowSort) const
{
    if (!aroundBeing || !localPlayer)
        return nullptr;

    std::set<std::string> attackMobs;
    std::set<std::string> priorityMobs;
    std::set<std::string> ignoreAttackMobs;
    StringIntMap attackMobsMap;
    StringIntMap priorityMobsMap;
    int defaultAttackIndex = 10000;
    int defaultPriorityIndex = 10000;
    const int attackRange = localPlayer->getAttackRange();

    bool specialDistance = false;
    if (settings.moveToTargetType == 11
        && localPlayer->getAttackRange() > 2)
    {
        specialDistance = true;
    }

    maxDist = maxDist * maxDist;

    const bool cycleSelect = allowSort == AllowSort_true
        && ((mCyclePlayers && type == ActorType::Player)
        || (mCycleMonsters && type == ActorType::Monster)
        || (mCycleNPC && type == ActorType::Npc));

    const bool filtered = allowSort == AllowSort_true
        && config.getBoolValue("enableAttackFilter")
        && type == ActorType::Monster;
    const bool modActive = inputManager.isActionActive(
        InputAction::STOP_ATTACK);

    bool ignoreDefault = false;
    if (filtered)
    {
        attackMobs = mAttackMobsSet;
        priorityMobs = mPriorityAttackMobsSet;
        ignoreAttackMobs = mIgnoreAttackMobsSet;
        attackMobsMap = mAttackMobsMap;
        priorityMobsMap = mPriorityAttackMobsMap;
        beingActorSorter.attackBeings = &attackMobsMap;
        beingActorSorter.priorityBeings = &priorityMobsMap;
        beingActorSorter.specialDistance = specialDistance;
        beingActorSorter.attackRange = attackRange;
        if (ignoreAttackMobs.find("") != ignoreAttackMobs.end())
            ignoreDefault = true;
        StringIntMapCIter itr = attackMobsMap.find("");
        if (itr != attackMobsMap.end())
            defaultAttackIndex = (*itr).second;
        itr = priorityMobsMap.find("");
        if (itr != priorityMobsMap.end())
            defaultPriorityIndex = (*itr).second;
    }

    if (cycleSelect)
    {
        std::vector<Being*> sortedBeings;

        FOR_EACH (ActorSprites::iterator, i, mActors)
        {
            if (!*i)
                continue;

            if ((*i)->getType() == ActorType::FloorItem
                || (*i)->getType() == ActorType::Portal)
            {
                continue;
            }

            Being *const being = static_cast<Being*>(*i);

            if (filtered)
            {
                if (ignoreAttackMobs.find(being->getName())
                    != ignoreAttackMobs.end())
                {
                    continue;
                }
                if (ignoreDefault && attackMobs.find(being->getName())
                    == attackMobs.end() && priorityMobs.find(being->getName())
                    == priorityMobs.end())
                {
                    continue;
                }
            }

            if (being->getInfo()
                && !(being->getInfo()->isTargetSelection() || modActive))
            {
                continue;
            }

            if (validateBeing(aroundBeing, being, type, nullptr, maxDist))
            {
                if (being != excluded)
                    sortedBeings.push_back(being);
            }
        }

        // no selectable beings
        if (sortedBeings.empty())
            return nullptr;

        beingActorSorter.x = x;
        beingActorSorter.y = y;
        if (filtered)
        {
            beingActorSorter.attackBeings = &attackMobsMap;
            beingActorSorter.defaultAttackIndex = defaultAttackIndex;
            beingActorSorter.priorityBeings = &priorityMobsMap;
            beingActorSorter.defaultPriorityIndex = defaultPriorityIndex;
        }
        else
        {
            beingActorSorter.attackBeings = nullptr;
            beingActorSorter.priorityBeings = nullptr;
        }
        std::sort(sortedBeings.begin(), sortedBeings.end(), beingActorSorter);
        if (filtered)
        {
            beingActorSorter.attackBeings = nullptr;
            beingActorSorter.priorityBeings = nullptr;
        }

        if (localPlayer->getTarget() == nullptr)
        {
            Being *const target = sortedBeings.at(0);

            if (specialDistance && target->getType() == ActorType::Monster
                && target->getDistance() <= 2)
            {
                return nullptr;
            }
            // if no selected being in vector, return first nearest being
            return target;
        }

        beingEqualActorFinder.findBeing = localPlayer->getTarget();
        std::vector<Being*>::const_iterator i = std::find_if(
            sortedBeings.begin(), sortedBeings.end(), beingEqualActorFinder);

        if (i == sortedBeings.end() || ++i == sortedBeings.end())
        {
            // if no selected being in vector, return first nearest being
            return sortedBeings.at(0);
        }

        // we find next being after target
        return *i;
    }
    else
    {
        int dist = 0;
        int index = defaultPriorityIndex;
        Being *closestBeing = nullptr;

        FOR_EACH (ActorSprites::iterator, i, mActors)
        {
            if (!*i)
                continue;

            if ((*i)->getType() == ActorType::FloorItem
                || (*i)->getType() == ActorType::Portal)
            {
                continue;
            }
            Being *const being = static_cast<Being*>(*i);

            if (filtered)
            {
                if (ignoreAttackMobs.find(being->getName())
                    != ignoreAttackMobs.end())
                {
                    continue;
                }
                if (ignoreDefault && attackMobs.find(being->getName())
                    == attackMobs.end() && priorityMobs.find(being->getName())
                    == priorityMobs.end())
                {
                    continue;
                }
            }

            if (being->getInfo()
                && !(being->getInfo()->isTargetSelection() || modActive))
            {
                continue;
            }

            const bool valid = validateBeing(aroundBeing, being,
                                             type, excluded, 50);
            int d = being->getDistance();
            if (being->getType() != ActorType::Monster
                || !mTargetOnlyReachable)
            {   // if distance not calculated, use old distance
                d = (being->getTileX() - x) * (being->getTileX() - x)
                    + (being->getTileY() - y) * (being->getTileY() - y);
            }

            if (!valid)
                continue;

            if (specialDistance && being->getDistance() <= 2
                && being->getType() == type)
            {
                continue;
            }

//            logger->log("being name:" + being->getName());
//            logger->log("index:" + toString(index));
//            logger->log("d:" + toString(d));

            if (!filtered && (d <= dist || !closestBeing))
            {
                dist = d;
                closestBeing = being;
            }
            else if (filtered)
            {
                int w2 = defaultPriorityIndex;
                if (closestBeing)
                {
                    const StringIntMapCIter it2 =  priorityMobsMap.find(
                        being->getName());
                    if (it2 != priorityMobsMap.end())
                        w2 = (*it2).second;

                    if (w2 < index)
                    {
                        dist = d;
                        closestBeing = being;
                        index = w2;
                        continue;
                    }
                    if (w2 == index && d <= dist)
                    {
                        dist = d;
                        closestBeing = being;
                        index = w2;
                        continue;
                    }
                }

                if (!closestBeing)
                {
                    dist = d;
                    closestBeing = being;
                    const StringIntMapCIter it1 = priorityMobsMap.find(
                        being->getName());
                    if (it1 != priorityMobsMap.end())
                        index = (*it1).second;
                    else
                        index = defaultPriorityIndex;
                }
            }
        }
        return (maxDist >= dist) ? closestBeing : nullptr;
    }
}

bool ActorManager::validateBeing(const Being *const aroundBeing,
                                 Being *const being,
                                 const ActorTypeT &type,
                                 const Being* const excluded,
                                 const int maxCost) const
{
    if (!localPlayer)
        return false;
    return being && ((being->getType() == type
        || type == ActorType::Unknown) && (being->isAlive()
        || (mTargetDeadPlayers && type == ActorType::Player))
        && being != aroundBeing) && being != excluded
        && (type != ActorType::Monster || !mTargetOnlyReachable
        || localPlayer->isReachable(being, maxCost));
}

void ActorManager::healTarget() const
{
    if (!localPlayer)
        return;

    heal(localPlayer->getTarget());
}

void ActorManager::heal(const Being *const target) const
{
    if (!localPlayer || !chatWindow || !localPlayer->isAlive()
        || !playerHandler->canUseMagic())
    {
        return;
    }

    // self
    if (target && localPlayer->getName() == target->getName())
    {
        if (PlayerInfo::getAttribute(Attributes::MP) >= 6
            && PlayerInfo::getAttribute(Attributes::HP)
            != PlayerInfo::getAttribute(Attributes::MAX_HP))
        {
            if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
                return;
            chatWindow->localChatInput(mSpellHeal1);
        }
    }
    // magic levels < 2
    else if (PlayerInfo::getSkillLevel(340) < 2
             || PlayerInfo::getSkillLevel(341) < 2)
    {
        if (PlayerInfo::getAttribute(Attributes::MP) >= 6)
        {
            if (target && target->getType() != ActorType::Monster)
            {
                if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
                    return;
                chatWindow->localChatInput(mSpellHeal1 + " "
                                           + target->getName());
            }
            else if (PlayerInfo::getAttribute(Attributes::HP)
                     != PlayerInfo::getAttribute(Attributes::MAX_HP))
            {
                if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
                    return;
                chatWindow->localChatInput(mSpellHeal1);
            }
        }
    }
    // magic level >= 2 and not self
    else
    {
        // mp > 10 and target not monster
        if (PlayerInfo::getAttribute(Attributes::MP) >= 10 && target
            && target->getType() != ActorType::Monster)
        {
            // target not enemy
            if (player_relations.getRelation(target->getName()) !=
                Relation::ENEMY2)
            {
                if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
                    return;
                chatWindow->localChatInput(mSpellHeal2 + " "
                    + target->getName());
            }
            // target enemy
            else
            {
                if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
                    return;
                chatWindow->localChatInput(mSpellHeal1);
            }
        }
        // heal self if selected monster or selection empty
        else if ((!target || target->getType() == ActorType::Monster)
                 && PlayerInfo::getAttribute(Attributes::MP) >= 6
                 && PlayerInfo::getAttribute(Attributes::HP)
                 != PlayerInfo::getAttribute(Attributes::MAX_HP))
        {
            if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
                return;
            chatWindow->localChatInput(mSpellHeal1);
        }
    }
}

Being* ActorManager::findMostDamagedPlayer(const int maxTileDist) const
{
    if (!localPlayer)
        return nullptr;

    int maxDamageTaken = 0;
    Being *target = nullptr;

    for_actors
    {
        if ((*it)->getType() != ActorType::Player)
            continue;

        Being *const being = static_cast<Being*>(*it);

        if (!being || !being->isAlive() ||     // don't heal dead
            player_relations.getRelation(being->getName()) ==
            Relation::ENEMY2 ||                // don't heal enemy
            localPlayer == being)              // don't heal self
        {
            continue;
        }

        const int dx = being->getTileX() - localPlayer->getTileX();
        const int dy = being->getTileY() - localPlayer->getTileY();
        const int distance = fastSqrtInt(dx * dx + dy * dy);

        if (distance > maxTileDist)
            continue;

        if (being->getDamageTaken() > maxDamageTaken)
        {
            maxDamageTaken = being->getDamageTaken();
            target = being;
        }
    }

    return target;
}

void ActorManager::itenplz() const
{
    if (!localPlayer || !chatWindow || !localPlayer->isAlive()
        || !playerHandler->canUseMagic())
    {
        return;
    }

    if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
        return;

    chatWindow->localChatInput(mSpellItenplz);
}

bool ActorManager::hasActorSprite(const ActorSprite *const actor) const
{
    for_actors
    {
        if (actor == *it)
            return true;
    }

    return false;
}

void ActorManager::addBlock(const BeingId id)
{
    mBlockedBeings.insert(id);
}

void ActorManager::deleteBlock(const BeingId id)
{
    mBlockedBeings.erase(id);
}

bool ActorManager::isBlocked(const BeingId id) const
{
    return mBlockedBeings.find(id) != mBlockedBeings.end();
}

void ActorManager::printAllToChat() const
{
    // TRANSLATORS: visible beings on map
    printBeingsToChat(getAll(), _("Visible on map"));
}

void ActorManager::printBeingsToChat(const ActorSprites &beings,
                                     const std::string &header)
{
    if (!debugChatTab)
        return;

    debugChatTab->chatLog("---------------------------------------",
        ChatMsgType::BY_SERVER);
    debugChatTab->chatLog(header, ChatMsgType::BY_SERVER);
    FOR_EACH (std::set<ActorSprite*>::const_iterator, it, beings)
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem)
            continue;

        const Being *const being = static_cast<const Being*>(*it);

        debugChatTab->chatLog(strprintf("%s (%d,%d) %d",
            being->getName().c_str(), being->getTileX(), being->getTileY(),
            toInt(being->getSubType(), int)), ChatMsgType::BY_SERVER);
    }
    debugChatTab->chatLog("---------------------------------------",
        ChatMsgType::BY_SERVER);
}

void ActorManager::printBeingsToChat(const std::vector<Being*> &beings,
                                     const std::string &header)
{
    if (!debugChatTab)
        return;

    debugChatTab->chatLog("---------------------------------------",
        ChatMsgType::BY_SERVER);
    debugChatTab->chatLog(header, ChatMsgType::BY_SERVER);

    FOR_EACH (std::vector<Being*>::const_iterator, i, beings)
    {
        if (!*i)
            continue;

        const Being *const being = *i;

        debugChatTab->chatLog(strprintf("%s (%d,%d) %d",
            being->getName().c_str(), being->getTileX(), being->getTileY(),
            toInt(being->getSubType(), int)), ChatMsgType::BY_SERVER);
    }
    debugChatTab->chatLog("---------------------------------------",
        ChatMsgType::BY_SERVER);
}

void ActorManager::getPlayerNames(StringVect &names,
                                  const NpcNames npcNames) const
{
    names.clear();

    for_actors
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem
            || (*it)->getType() == ActorType::Portal)
        {
            continue;
        }

        const Being *const being = static_cast<const Being*>(*it);
        if ((being->getType() == ActorType::Player ||
            (being->getType() == ActorType::Npc &&
            npcNames == NpcNames_true)) &&
            being->getName() != "")
        {
            names.push_back(being->getName());
        }
    }
}

void ActorManager::getMobNames(StringVect &names) const
{
    names.clear();

    for_actors
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem
            || (*it)->getType() == ActorType::Portal)
        {
            continue;
        }

        const Being *const being = static_cast<const Being*>(*it);
        if (being->getType() == ActorType::Monster && being->getName() != "")
            names.push_back(being->getName());
    }
}

void ActorManager::updatePlayerNames() const
{
    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem
            || (*it)->getType() == ActorType::Portal)
        {
            continue;
        }

        Being *const being = static_cast<Being*>(*it);
        being->setGoodStatus(-1);
        if (being->getType() == ActorType::Player && being->getName() != "")
            being->updateName();
    }
}

void ActorManager::updatePlayerColors() const
{
    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem
            || (*it)->getType() == ActorType::Portal)
        {
            continue;
        }

        Being *const being = static_cast<Being*>(*it);
        if (being->getType() == ActorType::Player && being->getName() != "")
            being->updateColors();
    }
}

void ActorManager::updatePlayerGuild() const
{
    for_actorsm
    {
        if (!*it)
            continue;

        if ((*it)->getType() == ActorType::FloorItem
            || (*it)->getType() == ActorType::Portal)
        {
            continue;
        }

        Being *const being = static_cast<Being*>(*it);
        if (being->getType() == ActorType::Player && being->getName() != "")
            being->updateGuild();
    }
}

#ifdef TMWA_SUPPORT
void ActorManager::parseLevels(std::string levels) const
{
    levels.append(", ");
    size_t f = 0;
    const std::string brkEnd("), ");
    size_t pos = levels.find(brkEnd, f);

    while (pos != std::string::npos)
    {
        std::string part = levels.substr(f, pos - f);
        if (part.empty())
            break;
        const size_t bktPos = part.rfind("(");
        if (bktPos != std::string::npos)
        {
            Being *const being = findBeingByName(part.substr(0, bktPos),
                ActorType::Player);
            if (being)
            {
                being->setLevel(atoi(part.substr(bktPos + 1).c_str()));
                being->addToCache();
            }
        }
        f = static_cast<size_t>(pos + brkEnd.length());
        pos = levels.find(brkEnd, f);
    }
    updatePlayerNames();
}
#endif

void ActorManager::optionChanged(const std::string &name)
{
    if (name == "targetDeadPlayers")
        mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers");
    else if (name == "targetOnlyReachable")
        mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable");
    else if (name == "cyclePlayers")
        mCyclePlayers = config.getBoolValue("cyclePlayers");
    else if (name == "cycleMonsters")
        mCycleMonsters = config.getBoolValue("cycleMonsters");
    else if (name == "cycleNPC")
        mCycleNPC = config.getBoolValue("cycleNPC");
    else if (name == "extMouseTargeting")
        mExtMouseTargeting = config.getBoolValue("extMouseTargeting");
    else if (name == "showBadges")
        updateBadges();
}

void ActorManager::removeAttackMob(const std::string &name)
{
    mPriorityAttackMobs.remove(name);
    mAttackMobs.remove(name);
    mIgnoreAttackMobs.remove(name);
    mPriorityAttackMobsSet.erase(name);
    mAttackMobsSet.erase(name);
    mIgnoreAttackMobsSet.erase(name);
    rebuildAttackMobs();
    rebuildPriorityAttackMobs();
    storeAttackList();
}

void ActorManager::removePickupItem(const std::string &name)
{
    mPickupItems.remove(name);
    mPickupItemsSet.erase(name);
    mIgnorePickupItems.remove(name);
    mIgnorePickupItemsSet.erase(name);
    rebuildPickupItems();
    storeAttackList();
}

#define addMobToList(name, mob) \
{\
    const int size = get##mob##sSize();\
    if (size > 0)\
    {\
        const int idx = get##mob##Index("");\
        if (idx + 1 == size)\
        {\
            std::list<std::string>::iterator itr = m##mob##s.end();\
            -- itr;\
            m##mob##s.insert(itr, name);\
        }\
        else\
        {\
            m##mob##s.push_back(name);\
        }\
    }\
    else\
    {\
        m##mob##s.push_back(name);\
    }\
    m##mob##sSet.insert(name);\
    rebuild##mob##s();\
}

#define rebuildMobsList(mob) \
{\
    m##mob##sMap.clear();\
    std::list<std::string>::const_iterator i = m##mob##s.begin();\
    int cnt = 0;\
    while (i != m##mob##s.end())\
    {\
        m##mob##sMap[*i] = cnt;\
        ++ i;\
        ++ cnt;\
    }\
}

void ActorManager::addAttackMob(const std::string &name)
{
    addMobToList(name, AttackMob);
    rebuildPriorityAttackMobs();
    storeAttackList();
}

void ActorManager::addPriorityAttackMob(const std::string &name)
{
    addMobToList(name, PriorityAttackMob);
    storeAttackList();
}

void ActorManager::addIgnoreAttackMob(const std::string &name)
{
    mIgnoreAttackMobs.push_back(name);
    mIgnoreAttackMobsSet.insert(name);
    rebuildAttackMobs();
    rebuildPriorityAttackMobs();
    storeAttackList();
}

void ActorManager::addPickupItem(const std::string &name)
{
    addMobToList(name, PickupItem);
    rebuildPickupItems();
    storeAttackList();
}

void ActorManager::addIgnorePickupItem(const std::string &name)
{
    mIgnorePickupItems.push_back(name);
    mIgnorePickupItemsSet.insert(name);
    rebuildPickupItems();
    storeAttackList();
}

void ActorManager::rebuildPriorityAttackMobs()
{
    rebuildMobsList(PriorityAttackMob);
}

void ActorManager::rebuildAttackMobs()
{
    rebuildMobsList(AttackMob);
}

void ActorManager::rebuildPickupItems()
{
    rebuildMobsList(PickupItem);
}

int ActorManager::getIndexByName(const std::string &name,
                                 const StringIntMap &map)
{
    const StringIntMapCIter i = map.find(name);
    if (i == map.end())
        return -1;

    return (*i).second;
}

int ActorManager::getPriorityAttackMobIndex(const std::string &name) const
{
    return getIndexByName(name, mPriorityAttackMobsMap);
}

int ActorManager::getAttackMobIndex(const std::string &name) const
{
    return getIndexByName(name, mAttackMobsMap);
}

int ActorManager::getPickupItemIndex(const std::string &name) const
{
    return getIndexByName(name, mPickupItemsMap);
}

#define loadList(key, mob) \
{\
    list = unpackList(serverConfig.getValue(key, ""));\
    i = list.begin();\
    i_end = list.end();\
    while (i != i_end)\
    {\
        if (*i == "")\
            empty = true;\
        m##mob##s.push_back(*i);\
        m##mob##sSet.insert(*i);\
        ++ i;\
    }\
}

void ActorManager::loadAttackList()
{
    bool empty = false;
    std::list<std::string> list;
    std::list<std::string>::const_iterator i;
    std::list<std::string>::const_iterator i_end;

    loadList("attackPriorityMobs", PriorityAttackMob);
    loadList("attackMobs", AttackMob);
    loadList("ignoreAttackMobs", IgnoreAttackMob);
    if (!empty)
    {
        mAttackMobs.push_back("");
        mAttackMobsSet.insert("");
    }
    empty = false;

    loadList("pickupItems", PickupItem);
    loadList("ignorePickupItems", IgnorePickupItem);
    if (!empty)
    {
        mPickupItems.push_back("");
        mPickupItemsSet.insert("");
    }

    rebuildAttackMobs();
    rebuildPriorityAttackMobs();
    rebuildPickupItems();
}

void ActorManager::storeAttackList() const
{
    serverConfig.setValue("attackPriorityMobs", packList(mPriorityAttackMobs));
    serverConfig.setValue("attackMobs", packList(mAttackMobs));
    serverConfig.setValue("ignoreAttackMobs", packList(mIgnoreAttackMobs));

    serverConfig.setValue("pickupItems", packList(mPickupItems));
    serverConfig.setValue("ignorePickupItems", packList(mIgnorePickupItems));
}

bool ActorManager::checkForPickup(const FloorItem *const item) const
{
    if (mPickupItemsSet.find("") != mPickupItemsSet.end())
    {
        if (mIgnorePickupItemsSet.find(item->getName())
            == mIgnorePickupItemsSet.end())
        {
            return true;
        }
    }
    else if (item && mPickupItemsSet.find(item->getName())
             != mPickupItemsSet.end())
    {
        return true;
    }
    return false;
}

void ActorManager::updateEffects(const std::map<BeingTypeId, int> &addEffects,
                                 const std::set<BeingTypeId> &removeEffects)
                                 const
{
    for_actorsm
    {
        if (!*it || (*it)->getType() != ActorType::Npc)
            continue;
        Being *const being = static_cast<Being*>(*it);
        const BeingTypeId type = being->getSubType();
        if (removeEffects.find(type) != removeEffects.end())
            being->removeSpecialEffect();
        const std::map<BeingTypeId, int>::const_iterator
            idAdd = addEffects.find(type);
        if (idAdd != addEffects.end())
            being->addSpecialEffect((*idAdd).second);
    }
}

Being *ActorManager::cloneBeing(const Being *const srcBeing,
                                const int dx, const int dy,
                                const int id)
{
    if (!srcBeing)
        return nullptr;
    Being *const dstBeing = actorManager->createBeing(fromInt(
        toInt(srcBeing->getId(), int) + id, BeingId),
        ActorType::Player,
        srcBeing->getSubType());
    if (!dstBeing)
        return nullptr;
    dstBeing->setGender(srcBeing->getGender());
    dstBeing->setAction(srcBeing->getCurrentAction(), 0);
    dstBeing->setTileCoords(srcBeing->getTileX() + dx,
        srcBeing->getTileY() + dy);
    dstBeing->setName(srcBeing->getName());
    dstBeing->setDirection(srcBeing->getDirection());
    const int sz = static_cast<int>(srcBeing->getSpritesCount());
    for (int slot = 0; slot < sz; slot ++)
    {
        const int spriteId = srcBeing->getSpriteID(slot);
        const ItemColor color = srcBeing->getSpriteColor(slot);
        dstBeing->setSprite(slot, spriteId, "", color, false);
    }
    const int hairSlot = charServerHandler->hairSprite();
    const int hairStyle = -srcBeing->getSpriteID(hairSlot);
    const ItemColor hairColor = srcBeing->getHairColor();
    dstBeing->setSprite(hairSlot, hairStyle * -1,
        ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
    dstBeing->setHairColor(hairColor);
    return dstBeing;
}

void ActorManager::updateBadges()
{
    const uint8_t showBadges = static_cast<uint8_t>(
        config.getIntValue("showBadges"));
    Being::mShowBadges = showBadges;
    for_actors
    {
        ActorSprite *const actor = *it;
        if (actor->getType() == ActorType::Player)
        {
            Being *const being = static_cast<Being*>(actor);
            being->showBadges(showBadges);
        }
    }
}

#ifdef EATHENA_SUPPORT
void ActorManager::removeRoom(const int chatId)
{
    for_actors
    {
        ActorSprite *const actor = *it;
        if (actor->getType() == ActorType::Npc)
        {
            Being *const being = static_cast<Being*>(actor);
            const ChatObject *const chat = being->getChat();
            if (chat && chat->chatId == chatId)
            {
                being->setChat(nullptr);
            }
        }
    }
}

void ActorManager::updateRoom(const ChatObject *const newChat)
{
    if (!newChat)
        return;

    for_actors
    {
        ActorSprite *const actor = *it;
        if (actor->getType() == ActorType::Npc)
        {
            Being *const being = static_cast<Being*>(actor);
            ChatObject *const chat = being->getChat();
            if (chat && chat->chatId == newChat->chatId)
            {
                chat->ownerId = newChat->ownerId;
                chat->maxUsers = newChat->maxUsers;
                chat->type = newChat->type;
                chat->title = newChat->title;
            }
        }
    }
}
#endif