/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011 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/tmwa/beinghandler.h" #include "actorspritemanager.h" #include "being.h" #include "client.h" #include "effectmanager.h" #include "guild.h" #include "keyboardconfig.h" #include "localplayer.h" #include "log.h" #include "party.h" #include "playerrelations.h" #include "configuration.h" #include "gui/botcheckerwindow.h" #include "gui/outfitwindow.h" #include "gui/socialwindow.h" #include "gui/killstats.h" #include "utils/gettext.h" #include "utils/stringutils.h" #include "net/playerhandler.h" #include "net/net.h" #include "net/tmwa/protocol.h" #include "resources/colordb.h" #include extern Net::BeingHandler *beingHandler; namespace TmwAthena { const int EMOTION_TIME = 500; /**< Duration of emotion icon */ Being *createBeing(int id, short job); BeingHandler::BeingHandler(bool enableSync): mSync(enableSync), mSpawnId(0) { static const Uint16 _messages[] = { SMSG_BEING_VISIBLE, SMSG_BEING_MOVE, SMSG_BEING_MOVE2, SMSG_BEING_REMOVE, SMSG_SKILL_DAMAGE, SMSG_BEING_ACTION, SMSG_BEING_SELFEFFECT, SMSG_BEING_EMOTION, SMSG_BEING_CHANGE_LOOKS, SMSG_BEING_CHANGE_LOOKS2, SMSG_BEING_NAME_RESPONSE, SMSG_BEING_NAME_RESPONSE2, SMSG_PLAYER_GUILD_PARTY_INFO, SMSG_BEING_CHANGE_DIRECTION, SMSG_PLAYER_UPDATE_1, SMSG_PLAYER_UPDATE_2, SMSG_PLAYER_MOVE, SMSG_PLAYER_STOP, SMSG_PLAYER_MOVE_TO_ATTACK, SMSG_PLAYER_STATUS_CHANGE, SMSG_BEING_STATUS_CHANGE, SMSG_BEING_RESURRECT, SMSG_SOLVE_CHAR_NAME, SMSG_BEING_SPAWN, SMSG_SKILL_CASTING, SMSG_SKILL_CAST_CANCEL, SMSG_SKILL_NO_DAMAGE, SMSG_BEING_IP_RESPONSE, SMSG_PVP_MAP_MODE, SMSG_PVP_SET, 0 }; handledMessages = _messages; beingHandler = this; } Being *createBeing(int id, short job) { if (!actorSpriteManager) return 0; 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 *being = actorSpriteManager->createBeing(id, type, job); if (type == ActorSprite::PLAYER || type == ActorSprite::NPC) { if (!being->updateFromCache()) { MessageOut outMsg(0x0094); outMsg.writeInt32(id); //readLong(2)); } else { if (player_node) player_node->checkNewName(being); } } if (type == Being::PLAYER) { if (botCheckerWindow) botCheckerWindow->updateList(); if (socialWindow) socialWindow->updateActiveList(); } return being; } void BeingHandler::requestNameById(int id) { MessageOut outMsg(0x0094); outMsg.writeInt32(id); //readLong(2)); } void BeingHandler::handleMessage(Net::MessageIn &msg) { if (!actorSpriteManager) return; int id; short job, speed, gender; Uint16 headTop, headMid, headBottom; Uint16 shoes, gloves; Uint16 weapon, shield; Uint16 gmstatus; int param1; Uint16 stunMode; int level; Uint32 statusEffects; int type, guild; Uint16 status; Being *srcBeing, *dstBeing; int hairStyle, hairColor, flag; int hp, maxHP, oldHP; unsigned char colors[9]; Uint8 dir; int spawnId; switch (msg.getId()) { case SMSG_BEING_VISIBLE: case SMSG_BEING_MOVE: // Information about a being in range id = msg.readInt32(); if (id == mSpawnId) spawnId = mSpawnId; else spawnId = 0; mSpawnId = 0; speed = msg.readInt16(); stunMode = msg.readInt16(); // opt1 statusEffects = msg.readInt16(); // opt2 statusEffects |= (static_cast( msg.readInt16())) << 16; // option job = msg.readInt16(); // class dstBeing = actorSpriteManager->findBeing(id); if (dstBeing && dstBeing->getType() == Being::MONSTER && !dstBeing->isAlive()) { actorSpriteManager->destroy(dstBeing); actorSpriteManager->erase(dstBeing); dstBeing = 0; } if (!dstBeing) { // Being with id >= 110000000 and job 0 are better // known as ghosts, so don't create those. if (job == 0 && id >= 110000000) break; if (actorSpriteManager->isBlocked(id) == true) break; dstBeing = createBeing(id, job); if (!dstBeing) break; if (job == 1022 && killStats) killStats->jackoAlive(dstBeing->getId()); } else { // undeleting marked for deletion being if (dstBeing->getType() == Being::NPC) { actorSpriteManager->undelete(dstBeing); } } if (dstBeing->getType() == Being::PLAYER) dstBeing->setMoveTime(); if (spawnId) { dstBeing->setAction(Being::SPAWN); } else if (msg.getId() == SMSG_BEING_VISIBLE) { dstBeing->clearPath(); dstBeing->setActionTime(tick_time); dstBeing->setAction(Being::STAND); } // Prevent division by 0 when calculating frame if (speed == 0) speed = 150; dstBeing->setWalkSpeed(Vector(speed, speed, 0)); dstBeing->setSubtype(job); if (dstBeing->getType() == ActorSprite::MONSTER && player_node) player_node->checkNewName(dstBeing); hairStyle = msg.readInt16(); weapon = msg.readInt16(); headBottom = msg.readInt16(); if (msg.getId() == SMSG_BEING_MOVE) msg.readInt32(); // server tick shield = msg.readInt16(); headTop = msg.readInt16(); headMid = msg.readInt16(); hairColor = msg.readInt16(); shoes = msg.readInt16(); // clothes color - "abused" as shoes if (dstBeing->getType() == ActorSprite::MONSTER) { hp = msg.readInt32(); maxHP = msg.readInt32(); if (hp && maxHP) { oldHP = dstBeing->getHP(); if (!oldHP || oldHP > hp) dstBeing->setHP(hp); dstBeing->setMaxHP(maxHP); } gloves = 0; guild = 0; } else { gloves = msg.readInt16(); // head dir - "abused" as gloves guild = msg.readInt32(); // guild msg.readInt16(); // guild emblem } // logger->log("being guild: " + toString(guild)); /* if (guild == 0) dstBeing->clearGuilds(); else dstBeing->setGuild(Guild::getGuild(static_cast(guild))); */ msg.readInt16(); // manner dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 msg.readInt8(); // karma gender = msg.readInt8(); if (dstBeing->getType() == ActorSprite::PLAYER) { dstBeing->setGender((gender == 0) ? GENDER_FEMALE : GENDER_MALE); // Set these after the gender, as the sprites may be gender-specific dstBeing->setSprite(SPRITE_HAIR, hairStyle * -1, ColorDB::getHairColor(hairColor)); dstBeing->setSprite(SPRITE_BOTTOMCLOTHES, headBottom); dstBeing->setSprite(SPRITE_TOPCLOTHES, headMid); dstBeing->setSprite(SPRITE_HAT, headTop); dstBeing->setSprite(SPRITE_SHOE, shoes); dstBeing->setSprite(SPRITE_GLOVES, gloves); dstBeing->setSprite(SPRITE_WEAPON, weapon, "", true); if (!config.getBoolValue("hideShield")) dstBeing->setSprite(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; default: dstBeing->setGender(GENDER_UNSPECIFIED); break; } } if (msg.getId() == SMSG_BEING_MOVE) { Uint16 srcX, srcY, dstX, dstY; msg.readCoordinatePair(srcX, srcY, dstX, dstY); dstBeing->setAction(Being::STAND); dstBeing->setTileCoords(srcX, srcY); dstBeing->setDestination(dstX, dstY); // if (player_node && player_node->getTarget() == dstBeing) // player_node->targetMoved(); } else { Uint8 dir; Uint16 x, y; msg.readCoordinates(x, y, dir); dstBeing->setTileCoords(x, y); if (job == 45 && socialWindow && outfitWindow) { 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.readInt8(); // unknown / sit msg.readInt16(); dstBeing->setStunMode(stunMode); dstBeing->setStatusEffectBlock(0, static_cast( (statusEffects >> 16) & 0xffff)); dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); break; case SMSG_BEING_MOVE2: /* * A simplified movement packet, used by the * later versions of eAthena for both mobs and * players */ dstBeing = actorSpriteManager->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) break; Uint16 srcX, srcY, dstX, dstY; msg.readCoordinatePair(srcX, srcY, dstX, dstY); msg.readInt32(); // Server tick dstBeing->setAction(Being::STAND); dstBeing->setTileCoords(srcX, srcY); dstBeing->setDestination(dstX, dstY); if (dstBeing->getType() == Being::PLAYER) dstBeing->setMoveTime(); // if (player_node && player_node->getTarget() == dstBeing) // { // logger->log("SMSG_BEING_MOVE2"); // player_node->targetMoved(); // } break; case SMSG_BEING_SPAWN: // skipping this packet mSpawnId = msg.readInt32(); // id msg.readInt16(); // speed msg.readInt16(); // opt1 msg.readInt16(); // opt2 msg.readInt16(); // option msg.readInt16(); // disguise break; case SMSG_BEING_REMOVE: // A being should be removed or has died id = msg.readInt32(); dstBeing = actorSpriteManager->findBeing(id); if (!dstBeing) break; 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(); if (msg.readInt8() == 1) { dstBeing->setAction(Being::DEAD); if (dstBeing->getName() == "Jack O" && killStats) killStats->jackoDead(id); } else { if (dstBeing->getType() == Being::PLAYER) { if (botCheckerWindow) botCheckerWindow->updateList(); if (socialWindow) socialWindow->updateActiveList(); } actorSpriteManager->destroy(dstBeing); } break; case SMSG_BEING_RESURRECT: // A being changed mortality status id = msg.readInt32(); dstBeing = actorSpriteManager->findBeing(id); if (!dstBeing) break; // 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); break; case SMSG_SKILL_DAMAGE: { msg.readInt16(); // Skill Id srcBeing = actorSpriteManager->findBeing(msg.readInt32()); dstBeing = actorSpriteManager->findBeing(msg.readInt32()); msg.readInt32(); // Server tick int srcSpeed = msg.readInt32(); // src speed int dstSpeed = msg.readInt32(); // dst speed param1 = msg.readInt32(); // Damage msg.readInt16(); // Skill level msg.readInt16(); // Div msg.readInt8(); // Skill hit/type (?) if (dstBeing) { // Perhaps a new skill attack type should be created and used? if (dstSpeed) dstBeing->setAttackDelay(dstSpeed); dstBeing->takeDamage(srcBeing, param1, Being::HIT); } if (srcBeing) { if (srcSpeed) srcBeing->setAttackDelay(srcSpeed); srcBeing->handleAttack(dstBeing, param1, Being::HIT); } break; } case SMSG_BEING_ACTION: { srcBeing = actorSpriteManager->findBeing(msg.readInt32()); dstBeing = actorSpriteManager->findBeing(msg.readInt32()); msg.readInt32(); // server tick int srcSpeed = msg.readInt32(); // src speed int dstSpeed = msg.readInt32(); // dst speed param1 = msg.readInt16(); msg.readInt16(); // param 2 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 (dstBeing) { if (dstSpeed) dstBeing->setAttackDelay(dstSpeed); dstBeing->takeDamage(srcBeing, param1, static_cast(type)); } if (srcBeing) { if (srcSpeed) srcBeing->setAttackDelay(srcSpeed); srcBeing->handleAttack(dstBeing, param1, static_cast(type)); if (srcBeing->getType() == Being::PLAYER) srcBeing->setAttackTime(); } break; case 0x02: // Sit if (srcBeing) { srcBeing->setAction(Being::SIT); 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); if (srcBeing->getType() == Being::PLAYER) { srcBeing->setMoveTime(); if (player_node) { player_node->imitateAction( srcBeing, Being::STAND); } } } break; default: break; /* 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; } case SMSG_BEING_SELFEFFECT: { if (!effectManager) return; id = static_cast(msg.readInt32()); Being* being = actorSpriteManager->findBeing(id); if (!being) break; int effectType = msg.readInt32(); effectManager->trigger(effectType, being); if (being && effectType == 3 && being->getType() == Being::PLAYER && socialWindow) { //reset received damage socialWindow->resetDamage(being->getName()); } break; } case SMSG_BEING_EMOTION: if (!player_node) break; if (!(dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) break; if (player_relations.hasPermission(dstBeing, PlayerRelation::EMOTE)) { unsigned char emote = msg.readInt8(); if (emote) { dstBeing->setEmote(emote, EMOTION_TIME); player_node->imitateEmote(dstBeing, emote); } } if (dstBeing->getType() == Being::PLAYER) dstBeing->setOtherTime(); break; case SMSG_BEING_CHANGE_LOOKS: case SMSG_BEING_CHANGE_LOOKS2: { /* * SMSG_BEING_CHANGE_LOOKS (0x00c3) and * SMSG_BEING_CHANGE_LOOKS2 (0x01d7) do basically the same * thing. The difference is that ...LOOKS carries a single * 8 bit value, where ...LOOKS2 carries two 16 bit values. * * If type = 2, then the first 16 bit value is the weapon ID, * and the second 16 bit value is the shield ID. If no * shield is equipped, or type is not 2, then the second * 16 bit value will be 0. */ if (!(dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) break; int type = msg.readInt8(); int id = 0; int id2 = 0; std::string color; if (msg.getId() == SMSG_BEING_CHANGE_LOOKS) { id = msg.readInt8(); id2 = 1; // default color } else { // SMSG_BEING_CHANGE_LOOKS2 id = msg.readInt16(); if (type == 2 || serverVersion > 0) id2 = msg.readInt16(); else id2 = 1; color = ""; } if (dstBeing->getType() == Being::PLAYER) dstBeing->setOtherTime(); if (!player_node) break; switch (type) { case 0: // change race dstBeing->setSubtype(static_cast(id)); break; case 1: // eAthena LOOK_HAIR dstBeing->setSpriteID(SPRITE_HAIR, id *-1); break; case 2: // Weapon ID in id, Shield ID in id2 dstBeing->setSprite(SPRITE_WEAPON, id, "", 1, true); if (!config.getBoolValue("hideShield")) dstBeing->setSprite(SPRITE_SHIELD, id2); player_node->imitateOutfit(dstBeing, SPRITE_SHIELD); break; case 3: // Change lower headgear for eAthena, pants for us dstBeing->setSprite(SPRITE_BOTTOMCLOTHES, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_BOTTOMCLOTHES); break; case 4: // Change upper headgear for eAthena, hat for us dstBeing->setSprite(SPRITE_HAT, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_HAT); break; case 5: // Change middle headgear for eathena, armor for us dstBeing->setSprite(SPRITE_TOPCLOTHES, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_TOPCLOTHES); break; case 6: // eAthena LOOK_HAIR_COLOR dstBeing->setSpriteColor(SPRITE_HAIR, ColorDB::getHairColor(id)); break; case 8: // eAthena LOOK_SHIELD if (!config.getBoolValue("hideShield")) { dstBeing->setSprite(SPRITE_SHIELD, id, color, static_cast(id2)); } player_node->imitateOutfit(dstBeing, SPRITE_SHIELD); break; case 9: // eAthena LOOK_SHOES dstBeing->setSprite(SPRITE_SHOE, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_SHOE); break; case 10: // LOOK_GLOVES dstBeing->setSprite(SPRITE_GLOVES, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_GLOVES); break; case 11: // LOOK_CAPE dstBeing->setSprite(SPRITE_CAPE, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_CAPE); break; case 12: dstBeing->setSprite(SPRITE_MISC1, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_MISC1); break; case 13: dstBeing->setSprite(SPRITE_MISC2, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_MISC2); break; case 14: dstBeing->setSprite(SPRITE_EVOL1, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_EVOL1); break; case 15: dstBeing->setSprite(SPRITE_EVOL2, id, color, static_cast(id2)); player_node->imitateOutfit(dstBeing, SPRITE_EVOL2); break; default: logger->log("QQQ3 CHANGE_LOOKS: unsupported type: " "%d, id: %d", type, id); if (dstBeing) { logger->log("ID: " + toString(dstBeing->getId())); logger->log("name: " + toString(dstBeing->getName())); } break; } } break; case SMSG_BEING_NAME_RESPONSE: { int beingId = msg.readInt32(); if ((dstBeing = actorSpriteManager->findBeing(beingId))) { 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) { Party *party = player_node->getParty(); if (party && party->isMember(dstBeing->getId())) { PartyMember *member = party->getMember( dstBeing->getId()); if (member) member->setName(dstBeing->getName()); } player_node->checkNewName(dstBeing); } } } } break; case SMSG_BEING_NAME_RESPONSE2: { int len = msg.readInt16(); int beingId = msg.readInt32(); std::string str = msg.readString(len - 8); if ((dstBeing = actorSpriteManager->findBeing(beingId))) { if (beingId == player_node->getId()) { player_node->pingResponse(); } else { dstBeing->setName(str); dstBeing->updateGuild(); dstBeing->addToCache(); if (dstBeing->getType() == Being::PLAYER) dstBeing->updateColors(); if (player_node) { Party *party = player_node->getParty(); if (party && party->isMember(dstBeing->getId())) { PartyMember *member = party->getMember( dstBeing->getId()); if (member) member->setName(dstBeing->getName()); } player_node->checkNewName(dstBeing); } } } } break; case SMSG_BEING_IP_RESPONSE: { if ((dstBeing = actorSpriteManager->findBeing( msg.readInt32()))) { dstBeing->setIp(ipToString(msg.readInt32())); } } break; case SMSG_SOLVE_CHAR_NAME: { logger->log1("SMSG_SOLVE_CHAR_NAME"); logger->log(toString(msg.readInt32())); logger->log(msg.readString(24)); } break; case SMSG_PLAYER_GUILD_PARTY_INFO: if ((dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) { dstBeing->setPartyName(msg.readString(24)); dstBeing->setGuildName(msg.readString(24)); dstBeing->setGuildPos(msg.readString(24)); dstBeing->addToCache(); msg.readString(24); // Discard this } break; case SMSG_BEING_CHANGE_DIRECTION: { if (!(dstBeing = actorSpriteManager->findBeing(msg.readInt32()))) break; msg.readInt16(); // unused unsigned char dir = msg.readInt8(); dstBeing->setDirection(dir); if (player_node) player_node->imitateDirection(dstBeing, dir); break; } case SMSG_PLAYER_UPDATE_1: case SMSG_PLAYER_UPDATE_2: case SMSG_PLAYER_MOVE: if (!actorSpriteManager || !player_node) break; // An update about a player, potentially including movement. id = msg.readInt32(); speed = msg.readInt16(); stunMode = msg.readInt16(); // opt1; Aethyra use this as cape statusEffects = msg.readInt16(); // opt2; Aethyra use this as misc1 statusEffects |= (static_cast(msg.readInt16())) << 16; // status.options; Aethyra uses this as misc2 job = msg.readInt16(); dstBeing = actorSpriteManager->findBeing(id); if (!dstBeing) { if (actorSpriteManager->isBlocked(id) == true) break; dstBeing = createBeing(id, job); if (!dstBeing) break; } dir = dstBeing->getDirectionDelayed(); if (dir) { if (dir != dstBeing->getDirection()) dstBeing->setDirection(dir); } if (Party *party = player_node->getParty()) { if (party->isMember(id)) dstBeing->setParty(party); } dstBeing->setWalkSpeed(Vector(speed, speed, 0)); dstBeing->setSubtype(job); hairStyle = msg.readInt16(); weapon = msg.readInt16(); shield = msg.readInt16(); headBottom = msg.readInt16(); if (msg.getId() == SMSG_PLAYER_MOVE) msg.readInt32(); // server tick headTop = msg.readInt16(); headMid = msg.readInt16(); hairColor = msg.readInt16(); colors[0] = msg.readInt8(); colors[1] = msg.readInt8(); colors[2] = msg.readInt8(); msg.readInt8(); //unused // shoes = msg.readInt16(); // gloves = msg.readInt16(); //sd->head_dir guild = msg.readInt32(); // guild if (guild == 0) dstBeing->clearGuilds(); else dstBeing->setGuild(Guild::getGuild(static_cast(guild))); msg.readInt16(); // emblem msg.readInt16(); // manner dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3 msg.readInt8(); // karma dstBeing->setGender((msg.readInt8() == 0) ? GENDER_FEMALE : GENDER_MALE); // Set these after the gender, as the sprites may be gender-specific dstBeing->setSprite(SPRITE_WEAPON, weapon, "", 1, true); if (!config.getBoolValue("hideShield")) dstBeing->setSprite(SPRITE_SHIELD, shield); //dstBeing->setSprite(SPRITE_SHOE, shoes); if (serverVersion > 0) { dstBeing->setSprite(SPRITE_BOTTOMCLOTHES, headBottom, "", colors[0]); dstBeing->setSprite(SPRITE_TOPCLOTHES, headMid, "", colors[2]); dstBeing->setSprite(SPRITE_HAT, headTop, "", colors[1]); } else { dstBeing->setSprite(SPRITE_BOTTOMCLOTHES, headBottom); dstBeing->setSprite(SPRITE_TOPCLOTHES, headMid); dstBeing->setSprite(SPRITE_HAT, headTop); } //dstBeing->setSprite(SPRITE_GLOVES, gloves); //dstBeing->setSprite(SPRITE_CAPE, cape); //dstBeing->setSprite(SPRITE_MISC1, misc1); //dstBeing->setSprite(SPRITE_MISC2, misc2); dstBeing->setSprite(SPRITE_HAIR, hairStyle * -1, ColorDB::getHairColor(hairColor)); player_node->imitateOutfit(dstBeing); if (msg.getId() == SMSG_PLAYER_MOVE) { Uint16 srcX, srcY, dstX, dstY; msg.readCoordinatePair(srcX, srcY, dstX, dstY); player_node->followMoveTo(dstBeing, srcX, srcY, dstX, dstY); dstBeing->setTileCoords(srcX, srcY); dstBeing->setDestination(dstX, dstY); // because server dont send direction in move packet, // we fixing it if (srcX != dstX || srcY != dstY) { int dir = dstBeing->calcDirection(dstX, dstY); if (dir && dstBeing->getDirection() != dir) dstBeing->setDirectionDelayed(static_cast(dir)); } if (player_node->getCurrentAction() != Being::STAND) player_node->imitateAction(dstBeing, Being::STAND); if (player_node->getDirection() != dstBeing->getDirection()) { player_node->imitateDirection(dstBeing, dstBeing->getDirection()); } } else { Uint8 dir; Uint16 x, y; msg.readCoordinates(x, y, dir); dstBeing->setTileCoords(x, y); dstBeing->setDirection(dir); player_node->imitateDirection(dstBeing, dir); } gmstatus = msg.readInt16(); if (gmstatus & 0x80) dstBeing->setGM(true); if (msg.getId() == SMSG_PLAYER_UPDATE_1) { int type = msg.readInt8(); switch (type) { case 0: dstBeing->setAction(Being::STAND); player_node->imitateAction(dstBeing, Being::STAND); break; case 1: dstBeing->setAction(Being::DEAD); break; case 2: dstBeing->setAction(Being::SIT); player_node->imitateAction(dstBeing, Being::SIT); break; default: //need set stay state? logger->log("QQQ2 SMSG_PLAYER_UPDATE_1:" + toString(id) + " " + toString(type)); if (dstBeing) { logger->log("dstBeing id:" + toString(dstBeing->getId())); logger->log("dstBeing name:" + dstBeing->getName()); } break; } } else if (msg.getId() == SMSG_PLAYER_MOVE) { msg.readInt8(); // unknown } level = msg.readInt8(); // Lv if (level) dstBeing->setLevel(level); msg.readInt8(); // unknown if (dstBeing->getType() != Being::PLAYER || msg.getId() != SMSG_PLAYER_MOVE) { dstBeing->setActionTime(tick_time); // dstBeing->reset(); } dstBeing->setStunMode(stunMode); dstBeing->setStatusEffectBlock(0, static_cast( (statusEffects >> 16) & 0xffff)); dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff); if (msg.getId() == SMSG_PLAYER_MOVE && dstBeing->getType() == Being::PLAYER) { dstBeing->setMoveTime(); } break; case SMSG_PLAYER_STOP: /* * 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. */ id = msg.readInt32(); if (mSync || id != player_node->getId()) { dstBeing = actorSpriteManager->findBeing(id); if (dstBeing) { Uint16 x, y; x = msg.readInt16(); y = msg.readInt16(); dstBeing->setTileCoords(x, y); if (dstBeing->getCurrentAction() == Being::MOVE) dstBeing->setAction(Being::STAND); } } break; case SMSG_PLAYER_MOVE_TO_ATTACK: /* * 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(); break; case SMSG_PLAYER_STATUS_CHANGE: // Change in players' flags id = msg.readInt32(); dstBeing = actorSpriteManager->findBeing(id); if (!dstBeing) break; stunMode = msg.readInt16(); 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, statusEffects & 0xffff); break; case SMSG_BEING_STATUS_CHANGE: // Status change status = msg.readInt16(); id = msg.readInt32(); flag = msg.readInt8(); // 0: stop, 1: start dstBeing = actorSpriteManager->findBeing(id); if (dstBeing) dstBeing->setStatusEffect(status, flag); break; case SMSG_SKILL_CASTING: 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 break; case SMSG_SKILL_CAST_CANCEL: msg.readInt32(); // id break; case SMSG_SKILL_NO_DAMAGE: msg.readInt16(); // skill id msg.readInt16(); // heal msg.readInt32(); // dst id msg.readInt32(); // src id msg.readInt8(); // fail break; case SMSG_PVP_MAP_MODE: { Game *game = Game::instance(); if (!game) break; Map *map = game->getCurrentMap(); if (map) map->setPvpMode(msg.readInt16()); break; } case SMSG_PVP_SET: { int id = msg.readInt32(); // id int rank = msg.readInt32(); // rank msg.readInt32(); // num if (actorSpriteManager) { dstBeing = actorSpriteManager->findBeing(id); if (dstBeing) dstBeing->setPvpRank(rank); } break; } default: break; } } void BeingHandler::undress(Being *being) { being->setSprite(SPRITE_BOTTOMCLOTHES, 0); being->setSprite(SPRITE_TOPCLOTHES, 0); being->setSprite(SPRITE_HAT, 0); being->setSprite(SPRITE_SHOE, 0); being->setSprite(SPRITE_GLOVES, 0); // being->setSprite(SPRITE_WEAPON, 0, "", true); } } // namespace TmwAthena