/*
 *  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 "net/eathena/beinghandler.h"

#include "actormanager.h"
#include "effectmanager.h"
#include "game.h"
#include "guildmanager.h"

#include "being/localplayer.h"
#include "being/mercenaryinfo.h"
#include "being/playerinfo.h"

#include "particle/particle.h"

#include "input/keyboardconfig.h"

#include "gui/windows/socialwindow.h"

#include "gui/windows/outfitwindow.h"

#include "net/serverfeatures.h"

#include "net/ea/eaprotocol.h"

#include "net/eathena/maptypeproperty2.h"
#include "net/eathena/messageout.h"
#include "net/eathena/protocol.h"
#include "net/eathena/sprite.h"

#include "resources/iteminfo.h"

#include "resources/db/itemdb.h"

#include "resources/map/map.h"

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

#include "debug.h"

extern Net::BeingHandler *beingHandler;
extern int serverVersion;

namespace EAthena
{

BeingHandler::BeingHandler(const bool enableSync) :
    MessageHandler(),
    Ea::BeingHandler(enableSync)
{
    static const uint16_t _messages[] =
    {
        SMSG_BEING_VISIBLE,
        SMSG_BEING_MOVE,
        SMSG_BEING_MOVE2,
        SMSG_BEING_REMOVE,
        SMSG_BEING_REMOVE_SKILL,
        SMSG_SKILL_DAMAGE,
        SMSG_BEING_ACTION,
        SMSG_BEING_ACTION2,
        SMSG_BEING_SELFEFFECT,
        SMSG_BEING_SPECIAL_EFFECT,
        SMSG_BEING_SPECIAL_EFFECT_NUM,
        SMSG_BEING_SOUND_EFFECT,
        SMSG_BEING_EMOTION,
        SMSG_BEING_CHANGE_LOOKS2,
        SMSG_BEING_NAME_RESPONSE,
        SMSG_BEING_NAME_RESPONSE2,
        SMSG_PLAYER_GUILD_PARTY_INFO,
        SMSG_BEING_CHANGE_DIRECTION,
        SMSG_PLAYER_STOP,
        SMSG_PLAYER_MOVE_TO_ATTACK,
        SMSG_PLAYER_STATUS_CHANGE,
        SMSG_PLAYER_STATUS_CHANGE2,
        SMSG_PLAYER_STATUS_CHANGE_NO_TICK,
        SMSG_BEING_STATUS_CHANGE,
        SMSG_BEING_STATUS_CHANGE2,
        SMSG_BEING_RESURRECT,
        SMSG_SOLVE_CHAR_NAME,
        SMSG_BEING_SPAWN,
        SMSG_SKILL_CASTING,
        SMSG_SKILL_CAST_CANCEL,
        SMSG_SKILL_NO_DAMAGE,
        SMSG_SKILL_GROUND_NO_DAMAGE,
        SMSG_SKILL_ENTRY,
        SMSG_PVP_MAP_MODE,
        SMSG_PVP_SET,
        SMSG_MAP_TYPE_PROPERTY2,
        SMSG_MAP_TYPE,
        SMSG_MONSTER_HP,
        SMSG_PLAYER_HP,
        SMSG_SKILL_AUTO_CAST,
        SMSG_RANKS_LIST,
        SMSG_BEING_FAKE_NAME,
        SMSG_BEING_STAT_UPDATE_1,
        SMSG_MOB_INFO,
        SMSG_BEING_MOVE3,
        SMSG_BEING_ATTRS,
        SMSG_MONSTER_INFO,
        SMSG_CLASS_CHANGE,
        CMSG_SPIRIT_BALLS,
        CMSG_BLADE_STOP,
        0
    };
    handledMessages = _messages;
    beingHandler = this;
}

void BeingHandler::requestNameById(const int id) const
{
    createOutPacket(CMSG_NAME_REQUEST);
    outMsg.writeInt32(id, "being id");
}

void BeingHandler::handleMessage(Net::MessageIn &msg)
{
    switch (msg.getId())
    {
        case SMSG_BEING_VISIBLE:
            processBeingVisible(msg);
            break;

        case SMSG_BEING_MOVE:
            processBeingMove(msg);
            break;

        case SMSG_BEING_SPAWN:
            processBeingSpawn(msg);
            break;

        case SMSG_BEING_MOVE2:
            processBeingMove2(msg);
            break;

        case SMSG_BEING_REMOVE:
            processBeingRemove(msg);
            break;

        case SMSG_BEING_REMOVE_SKILL:
            processBeingRemoveSkil(msg);
            break;

        case SMSG_BEING_RESURRECT:
            processBeingResurrect(msg);
            break;

        case SMSG_SKILL_DAMAGE:
            processSkillDamage(msg);
            break;

        case SMSG_SKILL_AUTO_CAST:
            processSkillAutoCast(msg);
            break;

        case SMSG_BEING_ACTION:
            processBeingAction(msg);
            break;

        case SMSG_BEING_ACTION2:
            processBeingAction2(msg);
            break;

        case SMSG_BEING_SELFEFFECT:
            processBeingSelfEffect(msg);
            break;

        case SMSG_BEING_SPECIAL_EFFECT:
            processBeingSpecialEffect(msg);
            break;

        case SMSG_BEING_SPECIAL_EFFECT_NUM:
            processBeingSpecialEffectNum(msg);
            break;

        case SMSG_BEING_SOUND_EFFECT:
            processBeingSoundEffect(msg);
            break;

        case SMSG_BEING_EMOTION:
            processBeingEmotion(msg);
            break;

        case SMSG_BEING_CHANGE_LOOKS2:
            processBeingChangeLook2(msg);
            break;

        case SMSG_BEING_NAME_RESPONSE:
            processNameResponse(msg);
            break;

        case SMSG_BEING_NAME_RESPONSE2:
            processNameResponse2(msg);
            break;

        case SMSG_SOLVE_CHAR_NAME:
            break;

        case SMSG_PLAYER_GUILD_PARTY_INFO:
            processPlayerGuilPartyInfo(msg);
            break;

        case SMSG_BEING_CHANGE_DIRECTION:
            processBeingChangeDirection(msg);
            break;

        case SMSG_PLAYER_STOP:
            processPlayerStop(msg);
            break;

        case SMSG_PLAYER_MOVE_TO_ATTACK:
            processPlayerMoveToAttack(msg);
            break;

        case SMSG_PLAYER_STATUS_CHANGE:
            processPlaterStatusChange(msg);
            break;

        case SMSG_PLAYER_STATUS_CHANGE2:
            processPlaterStatusChange2(msg);
            break;

        case SMSG_PLAYER_STATUS_CHANGE_NO_TICK:
            processPlaterStatusChangeNoTick(msg);
            break;

        case SMSG_BEING_STATUS_CHANGE:
            processBeingStatusChange(msg);
            break;

        case SMSG_BEING_STATUS_CHANGE2:
            processBeingStatusChange2(msg);
            break;

        case SMSG_SKILL_CASTING:
            processSkillCasting(msg);
            break;

        case SMSG_SKILL_CAST_CANCEL:
            msg.readInt32("id?");
            break;

        case SMSG_SKILL_NO_DAMAGE:
            processSkillNoDamage(msg);
            break;

        case SMSG_SKILL_GROUND_NO_DAMAGE:
            processSkillGroundNoDamage(msg);
            break;

        case SMSG_SKILL_ENTRY:
            processSkillEntry(msg);
            break;

        case SMSG_PVP_MAP_MODE:
            processPvpMapMode(msg);
            break;

        case SMSG_PVP_SET:
            processPvpSet(msg);
            break;

        case SMSG_MAP_TYPE_PROPERTY2:
            processMapTypeProperty(msg);
            break;

        case SMSG_MAP_TYPE:
            processMapType(msg);
            break;

        case SMSG_MONSTER_HP:
        case SMSG_PLAYER_HP:
            processMonsterHp(msg);
            break;

        case SMSG_RANKS_LIST:
            processRanksList(msg);
            break;

        case SMSG_BEING_FAKE_NAME:
            processBeingFakeName(msg);
            break;

        case SMSG_BEING_STAT_UPDATE_1:
            processBeingStatUpdate1(msg);
            break;

        case SMSG_MOB_INFO:
            processMobInfo(msg);
            break;

        case SMSG_BEING_MOVE3:
            processBeingMove3(msg);
            break;

        case SMSG_BEING_ATTRS:
            processBeingAttrs(msg);
            break;

        case SMSG_MONSTER_INFO:
            processMonsterInfo(msg);
            break;

        case SMSG_CLASS_CHANGE:
            processClassChange(msg);
            break;

        case CMSG_SPIRIT_BALLS:
            processSpiritBalls(msg);
            break;

        case CMSG_BLADE_STOP:
            processBladeStop(msg);
            break;

        default:
            break;
    }
}

Being *BeingHandler::createBeing2(const int id,
                                  const int16_t job,
                                  const BeingType::BeingType beingType)
{
    if (!actorManager)
        return nullptr;

    ActorType::Type type = ActorType::Unknown;
    switch (beingType)
    {
        case BeingType::PC:
            type = ActorType::Player;
            break;
        case BeingType::NPC:
        case BeingType::NPC_EVENT:
            type = ActorType::Npc;
            break;
        case BeingType::MONSTER:
            type = ActorType::Monster;
            break;
        case BeingType::MERSOL:
            type = ActorType::Mercenary;
            break;
        case BeingType::PET:
            type = ActorType::Pet;
            break;
        case BeingType::HOMUN:
            type = ActorType::Homunculus;
            break;
        case BeingType::ITEM:
        case BeingType::SKILL:
        case BeingType::ELEMENTAL:
            logger->log("not supported object type: %d, job: %d",
                static_cast<int>(beingType), static_cast<int>(job));
            break;
        case BeingType::CHAT:
        default:
            type = ActorType::Monster;
            logger->log("not supported object type: %d, job: %d",
                static_cast<int>(beingType), static_cast<int>(job));
            break;
    }
    if (job == 45 && beingType == BeingType::NPC_EVENT)
        type = ActorType::Portal;

    Being *const being = actorManager->createBeing(id, type, job);
    if (beingType == BeingType::MERSOL)
    {
        MercenaryInfo *const info = PlayerInfo::getMercenary();
        if (info && info->id == id)
            PlayerInfo::setMercenaryBeing(being);
    }
    else if (beingType == BeingType::PET)
    {
        if (PlayerInfo::getPetBeingId() == id)
            PlayerInfo::setPetBeing(being);
    }
    return being;
}

void BeingHandler::undress(Being *const being) const
{
    being->setSprite(SPRITE_WEAPON, 0);
    being->setSprite(SPRITE_HEAD_BOTTOM, 0);
    being->setSprite(SPRITE_CLOTHES_COLOR, 0);
    being->setSprite(SPRITE_HAIR, 0);
    being->setSprite(SPRITE_SHOES, 0);
//    being->setSprite(SPRITE_BODY, 0, "", true);
}

void BeingHandler::requestRanks(const Rank::Rank rank) const
{
    createOutPacket(CMSG_REQUEST_RANKS);
    outMsg.writeInt16(static_cast<int16_t>(rank), "type");
}

void BeingHandler::processBeingChangeLook2(Net::MessageIn &msg)
{
    if (!actorManager)
        return;

    Being *const dstBeing = actorManager->findBeing(
        msg.readInt32("being id"));
    const uint8_t type = msg.readUInt8("type");

    const int id = msg.readInt16("id1");
    unsigned int id2 = msg.readInt16("id2");
    if (type != 2)
        id2 = 1;

    if (!localPlayer || !dstBeing)
        return;

    processBeingChangeLookContinue(dstBeing, type, id, id2);
}

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

    const std::string color;
    switch (type)
    {
        case 0:  // change race
            dstBeing->setSubtype(static_cast<uint16_t>(id),
                dstBeing->getLook());
            break;
        case 1:  // eAthena LOOK_HAIR
            dstBeing->setHairColor(id);
            dstBeing->setSpriteID(SPRITE_HAIR_COLOR, id * -1);
            break;
        case 2:  // LOOK_WEAPON Weapon ID in id, Shield ID in id2
            dstBeing->setSprite(SPRITE_BODY, id, "", 1, true);
            if (!mHideShield)
                dstBeing->setSprite(SPRITE_FLOOR, id2);
            localPlayer->imitateOutfit(dstBeing, SPRITE_FLOOR);
            break;
        case 3:  // LOOK_HEAD_BOTTOM
            dstBeing->setSprite(SPRITE_WEAPON, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_WEAPON);
            break;
        case 4:  // LOOK_HEAD_TOP Change upper headgear for eAthena, hat for us
            dstBeing->setSprite(SPRITE_CLOTHES_COLOR, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_CLOTHES_COLOR);
            break;
        case 5:  // LOOK_HEAD_MID Change middle headgear for eathena,
                 // armor for us
            dstBeing->setSprite(SPRITE_HEAD_BOTTOM, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_BOTTOM);
            break;
        case 6:  // eAthena LOOK_HAIR_COLOR
            dstBeing->setHairColor(id);
            dstBeing->setSpriteColor(SPRITE_HAIR_COLOR,
                ItemDB::get(dstBeing->getSpriteID(
                SPRITE_HAIR_COLOR)).getDyeColorsString(id));
            break;
        case 7:  // Clothes color. Now used as look
            dstBeing->setLook(static_cast<uint8_t>(id));
            break;
        case 8:  // eAthena LOOK_SHIELD
            if (!mHideShield)
            {
                dstBeing->setSprite(SPRITE_FLOOR, id, color,
                    static_cast<unsigned char>(id2));
            }
            localPlayer->imitateOutfit(dstBeing, SPRITE_FLOOR);
            break;
        case 9:  // eAthena LOOK_SHOES
            dstBeing->setSprite(SPRITE_HAIR, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_HAIR);
            break;
        case 10:  // LOOK_GLOVES
            dstBeing->setSprite(SPRITE_SHOES, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_SHOES);
            break;
        case 11:  // LOOK_FLOOR
            dstBeing->setSprite(SPRITE_SHIELD, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_SHIELD);
            break;
        case 12:  // LOOK_ROBE
            dstBeing->setSprite(SPRITE_HEAD_TOP, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_TOP);
            break;
        case 13:  // COSTUME_HEAD_TOP
            dstBeing->setSprite(SPRITE_HEAD_MID, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_MID);
            break;
        case 14:  // COSTUME_HEAD_MID
            dstBeing->setSprite(SPRITE_ROBE, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_ROBE);
            break;
        case 15:  // COSTUME_HEAD_LOW
            dstBeing->setSprite(SPRITE_EVOL2, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL2);
            break;
        case 16:  // COSTUME_GARMENT
            dstBeing->setSprite(SPRITE_EVOL3, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL3);
            break;
        case 17:  // ARMOR
            dstBeing->setSprite(SPRITE_EVOL4, id, color,
                static_cast<unsigned char>(id2));
            localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL4);
            break;
        default:
            logger->log("QQQ3 CHANGE_LOOKS: unsupported type: "
                    "%d, id: %d", type, id);
            logger->log("ID: " + toString(dstBeing->getId()));
            logger->log("name: " + toString(dstBeing->getName()));
            break;
    }
}

void BeingHandler::processBeingVisible(Net::MessageIn &msg)
{
    if (!actorManager)
        return;

    msg.readInt16("len");
    const BeingType::BeingType type = static_cast<BeingType::BeingType>(
        msg.readUInt8("object type"));

    // Information about a being in range
    const int id = msg.readInt32("being id");
    int spawnId;
    if (id == mSpawnId)
        spawnId = mSpawnId;
    else
        spawnId = 0;
    mSpawnId = 0;

    int16_t speed = msg.readInt16("speed");
    const uint16_t stunMode = msg.readInt16("opt1");
    // probably wrong effect usage
    const uint32_t statusEffects = msg.readInt16("opt2");
    msg.readInt32("option");

    const int16_t job = msg.readInt16("class");

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

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

    if (!dstBeing)
    {
        if (actorManager->isBlocked(id) == true)
            return;

        dstBeing = createBeing2(id, job, type);

        if (!dstBeing)
            return;
    }
    else
    {
        // undeleting marked for deletion being
        if (dstBeing->getType() == ActorType::Npc)
            actorManager->undelete(dstBeing);
    }

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

    if (spawnId)
    {
        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;

    dstBeing->setWalkSpeed(Vector(speed, speed, 0));
    dstBeing->setSubtype(job, 0);
    if (dstBeing->getType() == ActorType::Monster && localPlayer)
        localPlayer->checkNewName(dstBeing);

    const int hairStyle = msg.readInt16("hair style");
    const uint32_t weapon = static_cast<uint32_t>(msg.readInt32("weapon"));
    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 int hairColor = msg.readInt16("hair color");
    const uint16_t shoes = msg.readInt16("shoes or clothes color?");

    const uint16_t gloves = msg.readInt16("head dir / gloves");
    // may be use robe as gloves?
    msg.readInt16("robe");
    msg.readInt32("guild id");
    msg.readInt16("guild emblem");
    dstBeing->setManner(msg.readInt16("manner"));
    dstBeing->setStatusEffectBlock(32, static_cast<uint16_t>(
        msg.readInt32("opt3")));
    dstBeing->setKarma(msg.readUInt8("karma"));
    uint8_t gender = static_cast<uint8_t>(msg.readUInt8("gender") & 3);

    if (dstBeing->getType() == ActorType::Player)
    {
        dstBeing->setGender(Being::intToGender(gender));
        dstBeing->setHairColor(hairColor);
        // Set these after the gender, as the sprites may be gender-specific
        setSprite(dstBeing, SPRITE_HAIR_COLOR, hairStyle * -1,
            ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        setSprite(dstBeing, SPRITE_WEAPON, headBottom);
        setSprite(dstBeing, SPRITE_HEAD_BOTTOM, headMid);
        setSprite(dstBeing, SPRITE_CLOTHES_COLOR, headTop);
        setSprite(dstBeing, SPRITE_HAIR, shoes);
        setSprite(dstBeing, SPRITE_SHOES, gloves);
        setSprite(dstBeing, SPRITE_BODY, weapon, "", 1, true);
//        if (!mHideShield)
//            setSprite(dstBeing, SPRITE_FLOOR, shield);
    }
    else if (dstBeing->getType() == ActorType::Npc
             && serverFeatures->haveNpcGender())
    {
        dstBeing->setGender(Being::intToGender(gender));
    }

    uint8_t dir;
    uint16_t x, y;
    msg.readCoordinates(x, y, dir, "position");
    msg.readInt8("xs");
    msg.readInt8("ys");
    msg.readUInt8("action type");
    dstBeing->setTileCoords(x, y);

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

    dstBeing->setDirection(dir);

    const int level = static_cast<int>(msg.readInt16("level"));
    if (level)
        dstBeing->setLevel(level);
    msg.readInt16("font");

    const int maxHP = msg.readInt32("max hp");
    const int hp = msg.readInt32("hp");
    dstBeing->setMaxHP(maxHP);
    dstBeing->setHP(hp);

    msg.readInt8("is boss");

    dstBeing->setStunMode(stunMode);
    dstBeing->setStatusEffectBlock(0, static_cast<uint16_t>(
        (statusEffects >> 16) & 0xffffU));
    dstBeing->setStatusEffectBlock(16, static_cast<uint16_t>(
        statusEffects & 0xffffU));
}

void BeingHandler::processBeingMove(Net::MessageIn &msg)
{
    if (!actorManager)
        return;

    msg.readInt16("len");
    const BeingType::BeingType type = static_cast<BeingType::BeingType>(
        msg.readUInt8("object type"));

    // Information about a being in range
    const int id = msg.readInt32("being id");
    int spawnId;
    if (id == mSpawnId)
        spawnId = mSpawnId;
    else
        spawnId = 0;
    mSpawnId = 0;
    int16_t speed = msg.readInt16("speed");
//    if (visible)
//    {
        const uint16_t stunMode = msg.readInt16("opt1");
        // probably wrong effect usage
        const uint32_t statusEffects = msg.readInt16("opt2");
//    }
//    else
//    {
// commented for now, probably it can be removed after testing
//        msg.readInt16("body state");
//        msg.readInt16("health state");
//    }
    msg.readInt32("effect state");

    const int16_t job = msg.readInt16("class");

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

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

    if (!dstBeing)
    {
        if (actorManager->isBlocked(id) == true)
            return;

        dstBeing = createBeing2(id, job, type);

        if (!dstBeing)
            return;
    }
    else
    {
        // undeleting marked for deletion being
        if (dstBeing->getType() == ActorType::Npc)
            actorManager->undelete(dstBeing);
    }

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

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

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

    dstBeing->setWalkSpeed(Vector(speed, speed, 0));
    dstBeing->setSubtype(job, 0);
    if (dstBeing->getType() == ActorType::Monster && localPlayer)
        localPlayer->checkNewName(dstBeing);

    const int hairStyle = msg.readInt16("hair style");
    const uint32_t weapon = static_cast<uint32_t>(msg.readInt32("weapon"));
    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 int hairColor = msg.readInt16("hair color");
    const uint16_t shoes = msg.readInt16("shoes or clothes color?");

    const uint16_t gloves = msg.readInt16("head dir / gloves");
    // may be use robe as gloves?
    msg.readInt16("robe");
    msg.readInt32("guild id");
    msg.readInt16("guild emblem");
    dstBeing->setManner(msg.readInt16("manner"));
    dstBeing->setStatusEffectBlock(32, static_cast<uint16_t>(
        msg.readInt32("opt3")));
    dstBeing->setKarma(msg.readUInt8("karma"));
    uint8_t gender = static_cast<uint8_t>(msg.readUInt8("gender") & 3);

    if (dstBeing->getType() == ActorType::Player)
    {
        dstBeing->setGender(Being::intToGender(gender));
        dstBeing->setHairColor(hairColor);
        // Set these after the gender, as the sprites may be gender-specific
        setSprite(dstBeing, SPRITE_HAIR_COLOR, hairStyle * -1,
            ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        setSprite(dstBeing, SPRITE_WEAPON, headBottom);
        setSprite(dstBeing, SPRITE_HEAD_BOTTOM, headMid);
        setSprite(dstBeing, SPRITE_CLOTHES_COLOR, headTop);
        setSprite(dstBeing, SPRITE_HAIR, shoes);
        setSprite(dstBeing, SPRITE_SHOES, gloves);
        setSprite(dstBeing, SPRITE_BODY, weapon, "", 1, true);
//        if (!mHideShield)
//            setSprite(dstBeing, SPRITE_FLOOR, shield);
    }
    else if (dstBeing->getType() == ActorType::Npc
             && serverFeatures->haveNpcGender())
    {
        dstBeing->setGender(Being::intToGender(gender));
    }

    uint16_t srcX, srcY, dstX, dstY;
    msg.readCoordinatePair(srcX, srcY, dstX, dstY, "move path");
    msg.readUInt8("(sx<<4) | (sy&0x0f)");
    msg.readInt8("xs");
    msg.readInt8("ys");
    dstBeing->setAction(BeingAction::STAND, 0);
    dstBeing->setTileCoords(srcX, srcY);
    if (localPlayer)
        localPlayer->followMoveTo(dstBeing, srcX, srcY, dstX, dstY);
    if (!serverFeatures->haveMove3())
        dstBeing->setDestination(dstX, dstY);

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

    uint8_t d = 0;
    if (srcX == dstX && srcY == dstY)
    {   // if player did one step from invisible area to visible,
        // move path is broken
        int x2 = localPlayer->getTileX();
        int y2 = localPlayer->getTileY();
        if (abs(x2 - srcX) > abs(y2 - srcY))
            y2 = srcY;
        else
            x2 = srcX;
        d = dstBeing->calcDirection(x2, y2);
    }
    else
    {
        d = dstBeing->calcDirection(dstX, dstY);
    }
    if (d && dstBeing->getDirection() != d)
        dstBeing->setDirection(d);

    const int level = static_cast<int>(msg.readInt16("level"));
    if (level)
        dstBeing->setLevel(level);
    msg.readInt16("font");

    const int maxHP = msg.readInt32("max hp");
    const int hp = msg.readInt32("hp");
    dstBeing->setMaxHP(maxHP);
    dstBeing->setHP(hp);

    msg.readInt8("is boss");

    dstBeing->setStunMode(stunMode);
    dstBeing->setStatusEffectBlock(0, static_cast<uint16_t>(
        (statusEffects >> 16) & 0xffffU));
    dstBeing->setStatusEffectBlock(16, static_cast<uint16_t>(
        statusEffects & 0xffffU));
}

void BeingHandler::processBeingSpawn(Net::MessageIn &msg)
{
    if (!actorManager)
        return;

    msg.readInt16("len");
    const BeingType::BeingType type = static_cast<BeingType::BeingType>(
        msg.readUInt8("object type"));

    // Information about a being in range
    const int id = msg.readInt32("being id");
    mSpawnId = id;
    const int spawnId = id;
    int16_t speed = msg.readInt16("speed");
//    if (visible)
//    {
        const uint16_t stunMode = msg.readInt16("opt1");
        // probably wrong effect usage
        const uint32_t statusEffects = msg.readInt16("opt2");
//    }
//    else
//    {
// commented for now, probably it can be removed after testing
//        msg.readInt16("body state");
//        msg.readInt16("health state");
//    }
    msg.readInt32("effect state");

    const int16_t job = msg.readInt16("class");

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

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

    if (!dstBeing)
    {
        if (actorManager->isBlocked(id) == true)
            return;

        dstBeing = createBeing2(id, job, type);

        if (!dstBeing)
            return;
    }
    else
    {
        // undeleting marked for deletion being
        if (dstBeing->getType() == ActorType::Npc)
            actorManager->undelete(dstBeing);
    }

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

    if (spawnId)
    {
        dstBeing->setAction(BeingAction::SPAWN, 0);
    }

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

    dstBeing->setWalkSpeed(Vector(speed, speed, 0));
    dstBeing->setSubtype(job, 0);
    if (dstBeing->getType() == ActorType::Monster && localPlayer)
        localPlayer->checkNewName(dstBeing);

    const int hairStyle = msg.readInt16("hair style");
    const uint32_t weapon = static_cast<uint32_t>(msg.readInt32("weapon"));
    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 int hairColor = msg.readInt16("hair color");
    const uint16_t shoes = msg.readInt16("shoes or clothes color?");

    const uint16_t gloves = msg.readInt16("head dir / gloves");
    // may be use robe as gloves?
    msg.readInt16("robe");
    msg.readInt32("guild id");
    msg.readInt16("guild emblem");
    dstBeing->setManner(msg.readInt16("manner"));
    dstBeing->setStatusEffectBlock(32, static_cast<uint16_t>(
        msg.readInt32("opt3")));
    dstBeing->setKarma(msg.readUInt8("karma"));
    uint8_t gender = static_cast<uint8_t>(msg.readUInt8("gender") & 3);

    if (dstBeing->getType() == ActorType::Player)
    {
        dstBeing->setGender(Being::intToGender(gender));
        dstBeing->setHairColor(hairColor);
        // Set these after the gender, as the sprites may be gender-specific
        setSprite(dstBeing, SPRITE_HAIR_COLOR, hairStyle * -1,
            ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
        setSprite(dstBeing, SPRITE_WEAPON, headBottom);
        setSprite(dstBeing, SPRITE_HEAD_BOTTOM, headMid);
        setSprite(dstBeing, SPRITE_CLOTHES_COLOR, headTop);
        setSprite(dstBeing, SPRITE_HAIR, shoes);
        setSprite(dstBeing, SPRITE_SHOES, gloves);
        setSprite(dstBeing, SPRITE_BODY, weapon, "", 1, true);
//        if (!mHideShield)
//            setSprite(dstBeing, SPRITE_FLOOR, shield);
    }
    else if (dstBeing->getType() == ActorType::Npc
             && serverFeatures->haveNpcGender())
    {
        dstBeing->setGender(Being::intToGender(gender));
    }

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

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

    dstBeing->setDirection(dir);

    const int level = static_cast<int>(msg.readInt16("level"));
    if (level)
        dstBeing->setLevel(level);
    msg.readInt16("font");

    const int maxHP = msg.readInt32("max hp");
    const int hp = msg.readInt32("hp");
    dstBeing->setMaxHP(maxHP);
    dstBeing->setHP(hp);

    msg.readInt8("is boss");

    dstBeing->setStunMode(stunMode);
    dstBeing->setStatusEffectBlock(0, static_cast<uint16_t>(
        (statusEffects >> 16) & 0xffffU));
    dstBeing->setStatusEffectBlock(16, static_cast<uint16_t>(
        statusEffects & 0xffffU));
}

void BeingHandler::processMapTypeProperty(Net::MessageIn &msg)
{
    const int16_t type = msg.readInt16("type");
    const int flags = msg.readInt32("flags");
    if (type == 0x28)
    {
        // +++ need get other flags from here
        MapTypeProperty2 props;
        props.data = static_cast<uint32_t>(flags);
        Game *const game = Game::instance();
        Map *const map = game->getCurrentMap();
        if (!map)
            return;
        map->setPvpMode(props.bits.party | (props.bits.guild * 2));
    }
}

void BeingHandler::processMapType(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    // battle ground map or not
    msg.readInt16("type");
}

void BeingHandler::processSkillCasting(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    msg.readInt32("src id");
    msg.readInt32("dst id");
    msg.readInt16("dst x");
    msg.readInt16("dst y");
    msg.readInt16("skill id");
    msg.readInt32("property");  // can be used to trigger effect
    msg.readInt32("cast time");
    msg.readInt8("dispossable");
}

void BeingHandler::processBeingStatusChange(Net::MessageIn &msg)
{
    BLOCK_START("BeingHandler::processBeingStatusChange")
    if (!actorManager)
    {
        BLOCK_END("BeingHandler::processBeingStatusChange")
        return;
    }

    // Status change
    const uint16_t status = msg.readInt16("status");
    const int id = msg.readInt32("being id");
    const bool flag = msg.readUInt8("flag: 0: stop, 1: start");
    msg.readInt32("total");
    msg.readInt32("left");
    msg.readInt32("val1");
    msg.readInt32("val2");
    msg.readInt32("val3");

    Being *const dstBeing = actorManager->findBeing(id);
    if (dstBeing)
        dstBeing->setStatusEffect(status, flag);
    BLOCK_END("BeingHandler::processBeingStatusChange")
}

void BeingHandler::processBeingStatusChange2(Net::MessageIn &msg)
{
    BLOCK_START("BeingHandler::processBeingStatusChange")
    if (!actorManager)
    {
        BLOCK_END("BeingHandler::processBeingStatusChange")
        return;
    }

    // Status change
    const uint16_t status = msg.readInt16("status");
    const int id = msg.readInt32("being id");
    const bool flag = msg.readUInt8("flag: 0: stop, 1: start");
    msg.readInt32("left");
    msg.readInt32("val1");
    msg.readInt32("val2");
    msg.readInt32("val3");

    Being *const dstBeing = actorManager->findBeing(id);
    if (dstBeing)
        dstBeing->setStatusEffect(status, flag);
    BLOCK_END("BeingHandler::processBeingStatusChange")
}

void BeingHandler::processBeingMove2(Net::MessageIn &msg)
{
    BLOCK_START("BeingHandler::processBeingMove2")
    if (!actorManager)
    {
        BLOCK_END("BeingHandler::processBeingMove2")
        return;
    }

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

    uint16_t srcX, srcY, dstX, dstY;
    msg.readCoordinatePair(srcX, srcY, dstX, dstY, "move path");
    msg.readUInt8("(sx<<4) | (sy&0x0f)");
    msg.readInt32("tick");

    /*
      * 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)
    {
        BLOCK_END("BeingHandler::processBeingMove2")
        return;
    }

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

void BeingHandler::processBeingAction2(Net::MessageIn &msg)
{
    BLOCK_START("BeingHandler::processBeingAction2")
    if (!actorManager)
    {
        BLOCK_END("BeingHandler::processBeingAction2")
        return;
    }

    Being *const srcBeing = actorManager->findBeing(
        msg.readInt32("src being id"));
    Being *const dstBeing = actorManager->findBeing(
        msg.readInt32("dst being id"));

    msg.readInt32("tick");
    const int srcSpeed = msg.readInt32("src speed");
    msg.readInt32("dst speed");
    const int param1 = msg.readInt32("damage");
    msg.readInt16("count");
    const uint8_t type = msg.readUInt8("action");
    msg.readInt32("left damage");

    switch (type)
    {
        case AttackType::HIT:  // Damage
        case AttackType::CRITICAL:  // Critical Damage
        case AttackType::MULTI:  // Critical Damage
        case AttackType::MULTI_REFLECT:
        case AttackType::REFLECT:  // Reflected Damage
        case AttackType::FLEE:  // Lucky Dodge
        case AttackType::SPLASH:
        case AttackType::SKILL:
        case AttackType::REPEATE:
            if (srcBeing)
            {
                if (srcSpeed && srcBeing->getType() == ActorType::Player)
                    srcBeing->setAttackDelay(srcSpeed);
                // attackid=1, type
                srcBeing->handleAttack(dstBeing, param1, 1);
                if (srcBeing->getType() == ActorType::Player)
                    srcBeing->setAttackTime();
            }
            if (dstBeing)
            {
                dstBeing->takeDamage(srcBeing, param1,
                    static_cast<AttackType::Type>(type));
            }
            break;

        case AttackType::PICKUP:
            break;

        case AttackType::TOUCH_SKILL:
            break;

        case AttackType::SIT:
            if (srcBeing)
            {
                srcBeing->setAction(BeingAction::SIT, 0);
                if (srcBeing->getType() == ActorType::Player)
                {
                    srcBeing->setMoveTime();
                    if (localPlayer)
                        localPlayer->imitateAction(srcBeing, BeingAction::SIT);
                }
            }
            break;

        case AttackType::STAND:
            if (srcBeing)
            {
                srcBeing->setAction(BeingAction::STAND, 0);
                if (srcBeing->getType() == ActorType::Player)
                {
                    srcBeing->setMoveTime();
                    if (localPlayer)
                    {
                        localPlayer->imitateAction(srcBeing,
                            BeingAction::STAND);
                    }
                }
            }
            break;
        default:
            logger->log("QQQ1 SMSG_BEING_ACTION:");
            if (srcBeing)
                logger->log("srcBeing:" + toString(srcBeing->getId()));
            if (dstBeing)
                logger->log("dstBeing:" + toString(dstBeing->getId()));
            logger->log("type: " + toString(type));
            break;
    }
    BLOCK_END("BeingHandler::processBeingAction2")
}

void BeingHandler::processMonsterHp(Net::MessageIn &msg)
{
    Being *const dstBeing = actorManager->findBeing(
        msg.readInt32("monster id"));
    const int hp = msg.readInt32("hp");
    const int maxHP = msg.readInt32("max hp");
    if (dstBeing)
    {
        dstBeing->setHP(hp);
        dstBeing->setMaxHP(maxHP);
    }
}

void BeingHandler::processSkillAutoCast(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    msg.readInt16("skill id");
    msg.readInt16("inf");
    msg.readInt16("unused");
    msg.readInt16("skill level");
    msg.readInt16("sp");
    msg.readInt16("range");
    msg.readString(24, "skill name");
    msg.readInt8("unused");
}

void BeingHandler::processRanksList(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    // +++ here need window with rank tables.
    msg.readInt16("rank type");
    for (int f = 0; f < 10; f ++)
    {
        msg.readString(24, "name");
        msg.readInt32("points");
    }
    msg.readInt32("my points");
}

void BeingHandler::processBeingChangeDirection(Net::MessageIn &msg)
{
    BLOCK_START("BeingHandler::processBeingChangeDirection")
    if (!actorManager)
    {
        BLOCK_END("BeingHandler::processBeingChangeDirection")
        return;
    }

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

    msg.readInt16("head direction");

    const uint8_t dir = Net::MessageIn::fromServerDirection(
        static_cast<uint8_t>(msg.readUInt8("player direction") & 0x0FU));

    if (!dstBeing)
    {
        BLOCK_END("BeingHandler::processBeingChangeDirection")
        return;
    }

    dstBeing->setDirection(dir);
    if (localPlayer)
        localPlayer->imitateDirection(dstBeing, dir);
    BLOCK_END("BeingHandler::processBeingChangeDirection")
}

void BeingHandler::processBeingSpecialEffect(Net::MessageIn &msg)
{
    if (!effectManager || !actorManager)
        return;

    const int id = static_cast<uint32_t>(msg.readInt32("being id"));
    Being *const being = actorManager->findBeing(id);
    if (!being)
        return;

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

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

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

void BeingHandler::processBeingSpecialEffectNum(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    // +++ need somhow show this effects.
    // type is not same with self/misc effect.
    msg.readInt32("account id");
    msg.readInt32("effect type");
    msg.readInt32("num");  // effect variable
}

void BeingHandler::processBeingSoundEffect(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    // +++ need play this effect.
    msg.readString(24, "sound effect name");
    msg.readUInt8("type");
    msg.readInt32("unused");
    msg.readInt32("source being id");
}

void BeingHandler::applyPlayerAction(Being *const being, const uint8_t type)
{
    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:
            // need set stand state?
            logger->log("QQQ2 SMSG_PLAYER_UPDATE_1:" + toString(type));
            logger->log("being id:" + toString(being->getId()));
            logger->log("being name:" + being->getName());
            break;
    }
}

void BeingHandler::viewPlayerEquipment(const Being *const being)
{
    if (!being)
        return;

    createOutPacket(CMSG_PLAYER_VIEW_EQUIPMENT);
    outMsg.writeInt32(being->getId(), "account id");
}

void BeingHandler::processSkillGroundNoDamage(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    msg.readInt16("skill id");
    msg.readInt32("src id");
    msg.readInt16("val");
    msg.readInt16("x");
    msg.readInt16("y");
    msg.readInt32("tick");
}

void BeingHandler::processSkillEntry(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    msg.readInt16("len");
    msg.readInt32("accound id");
    msg.readInt32("creator accound id");
    msg.readInt16("x");
    msg.readInt16("y");
    msg.readInt32("job");
    msg.readUInt8("radius");
    msg.readUInt8("visible");
    msg.readUInt8("level");
}

void BeingHandler::processPlaterStatusChange(Net::MessageIn &msg)
{
    BLOCK_START("BeingHandler::processPlayerStop")
    if (!actorManager)
    {
        BLOCK_END("BeingHandler::processPlayerStop")
        return;
    }

    // Change in players' flags
    const int id = msg.readInt32("account id");
    Being *const dstBeing = actorManager->findBeing(id);
    if (!dstBeing)
        return;

    const uint16_t stunMode = msg.readInt16("stun mode");
    uint32_t statusEffects = msg.readInt16("status effect");
    statusEffects |= (static_cast<uint32_t>(msg.readInt32("opt?"))) << 16;
    dstBeing->setKarma(msg.readUInt8("karma"));

    dstBeing->setStunMode(stunMode);
    dstBeing->setStatusEffectBlock(0, static_cast<uint16_t>(
        (statusEffects >> 16) & 0xffff));
    dstBeing->setStatusEffectBlock(16, static_cast<uint16_t>(
        statusEffects & 0xffff));
    BLOCK_END("BeingHandler::processPlayerStop")
}

void BeingHandler::processPlaterStatusChange2(Net::MessageIn &msg)
{
    if (!actorManager)
        return;

    const int id = msg.readInt32("account id");
    Being *const dstBeing = actorManager->findBeing(id);
    if (!dstBeing)
        return;

    uint32_t statusEffects = msg.readInt32("status effect");
    dstBeing->setLevel(msg.readInt32("level"));
    msg.readInt32("showEFST");

    dstBeing->setStatusEffectBlock(0, static_cast<uint16_t>(
        (statusEffects >> 16) & 0xffff));
    dstBeing->setStatusEffectBlock(16, static_cast<uint16_t>(
        statusEffects & 0xffff));
}

void BeingHandler::processPlaterStatusChangeNoTick(Net::MessageIn &msg)
{
    const uint16_t status = msg.readInt16("index");
    const int id = msg.readInt32("account id");
    const bool flag = msg.readUInt8("state") ? 1 : 0;

    Being *const dstBeing = actorManager->findBeing(id);
    if (!dstBeing)
        return;

    dstBeing->setStatusEffect(status, flag);
}

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

    // A being changed mortality status

    const int id = msg.readInt32("being id");
    msg.readInt16("unused");
    Being *const dstBeing = actorManager->findBeing(id);
    if (!dstBeing)
    {
        BLOCK_END("BeingHandler::processBeingResurrect")
        return;
    }

    // If this is player's current target, clear it.
    if (dstBeing == localPlayer->getTarget())
        localPlayer->stopAttack();

    dstBeing->setAction(BeingAction::STAND, 0);
    BLOCK_END("BeingHandler::processBeingResurrect")
}

void BeingHandler::processPlayerGuilPartyInfo(Net::MessageIn &msg)
{
    BLOCK_START("BeingHandler::processPlayerGuilPartyInfo")
    if (!actorManager)
    {
        BLOCK_END("BeingHandler::processPlayerGuilPartyInfo")
        return;
    }

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

    if (dstBeing)
    {
        dstBeing->setName(msg.readString(24, "char name"));
        dstBeing->setPartyName(msg.readString(24, "party name"));
        dstBeing->setGuildName(msg.readString(24, "guild name"));
        dstBeing->setGuildPos(msg.readString(24, "guild pos"));
        dstBeing->addToCache();
    }
    BLOCK_END("BeingHandler::processPlayerGuilPartyInfo")
}

void BeingHandler::processBeingRemoveSkil(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;
    // +++ if skill unit was added, here need remove it from actors
    msg.readInt32("skill unit id");
}

void BeingHandler::processBeingFakeName(Net::MessageIn &msg)
{
    const BeingType::BeingType type = static_cast<BeingType::BeingType>(
        msg.readUInt8("object type"));
    const int id = msg.readInt32("npc id");
    msg.skip(8, "unused");
    const uint16_t job = msg.readInt16("class?");  // 111
    msg.skip(30, "unused");
    uint16_t x, y;
    uint8_t dir;
    msg.readCoordinates(x, y, dir, "position");
    msg.readUInt8("sx");
    msg.readUInt8("sy");
    msg.skip(4, "unsued");

    Being *const dstBeing = createBeing2(id, job, type);
    dstBeing->setSubtype(job, 0);
    dstBeing->setTileCoords(x, y);
    dstBeing->setDirection(dir);
}

void BeingHandler::processBeingStatUpdate1(Net::MessageIn &msg)
{
    const int id = msg.readInt32("account id");
    const int type = msg.readInt16("type");
    const int value = msg.readInt32("value");

    Being *const dstBeing = actorManager->findBeing(id);
    if (!dstBeing)
        return;

    if (type != Ea::MANNER)
    {
        logger->log("Error: unknown being stat type: %d", type);
        return;
    }
    dstBeing->setManner(value);
}

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

    const int id = static_cast<uint32_t>(msg.readInt32("being id"));
    Being *const being = actorManager->findBeing(id);
    if (!being)
    {
        BLOCK_END("BeingHandler::processBeingSelfEffect")
        return;
    }

    const int effectType = msg.readInt32("effect type");
    if (Particle::enabled)
        effectManager->trigger(effectType, being);

    BLOCK_END("BeingHandler::processBeingSelfEffect")
}

void BeingHandler::processMobInfo(Net::MessageIn &msg)
{
    const int len = msg.readInt16("len");
    if (len < 12)
        return;
    Being *const dstBeing = actorManager->findBeing(
        msg.readInt32("monster id"));
    const int attackRange = msg.readInt32("range");
    if (dstBeing)
        dstBeing->setAttackRange(attackRange);
}

void BeingHandler::processBeingAttrs(Net::MessageIn &msg)
{
    const int len = msg.readInt16("len");
    if (len < 12)
        return;
    Being *const dstBeing = actorManager->findBeing(
        msg.readInt32("player id"));
    const int gmLevel = msg.readInt32("gm level");
    if (dstBeing && gmLevel)
    {
        if (dstBeing == localPlayer)
            localPlayer->setGMLevel(gmLevel);
        dstBeing->setGM(true);
    }
    else
    {
        if (dstBeing == localPlayer)
            localPlayer->setGMLevel(0);
        dstBeing->setGM(false);
    }
}

void BeingHandler::processMonsterInfo(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;

    msg.readInt16("class");
    msg.readInt16("level");
    msg.readInt16("size");
    msg.readInt32("hp");
    msg.readInt16("def");
    msg.readInt16("race");
    msg.readInt16("mdef");
    msg.readInt16("ele");
}

void BeingHandler::processClassChange(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;

    msg.readInt32("being id");
    msg.readUInt8("type");
    msg.readInt32("class");
}

void BeingHandler::processSpiritBalls(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;

    msg.readInt32("being id");
    msg.readInt16("spirits amount");
}

void BeingHandler::processBladeStop(Net::MessageIn &msg)
{
    UNIMPLIMENTEDPACKET;

    msg.readInt32("src being id");
    msg.readInt32("dst being id");
    msg.readInt32("flag");
}

}  // namespace EAthena