/*
* The ManaPlus Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2010 The Mana Developers
* Copyright (C) 2011-2018 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/beingrecv.h"
#include "actormanager.h"
#include "effectmanager.h"
#include "game.h"
#include "notifymanager.h"
#include "party.h"
#include "being/mercenaryinfo.h"
#include "const/utils/timer.h"
#include "enums/resources/notifytypes.h"
#include "particle/particleengine.h"
#include "input/keyboardconfig.h"
#include "gui/viewport.h"
#include "gui/windows/skilldialog.h"
#include "gui/windows/socialwindow.h"
#include "gui/windows/outfitwindow.h"
#include "net/character.h"
#include "net/charserverhandler.h"
#include "net/messagein.h"
#include "net/serverfeatures.h"
#include "net/ea/beingrecv.h"
#include "net/eathena/maptypeproperty2.h"
#include "net/eathena/sp.h"
#include "net/eathena/sprite.h"
#include "resources/claninfo.h"
#include "resources/iteminfo.h"
#include "resources/db/clandb.h"
#include "resources/db/itemdb.h"
#include "resources/map/map.h"
#include "utils/checkutils.h"
#include "utils/foreach.h"
#include "utils/timer.h"
#include "debug.h"
extern int serverVersion;
extern Window *deathNotice;
extern bool packets_re;
extern bool packets_main;
extern bool packets_zero;
extern int itemIdLen;
namespace EAthena
{
static void setBasicFields(Being *restrict const dstBeing,
const uint8_t gender,
const int hairStyle,
const ItemColor hairColor,
const uint32_t weapon,
const uint16_t headBottom,
const uint16_t headMid,
const uint16_t headTop,
const uint16_t shoes,
const uint16_t gloves,
const bool notMove) A_NONNULL(1);
static void setBasicFields(Being *restrict const dstBeing,
const uint8_t gender,
const int hairStyle,
const ItemColor hairColor,
const uint32_t weapon,
const uint16_t headBottom,
const uint16_t headMid,
const uint16_t headTop,
const uint16_t shoes,
const uint16_t gloves,
const bool updateSlots)
{
const ActorTypeT actorType = dstBeing->getType();
switch (actorType)
{
case ActorType::Player:
dstBeing->setGender(Being::intToGender(gender));
dstBeing->setHairColor(hairColor);
// Set these after the gender, as the sprites may be gender-specific
if (hairStyle == 0)
{
dstBeing->updateSprite(SPRITE_HAIR_COLOR,
0,
std::string());
}
else
{
dstBeing->updateSprite(SPRITE_HAIR_COLOR,
hairStyle * -1,
ItemDB::get(-hairStyle).getDyeColorsString(hairColor));
}
if (updateSlots)
{
dstBeing->updateSprite(SPRITE_WEAPON,
headBottom,
std::string());
dstBeing->updateSprite(SPRITE_HEAD_BOTTOM,
headMid,
std::string());
dstBeing->updateSprite(SPRITE_CLOTHES_COLOR,
headTop,
std::string());
dstBeing->updateSprite(SPRITE_HAIR,
shoes,
std::string());
dstBeing->updateSprite(SPRITE_SHOES,
gloves,
std::string());
dstBeing->updateSprite(SPRITE_BODY,
weapon,
std::string());
dstBeing->setWeaponId(weapon);
}
break;
case ActorType::Npc:
if (serverFeatures->haveNpcGender())
{
dstBeing->setGender(Being::intToGender(gender));
}
if (dstBeing->getAllowNpcEquipment())
{
dstBeing->setHairColor(hairColor);
dstBeing->setHairStyle(SPRITE_HAIR_COLOR, -hairStyle);
// for npc not checking updateSlots flag,
// probably because npc missing visible packet if moving
dstBeing->updateSprite(SPRITE_WEAPON,
headBottom,
std::string());
dstBeing->updateSprite(SPRITE_HEAD_BOTTOM,
headMid,
std::string());
dstBeing->updateSprite(SPRITE_CLOTHES_COLOR,
headTop,
std::string());
dstBeing->updateSprite(SPRITE_HAIR,
shoes,
std::string());
dstBeing->updateSprite(SPRITE_SHOES,
gloves,
std::string());
dstBeing->updateSprite(SPRITE_BODY,
weapon,
std::string());
dstBeing->setWeaponId(weapon);
}
break;
default:
case ActorType::Monster:
case ActorType::Portal:
case ActorType::Pet:
case ActorType::Mercenary:
case ActorType::Homunculus:
case ActorType::SkillUnit:
case ActorType::Elemental:
break;
case ActorType::FloorItem:
case ActorType::Avatar:
case ActorType::Unknown:
reportAlways("Wrong being type detected: %d",
CAST_S32(actorType));
break;
}
}
void BeingRecv::processBeingChangeLook2(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("being id"));
const uint8_t type = msg.readUInt8("type");
const int id = msg.readItemId("id1");
unsigned int id2 = msg.readItemId("id2");
if (type != 2)
id2 = 1;
if (localPlayer == nullptr || dstBeing == nullptr)
return;
processBeingChangeLookContinue(msg, dstBeing, type, id, id2, nullptr);
}
void BeingRecv::processBeingChangeLookCards(Net::MessageIn &msg)
{
Being *dstBeing = nullptr;
int cards[maxCards];
if (actorManager == nullptr)
{ // here can be look from char server
Net::Characters &chars = Net::CharServerHandler::mCharacters;
const BeingId id = msg.readBeingId("being id");
FOR_EACH (Net::Characters::iterator, it, chars)
{
const Net::Character *const character = *it;
if (character->dummy != nullptr &&
character->dummy->getId() == id)
{
dstBeing = character->dummy;
break;
}
}
}
else
{
dstBeing = actorManager->findBeing(
msg.readBeingId("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;
for (int f = 0; f < maxCards; f ++)
cards[f] = msg.readUInt16("card"); // +++ probably need use int32
if (dstBeing == nullptr)
return;
processBeingChangeLookContinue(msg, dstBeing, type, id, id2, &cards[0]);
}
void BeingRecv::processBeingChangeLookContinue(const Net::MessageIn &msg,
Being *const dstBeing,
const uint8_t type,
const int id,
const int id2,
const int *const cards)
{
if (dstBeing->getType() == ActorType::Player)
dstBeing->setOtherTime();
switch (type)
{
// here should be used SPRITE_* constants
// but for now they conflicting with sprites
// SPRITE_* is same with server LOOK_*
case 0: // change race
dstBeing->setSubtype(fromInt(id, BeingTypeId),
dstBeing->getLook());
break;
case 1: // eAthena LOOK_HAIR
dstBeing->setHairColor(fromInt(id, ItemColor));
dstBeing->setHairColorSpriteID(SPRITE_HAIR_COLOR,
id * -1);
break;
case 2: // LOOK_WEAPON Weapon ID in id, Shield ID in id2
dstBeing->setSpriteCards(SPRITE_BODY,
id,
CardsList(cards));
dstBeing->setWeaponId(id);
dstBeing->setSpriteId(SPRITE_FLOOR,
id2);
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_FLOOR);
break;
case 3: // LOOK_HEAD_BOTTOM
dstBeing->setSpriteCards(SPRITE_WEAPON,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_WEAPON);
break;
case 4: // LOOK_HEAD_TOP Change upper headgear for eAthena, hat for us
dstBeing->setSpriteCards(SPRITE_CLOTHES_COLOR,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_CLOTHES_COLOR);
break;
case 5: // LOOK_HEAD_MID Change middle headgear for eathena,
// armor for us
dstBeing->setSpriteCards(SPRITE_HEAD_BOTTOM,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_BOTTOM);
break;
case 6: // eAthena LOOK_HAIR_COLOR
dstBeing->setHairColor(fromInt(id, ItemColor));
dstBeing->setSpriteColor(SPRITE_HAIR_COLOR,
ItemDB::get(dstBeing->getSpriteID(
SPRITE_HAIR_COLOR)).getDyeColorsString(
fromInt(id, ItemColor)));
break;
case 7: // Clothes color. Now used as look
dstBeing->setLook(CAST_U8(id));
break;
case 8: // eAthena LOOK_SHIELD
dstBeing->setSpriteCards(SPRITE_FLOOR,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_FLOOR);
break;
case 9: // eAthena LOOK_SHOES
dstBeing->setSpriteCards(SPRITE_HAIR,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_HAIR);
break;
case 10: // LOOK_GLOVES
dstBeing->setSpriteCards(SPRITE_SHOES,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_SHOES);
break;
case 11: // LOOK_FLOOR
dstBeing->setSpriteCards(SPRITE_SHIELD,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_SHIELD);
break;
case 12: // LOOK_ROBE
dstBeing->setSpriteCards(SPRITE_HEAD_TOP,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_TOP);
break;
case 13: // COSTUME_HEAD_TOP
dstBeing->setSpriteCards(SPRITE_HEAD_MID,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_HEAD_MID);
break;
case 14: // COSTUME_HEAD_MID
dstBeing->setSpriteCards(SPRITE_ROBE,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_ROBE);
break;
case 15: // COSTUME_HEAD_LOW
dstBeing->setSpriteCards(SPRITE_EVOL2,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL2);
break;
case 16: // COSTUME_GARMENT
dstBeing->setSpriteCards(SPRITE_EVOL3,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL3);
break;
case 17: // ARMOR
dstBeing->setSpriteCards(SPRITE_EVOL4,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL4);
break;
case 18:
dstBeing->setSpriteCards(SPRITE_EVOL5,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL5);
break;
case 19:
dstBeing->setSpriteCards(SPRITE_EVOL6,
id,
CardsList(cards));
if (localPlayer != nullptr)
localPlayer->imitateOutfit(dstBeing, SPRITE_EVOL6);
break;
default:
UNIMPLEMENTEDPACKETFIELD(type);
break;
}
}
void BeingRecv::processBeingVisible(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
// need set type based on id
BeingTypeT type = BeingType::MONSTER;
if (msg.getVersion() >= 20091103)
{
msg.readInt16("len");
type = static_cast<BeingTypeT>(
msg.readUInt8("object type"));
}
// Information about a being in range
const BeingId id = msg.readBeingId("being id");
if (msg.getVersion() >= 20131223)
msg.readBeingId("char id");
BeingId spawnId;
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");
// probably wrong effect usage
const uint32_t opt2 = msg.readInt16("opt2");
uint32_t option;
if (msg.getVersion() >= 20080102)
option = msg.readInt32("option");
else
option = msg.readInt16("option");
const int16_t job = msg.readInt16("class");
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)
{
if (actorManager->isBlocked(id) == true)
return;
dstBeing = createBeing2(msg, id, job, type);
if (dstBeing == nullptr)
return;
}
else
{
// undeleting marked for deletion being
if (dstBeing->getType() == ActorType::Npc)
actorManager->undelete(dstBeing);
}
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;
dstBeing->setWalkSpeed(speed);
dstBeing->setSubtype(fromInt(job, BeingTypeId), 0);
if (dstBeing->getType() == ActorType::Monster && (localPlayer != nullptr))
localPlayer->checkNewName(dstBeing);
const int hairStyle = msg.readInt16("hair style");
uint32_t weapon;
if (msg.getVersion() >= 7)
{
weapon = msg.readItemId("weapon");
msg.readItemId("shield");
}
else
{
weapon = CAST_U32(msg.readInt16("weapon"));
}
const uint16_t headBottom = msg.readInt16("head bottom");
if (msg.getVersion() < 7)
msg.readInt16("shield");
const uint16_t headTop = msg.readInt16("head top");
const uint16_t headMid = msg.readInt16("head mid");
const ItemColor hairColor = fromInt(msg.readInt16("hair color"),
ItemColor);
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?
if (msg.getVersion() >= 20101124)
msg.readInt16("robe");
msg.readInt32("guild id");
msg.readInt16("guild emblem");
dstBeing->setManner(msg.readInt16("manner"));
uint32_t opt3;
if (msg.getVersion() >= 7)
opt3 = msg.readInt32("opt3");
else
opt3 = msg.readInt16("opt3");
dstBeing->setKarma(msg.readUInt8("karma"));
const uint8_t gender = CAST_U8(msg.readUInt8("gender") & 3);
setBasicFields(dstBeing,
gender,
hairStyle,
hairColor,
weapon,
headBottom,
headMid,
headTop,
shoes,
gloves,
true);
uint8_t dir;
uint16_t x, y;
msg.readCoordinates(x, y, dir, "position");
msg.readInt8("xs");
msg.readInt8("ys");
applyPlayerAction(msg, dstBeing, msg.readUInt8("action type"));
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);
const int level = CAST_S32(msg.readInt16("level"));
if (level != 0)
dstBeing->setLevel(level);
if (msg.getVersion() >= 20080102)
msg.readInt16("font");
if (msg.getVersion() >= 20120221)
{
const int maxHP = msg.readInt32("max hp");
const int hp = msg.readInt32("hp");
dstBeing->setMaxHP(maxHP);
dstBeing->setHP(hp);
msg.readInt8("is boss");
}
if (msg.getVersion() >= 20150513)
{
msg.readInt16("body2");
}
if (msg.getVersion() >= 20131223)
{
msg.readString(24, "name");
}
dstBeing->setStatusEffectOpitons(option,
opt1,
opt2,
opt3);
}
void BeingRecv::processBeingMove(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
if (msg.getVersion() >= 20091103)
msg.readInt16("len");
BeingTypeT type;
if (msg.getVersion() >= 20071106)
{
type = static_cast<BeingTypeT>(
msg.readUInt8("object type"));
}
else
{
// need detect type based on id
type = BeingType::MONSTER;
}
// Information about a being in range
const BeingId id = msg.readBeingId("being id");
if (msg.getVersion() >= 20131223)
msg.readBeingId("char id");
BeingId spawnId;
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");
// probably wrong effect usage
const uint32_t opt2 = msg.readInt16("opt2");
uint32_t option;
if (msg.getVersion() >= 7)
option = msg.readInt32("option");
else
option = msg.readInt16("option");
const int16_t job = msg.readInt16("class");
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)
{
if (actorManager->isBlocked(id) == true)
return;
dstBeing = createBeing2(msg, id, job, type);
if (dstBeing == nullptr)
return;
}
else
{
// undeleting marked for deletion being
if (dstBeing->getType() == ActorType::Npc)
actorManager->undelete(dstBeing);
}
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;
dstBeing->setWalkSpeed(speed);
dstBeing->setSubtype(fromInt(job, BeingTypeId), 0);
if (dstBeing->getType() == ActorType::Monster && (localPlayer != nullptr))
localPlayer->checkNewName(dstBeing);
const int hairStyle = msg.readInt16("hair style");
uint32_t weapon;
if (msg.getVersion() >= 7)
{
weapon = msg.readItemId("weapon");
msg.readItemId("shield");
}
else
{
weapon = CAST_U32(msg.readInt16("weapon"));
}
const uint16_t headBottom = msg.readInt16("head bottom");
msg.readInt32("tick");
if (msg.getVersion() < 7)
msg.readInt16("shield");
const uint16_t headTop = msg.readInt16("head top");
const uint16_t headMid = msg.readInt16("head mid");
const ItemColor hairColor = fromInt(
msg.readInt16("hair color"), ItemColor);
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?
if (msg.getVersion() >= 20101124)
msg.readInt16("robe");
msg.readInt32("guild id");
msg.readInt16("guild emblem");
dstBeing->setManner(msg.readInt16("manner"));
uint32_t opt3;
if (msg.getVersion() >= 7)
opt3 = msg.readInt32("opt3");
else
opt3 = msg.readInt16("opt3");
dstBeing->setKarma(msg.readUInt8("karma"));
const uint8_t gender = CAST_U8(msg.readUInt8("gender") & 3);
setBasicFields(dstBeing,
gender,
hairStyle,
hairColor,
weapon,
headBottom,
headMid,
headTop,
shoes,
gloves,
!serverFeatures->haveMove3());
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 != nullptr)
localPlayer->followMoveTo(dstBeing, srcX, srcY, dstX, dstY);
if (serverFeatures->haveMove3())
dstBeing->setCachedDestination(dstX, dstY);
else
dstBeing->setDestination(dstX, dstY);
// because server don't send direction in move packet, we fixing it
uint8_t d = 0;
if (localPlayer != nullptr &&
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 != 0u) && dstBeing->getDirection() != d)
dstBeing->setDirection(d);
const int level = CAST_S32(msg.readInt16("level"));
if (level != 0)
dstBeing->setLevel(level);
if (msg.getVersion() >= 20080102)
msg.readInt16("font");
if (msg.getVersion() >= 20120221)
{
const int maxHP = msg.readInt32("max hp");
const int hp = msg.readInt32("hp");
dstBeing->setMaxHP(maxHP);
dstBeing->setHP(hp);
msg.readInt8("is boss");
}
if (msg.getVersion() >= 20150513)
{
msg.readInt16("body2");
}
if (msg.getVersion() >= 20131223)
{
msg.readString(24, "name");
}
dstBeing->setStatusEffectOpitons(option,
opt1,
opt2,
opt3);
}
void BeingRecv::processBeingSpawn(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
// need get type from id
BeingTypeT type = BeingType::MONSTER;
if (msg.getVersion() >= 20091103)
{
msg.readInt16("len");
type = static_cast<BeingTypeT>(
msg.readUInt8("object type"));
}
// Information about a being in range
const BeingId id = msg.readBeingId("being id");
if (msg.getVersion() >= 20131223)
{
msg.readBeingId("char id");
}
Ea::BeingRecv::mSpawnId = id;
const BeingId spawnId = id;
int16_t speed = msg.readInt16("speed");
const uint32_t opt1 = msg.readInt16("opt1");
// probably wrong effect usage
const uint32_t opt2 = msg.readInt16("opt2");
uint32_t option;
if (msg.getVersion() >= 20080102)
option = msg.readInt32("option");
else
option = msg.readInt16("option");
const int16_t job = msg.readInt16("class");
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)
{
if (actorManager->isBlocked(id) == true)
return;
dstBeing = createBeing2(msg, id, job, type);
if (dstBeing == nullptr)
return;
}
else
{
// undeleting marked for deletion being
if (dstBeing->getType() == ActorType::Npc)
actorManager->undelete(dstBeing);
}
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;
dstBeing->setWalkSpeed(speed);
dstBeing->setSubtype(fromInt(job, BeingTypeId), 0);
if (dstBeing->getType() == ActorType::Monster && (localPlayer != nullptr))
localPlayer->checkNewName(dstBeing);
const int hairStyle = msg.readInt16("hair style");
uint32_t weapon;
if (msg.getVersion() >= 7)
{
weapon = msg.readItemId("weapon");
msg.readItemId("shield");
}
else
{
weapon = CAST_U32(msg.readInt16("weapon"));
}
const uint16_t headBottom = msg.readInt16("head bottom");
if (msg.getVersion() < 7)
msg.readInt16("shield");
const uint16_t headTop = msg.readInt16("head top");
const uint16_t headMid = msg.readInt16("head mid");
const ItemColor hairColor = fromInt(
msg.readInt16("hair color"), ItemColor);
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?
if (msg.getVersion() >= 20101124)
msg.readInt16("robe");
msg.readInt32("guild id");
msg.readInt16("guild emblem");
dstBeing->setManner(msg.readInt16("manner"));
uint32_t opt3;
if (msg.getVersion() >= 7)
opt3 = msg.readInt32("opt3");
else
opt3 = msg.readInt16("opt3");
dstBeing->setKarma(msg.readUInt8("karma"));
const uint8_t gender = CAST_U8(msg.readUInt8("gender") & 3);
setBasicFields(dstBeing,
gender,
hairStyle,
hairColor,
weapon,
headBottom,
headMid,
headTop,
shoes,
gloves,
true);
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 != 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);
const int level = CAST_S32(msg.readInt16("level"));
if (level != 0)
dstBeing->setLevel(level);
if (msg.getVersion() >= 20080102)
msg.readInt16("font");
if (msg.getVersion() >= 20120221)
{
const int maxHP = msg.readInt32("max hp");
const int hp = msg.readInt32("hp");
dstBeing->setMaxHP(maxHP);
dstBeing->setHP(hp);
msg.readInt8("is boss");
}
if (msg.getVersion() >= 20150513)
{
msg.readInt16("body2");
}
if (msg.getVersion() >= 20131223)
{
msg.readString(24, "name");
}
dstBeing->setStatusEffectOpitons(option,
opt1,
opt2,
opt3);
}
void BeingRecv::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 = CAST_U32(flags);
const Game *const game = Game::instance();
if (game == nullptr)
return;
Map *const map = game->getCurrentMap();
if (map == nullptr)
return;
map->setPvpMode(props.bits.party | (props.bits.guild * 2));
}
}
void BeingRecv::processMapType(Net::MessageIn &msg)
{
const int16_t type = msg.readInt16("type");
if (type == 19)
NotifyManager::notify(NotifyTypes::MAP_TYPE_BATTLEFIELD);
else
UNIMPLEMENTEDPACKETFIELD(type);
}
void BeingRecv::processSkillCasting(Net::MessageIn &msg)
{
const BeingId srcId = msg.readBeingId("src id");
const BeingId dstId = msg.readBeingId("dst id");
const int dstX = msg.readInt16("dst x");
const int dstY = msg.readInt16("dst y");
const int skillId = msg.readInt16("skill id");
msg.readInt32("property"); // can be used to trigger effect
const int castTime = msg.readInt32("cast time");
if (msg.getVersion() >= 20091124)
msg.readInt8("dispossable");
processSkillCastingContinue(msg,
srcId, dstId,
dstX, dstY,
skillId,
1,
0,
SkillType2::Unknown,
castTime);
}
void BeingRecv::processSkillCasting2(Net::MessageIn &msg)
{
msg.readInt16("len"); // for now unused
const BeingId srcId = msg.readBeingId("src id");
const BeingId dstId = msg.readBeingId("dst id");
const int dstX = msg.readInt16("dst x");
const int dstY = msg.readInt16("dst y");
const int skillId = msg.readInt16("skill id");
const int skillLevel = msg.readInt16("skill level");
msg.readInt32("property"); // can be used to trigger effect
const int castTime = msg.readInt32("cast time");
const int range = msg.readInt32("skill range");
const SkillType2::SkillType2 inf2 =
static_cast<SkillType2::SkillType2>(msg.readInt32("inf2"));
processSkillCastingContinue(msg,
srcId, dstId,
dstX, dstY,
skillId,
skillLevel,
range,
inf2,
castTime);
}
void BeingRecv::processSkillCastingContinue(Net::MessageIn &msg,
const BeingId srcId,
const BeingId dstId,
const int dstX,
const int dstY,
const int skillId,
const int skillLevel,
const int range,
const SkillType2::SkillType2 inf2,
const int castTime)
{
if (effectManager == nullptr ||
actorManager == nullptr)
return;
if (srcId == BeingId_zero)
{
UNIMPLEMENTEDPACKETFIELD(0);
return;
}
Being *const srcBeing = actorManager->findBeing(srcId);
if (dstId != BeingId_zero)
{ // being to being
Being *const dstBeing = actorManager->findBeing(dstId);
if (srcBeing != nullptr)
{
srcBeing->handleSkillCasting(dstBeing, skillId, skillLevel);
if (dstBeing != nullptr)
{
srcBeing->addCast(dstBeing->getTileX(),
dstBeing->getTileY(),
skillId,
skillLevel,
range,
castTime / MILLISECONDS_IN_A_TICK);
}
}
}
else if (dstX != 0 || dstY != 0)
{ // being to position
if (srcBeing != nullptr)
srcBeing->setAction(BeingAction::CAST, skillId);
skillDialog->playCastingDstTileEffect(skillId,
skillLevel,
dstX, dstY,
castTime);
if (srcBeing != nullptr)
{
srcBeing->addCast(dstX, dstY,
skillId,
skillLevel,
range,
castTime / MILLISECONDS_IN_A_TICK);
}
}
if ((localPlayer != nullptr) &&
srcBeing == localPlayer &&
(inf2 & SkillType2::FreeCastAny) == 0)
{
localPlayer->freezeMoving(castTime / MILLISECONDS_IN_A_TICK);
}
}
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);
if (msg.getVersion() >= 20120618)
msg.readInt32("total");
if (msg.getVersion() >= 20090121)
{
msg.readInt32("left");
msg.readInt32("val1");
msg.readInt32("val2");
msg.readInt32("val3");
}
const IsStart start = msg.getVersion() == 20090121 ?
IsStart_false : IsStart_true;
Being *const dstBeing = actorManager->findBeing(id);
if (dstBeing != nullptr)
dstBeing->setStatusEffect(status, flag, start);
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"));
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 == nullptr)
{
BLOCK_END("BeingRecv::processBeingMove2")
return;
}
dstBeing->setTileCoords(srcX, srcY);
if (localPlayer != nullptr)
localPlayer->followMoveTo(dstBeing, srcX, srcY, dstX, dstY);
if (serverFeatures->haveMove3())
dstBeing->setCachedDestination(dstX, dstY);
else
dstBeing->setDestination(dstX, dstY);
if (dstBeing->getType() == ActorType::Player)
dstBeing->setMoveTime();
BLOCK_END("BeingRecv::processBeingMove2")
}
void BeingRecv::processBeingAction2(Net::MessageIn &msg)
{
BLOCK_START("BeingRecv::processBeingAction2")
if (actorManager == nullptr)
{
BLOCK_END("BeingRecv::processBeingAction2")
return;
}
Being *const srcBeing = actorManager->findBeing(
msg.readBeingId("src being id"));
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("dst being id"));
msg.readInt32("tick");
const int srcSpeed = msg.readInt32("src speed");
msg.readInt32("dst speed");
int param1;
if (msg.getVersion() >= 20071113)
param1 = msg.readInt32("damage");
else
param1 = msg.readInt16("damage");
if (msg.getVersion() >= 20131223)
msg.readUInt8("is sp damaged");
msg.readInt16("count");
const AttackTypeT type = static_cast<AttackTypeT>(
msg.readUInt8("action"));
if (msg.getVersion() >= 20071113)
msg.readInt32("left damage");
else
msg.readInt16("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 != nullptr)
{
if (srcSpeed != 0 && srcBeing->getType() == ActorType::Player)
srcBeing->setAttackDelay(srcSpeed);
// attackid=1, type
srcBeing->handleAttack(dstBeing, param1, 1);
if (srcBeing->getType() == ActorType::Player)
srcBeing->setAttackTime();
}
if (dstBeing != nullptr)
{
// level not present, using 1
dstBeing->takeDamage(srcBeing, param1,
static_cast<AttackTypeT>(type), 1, 1);
}
break;
case AttackType::PICKUP:
break;
case AttackType::TOUCH_SKILL:
break;
case AttackType::SIT:
if (srcBeing != nullptr)
{
srcBeing->setAction(BeingAction::SIT, 0);
if (srcBeing->getType() == ActorType::Player)
{
srcBeing->setMoveTime();
if (localPlayer != nullptr)
localPlayer->imitateAction(srcBeing, BeingAction::SIT);
}
}
break;
case AttackType::STAND:
if (srcBeing != nullptr)
{
srcBeing->setAction(BeingAction::STAND, 0);
if (srcBeing->getType() == ActorType::Player)
{
srcBeing->setMoveTime();
if (localPlayer != nullptr)
{
localPlayer->imitateAction(srcBeing,
BeingAction::STAND);
}
}
}
break;
default:
case AttackType::MISS:
case AttackType::SKILLMISS:
UNIMPLEMENTEDPACKETFIELD(CAST_S32(type));
break;
}
BLOCK_END("BeingRecv::processBeingAction2")
}
void BeingRecv::processBeingHp(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("being id"));
int hp;
int maxHP;
if (msg.getVersion() >= 20100126)
{
hp = msg.readInt32("hp");
maxHP = msg.readInt32("max hp");
}
else
{
hp = msg.readInt16("hp");
maxHP = msg.readInt16("max hp");
}
if (dstBeing != nullptr)
{
dstBeing->setHP(hp);
dstBeing->setMaxHP(maxHP);
}
}
void BeingRecv::processMonsterHp(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("monster id"));
const int hp = msg.readInt32("hp");
const int maxHP = msg.readInt32("max hp");
if (dstBeing != nullptr)
{
dstBeing->setHP(hp);
dstBeing->setMaxHP(maxHP);
}
}
void BeingRecv::processSkillAutoCast(Net::MessageIn &msg)
{
const int id = msg.readInt16("skill id");
msg.readInt16("inf");
msg.readInt16("unused");
const int level = msg.readInt16("skill level");
msg.readInt16("sp");
msg.readInt16("range");
msg.readString(24, "skill name");
msg.readInt8("unused");
if (localPlayer != nullptr)
{
localPlayer->handleSkill(localPlayer, 0, id, level);
localPlayer->takeDamage(localPlayer, 0, AttackType::SKILL, id, level);
}
}
void BeingRecv::processRanksList(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// +++ here need window with rank tables.
msg.readInt16("rank type");
for (int f = 0; f < 10; f ++)
msg.readString(24, "name");
for (int f = 0; f < 10; f ++)
msg.readInt32("points");
msg.readInt32("my points");
}
void BeingRecv::processBlacksmithRanksList(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// +++ here need window with rank tables.
for (int f = 0; f < 10; f ++)
msg.readString(24, "name");
for (int f = 0; f < 10; f ++)
msg.readInt32("points");
}
void BeingRecv::processAlchemistRanksList(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// +++ here need window with rank tables.
for (int f = 0; f < 10; f ++)
msg.readString(24, "name");
for (int f = 0; f < 10; f ++)
msg.readInt32("points");
}
void BeingRecv::processTaekwonRanksList(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// +++ here need window with rank tables.
for (int f = 0; f < 10; f ++)
msg.readString(24, "name");
for (int f = 0; f < 10; f ++)
msg.readInt32("points");
}
void BeingRecv::processPkRanksList(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// +++ here need window with rank tables.
for (int f = 0; f < 10; f ++)
msg.readString(24, "name");
for (int f = 0; f < 10; f ++)
msg.readInt32("points");
}
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"));
msg.readInt16("head direction");
const uint8_t dir = Net::MessageIn::fromServerDirection(
CAST_U8(msg.readUInt8("player direction") & 0x0FU));
if (dstBeing == nullptr)
{
BLOCK_END("BeingRecv::processBeingChangeDirection")
return;
}
dstBeing->setDirection(dir);
if (localPlayer != nullptr)
localPlayer->imitateDirection(dstBeing, dir);
BLOCK_END("BeingRecv::processBeingChangeDirection")
}
void BeingRecv::processBeingSpecialEffect(Net::MessageIn &msg)
{
if ((effectManager == nullptr) || (actorManager == nullptr))
return;
const BeingId id = msg.readBeingId("being id");
Being *const being = actorManager->findBeing(id);
if (being == nullptr)
{
msg.readInt32("effect type");
return;
}
const int effectType = msg.readInt32("effect type");
if (ParticleEngine::enabled)
effectManager->trigger(effectType, being, 0);
// +++ need dehard code effectType == 3
if (effectType == 3 && being->getType() == ActorType::Player
&& (socialWindow != nullptr))
{ // reset received damage
socialWindow->resetDamage(being->getName());
}
}
void BeingRecv::processBeingHatEffects(Net::MessageIn &msg)
{
// +++ add new type of permanent effects?
const int cnt = (msg.readInt16("len") - 9) / 2;
if (cnt > 0)
{
UNIMPLEMENTEDPACKET;
}
msg.readBeingId("being id");
msg.readUInt8("enable");
for (int f = 0; f < cnt; f ++)
msg.readInt16("hat effect");
}
void BeingRecv::processBeingSpecialEffectNum(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// +++ need somhow show this effects.
// type is not same with self/misc effect.
msg.readBeingId("account id");
msg.readInt32("effect type");
msg.readInt32("num"); // effect variable
}
void BeingRecv::processBeingSoundEffect(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// +++ need play this effect.
msg.readString(24, "sound effect name");
msg.readUInt8("type");
msg.readInt32("unused");
msg.readInt32("source being id");
}
void BeingRecv::processSkillGroundNoDamage(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readInt16("skill id");
msg.readInt32("src id");
msg.readInt16("val");
msg.readInt16("x");
msg.readInt16("y");
msg.readInt32("tick");
}
void BeingRecv::processSkillEntry(Net::MessageIn &msg)
{
if (msg.getVersion() >= 20110718)
msg.readInt16("len");
const BeingId id = msg.readBeingId("skill unit id");
const BeingId creatorId = msg.readBeingId("creator accound id");
const int x = msg.readInt16("x");
const int y = msg.readInt16("y");
int job = 0;
if (msg.getVersion() >= 20121212)
job = msg.readInt32("job");
if (msg.getVersion() >= 20110718)
msg.readUInt8("radius");
msg.readUInt8("visible");
int level = 0;
if (msg.getVersion() >= 20130731)
level = msg.readUInt8("level");
Being *const dstBeing = createBeing2(msg,
id,
job,
BeingType::SKILL);
if (dstBeing == nullptr)
return;
dstBeing->setAction(BeingAction::STAND, 0);
dstBeing->setTileCoords(x, y);
dstBeing->setLevel(level);
dstBeing->setCreatorId(creatorId);
}
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)
{
msg.readInt16("opt1");
msg.readInt16("opt2");
if (msg.getVersion() >= 7)
msg.readInt32("option");
else
msg.readInt16("option");
msg.readUInt8("karma");
return;
}
const uint32_t opt1 = msg.readInt16("opt1");
const uint32_t opt2 = msg.readInt16("opt2");
uint32_t option;
if (msg.getVersion() >= 7)
option = msg.readInt32("option");
else
option = msg.readInt16("option");
dstBeing->setKarma(msg.readUInt8("karma"));
dstBeing->setStatusEffectOpitons(option,
opt1,
opt2);
BLOCK_END("BeingRecv::processPlayerStop")
}
void BeingRecv::processPlayerStatusChange2(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
// look like this function unused on server
const BeingId id = msg.readBeingId("account id");
Being *const dstBeing = actorManager->findBeing(id);
if (dstBeing == nullptr)
return;
const uint32_t option = msg.readInt32("option");
dstBeing->setLevel(msg.readInt32("level"));
msg.readInt32("showEFST");
dstBeing->setStatusEffectOpiton0(option);
}
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");
msg.readInt16("unused");
Being *const dstBeing = actorManager->findBeing(id);
if (dstBeing == nullptr)
{
DEBUGLOGSTR("insible player?");
BLOCK_END("BeingRecv::processBeingResurrect")
return;
}
// If this is player's current target, clear it.
if (dstBeing == localPlayer->getTarget())
localPlayer->stopAttack(false);
if (dstBeing == localPlayer &&
deathNotice != nullptr)
{
deathNotice->scheduleDelete();
deathNotice = nullptr;
}
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;
}
const BeingId beingId = msg.readBeingId("being id");
const std::string name = msg.readString(24, "char name");
actorManager->updateNameId(name, beingId);
Being *const dstBeing = actorManager->findBeing(beingId);
if (dstBeing != nullptr)
{
if (beingId == localPlayer->getId())
{
localPlayer->pingResponse();
}
dstBeing->setName(name);
dstBeing->setPartyName(msg.readString(24, "party name"));
dstBeing->setGuildName(msg.readString(24, "guild name"));
dstBeing->setGuildPos(msg.readString(24, "guild pos"));
dstBeing->addToCache();
}
else
{
msg.readString(24, "party name");
msg.readString(24, "guild name");
msg.readString(24, "guild pos");
}
BLOCK_END("BeingRecv::processPlayerGuilPartyInfo")
}
void BeingRecv::processPlayerGuilPartyInfo2(Net::MessageIn &msg)
{
BLOCK_START("BeingRecv::processPlayerGuilPartyInfo2")
if (actorManager == nullptr)
{
BLOCK_END("BeingRecv::processPlayerGuilPartyInfo2")
return;
}
const BeingId beingId = msg.readBeingId("being id");
const std::string name = msg.readString(24, "char name");
actorManager->updateNameId(name, beingId);
Being *const dstBeing = actorManager->findBeing(beingId);
if (dstBeing != nullptr)
{
if (beingId == localPlayer->getId())
{
localPlayer->pingResponse();
}
dstBeing->setName(name);
dstBeing->setPartyName(msg.readString(24, "party name"));
dstBeing->setGuildName(msg.readString(24, "guild name"));
dstBeing->setGuildPos(msg.readString(24, "guild pos"));
dstBeing->addToCache();
}
else
{
msg.readString(24, "party name");
msg.readString(24, "guild name");
msg.readString(24, "guild pos");
}
// +++ need use it for show player title
msg.readInt32("title");
BLOCK_END("BeingRecv::processPlayerGuilPartyInfo2")
}
void BeingRecv::processBeingRemoveSkill(Net::MessageIn &msg)
{
const BeingId id = msg.readBeingId("skill unit id");
if (actorManager == nullptr)
return;
Being *const dstBeing = actorManager->findBeing(id);
if (dstBeing == nullptr)
return;
actorManager->destroy(dstBeing);
}
void BeingRecv::processBeingFakeName(Net::MessageIn &msg)
{
uint16_t x, y;
uint8_t dir;
if (msg.getVersion() < 20071106)
{
msg.readBeingId("npc id");
msg.skip(8, "unused");
msg.readInt16("class?"); // 111
msg.skip(30, "unused");
msg.readCoordinates(x, y, dir, "position");
msg.readUInt8("sx");
msg.readUInt8("sy");
msg.skip(3, "unused");
return;
}
const BeingTypeT type = static_cast<BeingTypeT>(
msg.readUInt8("object type"));
const BeingId id = msg.readBeingId("npc id");
msg.skip(8, "unused");
const uint16_t job = msg.readInt16("class?"); // 111
msg.skip(30, "unused");
msg.readCoordinates(x, y, dir, "position");
msg.readUInt8("sx");
msg.readUInt8("sy");
msg.skip(3, "unused");
Being *const dstBeing = createBeing2(msg, id, job, type);
if (dstBeing == nullptr)
return;
dstBeing->setSubtype(fromInt(job, BeingTypeId), 0);
dstBeing->setTileCoords(x, y);
dstBeing->setDirection(dir);
}
void BeingRecv::processBeingStatUpdate1(Net::MessageIn &msg)
{
const BeingId id = msg.readBeingId("account id");
const int type = msg.readInt16("type");
const int value = msg.readInt32("value");
if (actorManager == nullptr)
return;
Being *const dstBeing = actorManager->findBeing(id);
if (dstBeing == nullptr)
return;
if (type != Sp::MANNER)
{
UNIMPLEMENTEDPACKETFIELD(type);
return;
}
dstBeing->setManner(value);
}
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, 0);
BLOCK_END("BeingRecv::processBeingSelfEffect")
}
void BeingRecv::processMobInfo(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
const int len = msg.readInt16("len");
if (len < 12)
return;
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("monster id"));
const int attackRange = msg.readInt32("range");
if (dstBeing != nullptr)
dstBeing->setAttackRange(attackRange);
}
void BeingRecv::processBeingAttrs(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
const int len = msg.readInt16("len");
if (len < 14)
return;
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("player id"));
const int groupId = msg.readInt32("group id");
uint16_t mount = 0;
mount = msg.readInt16("mount");
int language = -1;
if (len > 14)
language = msg.readInt16("language");
int clanId = 0;
if (len > 16)
clanId = msg.readInt32("clan id");
if (dstBeing != nullptr)
{
if (dstBeing != localPlayer)
{
dstBeing->setGroupId(groupId);
}
dstBeing->setHorse(mount);
dstBeing->setLanguageId(language);
if (clanId != 0)
{
const ClanInfo *const info = ClanDb::get(clanId);
if (info == nullptr)
dstBeing->setClanName(std::string());
else
dstBeing->setClanName(info->name);
}
else
{
dstBeing->setClanName(std::string());
}
if (dstBeing == localPlayer)
PlayerInfo::setServerLanguage(language);
}
}
void BeingRecv::processMonsterInfo(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
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 BeingRecv::processClassChange(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readBeingId("being id");
msg.readUInt8("type");
msg.readInt32("class");
}
void BeingRecv::processSpiritBalls(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("being id"));
const int balls = msg.readInt16("spirits amount");
if (dstBeing != nullptr)
dstBeing->setSpiritBalls(balls);
}
void BeingRecv::processBladeStop(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readInt32("src being id");
msg.readInt32("dst being id");
msg.readInt32("flag");
}
void BeingRecv::processComboDelay(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readBeingId("being id");
msg.readInt32("wait");
}
void BeingRecv::processWddingEffect(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readBeingId("being id");
}
void BeingRecv::processBeingSlide(Net::MessageIn &msg)
{
if (actorManager == nullptr)
return;
Being *const dstBeing = actorManager->findBeing(
msg.readBeingId("being id"));
const int x = msg.readInt16("x");
const int y = msg.readInt16("y");
if (dstBeing == nullptr)
return;
if (localPlayer == dstBeing)
{
localPlayer->stopAttack(false);
localPlayer->navigateClean();
if (viewport != nullptr)
viewport->returnCamera();
}
dstBeing->setAction(BeingAction::STAND, 0);
dstBeing->setTileCoords(x, y);
}
void BeingRecv::processStarsKill(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readString(24, "map name");
msg.readInt32("monster id");
msg.readUInt8("start");
msg.readUInt8("result");
}
void BeingRecv::processGladiatorFeelRequest(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readUInt8("which");
}
void BeingRecv::processBossMapInfo(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readUInt8("info type");
msg.readInt32("x");
msg.readInt32("y");
msg.readInt16("min hours");
msg.readInt16("min minutes");
msg.readInt16("max hours");
msg.readInt16("max minutes");
msg.readString(24, "monster name"); // really can be used 51 byte?
}
void BeingRecv::processBeingFont(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readBeingId("account id");
msg.readInt16("font");
}
void BeingRecv::processBeingMilleniumShield(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readBeingId("account id");
msg.readInt16("shields");
msg.readInt16("unused");
}
void BeingRecv::processBeingCharm(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
msg.readBeingId("account id");
msg.readInt16("charm type");
msg.readInt16("charm count");
}
void BeingRecv::processBeingViewEquipment(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
const int count = (msg.readInt16("len") - 45) / (21 + itemIdLen * 5);
msg.readString(24, "name");
msg.readInt16("job");
msg.readInt16("head");
msg.readInt16("accessory");
msg.readInt16("accessory2");
msg.readInt16("accessory3");
if (msg.getVersion() >= 20101124)
msg.readInt16("robe");
msg.readInt16("hair color");
msg.readInt16("body color");
msg.readUInt8("gender");
for (int f = 0; f < count; f ++)
{
msg.readInt16("index");
msg.readItemId("item id");
msg.readUInt8("item type");
msg.readInt32("location");
msg.readInt32("wear state");
msg.readInt8("refine");
for (int d = 0; d < maxCards; d ++)
msg.readItemId("card");
msg.readInt32("hire expire date (?)");
msg.readInt16("equip type");
msg.readInt16("item sprite number");
msg.readUInt8("flags");
}
}
void BeingRecv::processPvpSet(Net::MessageIn &msg)
{
BLOCK_START("BeingRecv::processPvpSet")
const BeingId id = msg.readBeingId("being id");
const int rank = msg.readInt32("rank");
msg.readInt32("num");
if (actorManager != nullptr)
{
Being *const dstBeing = actorManager->findBeing(id);
if (dstBeing != nullptr)
dstBeing->setPvpRank(rank);
}
BLOCK_END("BeingRecv::processPvpSet")
}
void BeingRecv::processNameResponse2(Net::MessageIn &msg)
{
BLOCK_START("BeingRecv::processNameResponse2")
if ((actorManager == nullptr) || (localPlayer == nullptr))
{
BLOCK_END("BeingRecv::processNameResponse2")
return;
}
const int len = msg.readInt16("len");
const BeingId beingId = msg.readBeingId("account id");
const std::string str = msg.readString(len - 8, "name");
actorManager->updateNameId(str, beingId);
Being *const dstBeing = actorManager->findBeing(beingId);
if (dstBeing != nullptr)
{
if (beingId == localPlayer->getId())
{
localPlayer->pingResponse();
}
else
{
dstBeing->setName(str);
dstBeing->updateGuild();
dstBeing->addToCache();
if (dstBeing->getType() == ActorType::Player)
dstBeing->updateColors();
if (localPlayer != nullptr)
{
const Party *const party = localPlayer->getParty();
if (party != nullptr && party->isMember(dstBeing->getId()))
{
PartyMember *const member = party->getMember(
dstBeing->getId());
if (member != nullptr)
member->setName(dstBeing->getName());
}
localPlayer->checkNewName(dstBeing);
}
}
}
BLOCK_END("BeingRecv::processNameResponse2")
}
Being *BeingRecv::createBeing2(Net::MessageIn &msg,
const BeingId id,
const int32_t job,
const BeingTypeT beingType)
{
if (actorManager == nullptr)
return nullptr;
ActorTypeT 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::SKILL:
type = ActorType::SkillUnit;
break;
case BeingType::ELEMENTAL:
type = ActorType::Elemental;
break;
case BeingType::ITEM:
logger->log("not supported object type: %d, job: %d",
CAST_S32(beingType), CAST_S32(job));
break;
case BeingType::CHAT:
default:
UNIMPLEMENTEDPACKETFIELD(CAST_S32(beingType));
type = ActorType::Monster;
logger->log("not supported object type: %d, job: %d",
CAST_S32(beingType), CAST_S32(job));
break;
}
if (job == 45 && beingType == BeingType::NPC_EVENT)
type = ActorType::Portal;
Being *const being = actorManager->createBeing(
id, type, fromInt(job, BeingTypeId));
if (beingType == BeingType::MERSOL)
{
const MercenaryInfo *const info = PlayerInfo::getMercenary();
if ((info != nullptr) && info->id == id)
PlayerInfo::setMercenaryBeing(being);
}
else if (beingType == BeingType::PET)
{
if (PlayerInfo::getPetBeingId() == id)
PlayerInfo::setPetBeing(being);
}
return being;
}
void BeingRecv::processSkillCancel(Net::MessageIn &msg)
{
msg.readInt32("id?");
}
void BeingRecv::processSolveCharName(Net::MessageIn &msg)
{
if ((packets_re == true && msg.getVersion() >= 20180221) ||
(packets_main == true && msg.getVersion() >= 20180307) ||
(packets_zero == true && msg.getVersion() >= 20180328))
{
const int flag = msg.readInt16("flag");
// name request errors
if (flag != 3)
return;
}
const int id = msg.readInt32("char id");
if (actorManager == nullptr)
{
msg.readString(24, "name");
return;
}
actorManager->addChar(id, msg.readString(24, "name"));
}
void BeingRecv::processGraffiti(Net::MessageIn &msg)
{
const BeingId id = msg.readBeingId("graffiti id");
const BeingId creatorId = msg.readBeingId("creator id");
const int x = msg.readInt16("x");
const int y = msg.readInt16("y");
const int job = msg.readUInt8("job");
msg.readUInt8("visible");
msg.readUInt8("is content");
const std::string text = msg.readString(80, "text");
Being *const dstBeing = createBeing2(msg, id, job, BeingType::SKILL);
if (dstBeing == nullptr)
return;
dstBeing->setAction(BeingAction::STAND, 0);
dstBeing->setTileCoords(x, y);
dstBeing->setShowName(true);
dstBeing->setName(text);
dstBeing->setCreatorId(creatorId);
}
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");
int param1;
if (msg.getVersion() >= 3)
param1 = msg.readInt32("damage");
else
param1 = msg.readInt16("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::processNavigateTo(Net::MessageIn &msg)
{
UNIMPLEMENTEDPACKET;
// 0 position
// 1 no position
// 3 monster
msg.readUInt8("navigate type");
msg.readUInt8("transportation flag");
msg.readUInt8("hide window");
msg.readString(16, "map name");
msg.readInt16("x");
msg.readInt16("y");
msg.readInt16("mob id");
}
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;
}
}
} // namespace EAthena