/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2014 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 . */ #include "net/ea/beinghandler.h" #include "net/ea/eaprotocol.h" #include "actormanager.h" #include "configuration.h" #include "effectmanager.h" #include "game.h" #include "guildmanager.h" #include "party.h" #include "being/localplayer.h" #include "being/playerrelations.h" #include "particle/particle.h" #include "input/keyboardconfig.h" #include "gui/windows/botcheckerwindow.h" #include "gui/windows/outfitwindow.h" #include "gui/windows/socialwindow.h" #include "gui/windows/killstats.h" #include "gui/windows/questswindow.h" #include "utils/timer.h" #include "resources/iteminfo.h" #include "resources/db/itemdb.h" #include "debug.h" extern int serverVersion; namespace Ea { BeingHandler::BeingHandler(const bool enableSync) : mSync(enableSync), mSpawnId(0), mHideShield(config.getBoolValue("hideShield")) { } Being *BeingHandler::createBeing(const int id, const int16_t job) const { if (!actorManager) return nullptr; ActorSprite::Type type = ActorSprite::UNKNOWN; if (job <= 25 || (job >= 4001 && job <= 4049)) type = ActorSprite::PLAYER; else if (job >= 46 && job <= 1000) type = ActorSprite::NPC; else if (job > 1000 && job <= 2000) type = ActorSprite::MONSTER; else if (job == 45) type = ActorSprite::PORTAL; Being *const being = actorManager->createBeing(id, type, job); if (type == ActorSprite::PLAYER || type == ActorSprite::NPC) { being->updateFromCache(); requestNameById(id); if (player_node) player_node->checkNewName(being); } if (type == Being::PLAYER) { if (botCheckerWindow) botCheckerWindow->updateList(); if (socialWindow) socialWindow->updateActiveList(); } else if (type == Being::NPC) { if (questsWindow) questsWindow->addEffect(being); } return being; } void BeingHandler::setSprite(Being *const being, const unsigned int slot, const int id, const std::string &color, const unsigned char colorId, const bool isWeapon, const bool isTempSprite) const { if (!being) return; being->updateSprite(slot, id, color, colorId, isWeapon, isTempSprite); } void BeingHandler::processBeingVisibleOrMove(Net::MessageIn &msg, const bool visible) { if (!actorManager) return; int spawnId; // Information about a being in range const int id = msg.readInt32(); if (id == mSpawnId) spawnId = mSpawnId; else spawnId = 0; mSpawnId = 0; int16_t speed = msg.readInt16(); const uint16_t stunMode = msg.readInt16(); // opt1 uint32_t statusEffects = msg.readInt16(); // opt2 statusEffects |= (static_cast(msg.readInt16())) << 16; // option const int16_t job = msg.readInt16(); // class int disguiseId = 0; if (id == player_node->getId() && job >= 1000) disguiseId = job; Being *dstBeing = actorManager->findBeing(id); if (dstBeing && dstBeing->getType() == Being::MONSTER && !dstBeing->isAlive()) { actorManager->destroy(dstBeing); actorManager->erase(dstBeing); dstBeing = nullptr; } if (!dstBeing) { // Being with id >= 110000000 and job 0 are better // known as ghosts, so don't create those. if (job == 0 && id >= 110000000) return; if (actorManager->isBlocked(id) == true) return; dstBeing = createBeing(id, job); if (!dstBeing) return; if (job == 1022 && killStats) killStats->jackoAlive(dstBeing->getId()); } else { if (dstBeing->getType() == Being::NPC) { actorManager->undelete(dstBeing); if (serverVersion < 1) requestNameById(id); } } if (dstBeing->getType() == Being::PLAYER) dstBeing->setMoveTime(); if (spawnId) { dstBeing->setAction(Being::SPAWN, 0); } else if (visible) { dstBeing->clearPath(); dstBeing->setActionTime(tick_time); dstBeing->setAction(Being::STAND, 0); } // Prevent division by 0 when calculating frame if (speed == 0) speed = 150; const int hairStyle = msg.readInt8(); const int look = msg.readInt8(); dstBeing->setSubtype(job, look); if (dstBeing->getType() == ActorSprite::MONSTER && player_node) player_node->checkNewName(dstBeing); dstBeing->setWalkSpeed(Vector(speed, speed, 0)); const uint16_t weapon = msg.readInt16(); const uint16_t headBottom = msg.readInt16(); if (!visible) msg.readInt32(); // server tick const uint16_t shield = msg.readInt16(); const uint16_t headTop = msg.readInt16(); const uint16_t headMid = msg.readInt16(); const int hairColor = msg.readInt8(); msg.readInt8(); // free const uint16_t shoes = msg.readInt16(); // clothes color uint16_t gloves; if (dstBeing->getType() == ActorSprite::MONSTER) { if (serverVersion > 0) { const int hp = msg.readInt32(); const int maxHP = msg.readInt32(); if (hp && maxHP) { dstBeing->setMaxHP(maxHP); const int oldHP = dstBeing->getHP(); if (!oldHP || oldHP > hp) dstBeing->setHP(hp); } } else { msg.readInt32(); msg.readInt32(); } gloves = 0; } else { gloves = msg.readInt16(); // head dir - "abused" as gloves msg.readInt32(); // guild msg.readInt16(); // guild emblem } // logger->log("being guild: " + toString(guild)); msg.readInt16(); // manner dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 if (serverVersion > 0 && dstBeing->getType() == ActorSprite::MONSTER) { const int attackRange = msg.readInt8(); // karma dstBeing->setAttackRange(attackRange); } else { msg.readInt8(); // karma } uint8_t gender = msg.readInt8(); if (!disguiseId && dstBeing->getType() == ActorSprite::PLAYER) { // reserving bits for future usage gender &= 3; dstBeing->setGender(Being::intToGender(gender)); // Set these after the gender, as the sprites may be gender-specific setSprite(dstBeing, EA_SPRITE_HAIR, hairStyle * -1, ItemDB::get(-hairStyle).getDyeColorsString(hairColor)); dstBeing->setHairColor(hairColor); setSprite(dstBeing, EA_SPRITE_BOTTOMCLOTHES, headBottom); setSprite(dstBeing, EA_SPRITE_TOPCLOTHES, headMid); setSprite(dstBeing, EA_SPRITE_HAT, headTop); setSprite(dstBeing, EA_SPRITE_SHOE, shoes); setSprite(dstBeing, EA_SPRITE_GLOVES, gloves); setSprite(dstBeing, EA_SPRITE_WEAPON, weapon, "", 1, true); if (!mHideShield) setSprite(dstBeing, EA_SPRITE_SHIELD, shield); } else if (dstBeing->getType() == ActorSprite::NPC) { switch (gender) { case 2: dstBeing->setGender(GENDER_FEMALE); break; case 3: dstBeing->setGender(GENDER_MALE); break; case 4: dstBeing->setGender(GENDER_OTHER); break; default: dstBeing->setGender(GENDER_UNSPECIFIED); break; } } if (!visible) { uint16_t srcX, srcY, dstX, dstY; msg.readCoordinatePair(srcX, srcY, dstX, dstY); if (!disguiseId) { dstBeing->setAction(Being::STAND, 0); dstBeing->setTileCoords(srcX, srcY); if (serverVersion < 10) dstBeing->setDestination(dstX, dstY); } } else { uint8_t dir; uint16_t x, y; msg.readCoordinates(x, y, dir); 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); } msg.readInt8(); // unknown msg.readInt8(); // unknown msg.readInt16(); dstBeing->setStunMode(stunMode); dstBeing->setStatusEffectBlock(0, static_cast( (statusEffects >> 16) & 0xffff)); dstBeing->setStatusEffectBlock(16, static_cast( statusEffects & 0xffff)); } void BeingHandler::processBeingMove2(Net::MessageIn &msg) const { if (!actorManager) return; /* * A simplified movement packet, used by the * later versions of eAthena for both mobs and * players */ Being *const dstBeing = actorManager->findBeing(msg.readInt32()); /* * 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) return; uint16_t srcX, srcY, dstX, dstY; msg.readCoordinatePair(srcX, srcY, dstX, dstY); msg.readInt32(); // Server tick dstBeing->setAction(Being::STAND, 0); dstBeing->setTileCoords(srcX, srcY); dstBeing->setDestination(dstX, dstY); if (dstBeing->getType() == Being::PLAYER) dstBeing->setMoveTime(); } void BeingHandler::processBeingSpawn(Net::MessageIn &msg) { // skipping this packet mSpawnId = msg.readInt32(); // id msg.readInt16(); // speed msg.readInt16(); // opt1 msg.readInt16(); // opt2 msg.readInt16(); // option msg.readInt16(); // disguise } void BeingHandler::processBeingRemove(Net::MessageIn &msg) const { if (!actorManager || !player_node) return; // A being should be removed or has died const int id = msg.readInt32(); Being *const dstBeing = actorManager->findBeing(id); if (!dstBeing) return; player_node->followMoveTo(dstBeing, player_node->getNextDestX(), player_node->getNextDestY()); // If this is player's current target, clear it. if (dstBeing == player_node->getTarget()) player_node->stopAttack(true); if (msg.readInt8() == 1) { if (dstBeing->getCurrentAction() != Being::DEAD) { dstBeing->setAction(Being::DEAD, 0); dstBeing->recalcSpritesOrder(); } if (dstBeing->getName() == "Jack O" && killStats) killStats->jackoDead(id); } else { if (dstBeing->getType() == Being::PLAYER) { if (botCheckerWindow) botCheckerWindow->updateList(); if (socialWindow) socialWindow->updateActiveList(); } actorManager->destroy(dstBeing); } } void BeingHandler::processBeingResurrect(Net::MessageIn &msg) const { if (!actorManager || !player_node) return; // A being changed mortality status const int id = msg.readInt32(); Being *const dstBeing = actorManager->findBeing(id); if (!dstBeing) return; // If this is player's current target, clear it. if (dstBeing == player_node->getTarget()) player_node->stopAttack(); if (msg.readInt8() == 1) dstBeing->setAction(Being::STAND, 0); } void BeingHandler::processSkillDamage(Net::MessageIn &msg) const { if (!actorManager) return; const int id = msg.readInt16(); // Skill Id Being *const srcBeing = actorManager->findBeing(msg.readInt32()); Being *const dstBeing = actorManager->findBeing(msg.readInt32()); msg.readInt32(); // Server tick msg.readInt32(); // src speed msg.readInt32(); // dst speed const int param1 = msg.readInt32(); // Damage const int level = msg.readInt16(); // Skill level msg.readInt16(); // Div msg.readInt8(); // Skill hit/type (?) if (srcBeing) srcBeing->handleSkill(dstBeing, param1, id, level); if (dstBeing) dstBeing->takeDamage(srcBeing, param1, Being::SKILL, id); } void BeingHandler::processBeingAction(Net::MessageIn &msg) const { if (!actorManager) return; Being *const srcBeing = actorManager->findBeing(msg.readInt32()); Being *const dstBeing = actorManager->findBeing(msg.readInt32()); msg.readInt32(); // server tick const int srcSpeed = msg.readInt32(); // src speed msg.readInt32(); // dst speed const int param1 = msg.readInt16(); msg.readInt16(); // param 2 const int type = msg.readInt8(); msg.readInt16(); // param 3 switch (type) { case Being::HIT: // Damage case Being::CRITICAL: // Critical Damage case Being::MULTI: // Critical Damage case Being::REFLECT: // Reflected Damage case Being::FLEE: // Lucky Dodge if (srcBeing) { if (srcSpeed && srcBeing->getType() == Being::PLAYER) srcBeing->setAttackDelay(srcSpeed); // attackid=1, type srcBeing->handleAttack(dstBeing, param1, 1); if (srcBeing->getType() == Being::PLAYER) srcBeing->setAttackTime(); } if (dstBeing) { dstBeing->takeDamage(srcBeing, param1, static_cast(type)); } break; case 0x01: // dead break; // tmw server can send here garbage? // if (srcBeing) // srcBeing->setAction(Being::DEAD, 0); case 0x02: // Sit if (srcBeing) { srcBeing->setAction(Being::SIT, 0); if (srcBeing->getType() == Being::PLAYER) { srcBeing->setMoveTime(); if (player_node) player_node->imitateAction(srcBeing, Being::SIT); } } break; case 0x03: // Stand up if (srcBeing) { srcBeing->setAction(Being::STAND, 0); if (srcBeing->getType() == Being::PLAYER) { srcBeing->setMoveTime(); if (player_node) player_node->imitateAction(srcBeing, Being::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; } } void BeingHandler::processBeingSelfEffect(Net::MessageIn &msg) const { if (!effectManager || !actorManager) return; const int id = static_cast(msg.readInt32()); Being *const being = actorManager->findBeing(id); if (!being) return; const int effectType = msg.readInt32(); if (Particle::enabled) effectManager->trigger(effectType, being); // +++ need dehard code effectType == 3 if (effectType == 3 && being->getType() == Being::PLAYER && socialWindow) { // reset received damage socialWindow->resetDamage(being->getName()); } } void BeingHandler::processBeingEmotion(Net::MessageIn &msg) const { if (!player_node || !actorManager) return; Being *const dstBeing = actorManager->findBeing(msg.readInt32()); if (!dstBeing) return; if (player_relations.hasPermission(dstBeing, PlayerRelation::EMOTE)) { const unsigned char emote = msg.readInt8(); if (emote) { dstBeing->setEmote(emote, 0); player_node->imitateEmote(dstBeing, emote); } } if (dstBeing->getType() == Being::PLAYER) dstBeing->setOtherTime(); } void BeingHandler::processNameResponse(Net::MessageIn &msg) const { if (!player_node || !actorManager) return; const int beingId = msg.readInt32(); Being *const dstBeing = actorManager->findBeing(beingId); if (dstBeing) { if (beingId == player_node->getId()) { player_node->pingResponse(); } else { dstBeing->setName(msg.readString(24)); dstBeing->updateGuild(); dstBeing->addToCache(); if (dstBeing->getType() == Being::PLAYER) dstBeing->updateColors(); if (player_node) { const Party *const party = player_node->getParty(); if (party && party->isMember(dstBeing->getId())) { PartyMember *const member = party->getMember( dstBeing->getId()); if (member) member->setName(dstBeing->getName()); } player_node->checkNewName(dstBeing); } } } } void BeingHandler::processIpResponse(Net::MessageIn &msg) const { if (!actorManager) return; Being *const dstBeing = actorManager->findBeing(msg.readInt32()); if (dstBeing) dstBeing->setIp(ipToString(msg.readInt32())); } void BeingHandler::processPlayerGuilPartyInfo(Net::MessageIn &msg) const { if (!actorManager) return; Being *const dstBeing = actorManager->findBeing(msg.readInt32()); if (dstBeing) { dstBeing->setPartyName(msg.readString(24)); if (!guildManager || !GuildManager::getEnableGuildBot()) { dstBeing->setGuildName(msg.readString(24)); dstBeing->setGuildPos(msg.readString(24)); } else { msg.skip(48); } dstBeing->addToCache(); msg.readString(24); // Discard this } } void BeingHandler::processBeingChangeDirection(Net::MessageIn &msg) const { if (!actorManager) return; Being *const dstBeing = actorManager->findBeing(msg.readInt32()); if (!dstBeing) return; msg.readInt16(); // unused const unsigned char dir = msg.readInt8() & 0x0f; dstBeing->setDirection(dir); if (player_node) player_node->imitateDirection(dstBeing, dir); } void BeingHandler::processPlayerStop(Net::MessageIn &msg) const { if (!actorManager || !player_node) return; /* * Instruction from server to stop walking at x, y. * * Some people like having this enabled. Others absolutely * despise it. So I'm setting to so that it only affects the * local player if the person has set a key "EnableSync" to "1" * in their config.xml file. * * This packet will be honored for all other beings, regardless * of the config setting. */ const int id = msg.readInt32(); if (mSync || id != player_node->getId()) { Being *const dstBeing = actorManager->findBeing(id); if (dstBeing) { const uint16_t x = msg.readInt16(); const uint16_t y = msg.readInt16(); dstBeing->setTileCoords(x, y); if (dstBeing->getCurrentAction() == Being::MOVE) dstBeing->setAction(Being::STAND, 0); } } } void BeingHandler::processPlayerMoveToAttack(Net::MessageIn &msg A_UNUSED) const { /* * This is an *advisory* message, telling the client that * it needs to move the character before attacking * a target (out of range, obstruction in line of fire). * We can safely ignore this... */ if (player_node) player_node->fixAttackTarget(); } void BeingHandler::processPlaterStatusChange(Net::MessageIn &msg) const { if (!actorManager) return; // Change in players' flags const int id = msg.readInt32(); Being *const dstBeing = actorManager->findBeing(id); if (!dstBeing) return; const uint16_t stunMode = msg.readInt16(); uint32_t statusEffects = msg.readInt16(); statusEffects |= (static_cast(msg.readInt16())) << 16; msg.readInt8(); // Unused? dstBeing->setStunMode(stunMode); dstBeing->setStatusEffectBlock(0, static_cast( (statusEffects >> 16) & 0xffff)); dstBeing->setStatusEffectBlock(16, static_cast( statusEffects & 0xffff)); } void BeingHandler::processBeingStatusChange(Net::MessageIn &msg) const { if (!actorManager) return; // Status change const uint16_t status = msg.readInt16(); const int id = msg.readInt32(); const int flag = msg.readInt8(); // 0: stop, 1: start Being *const dstBeing = actorManager->findBeing(id); if (dstBeing) dstBeing->setStatusEffect(status, flag); } void BeingHandler::processSkilCasting(Net::MessageIn &msg) const { msg.readInt32(); // src id msg.readInt32(); // dst id msg.readInt16(); // dst x msg.readInt16(); // dst y msg.readInt16(); // skill num msg.readInt32(); // skill get pl msg.readInt32(); // cast time } void BeingHandler::processSkillNoDamage(Net::MessageIn &msg) const { msg.readInt16(); // skill id msg.readInt16(); // heal msg.readInt32(); // dst id msg.readInt32(); // src id msg.readInt8(); // fail } void BeingHandler::processPvpMapMode(Net::MessageIn &msg) const { const Game *const game = Game::instance(); if (!game) return; Map *const map = game->getCurrentMap(); if (map) map->setPvpMode(msg.readInt16()); } void BeingHandler::processPvpSet(Net::MessageIn &msg) const { const int id = msg.readInt32(); // id const int rank = msg.readInt32(); // rank msg.readInt32(); // num if (actorManager) { Being *const dstBeing = actorManager->findBeing(id); if (dstBeing) dstBeing->setPvpRank(rank); } } } // namespace Ea