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

#include "actormanager.h"
#include "configuration.h"
#include "effectmanager.h"
#include "guild.h"
#include "party.h"

#include "being/localplayer.h"

#include "particle/particleengine.h"

#include "input/keyboardconfig.h"

#include "gui/windows/okdialog.h"
#include "gui/windows/outfitwindow.h"
#include "gui/windows/socialwindow.h"

#include "net/beinghandler.h"
#include "net/serverfeatures.h"

#include "net/messagein.h"

#include "net/ea/beingrecv.h"

#include "net/tmwa/guildmanager.h"
#include "net/tmwa/sprite.h"

#include "resources/iteminfo.h"

#include "resources/db/itemdb.h"

#include "utils/timer.h"

#include "debug.h"

extern OkDialog *deathNotice;

namespace TmwAthena
{

static void setGm(Being *const dstBeing,
                  const uint16_t gmstatus)
{
    if (dstBeing != localPlayer)
    {
        if ((gmstatus & 0x80) != 0)
        {
            dstBeing->setGroupId(paths.getIntValue("gmDefaultLevel"));
            dstBeing->setGM(true);
        }
        else
        {
            dstBeing->setGroupId(0);
            dstBeing->setGM(false);
        }
    }
    else
    {
        if ((gmstatus & 0x80) != 0)
            dstBeing->setGM(true);
        else
            dstBeing->setGM(false);
    }
}

void BeingRecv::processBeingChangeLook(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingChangeLook")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingChangeLook")
        return;
    }

    Being *const dstBeing = actorManager->findBeing(
        msg.readBeingId("being id"));

    const uint8_t type = msg.readUInt8("type");
    const int16_t id = CAST_S16(msg.readUInt8("id"));
    const int id2 = 1;

    if ((localPlayer == nullptr) || (dstBeing == nullptr))
    {
        BLOCK_END("BeingRecv::processBeingChangeLook")
        return;
    }
    processBeingChangeLookContinue(msg, dstBeing, type, id, id2);
    BLOCK_END("BeingRecv::processBeingChangeLook")
}

void BeingRecv::processBeingChangeLook2(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingChangeLook")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingChangeLook")
        return;
    }

    Being *const dstBeing = actorManager->findBeing(
        msg.readBeingId("being id"));

    const uint8_t type = msg.readUInt8("type");
    int id2 = 0;

    const int16_t id = msg.readInt16("id1");
    if (type == 2)
    {
        id2 = msg.readInt16("id2");
    }
    else
    {
        msg.readInt16("id2");
        id2 = 1;
    }

    if ((localPlayer == nullptr) || (dstBeing == nullptr))
    {
        BLOCK_END("BeingRecv::processBeingChangeLook")
        return;
    }
    processBeingChangeLookContinue(msg, dstBeing, type, id, id2);
    BLOCK_END("BeingRecv::processBeingChangeLook")
}

void BeingRecv::processBeingChangeLookContinue(const Net::MessageIn &msg,
                                               Being *const dstBeing,
                                               const uint8_t type,
                                               const int id,
                                               const int id2)
{
    if (dstBeing->getType() == ActorType::Player)
        dstBeing->setOtherTime();

    switch (type)
    {
        case 0:     // change race
            dstBeing->setSubtype(fromInt(id, BeingTypeId),
                dstBeing->getLook());
            break;
        case 1:     // eAthena LOOK_HAIR
        {
            const uint16_t look = CAST_U16(id / 256);
            const int hair = id % 256;
            dstBeing->setHairStyle(SPRITE_HAIR_COLOR, hair * -1);
            dstBeing->setLook(look);
            break;
        }
        case 2:     // Weapon ID in id, Shield ID in id2
            dstBeing->setSpriteId(SPRITE_BODY,
                id);
            dstBeing->setWeaponId(id);
            dstBeing->setSpriteId(SPRITE_FLOOR,
                id2);
            localPlayer->imitateOutfit(dstBeing, SPRITE_FLOOR);
            break;
        case 3:     // Change lower headgear for eAthena, pants for us
            dstBeing->setSpriteId(SPRITE_WEAPON,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_WEAPON);
            break;
        case 4:     // Change upper headgear for eAthena, hat for us
            dstBeing->setSpriteId(SPRITE_CLOTHES_COLOR,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_CLOTHES_COLOR);
            break;
        case 5:     // Change middle headgear for eathena, armor for us
            dstBeing->setSpriteId(SPRITE_HEAD_BOTTOM,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_BOTTOM);
            break;
        case 6:     // eAthena LOOK_HAIR_COLOR
            dstBeing->setHairColor(SPRITE_HAIR_COLOR,
                fromInt(id, ItemColor));
            break;
        case 7:     // Clothes color
            // ignoring it
            break;
        case 8:     // eAthena LOOK_SHIELD
            dstBeing->setSpriteId(SPRITE_FLOOR,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_FLOOR);
            break;
        case 9:     // eAthena LOOK_SHOES
            dstBeing->setSpriteId(SPRITE_HAIR,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_HAIR);
            break;
        case 10:   // LOOK_GLOVES
            dstBeing->setSpriteId(SPRITE_SHOES,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_SHOES);
            break;
        case 11:  // LOOK_CAPE
            dstBeing->setSpriteId(SPRITE_SHIELD,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_SHIELD);
            break;
        case 12:
            dstBeing->setSpriteId(SPRITE_HEAD_TOP,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_TOP);
            break;
        case 13:
            dstBeing->setSpriteId(SPRITE_HEAD_MID,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_MID);
            break;
        case 14:
            dstBeing->setSpriteId(SPRITE_ROBE,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_ROBE);
            break;
        case 15:
            dstBeing->setSpriteId(SPRITE_EVOL2,
                id);
            localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL2);
            break;
        case 16:
            dstBeing->setLook(CAST_U16(id));
            break;
        default:
            UNIMPLEMENTEDPACKETFIELD(type);
            break;
    }
}

void BeingRecv::processPlayerUpdate1(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processPlayerMoveUpdate")
    if ((actorManager == nullptr) || (localPlayer == nullptr))
    {
        BLOCK_END("BeingRecv::processPlayerMoveUpdate")
        return;
    }

    // An update about a player, potentially including movement.
    const BeingId id = msg.readBeingId("account id");
    const int16_t speed = msg.readInt16("speed");
    const uint32_t opt1 = msg.readInt16("opt1");
    const uint32_t opt2 = msg.readInt16("opt2");
    const uint32_t option = msg.readInt16("option");
    const int16_t job = msg.readInt16("job");
    int disguiseId = 0;
    if (toInt(id, int) < 110000000 && job >= 1000)
        disguiseId = job;

    Being *dstBeing = actorManager->findBeing(id);
    if (dstBeing == nullptr)
    {
        if (actorManager->isBlocked(id) == true)
        {
            BLOCK_END("BeingRecv::processPlayerMoveUpdate")
            return;
        }

        dstBeing = Ea::BeingRecv::createBeing(id, job);

        if (dstBeing == nullptr)
        {
            BLOCK_END("BeingRecv::processPlayerMoveUpdate")
            return;
        }
    }
    else if (disguiseId != 0)
    {
        actorManager->undelete(dstBeing);
        beingHandler->requestNameById(id);
    }

    uint8_t dir = dstBeing->getDirectionDelayed();
    if (dir != 0u)
    {
        if (dir != dstBeing->getDirection())
            dstBeing->setDirection(dir);
    }

    if (Party *const party = localPlayer->getParty())
    {
        if (party->isMember(id))
            dstBeing->setParty(party);
    }

    dstBeing->setWalkSpeed(speed);

    const uint8_t hairStyle = msg.readUInt8("hair style");
    const uint16_t look = msg.readUInt8("look");
    dstBeing->setSubtype(fromInt(job, BeingTypeId), look);
    const uint16_t weapon = msg.readInt16("weapon");
    const uint16_t shield = msg.readInt16("shield");
    const uint16_t headBottom = msg.readInt16("head bottom");

    const uint16_t headTop = msg.readInt16("head top");
    const uint16_t headMid = msg.readInt16("head mid");
    const ItemColor hairColor = fromInt(
        msg.readUInt8("hair color"), ItemColor);
    msg.readUInt8("unused");
    msg.readInt32("unused");

    const int guild = msg.readInt32("guild");

    if ((guildManager == nullptr) || !GuildManager::getEnableGuildBot())
    {
        if (guild == 0)
            dstBeing->clearGuilds();
        else
            dstBeing->setGuild(Guild::getGuild(CAST_S16(guild)));
    }

    msg.readInt16("emblem");
    dstBeing->setManner(msg.readInt16("manner"));
    const uint32_t opt3 = msg.readInt16("opt3");
    dstBeing->setKarma(msg.readUInt8("karma"));
    // reserving bit for future usage
    dstBeing->setGender(Being::intToGender(
        CAST_U8(msg.readUInt8("gender") & 3)));

    if (disguiseId == 0)
    {
        // Set these after the gender, as the sprites may be gender-specific
        dstBeing->updateSprite(SPRITE_BODY,
            weapon,
            "");
        dstBeing->setWeaponId(weapon);
        dstBeing->updateSprite(SPRITE_FLOOR, shield);
        dstBeing->updateSprite(SPRITE_WEAPON, headBottom);
        dstBeing->updateSprite(SPRITE_HEAD_BOTTOM, headMid);
        dstBeing->updateSprite(SPRITE_CLOTHES_COLOR, headTop);
        if (hairStyle == 0)
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                0,
                std::string());
        }
        else
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                hairStyle * -1,
                ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        }
        dstBeing->setHairColor(hairColor);
    }
    localPlayer->imitateOutfit(dstBeing);

    uint16_t x, y;
    msg.readCoordinates(x, y, dir, "position");
    dstBeing->setTileCoords(x, y);
    dstBeing->setDirection(dir);

    localPlayer->imitateDirection(dstBeing, dir);

    const uint16_t gmstatus = msg.readInt16("gm status");

    setGm(dstBeing, gmstatus);

    applyPlayerAction(msg, dstBeing, msg.readUInt8("action type"));
    const int level = CAST_S32(msg.readUInt8("level"));
    if (level != 0)
        dstBeing->setLevel(level);

    msg.readUInt8("unused");

    dstBeing->setActionTime(tick_time);

    dstBeing->setStatusEffectOpitons(option,
        opt1,
        opt2,
        opt3);

    BLOCK_END("BeingRecv::processPlayerMoveUpdate")
}

void BeingRecv::processPlayerUpdate2(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processPlayerMoveUpdate")
    if ((actorManager == nullptr) || (localPlayer == nullptr))
    {
        BLOCK_END("BeingRecv::processPlayerMoveUpdate")
        return;
    }

    // An update about a player, potentially including movement.
    const BeingId id = msg.readBeingId("account id");
    const int16_t speed = msg.readInt16("speed");
    const uint32_t opt1 = msg.readInt16("opt1");
    const uint32_t opt2 = msg.readInt16("opt2");
    const uint32_t option = msg.readInt16("option");
    const int16_t job = msg.readInt16("job");
    int disguiseId = 0;
    if (toInt(id, int) < 110000000 && job >= 1000)
        disguiseId = job;

    Being *dstBeing = actorManager->findBeing(id);
    if (dstBeing == nullptr)
    {
        if (actorManager->isBlocked(id) == true)
        {
            BLOCK_END("BeingRecv::processPlayerMoveUpdate")
            return;
        }

        dstBeing = Ea::BeingRecv::createBeing(id, job);

        if (dstBeing == nullptr)
        {
            BLOCK_END("BeingRecv::processPlayerMoveUpdate")
            return;
        }
    }
    else if (disguiseId != 0)
    {
        actorManager->undelete(dstBeing);
        beingHandler->requestNameById(id);
    }

    uint8_t dir = dstBeing->getDirectionDelayed();
    if (dir != 0u)
    {
        if (dir != dstBeing->getDirection())
            dstBeing->setDirection(dir);
    }

    if (Party *const party = localPlayer->getParty())
    {
        if (party->isMember(id))
            dstBeing->setParty(party);
    }

    dstBeing->setWalkSpeed(speed);

    const uint8_t hairStyle = msg.readUInt8("hair style");
    const uint16_t look = msg.readUInt8("look");
    dstBeing->setSubtype(fromInt(job, BeingTypeId), look);
    const uint16_t weapon = msg.readInt16("weapon");
    const uint16_t shield = msg.readInt16("shield");
    const uint16_t headBottom = msg.readInt16("head bottom");
    const uint16_t headTop = msg.readInt16("head top");
    const uint16_t headMid = msg.readInt16("head mid");
    const ItemColor hairColor = fromInt(
        msg.readUInt8("hair color"), ItemColor);
    msg.readUInt8("unused");
    msg.readInt32("unused");

    const int guild = msg.readInt32("guild");

    if ((guildManager == nullptr) || !GuildManager::getEnableGuildBot())
    {
        if (guild == 0)
            dstBeing->clearGuilds();
        else
            dstBeing->setGuild(Guild::getGuild(CAST_S16(guild)));
    }

    msg.readInt16("emblem");
    dstBeing->setManner(msg.readInt16("manner"));
    const uint32_t opt3 = msg.readInt16("opt3");
    dstBeing->setKarma(msg.readUInt8("karma"));
    // reserving bit for future usage
    dstBeing->setGender(Being::intToGender(
        CAST_U8(msg.readUInt8("gender") & 3)));

    if (disguiseId == 0)
    {
        // Set these after the gender, as the sprites may be gender-specific
        dstBeing->updateSprite(SPRITE_BODY,
            weapon,
            "");
        dstBeing->setWeaponId(weapon);
        dstBeing->updateSprite(SPRITE_FLOOR, shield);
        dstBeing->updateSprite(SPRITE_WEAPON, headBottom);
        dstBeing->updateSprite(SPRITE_HEAD_BOTTOM, headMid);
        dstBeing->updateSprite(SPRITE_CLOTHES_COLOR, headTop);
        if (hairStyle == 0)
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                0,
                std::string());
        }
        else
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                hairStyle * -1,
                ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        }
        dstBeing->setHairColor(hairColor);
    }
    localPlayer->imitateOutfit(dstBeing);

    uint16_t x, y;
    msg.readCoordinates(x, y, dir, "position");
    dstBeing->setTileCoords(x, y);
    dstBeing->setDirection(dir);

    localPlayer->imitateDirection(dstBeing, dir);

    const uint16_t gmstatus = msg.readInt16("gm status");

    setGm(dstBeing, gmstatus);

    applyPlayerAction(msg, dstBeing, msg.readUInt8("action type"));
    const int level = CAST_S32(msg.readUInt8("level"));
    if (level != 0)
        dstBeing->setLevel(level);

    dstBeing->setActionTime(tick_time);
    dstBeing->setStatusEffectOpitons(option,
        opt1,
        opt2,
        opt3);

    BLOCK_END("BeingRecv::processPlayerMoveUpdate")
}

void BeingRecv::processPlayerMove(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processPlayerMoveUpdate")
    if ((actorManager == nullptr) || (localPlayer == nullptr))
    {
        BLOCK_END("BeingRecv::processPlayerMoveUpdate")
        return;
    }

    // An update about a player, potentially including movement.
    const BeingId id = msg.readBeingId("account id");
    const int16_t speed = msg.readInt16("speed");
    const uint32_t opt1 = msg.readInt16("opt1");
    const uint32_t opt2 = msg.readInt16("opt2");
    const uint32_t option = msg.readInt16("option");
    const int16_t job = msg.readInt16("job");
    int disguiseId = 0;
    if (toInt(id, int) < 110000000 && job >= 1000)
        disguiseId = job;

    Being *dstBeing = actorManager->findBeing(id);
    if (dstBeing == nullptr)
    {
        if (actorManager->isBlocked(id) == true)
        {
            BLOCK_END("BeingRecv::processPlayerMoveUpdate")
            return;
        }

        dstBeing = Ea::BeingRecv::createBeing(id, job);

        if (dstBeing == nullptr)
        {
            BLOCK_END("BeingRecv::processPlayerMoveUpdate")
            return;
        }
    }
    else if (disguiseId != 0)
    {
        actorManager->undelete(dstBeing);
        beingHandler->requestNameById(id);
    }

    const uint8_t dir = dstBeing->getDirectionDelayed();
    if (dir != 0u)
    {
        if (dir != dstBeing->getDirection())
            dstBeing->setDirection(dir);
    }

    if (Party *const party = localPlayer->getParty())
    {
        if (party->isMember(id))
            dstBeing->setParty(party);
    }

    dstBeing->setWalkSpeed(speed);

    const uint8_t hairStyle = msg.readUInt8("hair style");
    const uint16_t look = msg.readUInt8("look");
    dstBeing->setSubtype(fromInt(job, BeingTypeId), look);
    const uint16_t weapon = msg.readInt16("weapon");
    const uint16_t shield = msg.readInt16("shield");
    const uint16_t headBottom = msg.readInt16("head bottom");

    msg.readInt32("tick");

    const uint16_t headTop = msg.readInt16("head top");
    const uint16_t headMid = msg.readInt16("head mid");
    const ItemColor hairColor = fromInt(
        msg.readUInt8("hair color"), ItemColor);
    msg.readUInt8("unused");
    msg.readInt32("unused");

    const int guild = msg.readInt32("guild");

    if ((guildManager == nullptr) || !GuildManager::getEnableGuildBot())
    {
        if (guild == 0)
            dstBeing->clearGuilds();
        else
            dstBeing->setGuild(Guild::getGuild(CAST_S16(guild)));
    }

    msg.readInt16("emblem");
    dstBeing->setManner(msg.readInt16("manner"));
    const uint32_t opt3 = msg.readInt16("opt3");
    dstBeing->setKarma(msg.readUInt8("karma"));
    // reserving bit for future usage
    dstBeing->setGender(Being::intToGender(
        CAST_U8(msg.readUInt8("gender") & 3)));

    if (disguiseId == 0)
    {
        // Set these after the gender, as the sprites may be gender-specific
        dstBeing->updateSprite(SPRITE_BODY,
            weapon,
            "");
        dstBeing->setWeaponId(weapon);
        dstBeing->updateSprite(SPRITE_FLOOR, shield);
        dstBeing->updateSprite(SPRITE_WEAPON, headBottom);
        dstBeing->updateSprite(SPRITE_HEAD_BOTTOM, headMid);
        dstBeing->updateSprite(SPRITE_CLOTHES_COLOR, headTop);
        if (hairStyle == 0)
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                0,
                std::string());
        }
        else
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                hairStyle * -1,
                ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        }
        dstBeing->setHairColor(hairColor);
    }
    localPlayer->imitateOutfit(dstBeing);

    uint16_t srcX, srcY, dstX, dstY;
    msg.readCoordinatePair(srcX, srcY, dstX, dstY, "moving path");

    localPlayer->followMoveTo(dstBeing, srcX, srcY, dstX, dstY);

    dstBeing->setTileCoords(srcX, srcY);
    dstBeing->setDestination(dstX, dstY);

    // because server don't send direction in move packet,
    // we fixing it

    if (srcX != dstX || srcY != dstY)
    {
        const int d = dstBeing->calcDirection(dstX, dstY);

        if ((d != 0) && dstBeing->getDirection() != d)
            dstBeing->setDirectionDelayed(CAST_U8(d));
    }

    if (localPlayer->getCurrentAction() != BeingAction::STAND)
        localPlayer->imitateAction(dstBeing, BeingAction::STAND);
    if (localPlayer->getDirection() != dstBeing->getDirection())
    {
        localPlayer->imitateDirection(dstBeing,
            dstBeing->getDirection());
    }

    const uint16_t gmstatus = msg.readInt16("gm status");

    setGm(dstBeing, gmstatus);

    msg.readUInt8("unused");

    const int level = CAST_S32(msg.readUInt8("level"));
    if (level != 0)
        dstBeing->setLevel(level);

    msg.readUInt8("unused");

    if (dstBeing->getType() != ActorType::Player)
        dstBeing->setActionTime(tick_time);

    dstBeing->setStatusEffectOpitons(option,
        opt1,
        opt2,
        opt3);

    if (dstBeing->getType() == ActorType::Player)
        dstBeing->setMoveTime();
    BLOCK_END("BeingRecv::processPlayerMoveUpdate")
}

void BeingRecv::processBeingVisible(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingVisibleOrMove")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingVisibleOrMove")
        return;
    }

    BeingId spawnId;

    // Information about a being in range
    const BeingId id = msg.readBeingId("being id");
    if (id == Ea::BeingRecv::mSpawnId)
        spawnId = Ea::BeingRecv::mSpawnId;
    else
        spawnId = BeingId_zero;
    Ea::BeingRecv::mSpawnId = BeingId_zero;
    int16_t speed = msg.readInt16("speed");
    const uint32_t opt1 = msg.readInt16("opt1");
    const uint32_t opt2 = msg.readInt16("opt2");
    const uint32_t option = msg.readInt16("option");
    const int16_t job = msg.readInt16("class");
    int disguiseId = 0;
    if (id == localPlayer->getId() && job >= 1000)
        disguiseId = job;

    Being *dstBeing = actorManager->findBeing(id);

    if ((dstBeing != nullptr) && dstBeing->getType() == ActorType::Monster
        && !dstBeing->isAlive())
    {
        actorManager->destroy(dstBeing);
        actorManager->erase(dstBeing);
        dstBeing = nullptr;
    }

    if (dstBeing == nullptr)
    {
        // Being with id >= 110000000 and job 0 are better
        // known as ghosts, so don't create those.
        if (job == 0 && toInt(id, int) >= 110000000)
        {
            BLOCK_END("BeingRecv::processBeingVisibleOrMove")
            return;
        }

        if (actorManager->isBlocked(id) == true)
        {
            BLOCK_END("BeingRecv::processBeingVisibleOrMove")
            return;
        }

        dstBeing = Ea::BeingRecv::createBeing(id, job);

        if (dstBeing == nullptr)
        {
            BLOCK_END("BeingRecv::processBeingVisibleOrMove")
            return;
        }
    }
    else
    {
        if (dstBeing->getType() == ActorType::Npc)
        {
            actorManager->undelete(dstBeing);
            beingHandler->requestNameById(id);
        }
    }

    if (dstBeing->getType() == ActorType::Player)
        dstBeing->setMoveTime();

    if (spawnId != BeingId_zero)
    {
        dstBeing->setAction(BeingAction::SPAWN, 0);
    }
    else
    {
        dstBeing->clearPath();
        dstBeing->setActionTime(tick_time);
        dstBeing->setAction(BeingAction::STAND, 0);
    }

    // Prevent division by 0 when calculating frame
    if (speed == 0)
        speed = 150;

    const uint8_t hairStyle = msg.readUInt8("hair style");
    const uint16_t look = msg.readUInt8("look");
    dstBeing->setSubtype(fromInt(job, BeingTypeId), look);
    if (dstBeing->getType() == ActorType::Monster && (localPlayer != nullptr))
        localPlayer->checkNewName(dstBeing);
    dstBeing->setWalkSpeed(speed);
    const uint16_t weapon = msg.readInt16("weapon");
    const uint16_t headBottom = msg.readInt16("head bottom");

    const uint16_t shield = msg.readInt16("shield");
    const uint16_t headTop = msg.readInt16("head top");
    const uint16_t headMid = msg.readInt16("head mid");
    const ItemColor hairColor = fromInt(msg.readUInt8("hair color"),
        ItemColor);
    msg.readUInt8("unused");
    const uint16_t shoes = msg.readInt16("shoes / clothes color");

    uint16_t gloves;
    if (dstBeing->getType() == ActorType::Monster)
    {
        if (serverFeatures->haveServerHp())
        {
            const int hp = msg.readInt32("hp");
            const int maxHP = msg.readInt32("max hp");
            if ((hp != 0) && (maxHP != 0))
            {
                dstBeing->setMaxHP(maxHP);
                const int oldHP = dstBeing->getHP();
                if ((oldHP == 0) || oldHP > hp)
                    dstBeing->setHP(hp);
            }
        }
        else
        {
            msg.readInt32("unused");
            msg.readInt32("unused");
        }
        gloves = 0;
    }
    else
    {
        gloves = msg.readInt16("gloves / head dir");
        msg.readInt32("guild");
        msg.readInt16("guild emblem");
    }

    dstBeing->setManner(msg.readInt16("manner"));
    const uint32_t opt3 = msg.readInt16("opt3");
    if (serverFeatures->haveMonsterAttackRange()
        && dstBeing->getType() == ActorType::Monster)
    {
        const int attackRange = CAST_S32(
            msg.readUInt8("attack range (was karma)"));
        dstBeing->setAttackRange(attackRange);
    }
    else
    {
        dstBeing->setKarma(msg.readUInt8("karma"));
    }
    uint8_t gender = msg.readUInt8("gender");

    if ((disguiseId == 0) && dstBeing->getType() == ActorType::Player)
    {
        // reserving bits for future usage
        gender &= 3;
        dstBeing->setGender(Being::intToGender(gender));
        // Set these after the gender, as the sprites may be gender-specific
        if (hairStyle == 0)
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR, 0);
        }
        else
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                hairStyle * -1,
                ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        }
        dstBeing->setHairColor(hairColor);
        dstBeing->updateSprite(SPRITE_WEAPON, headBottom);
        dstBeing->updateSprite(SPRITE_HEAD_BOTTOM, headMid);
        dstBeing->updateSprite(SPRITE_CLOTHES_COLOR, headTop);
        dstBeing->updateSprite(SPRITE_HAIR, shoes);
        dstBeing->updateSprite(SPRITE_SHOES, gloves);
        dstBeing->updateSprite(SPRITE_BODY, weapon);
        dstBeing->setWeaponId(weapon);
        dstBeing->updateSprite(SPRITE_FLOOR, shield);
    }
    else if (dstBeing->getType() == ActorType::Npc
             && serverFeatures->haveNpcGender())
    {
        setServerGender(dstBeing, gender);
    }

    uint8_t dir;
    uint16_t x, y;
    msg.readCoordinates(x, y, dir, "position");
    dstBeing->setTileCoords(x, y);

    if (job == 45 && (socialWindow != nullptr) && (outfitWindow != nullptr))
    {
        const int num = socialWindow->getPortalIndex(x, y);
        if (num >= 0)
        {
            dstBeing->setName(KeyboardConfig::getKeyShortString(
                OutfitWindow::keyName(num)));
        }
        else
        {
            dstBeing->setName("");
        }
    }

    dstBeing->setDirection(dir);

    msg.readUInt8("unknown");
    msg.readUInt8("unknown");
    msg.readUInt8("unknown");
    msg.readUInt8("unknown");
    msg.readUInt8("unknown");

    dstBeing->setStatusEffectOpitons(option,
        opt1,
        opt2,
        opt3);
    BLOCK_END("BeingRecv::processBeingVisibleOrMove")
}

void BeingRecv::processBeingMove(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingVisibleOrMove")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingVisibleOrMove")
        return;
    }

    BeingId spawnId;

    // Information about a being in range
    const BeingId id = msg.readBeingId("being id");
    if (id == Ea::BeingRecv::mSpawnId)
        spawnId = Ea::BeingRecv::mSpawnId;
    else
        spawnId = BeingId_zero;
    Ea::BeingRecv::mSpawnId = BeingId_zero;
    int16_t speed = msg.readInt16("speed");
    const uint32_t opt1 = msg.readInt16("opt1");
    const uint32_t opt2 = msg.readInt16("opt2");
    const uint32_t option = msg.readInt16("option");
    const int16_t job = msg.readInt16("class");
    int disguiseId = 0;
    if (id == localPlayer->getId() && job >= 1000)
        disguiseId = job;

    Being *dstBeing = actorManager->findBeing(id);

    if ((dstBeing != nullptr) && dstBeing->getType() == ActorType::Monster
        && !dstBeing->isAlive())
    {
        actorManager->destroy(dstBeing);
        actorManager->erase(dstBeing);
        dstBeing = nullptr;
    }

    if (dstBeing == nullptr)
    {
        // Being with id >= 110000000 and job 0 are better
        // known as ghosts, so don't create those.
        if (job == 0 && toInt(id, int) >= 110000000)
        {
            BLOCK_END("BeingRecv::processBeingVisibleOrMove")
            return;
        }

        if (actorManager->isBlocked(id) == true)
        {
            BLOCK_END("BeingRecv::processBeingVisibleOrMove")
            return;
        }

        dstBeing = Ea::BeingRecv::createBeing(id, job);

        if (dstBeing == nullptr)
        {
            BLOCK_END("BeingRecv::processBeingVisibleOrMove")
            return;
        }
    }
    else
    {
        if (dstBeing->getType() == ActorType::Npc)
        {
            actorManager->undelete(dstBeing);
            beingHandler->requestNameById(id);
        }
    }

    if (dstBeing->getType() == ActorType::Player)
        dstBeing->setMoveTime();

    if (spawnId != BeingId_zero)
        dstBeing->setAction(BeingAction::SPAWN, 0);

    // Prevent division by 0 when calculating frame
    if (speed == 0)
        speed = 150;

    const uint8_t hairStyle = msg.readUInt8("hair style");
    const uint16_t look = msg.readUInt8("look");
    dstBeing->setSubtype(fromInt(job, BeingTypeId), look);
    if (dstBeing->getType() == ActorType::Monster && (localPlayer != nullptr))
        localPlayer->checkNewName(dstBeing);
    dstBeing->setWalkSpeed(speed);
    const uint16_t weapon = msg.readInt16("weapon");
    const uint16_t headBottom = msg.readInt16("head bottom");

    msg.readInt32("tick");

    const uint16_t shield = msg.readInt16("shield");
    const uint16_t headTop = msg.readInt16("head top");
    const uint16_t headMid = msg.readInt16("head mid");
    const ItemColor hairColor = fromInt(
        msg.readUInt8("hair color"), ItemColor);
    msg.readUInt8("unused");
    const uint16_t shoes = msg.readInt16("shoes / clothes color");

    uint16_t gloves;
    if (dstBeing->getType() == ActorType::Monster)
    {
        if (serverFeatures->haveServerHp())
        {
            const int hp = msg.readInt32("hp");
            const int maxHP = msg.readInt32("max hp");
            if ((hp != 0) && (maxHP != 0))
            {
                dstBeing->setMaxHP(maxHP);
                const int oldHP = dstBeing->getHP();
                if ((oldHP == 0) || oldHP > hp)
                    dstBeing->setHP(hp);
            }
        }
        else
        {
            msg.readInt32("unused");
            msg.readInt32("unused");
        }
        gloves = 0;
    }
    else
    {
        gloves = msg.readInt16("gloves / head dir");
        msg.readInt32("guild");
        msg.readInt16("guild emblem");
    }

    dstBeing->setManner(msg.readInt16("manner"));
    const uint32_t opt3 = msg.readInt16("opt3");
    if (serverFeatures->haveMonsterAttackRange()
        && dstBeing->getType() == ActorType::Monster)
    {
        const int attackRange = CAST_S32(
            msg.readUInt8("attack range (was karma)"));
        dstBeing->setAttackRange(attackRange);
    }
    else
    {
        dstBeing->setKarma(msg.readUInt8("karma"));
    }
    uint8_t gender = msg.readUInt8("gender");

    if ((disguiseId == 0) && dstBeing->getType() == ActorType::Player)
    {
        // reserving bits for future usage
        gender &= 3;
        dstBeing->setGender(Being::intToGender(gender));
        // Set these after the gender, as the sprites may be gender-specific
        if (hairStyle == 0)
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR, 0);
        }
        else
        {
            dstBeing->updateSprite(SPRITE_HAIR_COLOR,
                hairStyle * -1,
                ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        }
        dstBeing->setHairColor(hairColor);
        dstBeing->updateSprite(SPRITE_WEAPON, headBottom);
        dstBeing->updateSprite(SPRITE_HEAD_BOTTOM, headMid);
        dstBeing->updateSprite(SPRITE_CLOTHES_COLOR, headTop);
        dstBeing->updateSprite(SPRITE_HAIR, shoes);
        dstBeing->updateSprite(SPRITE_SHOES, gloves);
        dstBeing->updateSprite(SPRITE_BODY, weapon);
        dstBeing->setWeaponId(weapon);
        dstBeing->updateSprite(SPRITE_FLOOR, shield);
    }
    else if (dstBeing->getType() == ActorType::Npc
             && serverFeatures->haveNpcGender())
    {
        setServerGender(dstBeing, gender);
    }

    uint16_t srcX, srcY, dstX, dstY;
    msg.readCoordinatePair(srcX, srcY, dstX, dstY, "move path");
    if (disguiseId == 0)
    {
        dstBeing->setAction(BeingAction::STAND, 0);
        dstBeing->setTileCoords(srcX, srcY);
        if (serverFeatures->haveMove3())
            dstBeing->setCachedDestination(dstX, dstY);
        else
            dstBeing->setDestination(dstX, dstY);
    }

    msg.readUInt8("unknown");
    msg.readUInt8("unknown");
    msg.readUInt8("unknown");
    msg.readUInt8("unknown");
    msg.readUInt8("unknown");

    dstBeing->setStatusEffectOpitons(option,
        opt1,
        opt2,
        opt3);
    BLOCK_END("BeingRecv::processBeingVisibleOrMove")
}

void BeingRecv::processBeingSpawn(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingSpawn")
    // skipping this packet
    Ea::BeingRecv::mSpawnId = msg.readBeingId("being id");
    msg.readInt16("speed");
    msg.readInt16("opt1");
    msg.readInt16("opt2");
    msg.readInt16("option");
    msg.readInt16("disguise");
    msg.skip(25, "unused");
    BLOCK_END("BeingRecv::processBeingSpawn")
}

void BeingRecv::processSkillCasting(Net::MessageIn &msg)
{
    msg.readInt32("src id");
    msg.readInt32("dst id");
    msg.readInt16("dst x");
    msg.readInt16("dst y");
    msg.readInt16("skill num");
    msg.readInt32("skill get p1");
    msg.readInt32("cast time");
}

void BeingRecv::processBeingStatusChange(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingStatusChange")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingStatusChange")
        return;
    }

    // Status change
    const uint16_t status = msg.readInt16("status");
    const BeingId id = msg.readBeingId("being id");
    const Enable flag = fromBool(
        msg.readUInt8("flag: 0: stop, 1: start"), Enable);

    Being *const dstBeing = actorManager->findBeing(id);
    if (dstBeing != nullptr)
    {
        // dont know on legacy servers is effect really started
        // or not. Because this always sending IsStart_true
        dstBeing->setStatusEffect(status, flag, IsStart_true);
    }
    BLOCK_END("BeingRecv::processBeingStatusChange")
}

void BeingRecv::processBeingMove2(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingMove2")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingMove2")
        return;
    }

    /*
      * A simplified movement packet, used by the
      * later versions of eAthena for both mobs and
      * players
      */
    Being *const dstBeing = actorManager->findBeing(
        msg.readBeingId("being id"));

    /*
      * This packet doesn't have enough info to actually
      * create a new being, so if the being isn't found,
      * we'll just pretend the packet didn't happen
      */

    if (dstBeing == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingMove2")
        return;
    }

    uint16_t srcX, srcY, dstX, dstY;
    msg.readCoordinatePair(srcX, srcY, dstX, dstY, "move path");
    msg.readInt32("tick");

    dstBeing->setAction(BeingAction::STAND, 0);
    dstBeing->setTileCoords(srcX, srcY);
    dstBeing->setDestination(dstX, dstY);
    if (dstBeing->getType() == ActorType::Player)
        dstBeing->setMoveTime();
    BLOCK_END("BeingRecv::processBeingMove2")
}

void BeingRecv::processBeingChangeDirection(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingChangeDirection")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingChangeDirection")
        return;
    }

    Being *const dstBeing = actorManager->findBeing(
        msg.readBeingId("being id"));

    if (dstBeing == nullptr)
    {
        DEBUGLOGSTR("invisible player?");
        msg.readInt16("unused");
        msg.readUInt8("direction");
        BLOCK_END("BeingRecv::processBeingChangeDirection");
        return;
    }

    msg.readInt16("unused");

    const uint8_t dir = Net::MessageIn::fromServerDirection(
        CAST_U8(msg.readUInt8("direction") & 0x0FU));
    dstBeing->setDirection(dir);
    if (localPlayer != nullptr)
        localPlayer->imitateDirection(dstBeing, dir);
    BLOCK_END("BeingRecv::processBeingChangeDirection")
}

void BeingRecv::processPlayerStatusChange(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processPlayerStop")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processPlayerStop")
        return;
    }

    // Change in players' flags
    const BeingId id = msg.readBeingId("account id");
    Being *const dstBeing = actorManager->findBeing(id);
    if (dstBeing == nullptr)
    {
        DEBUGLOGSTR("invisible player?");
        msg.readInt16("stun mode");
        msg.readInt16("status effect");
        msg.readInt16("opt?");
        msg.readUInt8("Unused?");
        return;
    }

    const uint32_t opt1 = msg.readInt16("opt1");
    const uint32_t opt2 = msg.readInt16("opt2");
    const uint32_t option = msg.readInt16("option");
    msg.readUInt8("Unused?");

    dstBeing->setStatusEffectOpitons(option,
        opt1,
        opt2);
    BLOCK_END("BeingRecv::processPlayerStop")
}

void BeingRecv::processBeingResurrect(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingResurrect")
    if (actorManager == nullptr || localPlayer == nullptr)
    {
        BLOCK_END("BeingRecv::processBeingResurrect")
        return;
    }

    // A being changed mortality status

    const BeingId id = msg.readBeingId("being id");
    Being *const dstBeing = actorManager->findBeing(id);
    if (dstBeing == nullptr)
    {
        DEBUGLOGSTR("insible player?");
        msg.readInt16("flag?");
        BLOCK_END("BeingRecv::processBeingResurrect")
        return;
    }

    // If this is player's current target, clear it.
    if (dstBeing == localPlayer->getTarget())
        localPlayer->stopAttack();
    if (dstBeing == localPlayer &&
        deathNotice != nullptr)
    {
        deathNotice->scheduleDelete();
        deathNotice = nullptr;
    }

    if (msg.readInt16("flag?") == 1)
        dstBeing->setAction(BeingAction::STAND, 0);
    BLOCK_END("BeingRecv::processBeingResurrect")
}

void BeingRecv::processPlayerGuilPartyInfo(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processPlayerGuilPartyInfo")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processPlayerGuilPartyInfo")
        return;
    }

    Being *const dstBeing = actorManager->findBeing(
        msg.readBeingId("being id"));

    if (dstBeing != nullptr)
    {
        dstBeing->setPartyName(msg.readString(24, "party name"));
        if ((guildManager == nullptr) || !GuildManager::getEnableGuildBot())
        {
            dstBeing->setGuildName(msg.readString(24, "guild name"));
            dstBeing->setGuildPos(msg.readString(24, "guild pos"));
        }
        else
        {
            msg.readString(24, "guild name");
            msg.readString(24, "guild pos");
        }
        dstBeing->addToCache();
        msg.readString(24, "?");
    }
    else
    {
        msg.readString(24, "party name");
        msg.readString(24, "guild name");
        msg.readString(24, "guild pos");
        msg.readString(24, "?");
    }
    BLOCK_END("BeingRecv::processPlayerGuilPartyInfo")
}

void BeingRecv::processBeingSelfEffect(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processBeingSelfEffect")
    if ((effectManager == nullptr) || (actorManager == nullptr))
    {
        BLOCK_END("BeingRecv::processBeingSelfEffect")
        return;
    }

    const BeingId id = msg.readBeingId("being id");
    Being *const being = actorManager->findBeing(id);
    if (being == nullptr)
    {
        DEBUGLOGSTR("insible player?");
        msg.readInt32("effect type");
        BLOCK_END("BeingRecv::processBeingSelfEffect")
        return;
    }

    const int effectType = msg.readInt32("effect type");

    if (ParticleEngine::enabled)
        effectManager->trigger(effectType, being);

    // +++ need dehard code effectType == 3
    if (effectType == 3 && being->getType() == ActorType::Player
        && (socialWindow != nullptr))
    {   // reset received damage
        socialWindow->resetDamage(being->getName());
    }
    BLOCK_END("BeingRecv::processBeingSelfEffect")
}

void BeingRecv::processSkillCastCancel(Net::MessageIn &msg)
{
    msg.readInt32("skill id");
}

void BeingRecv::processIpResponse(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processIpResponse")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processIpResponse")
        return;
    }

    Being *const dstBeing = actorManager->findBeing(
        msg.readBeingId("being id"));
    if (dstBeing != nullptr)
    {
        const std::string ip = ipToString(msg.readInt32("ip address"));
        dstBeing->setIp(ip);
    }
    else
    {
        msg.readInt32("ip address");
        DEBUGLOGSTR("invisible player?");
    }

    BLOCK_END("BeingRecv::processIpResponse")
}

void BeingRecv::processPvpSet(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processPvpSet")
    const BeingId id = msg.readBeingId("being id");
    const int rank = msg.readInt32("rank");
    int teamId = 0;
    teamId = msg.readInt32("team");
    if (actorManager != nullptr)
    {
        Being *const dstBeing = actorManager->findBeing(id);
        if (dstBeing != nullptr)
        {
            dstBeing->setPvpRank(rank);
            dstBeing->setTeamId(CAST_U16(teamId));
            dstBeing->addToCache();
        }
    }
    BLOCK_END("BeingRecv::processPvpSet")
}

void BeingRecv::applyPlayerAction(Net::MessageIn &msg,
                                  Being *const being,
                                  const uint8_t type)
{
    if (being == nullptr)
        return;
    switch (type)
    {
        case 0:
            being->setAction(BeingAction::STAND, 0);
            localPlayer->imitateAction(being, BeingAction::STAND);
            break;

        case 1:
            if (being->getCurrentAction() != BeingAction::DEAD)
            {
                being->setAction(BeingAction::DEAD, 0);
                being->recalcSpritesOrder();
            }
            break;

        case 2:
            being->setAction(BeingAction::SIT, 0);
            localPlayer->imitateAction(being, BeingAction::SIT);
            break;

        default:
            UNIMPLEMENTEDPACKETFIELD(type);
            break;
    }
}

void BeingRecv::processSkillDamage(Net::MessageIn &msg)
{
    BLOCK_START("BeingRecv::processSkillDamage")
    if (actorManager == nullptr)
    {
        BLOCK_END("BeingRecv::processSkillDamage")
        return;
    }

    const int id = msg.readInt16("skill id");
    Being *const srcBeing = actorManager->findBeing(
        msg.readBeingId("src being id"));
    Being *const dstBeing = actorManager->findBeing(
        msg.readBeingId("dst being id"));
    msg.readInt32("tick");
    msg.readInt32("src speed");
    msg.readInt32("dst speed");
    const int param1 = msg.readInt32("damage");
    const int level = msg.readInt16("skill level");
    msg.readInt16("div");
    msg.readUInt8("skill hit/type?");
    if (srcBeing != nullptr)
        srcBeing->handleSkill(dstBeing, param1, id, level);
    if (dstBeing != nullptr)
        dstBeing->takeDamage(srcBeing, param1, AttackType::SKILL, id, level);
    BLOCK_END("BeingRecv::processSkillDamage")
}

void BeingRecv::setServerGender(Being *const being,
                                const uint8_t gender)
{
    if (being == nullptr)
        return;
    switch (gender)
    {
        case 2:
            being->setGender(Gender::FEMALE);
            break;
        case 3:
            being->setGender(Gender::MALE);
            break;
        case 4:
            being->setGender(Gender::OTHER);
            break;
        default:
            being->setGender(Gender::UNSPECIFIED);
            break;
    }
}

}  // namespace TmwAthena
