summaryrefslogtreecommitdiff
path: root/src/net/tmwa/beinghandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/tmwa/beinghandler.cpp')
-rw-r--r--src/net/tmwa/beinghandler.cpp714
1 files changed, 714 insertions, 0 deletions
diff --git a/src/net/tmwa/beinghandler.cpp b/src/net/tmwa/beinghandler.cpp
new file mode 100644
index 00000000..04690c50
--- /dev/null
+++ b/src/net/tmwa/beinghandler.cpp
@@ -0,0 +1,714 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "net/tmwa/beinghandler.h"
+
+#include "being.h"
+#include "beingmanager.h"
+#include "client.h"
+#include "effectmanager.h"
+#include "guild.h"
+#include "localplayer.h"
+#include "log.h"
+#include "npc.h"
+#include "party.h"
+#include "playerrelations.h"
+
+#include "net/tmwa/protocol.h"
+
+#include "resources/colordb.h"
+
+#include <iostream>
+
+namespace TmwAthena {
+
+const int EMOTION_TIME = 150; /**< Duration of emotion icon */
+
+BeingHandler::BeingHandler(bool enableSync):
+ mSync(enableSync)
+{
+ 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_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,
+ 0
+ };
+ handledMessages = _messages;
+}
+
+Being *createBeing(int id, short job)
+{
+ Being::Type type = Being::UNKNOWN;
+ if (job <= 25 || (job >= 4001 && job <= 4049))
+ type = Being::PLAYER;
+ else if (job >= 46 && job <= 1000)
+ type = Being::NPC;
+ else if (job > 1000 && job <= 2000)
+ type = Being::MONSTER;
+ else if (job == 45)
+ return NULL; // Skip portals
+
+ Being *being = beingManager->createBeing(id, type, job);
+
+ if (type == Being::PLAYER || type == Being::NPC)
+ {
+ MessageOut outMsg(0x0094);
+ outMsg.writeInt32(id);//readLong(2));
+ }
+
+ return being;
+}
+
+void BeingHandler::handleMessage(Net::MessageIn &msg)
+{
+ if (!beingManager)
+ return;
+
+ int id;
+ short job, speed, gender;
+ Uint16 headTop, headMid, headBottom;
+ Uint16 shoes, gloves;
+ Uint16 weapon, shield;
+ Uint16 gmstatus;
+ int param1;
+ int stunMode;
+ Uint32 statusEffects;
+ int type, guild;
+ Uint16 status;
+ Being *srcBeing, *dstBeing;
+ Player *player = 0;
+ int hairStyle, hairColor, flag;
+ std::string player_followed;
+
+ switch (msg.getId())
+ {
+ case SMSG_BEING_VISIBLE:
+ case SMSG_BEING_MOVE:
+ // Information about a being in range
+ id = msg.readInt32();
+ speed = msg.readInt16();
+ stunMode = msg.readInt16(); // opt1
+ statusEffects = msg.readInt16(); // opt2
+ statusEffects |= ((Uint32)msg.readInt16()) << 16; // option
+ job = msg.readInt16(); // class
+
+ dstBeing = beingManager->findBeing(id);
+
+ 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;
+ }
+
+ dstBeing = createBeing(id, job);
+
+ if (!dstBeing)
+ break;
+ }
+
+ if (dstBeing->getType() == Being::PLAYER)
+ player = static_cast<Player*>(dstBeing);
+
+ if (msg.getId() == 0x0078)
+ {
+ dstBeing->clearPath();
+ dstBeing->setFrame(0);
+ dstBeing->setWalkTime(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);
+ 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
+ gloves = msg.readInt16(); // head dir - "abused" as gloves
+ guild = msg.readInt32(); // guild
+ if (player)
+ {
+ if (guild == 0)
+ {
+ player->clearGuilds();
+ }
+ else
+ {
+ player->addGuild(Guild::getGuild(guild));
+ }
+ }
+ msg.readInt16(); // guild emblem
+ msg.readInt16(); // manner
+ dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3
+ msg.readInt8(); // karma
+ gender = msg.readInt8();
+
+ if (player)
+ {
+ player->setGender((gender == 0)
+ ? GENDER_FEMALE : GENDER_MALE);
+ // Set these after the gender, as the sprites may be gender-specific
+ player->setSprite(SPRITE_HAIR, hairStyle * -1, ColorDB::get(hairColor));
+ player->setSprite(SPRITE_BOTTOMCLOTHES, headBottom);
+ player->setSprite(SPRITE_TOPCLOTHES, headMid);
+ player->setSprite(SPRITE_HAT, headTop);
+ player->setSprite(SPRITE_SHOE, shoes);
+ player->setSprite(SPRITE_GLOVES, gloves);
+ player->setSprite(SPRITE_WEAPON, weapon, "", true);
+ player->setSprite(SPRITE_SHIELD, shield);
+ }
+
+ 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);
+ }
+ else
+ {
+ Uint8 dir;
+ Uint16 x, y;
+ msg.readCoordinates(x, y, dir);
+ dstBeing->setTileCoords(x, y);
+ dstBeing->setDirection(dir);
+ }
+
+ msg.readInt8(); // unknown
+ msg.readInt8(); // unknown
+ msg.readInt8(); // unknown / sit
+
+ dstBeing->setStunMode(stunMode);
+ dstBeing->setStatusEffectBlock(0, (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 = beingManager->findBeing(msg.readInt32());
+
+ Uint16 srcX, srcY, dstX, dstY;
+ msg.readCoordinatePair(srcX, srcY, dstX, dstY);
+ msg.readInt32(); // Server 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)
+ {
+ dstBeing->setAction(Being::STAND);
+ dstBeing->setTileCoords(srcX, srcY);
+ dstBeing->setDestination(dstX, dstY);
+ }
+
+ break;
+
+ case SMSG_BEING_REMOVE:
+ // A being should be removed or has died
+ id = msg.readInt32();
+
+ dstBeing = beingManager->findBeing(id);
+
+ if (!dstBeing)
+ break;
+
+ player_followed = player_node->getFollow();
+
+ if (!player_followed.empty())
+ {
+ if (dstBeing->getName() == player_followed)
+ {
+ player_node->setDestination(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);
+ else
+ beingManager->destroyBeing(dstBeing);
+
+ break;
+
+ case SMSG_BEING_RESURRECT:
+ // A being changed mortality status
+ id = msg.readInt32();
+
+ dstBeing = beingManager->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 = beingManager->findBeing(msg.readInt32());
+ dstBeing = beingManager->findBeing(msg.readInt32());
+ msg.readInt32(); // Server tick
+ msg.readInt32(); // src speed
+ msg.readInt32(); // dst speed
+ param1 = msg.readInt32(); // Damage
+ msg.readInt16(); // Skill level
+ msg.readInt16(); // Div
+ msg.readInt8(); // Skill hit/type (?)
+ if (dstBeing)
+ dstBeing->takeDamage(srcBeing, param1, Being::HIT); // Perhaps a new skill attack type should be created and used?
+ if (srcBeing)
+ srcBeing->handleAttack(dstBeing, param1, Being::HIT);
+ break;
+
+ case SMSG_BEING_ACTION:
+ srcBeing = beingManager->findBeing(msg.readInt32());
+ dstBeing = beingManager->findBeing(msg.readInt32());
+ msg.readInt32(); // server tick
+ msg.readInt32(); // src speed
+ 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)
+ dstBeing->takeDamage(srcBeing, param1,
+ (Being::AttackType)type);
+ if (srcBeing)
+ srcBeing->handleAttack(dstBeing, param1,
+ (Being::AttackType)type);
+ break;
+
+ case 0x02: // Sit
+ if (srcBeing)
+ {
+ srcBeing->setFrame(0);
+ srcBeing->setAction(Being::SIT);
+ }
+ break;
+
+ case 0x03: // Stand up
+ if (srcBeing)
+ {
+ srcBeing->setFrame(0);
+ srcBeing->setAction(Being::STAND);
+ }
+ break;
+ }
+ break;
+
+ case SMSG_BEING_SELFEFFECT: {
+ id = (Uint32)msg.readInt32();
+ if (!beingManager->findBeing(id))
+ break;
+
+ int effectType = msg.readInt32();
+ Being* being = beingManager->findBeing(id);
+
+ effectManager->trigger(effectType, being);
+
+ break;
+ }
+
+ case SMSG_BEING_EMOTION:
+ if (!(dstBeing = beingManager->findBeing(msg.readInt32())))
+ {
+ break;
+ }
+
+ if (player_relations.hasPermission(dstBeing, PlayerRelation::EMOTE))
+ {
+ // only set emote if one doesnt already exist
+ if (!dstBeing->getEmotion())
+ dstBeing->setEmote(msg.readInt8(), EMOTION_TIME);
+ }
+
+ 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 = beingManager->findBeing(msg.readInt32())))
+ {
+ break;
+ }
+
+ if (dstBeing->getType() == Being::PLAYER)
+ player = static_cast<Player*>(dstBeing);
+
+ int type = msg.readInt8();
+ int id = 0;
+ int id2 = 0;
+
+ if (msg.getId() == SMSG_BEING_CHANGE_LOOKS)
+ {
+ id = msg.readInt8();
+ }
+ else
+ { // SMSG_BEING_CHANGE_LOOKS2
+ id = msg.readInt16();
+ id2 = msg.readInt16();
+ }
+
+ switch (type)
+ {
+ case 1: // eAthena LOOK_HAIR
+ player->setSpriteID(SPRITE_HAIR, id *-1);
+ break;
+ case 2: // Weapon ID in id, Shield ID in id2
+ player->setSprite(SPRITE_WEAPON, id, "", true);
+ player->setSprite(SPRITE_SHIELD, id2);
+ break;
+ case 3: // Change lower headgear for eAthena, pants for us
+ player->setSprite(SPRITE_BOTTOMCLOTHES, id);
+ break;
+ case 4: // Change upper headgear for eAthena, hat for us
+ player->setSprite(SPRITE_HAT, id);
+ break;
+ case 5: // Change middle headgear for eathena, armor for us
+ player->setSprite(SPRITE_TOPCLOTHES, id);
+ break;
+ case 6: // eAthena LOOK_HAIR_COLOR
+ player->setSpriteColor(SPRITE_HAIR, ColorDB::get(id));
+ break;
+ case 8: // eAthena LOOK_SHIELD
+ player->setSprite(SPRITE_SHIELD, id);
+ break;
+ case 9: // eAthena LOOK_SHOES
+ player->setSprite(SPRITE_SHOE, id);
+ break;
+ case 10: // LOOK_GLOVES
+ player->setSprite(SPRITE_GLOVES, id);
+ break;
+ case 11: // LOOK_CAPE
+ player->setSprite(SPRITE_CAPE, id);
+ break;
+ case 12:
+ player->setSprite(SPRITE_MISC1, id);
+ break;
+ case 13:
+ player->setSprite(SPRITE_MISC2, id);
+ break;
+ default:
+ logger->log("SMSG_BEING_CHANGE_LOOKS: unsupported type: "
+ "%d, id: %d", type, id);
+ break;
+ }
+ }
+ break;
+
+ case SMSG_BEING_NAME_RESPONSE:
+ if ((dstBeing = beingManager->findBeing(msg.readInt32())))
+ {
+ dstBeing->setName(msg.readString(24));
+ }
+ break;
+ case SMSG_PLAYER_GUILD_PARTY_INFO:
+ if ((dstBeing = beingManager->findBeing(msg.readInt32())))
+ {
+ dstBeing->setPartyName(msg.readString(24));
+ dstBeing->setGuildName(msg.readString(24));
+ dstBeing->setGuildPos(msg.readString(24));
+ msg.readString(24); // Discard this
+ }
+ break;
+ case SMSG_BEING_CHANGE_DIRECTION:
+ if (!(dstBeing = beingManager->findBeing(msg.readInt32())))
+ {
+ break;
+ }
+
+ msg.readInt16(); // unused
+
+ dstBeing->setDirection(msg.readInt8());
+
+ break;
+
+ case SMSG_PLAYER_UPDATE_1:
+ case SMSG_PLAYER_UPDATE_2:
+ case SMSG_PLAYER_MOVE:
+ // 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 |= ((Uint32) msg.readInt16())
+ << 16; // status.options; Aethyra uses this as misc2
+ job = msg.readInt16();
+
+ dstBeing = beingManager->findBeing(id);
+
+ if (!dstBeing)
+ {
+ dstBeing = createBeing(id, job);
+
+ if (!dstBeing)
+ break;
+ }
+
+ if (dstBeing->getType() == Being::PLAYER)
+ player = static_cast<Player*>(dstBeing);
+
+ if (Party *party = player_node->getParty()){
+ if (party->isMember(id))
+ {
+ player->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();
+ shoes = msg.readInt16();
+ gloves = msg.readInt16();
+ msg.readInt32(); // guild
+ msg.readInt16(); // emblem
+ msg.readInt16(); // manner
+ dstBeing->setStatusEffectBlock(32, msg.readInt16()); // opt3
+ msg.readInt8(); // karma
+ player->setGender((msg.readInt8() == 0)
+ ? GENDER_FEMALE : GENDER_MALE);
+
+ // Set these after the gender, as the sprites may be gender-specific
+ player->setSprite(SPRITE_WEAPON, weapon, "", true);
+ player->setSprite(SPRITE_SHIELD, shield);
+ //player->setSprite(SPRITE_SHOE, shoes);
+ player->setSprite(SPRITE_BOTTOMCLOTHES, headBottom);
+ player->setSprite(SPRITE_TOPCLOTHES, headMid);
+ player->setSprite(SPRITE_HAT, headTop);
+ //player->setSprite(SPRITE_GLOVES, gloves);
+ //player->setSprite(SPRITE_CAPE, cape);
+ //player->setSprite(SPRITE_MISC1, misc1);
+ //player->setSprite(SPRITE_MISC2, misc2);
+ player->setSprite(SPRITE_HAIR, hairStyle * -1, ColorDB::get(hairColor));
+
+ if (msg.getId() == SMSG_PLAYER_MOVE)
+ {
+ Uint16 srcX, srcY, dstX, dstY;
+ msg.readCoordinatePair(srcX, srcY, dstX, dstY);
+ dstBeing->setTileCoords(srcX, srcY);
+ dstBeing->setDestination(dstX, dstY);
+
+ player_followed = player_node->getFollow();
+ if (!player_followed.empty())
+ {
+ if (dstBeing->getName() == player_followed)
+ {
+ player_node->setNextDest(dstX, dstY);
+ player_node->setDestination(srcX, srcY);
+ }
+ }
+ }
+ else
+ {
+ Uint8 dir;
+ Uint16 x, y;
+ msg.readCoordinates(x, y, dir);
+ dstBeing->setTileCoords(x, y);
+ dstBeing->setDirection(dir);
+ }
+
+ gmstatus = msg.readInt16();
+ if (gmstatus & 0x80)
+ player->setGM(true);
+
+ if (msg.getId() == SMSG_PLAYER_UPDATE_1)
+ {
+ switch (msg.readInt8())
+ {
+ case 1:
+ dstBeing->setAction(Being::DEAD);
+ break;
+
+ case 2:
+ dstBeing->setAction(Being::SIT);
+ break;
+ }
+ }
+ else if (msg.getId() == SMSG_PLAYER_MOVE)
+ {
+ msg.readInt8(); // unknown
+ }
+
+ msg.readInt8(); // Lv
+ msg.readInt8(); // unknown
+
+ dstBeing->setWalkTime(tick_time);
+ dstBeing->setFrame(0);
+
+ dstBeing->setStunMode(stunMode);
+ dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff);
+ dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff);
+ 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 = beingManager->findBeing(id);
+ if (dstBeing)
+ {
+ Uint16 x, y;
+ x = msg.readInt16();
+ y = msg.readInt16();
+ dstBeing->setTileCoords(x, y);
+ if (dstBeing->getCurrentAction() == Being::WALK)
+ {
+ dstBeing->setFrame(0);
+ 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...
+ */
+ break;
+
+ case SMSG_PLAYER_STATUS_CHANGE:
+ // Change in players' flags
+ id = msg.readInt32();
+ dstBeing = beingManager->findBeing(id);
+ stunMode = msg.readInt16();
+ statusEffects = msg.readInt16();
+ statusEffects |= ((Uint32) msg.readInt16()) << 16;
+ msg.readInt8();
+
+ if (dstBeing)
+ {
+ dstBeing->setStunMode(stunMode);
+ dstBeing->setStatusEffectBlock(0, (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 = beingManager->findBeing(id);
+ if (dstBeing)
+ dstBeing->setStatusEffect(status, flag);
+ break;
+ }
+}
+
+} // namespace TmwAthena