diff options
author | Bjørn Lindeijer <bjorn@lindeijer.nl> | 2009-03-22 19:45:03 +0100 |
---|---|---|
committer | Bjørn Lindeijer <bjorn@lindeijer.nl> | 2009-03-22 19:45:56 +0100 |
commit | 0c43d04b438d41c277ae80402d4b4888db1a0b64 (patch) | |
tree | 3aaeb75ecd1bcbe85decedab5f1fa426fe0411e3 /src/net/ea | |
parent | a7f5eaeb7f643658d356533a608f0f18d85b6d32 (diff) | |
parent | 401802c1d7a1b3d659bdc53a45d9a6292fc1121e (diff) | |
download | mana-0c43d04b438d41c277ae80402d4b4888db1a0b64.tar.gz mana-0c43d04b438d41c277ae80402d4b4888db1a0b64.tar.bz2 mana-0c43d04b438d41c277ae80402d4b4888db1a0b64.tar.xz mana-0c43d04b438d41c277ae80402d4b4888db1a0b64.zip |
Merged the tmwserv client with the eAthena client
This merge involved major changes on both sides, and as such took
several weeks. Lots of things are expected to be broken now, however, we
now have a single code base to improve and extend, which can be compiled
to support either eAthena or tmwserv.
In the coming months, the plan is to work towards a client that supports
both eAthena and tmwserv, without needing to be recompiled.
Conflicts:
Everywhere!
Diffstat (limited to 'src/net/ea')
30 files changed, 3997 insertions, 0 deletions
diff --git a/src/net/ea/beinghandler.cpp b/src/net/ea/beinghandler.cpp new file mode 100644 index 00000000..1edc6079 --- /dev/null +++ b/src/net/ea/beinghandler.cpp @@ -0,0 +1,542 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <iostream> +#include <SDL_types.h> + +#include "beinghandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../being.h" +#include "../../beingmanager.h" +#include "../../effectmanager.h" +#include "../../game.h" +#include "../../localplayer.h" +#include "../../log.h" +#include "../../npc.h" +#include "../../player_relations.h" + +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_BEING_ACTION, + SMSG_BEING_SELFEFFECT, + SMSG_BEING_EMOTION, + SMSG_BEING_CHANGE_LOOKS, + SMSG_BEING_CHANGE_LOOKS2, + SMSG_BEING_NAME_RESPONSE, + SMSG_PLAYER_UPDATE_1, + SMSG_PLAYER_UPDATE_2, + SMSG_PLAYER_MOVE, + SMSG_PLAYER_STOP, + SMSG_PLAYER_MOVE_TO_ATTACK, + 0x0119, + 0x0196, + 0 + }; + handledMessages = _messages; +} + +void BeingHandler::handleMessage(MessageIn &msg) +{ + Uint32 id; + Uint16 job, speed; + Uint16 headTop, headMid, headBottom; + Uint16 shoes, gloves; + Uint16 weapon, shield; + Uint16 gmstatus; + Sint16 param1; + int stunMode; + Uint32 statusEffects; + Sint8 type; + Uint16 status; + Being *srcBeing, *dstBeing; + int hairStyle, hairColor, flag; + + 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 = beingManager->createBeing(id, job); + } + else if (msg.getId() == 0x0078) + { + dstBeing->clearPath(); + dstBeing->mFrame = 0; + dstBeing->mWalkTime = tick_time; + dstBeing->setAction(Being::STAND); + } + + + // Prevent division by 0 when calculating frame + if (speed == 0) { speed = 150; } + + dstBeing->setWalkSpeed(speed); + dstBeing->mJob = job; + hairStyle = msg.readInt16(); + dstBeing->setSprite(Being::WEAPON_SPRITE, msg.readInt16()); + headBottom = msg.readInt16(); + + if (msg.getId() == SMSG_BEING_MOVE) + { + msg.readInt32(); // server tick + } + + dstBeing->setSprite(Being::SHIELD_SPRITE, 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 + msg.readInt16(); // guild + msg.readInt16(); // unknown + msg.readInt16(); // unknown + 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(Being::BOTTOMCLOTHES_SPRITE, headBottom); + dstBeing->setSprite(Being::TOPCLOTHES_SPRITE, headMid); + dstBeing->setSprite(Being::HAT_SPRITE, headTop); + dstBeing->setSprite(Being::SHOE_SPRITE, shoes); + dstBeing->setSprite(Being::GLOVES_SPRITE, gloves); + dstBeing->setHairStyle(hairStyle, hairColor); + + if (msg.getId() == SMSG_BEING_MOVE) + { + Uint16 srcX, srcY, dstX, dstY; + msg.readCoordinatePair(srcX, srcY, dstX, dstY); + dstBeing->setAction(Being::STAND); + dstBeing->mX = srcX; + dstBeing->mY = srcY; + dstBeing->setDestination(dstX, dstY); + } + else + { + Uint8 dir; + msg.readCoordinates(dstBeing->mX, dstBeing->mY, dir); + 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->mX = srcX; + dstBeing->mY = srcY; + dstBeing->setDestination(dstX, dstY); + } + + break; + + case SMSG_BEING_REMOVE: + // A being should be removed or has died + dstBeing = beingManager->findBeing(msg.readInt32()); + + if (!dstBeing) + break; + + // If this is player's current target, clear it. + if (dstBeing == player_node->getTarget()) + player_node->stopAttack(); + + if (dstBeing == current_npc) + current_npc->handleDeath(); + + if (msg.readInt8() == 1) + dstBeing->setAction(Being::DEAD); + else + beingManager->destroyBeing(dstBeing); + + 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 0x0a: // Critical Damage + if (dstBeing) + dstBeing->showCrit(); + case 0x00: // Damage + if (dstBeing) + dstBeing->takeDamage(param1); + if (srcBeing) + srcBeing->handleAttack(dstBeing, param1); + break; + + case 0x02: // Sit + if (srcBeing) + { + srcBeing->mFrame = 0; + srcBeing->setAction(Being::SIT); + } + break; + + case 0x03: // Stand up + if (srcBeing) + { + srcBeing->mFrame = 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)) + 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; + } + + 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 + dstBeing->setHairStyle(id, -1); + break; + case 2: // Weapon ID in id, Shield ID in id2 + dstBeing->setSprite(Being::WEAPON_SPRITE, id); + dstBeing->setSprite(Being::SHIELD_SPRITE, id2); + break; + case 3: // Change lower headgear for eAthena, pants for us + dstBeing->setSprite(Being::BOTTOMCLOTHES_SPRITE, id); + break; + case 4: // Change upper headgear for eAthena, hat for us + dstBeing->setSprite(Being::HAT_SPRITE, id); + break; + case 5: // Change middle headgear for eathena, armor for us + dstBeing->setSprite(Being::TOPCLOTHES_SPRITE, id); + break; + case 6: // eAthena LOOK_HAIR_COLOR + dstBeing->setHairStyle(-1, id); + break; + case 8: // eAthena LOOK_SHIELD + dstBeing->setSprite(Being::SHIELD_SPRITE, id); + break; + case 9: // eAthena LOOK_SHOES + dstBeing->setSprite(Being::SHOE_SPRITE, id); + break; + case 10: // LOOK_GLOVES + dstBeing->setSprite(Being::GLOVES_SPRITE, id); + break; + case 11: // LOOK_CAPE + dstBeing->setSprite(Being::CAPE_SPRITE, id); + break; + case 12: + dstBeing->setSprite(Being::MISC1_SPRITE, id); + break; + case 13: + dstBeing->setSprite(Being::MISC2_SPRITE, 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_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 = beingManager->createBeing(id, job); + } + + dstBeing->setWalkSpeed(speed); + dstBeing->mJob = 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(); + msg.readInt16(); // clothes color - Aethyra-"abused" as shoes, we ignore it + msg.readInt16(); // head dir - Aethyra-"abused" as gloves, we ignore it + msg.readInt32(); // 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(Being::WEAPON_SPRITE, weapon); + dstBeing->setSprite(Being::SHIELD_SPRITE, shield); + dstBeing->setSprite(Being::BOTTOMCLOTHES_SPRITE, headBottom); + dstBeing->setSprite(Being::TOPCLOTHES_SPRITE, headMid); + dstBeing->setSprite(Being::HAT_SPRITE, headTop); + //dstBeing->setSprite(Being::CAPE_SPRITE, cape); + //dstBeing->setSprite(Being::MISC1_SPRITE, misc1); + //dstBeing->setSprite(Being::MISC2_SPRITE, misc2); + dstBeing->setHairStyle(hairStyle, hairColor); + + if (msg.getId() == SMSG_PLAYER_MOVE) + { + Uint16 srcX, srcY, dstX, dstY; + msg.readCoordinatePair(srcX, srcY, dstX, dstY); + dstBeing->mX = srcX; + dstBeing->mY = srcY; + dstBeing->setDestination(dstX, dstY); + } + else + { + Uint8 dir; + msg.readCoordinates(dstBeing->mX, dstBeing->mY, dir); + dstBeing->setDirection(dir); + } + + gmstatus = msg.readInt16(); + if (gmstatus & 0x80) + dstBeing->setGM(); + + if (msg.getId() == SMSG_PLAYER_UPDATE_1) + { + switch (msg.readInt8()) + { + case 1: + if (dstBeing->getType() != Being::NPC) + 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->mWalkTime = tick_time; + dstBeing->mFrame = 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) { + dstBeing->mX = msg.readInt16(); + dstBeing->mY = msg.readInt16(); + if (dstBeing->mAction == Being::WALK) { + dstBeing->mFrame = 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 0x0119: + // 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 0x0196: + // 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; + } +} diff --git a/src/net/ea/beinghandler.h b/src/net/ea/beinghandler.h new file mode 100644 index 00000000..16a7c8d6 --- /dev/null +++ b/src/net/ea/beinghandler.h @@ -0,0 +1,39 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_BEINGHANDLER_H +#define NET_BEINGHANDLER_H + +#include "../messagehandler.h" + +class BeingHandler : public MessageHandler +{ + public: + BeingHandler(bool enableSync); + + void handleMessage(MessageIn &msg); + + private: + // Should we honor server "Stop Walking" packets + bool mSync; +}; + +#endif diff --git a/src/net/ea/buysellhandler.cpp b/src/net/ea/buysellhandler.cpp new file mode 100644 index 00000000..480c71b8 --- /dev/null +++ b/src/net/ea/buysellhandler.cpp @@ -0,0 +1,133 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <SDL_types.h> + +#include "buysellhandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../beingmanager.h" +#include "../../inventory.h" +#include "../../item.h" +#include "../../localplayer.h" +#include "../../npc.h" + +#include "../../gui/buy.h" +#include "../../gui/chat.h" +#include "../../gui/sell.h" + +#include "../../utils/gettext.h" + +extern BuyDialog *buyDialog; +extern Window *buySellDialog; +extern SellDialog *sellDialog; + +BuySellHandler::BuySellHandler() +{ + static const Uint16 _messages[] = { + SMSG_NPC_BUY_SELL_CHOICE, + SMSG_NPC_BUY, + SMSG_NPC_SELL, + SMSG_NPC_BUY_RESPONSE, + SMSG_NPC_SELL_RESPONSE, + 0 + }; + handledMessages = _messages; +} + +void BuySellHandler::handleMessage(MessageIn &msg) +{ + int n_items; + switch (msg.getId()) + { + case SMSG_NPC_BUY_SELL_CHOICE: + buyDialog->setVisible(false); + buyDialog->reset(); + sellDialog->setVisible(false); + sellDialog->reset(); + buySellDialog->setVisible(true); + current_npc = dynamic_cast<NPC*>(beingManager->findBeing(msg.readInt32())); + break; + + case SMSG_NPC_BUY: + msg.readInt16(); // length + n_items = (msg.getLength() - 4) / 11; + buyDialog->reset(); + buyDialog->setMoney(player_node->getMoney()); + buyDialog->setVisible(true); + + for (int k = 0; k < n_items; k++) + { + Sint32 value = msg.readInt32(); + msg.readInt32(); // DCvalue + msg.readInt8(); // type + Sint16 itemId = msg.readInt16(); + buyDialog->addItem(itemId, 0, value); + } + break; + + case SMSG_NPC_SELL: + msg.readInt16(); // length + n_items = (msg.getLength() - 4) / 10; + if (n_items > 0) { + sellDialog->setMoney(player_node->getMoney()); + sellDialog->reset(); + sellDialog->setVisible(true); + + for (int k = 0; k < n_items; k++) + { + Sint16 index = msg.readInt16(); + Sint32 value = msg.readInt32(); + msg.readInt32(); // OCvalue + + Item *item = player_node->getInventory()->getItem(index); + if (item && !(item->isEquipped())) { + sellDialog->addItem(item, value); + } + } + } + else { + chatWindow->chatLog(_("Nothing to sell"), BY_SERVER); + if (current_npc) current_npc->handleDeath(); + } + break; + + case SMSG_NPC_BUY_RESPONSE: + if (msg.readInt8() == 0) { + chatWindow->chatLog(_("Thanks for buying"), BY_SERVER); + } else { + // Reset player money since buy dialog already assumed purchase + // would go fine + buyDialog->setMoney(player_node->getMoney()); + chatWindow->chatLog(_("Unable to buy"), BY_SERVER); + } + break; + + case SMSG_NPC_SELL_RESPONSE: + if (msg.readInt8() == 0) { + chatWindow->chatLog(_("Thanks for selling"), BY_SERVER); + } else { + chatWindow->chatLog(_("Unable to sell"), BY_SERVER); + } + break; + } +} diff --git a/src/net/ea/buysellhandler.h b/src/net/ea/buysellhandler.h new file mode 100644 index 00000000..5bf58d8e --- /dev/null +++ b/src/net/ea/buysellhandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_BUYSELLHANDLER_H +#define NET_BUYSELLHANDLER_H + +#include "../messagehandler.h" + +class BuySellHandler : public MessageHandler +{ + public: + BuySellHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/charserverhandler.cpp b/src/net/ea/charserverhandler.cpp new file mode 100644 index 00000000..0fef3de7 --- /dev/null +++ b/src/net/ea/charserverhandler.cpp @@ -0,0 +1,235 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "charserverhandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../game.h" +#include "../../localplayer.h" +#include "../../log.h" +#include "../../logindata.h" +#include "../../main.h" + +#include "../../gui/char_select.h" +#include "../../gui/ok_dialog.h" + +#include "../../utils/gettext.h" +#include "../../utils/stringutils.h" + +CharServerHandler::CharServerHandler(): + mCharCreateDialog(0) +{ + static const Uint16 _messages[] = { + SMSG_CONNECTION_PROBLEM, + 0x006b, + 0x006c, + 0x006d, + 0x006e, + 0x006f, + 0x0070, + 0x0071, + 0 + }; + handledMessages = _messages; +} + +void CharServerHandler::handleMessage(MessageIn &msg) +{ + int slot, flags, code; + LocalPlayer *tempPlayer; + + logger->log("CharServerHandler: Packet ID: %x, Length: %d", + msg.getId(), msg.getLength()); + switch (msg.getId()) + { + case SMSG_CONNECTION_PROBLEM: + code = msg.readInt8(); + logger->log("Connection problem: %i", code); + + switch (code) { + case 0: + errorMessage = _("Authentication failed"); + break; + case 1: + errorMessage = _("Map server(s) offline"); + break; + case 2: + errorMessage = _("This account is already logged in"); + break; + case 3: + errorMessage = _("Speed hack detected"); + break; + case 8: + errorMessage = _("Duplicated login"); + break; + default: + errorMessage = _("Unknown connection error"); + break; + } + state = STATE_ERROR; + break; + + case 0x006b: + msg.skip(2); // Length word + flags = msg.readInt32(); // Aethyra extensions flags + logger->log("Server flags are: %x", flags); + msg.skip(16); // Unused + + // Derive number of characters from message length + n_character = (msg.getLength() - 24) / 106; + + for (int i = 0; i < n_character; i++) + { + tempPlayer = readPlayerData(msg, slot); + mCharInfo->select(slot); + mCharInfo->setEntry(tempPlayer); + logger->log("CharServer: Player: %s (%d)", + tempPlayer->getName().c_str(), slot); + } + + state = STATE_CHAR_SELECT; + break; + + case 0x006c: + switch (msg.readInt8()) { + case 0: + errorMessage = _("Access denied"); + break; + case 1: + errorMessage = _("Cannot use this ID"); + break; + default: + errorMessage = _("Unknown failure to select character"); + break; + } + mCharInfo->unlock(); + break; + + case 0x006d: + tempPlayer = readPlayerData(msg, slot); + mCharInfo->unlock(); + mCharInfo->select(slot); + mCharInfo->setEntry(tempPlayer); + n_character++; + + // Close the character create dialog + if (mCharCreateDialog) + { + mCharCreateDialog->scheduleDelete(); + mCharCreateDialog = 0; + } + break; + + case 0x006e: + new OkDialog(_("Error"), _("Failed to create character. Most likely" + " the name is already taken.")); + + if (mCharCreateDialog) + mCharCreateDialog->unlock(); + break; + + case 0x006f: + delete mCharInfo->getEntry(); + mCharInfo->setEntry(0); + mCharInfo->unlock(); + n_character--; + new OkDialog(_("Info"), _("Player deleted")); + break; + + case 0x0070: + mCharInfo->unlock(); + new OkDialog(_("Error"), _("Failed to delete character.")); + break; + + case 0x0071: + player_node = mCharInfo->getEntry(); + slot = mCharInfo->getPos(); + msg.skip(4); // CharID, must be the same as player_node->charID + map_path = msg.readString(16); + mLoginData->hostname = ipToString(msg.readInt32()); + mLoginData->port = msg.readInt16(); + mCharInfo->unlock(); + mCharInfo->select(0); + // Clear unselected players infos + do + { + LocalPlayer *tmp = mCharInfo->getEntry(); + if (tmp != player_node) + { + delete tmp; + mCharInfo->setEntry(0); + } + mCharInfo->next(); + } while (mCharInfo->getPos()); + + mCharInfo->select(slot); + state = STATE_CONNECTING; + break; + } +} + +LocalPlayer *CharServerHandler::readPlayerData(MessageIn &msg, int &slot) +{ + LocalPlayer *tempPlayer = new LocalPlayer(mLoginData->account_ID, 0, NULL); + tempPlayer->setGender( + (mLoginData->sex == 0) ? GENDER_FEMALE : GENDER_MALE); + + tempPlayer->mCharId = msg.readInt32(); + tempPlayer->setXp(msg.readInt32()); + tempPlayer->setMoney(msg.readInt32()); + tempPlayer->mJobXp = msg.readInt32(); + tempPlayer->mJobLevel = msg.readInt32(); + tempPlayer->setSprite(Being::SHOE_SPRITE, msg.readInt16()); + tempPlayer->setSprite(Being::GLOVES_SPRITE, msg.readInt16()); + tempPlayer->setSprite(Being::CAPE_SPRITE, msg.readInt16()); + tempPlayer->setSprite(Being::MISC1_SPRITE, msg.readInt16()); + msg.readInt32(); // option + msg.readInt32(); // karma + msg.readInt32(); // manner + msg.skip(2); // unknown + tempPlayer->setHp(msg.readInt16()); + tempPlayer->setMaxHp(msg.readInt16()); + tempPlayer->mMp = msg.readInt16(); + tempPlayer->mMaxMp = msg.readInt16(); + msg.readInt16(); // speed + msg.readInt16(); // class + int hairStyle = msg.readInt16(); + Uint16 weapon = msg.readInt16(); + tempPlayer->setSprite(Being::WEAPON_SPRITE, weapon); + tempPlayer->setLevel(msg.readInt16()); + msg.readInt16(); // skill point + tempPlayer->setSprite(Being::BOTTOMCLOTHES_SPRITE, msg.readInt16()); // head bottom + tempPlayer->setSprite(Being::SHIELD_SPRITE, msg.readInt16()); + tempPlayer->setSprite(Being::HAT_SPRITE, msg.readInt16()); // head option top + tempPlayer->setSprite(Being::TOPCLOTHES_SPRITE, msg.readInt16()); // head option mid + int hairColor = msg.readInt16(); + tempPlayer->setHairStyle(hairStyle, hairColor); + tempPlayer->setSprite(Being::MISC2_SPRITE, msg.readInt16()); + tempPlayer->setName(msg.readString(24)); + for (int i = 0; i < 6; i++) { + tempPlayer->mAttr[i] = msg.readInt8(); + } + slot = msg.readInt8(); // character slot + msg.readInt8(); // unknown + + return tempPlayer; +} diff --git a/src/net/ea/charserverhandler.h b/src/net/ea/charserverhandler.h new file mode 100644 index 00000000..237f5e49 --- /dev/null +++ b/src/net/ea/charserverhandler.h @@ -0,0 +1,65 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_CHARSERVERHANDLER_H +#define NET_CHARSERVERHANDLER_H + +#include "../messagehandler.h" + +#include "../../lockedarray.h" + +class CharCreateDialog; +class LocalPlayer; +class LoginData; + +/** + * Deals with incoming messages from the character server. + */ +class CharServerHandler : public MessageHandler +{ + public: + CharServerHandler(); + + void handleMessage(MessageIn &msg); + + void setCharInfo(LockedArray<LocalPlayer*> *charInfo) + { mCharInfo = charInfo; } + + void setLoginData(LoginData *loginData) + { mLoginData = loginData; } + + /** + * Sets the character create dialog. The handler will clean up this + * dialog when a new character is succesfully created, and will unlock + * the dialog when a new character failed to be created. + */ + void setCharCreateDialog(CharCreateDialog *window) + { mCharCreateDialog = window; } + + protected: + LoginData *mLoginData; + LockedArray<LocalPlayer*> *mCharInfo; + CharCreateDialog *mCharCreateDialog; + + LocalPlayer* readPlayerData(MessageIn &msg, int &slot); +}; + +#endif diff --git a/src/net/ea/chathandler.cpp b/src/net/ea/chathandler.cpp new file mode 100644 index 00000000..0293f987 --- /dev/null +++ b/src/net/ea/chathandler.cpp @@ -0,0 +1,175 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <SDL_types.h> +#include <string> + +#include "chathandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../being.h" +#include "../../beingmanager.h" +#include "../../game.h" +#include "../../player_relations.h" + +#include "../../gui/chat.h" + +#include "../../utils/gettext.h" +#include "../../utils/stringutils.h" + +extern Being *player_node; + +#define SERVER_NAME "Server" + +ChatHandler::ChatHandler() +{ + static const Uint16 _messages[] = { + SMSG_BEING_CHAT, + SMSG_PLAYER_CHAT, + SMSG_WHISPER, + SMSG_WHISPER_RESPONSE, + SMSG_GM_CHAT, + SMSG_WHO_ANSWER, + 0x10c, // MVP + 0 + }; + handledMessages = _messages; +} + +void ChatHandler::handleMessage(MessageIn &msg) +{ + Being *being; + std::string chatMsg; + std::string nick; + Sint16 chatMsgLength; + + switch (msg.getId()) + { + case SMSG_WHISPER_RESPONSE: + switch (msg.readInt8()) + { + case 0x00: + // comment out since we'll local echo in chat.cpp instead, then only report failures + //chatWindow->chatLog("Whisper sent", BY_SERVER); + break; + case 0x01: + chatWindow->chatLog(_("Whisper could not be sent, user is offline"), BY_SERVER); + break; + case 0x02: + chatWindow->chatLog(_("Whisper could not be sent, ignored by user"), BY_SERVER); + break; + } + break; + + // Received whisper + case SMSG_WHISPER: + chatMsgLength = msg.readInt16() - 28; + nick = msg.readString(24); + + if (chatMsgLength <= 0) + break; + + chatMsg = msg.readString(chatMsgLength); + if (nick != SERVER_NAME) + chatMsg = nick + " : " + chatMsg; + + if (nick == SERVER_NAME) + chatWindow->chatLog(chatMsg, BY_SERVER); + else { + if (player_relations.hasPermission(nick, PlayerRelation::WHISPER)) + chatWindow->chatLog(chatMsg, ACT_WHISPER); + } + + break; + + // Received speech from being + case SMSG_BEING_CHAT: { + chatMsgLength = msg.readInt16() - 8; + being = beingManager->findBeing(msg.readInt32()); + + if (!being || chatMsgLength <= 0) + { + break; + } + + chatMsg = msg.readString(chatMsgLength); + + std::string::size_type pos = chatMsg.find(" : ", 0); + std::string sender_name = ((pos == std::string::npos) + ? "" + : chatMsg.substr(0, pos)); + + // We use getIgnorePlayer instead of ignoringPlayer here because ignorePlayer' side + // effects are triggered right below for Being::IGNORE_SPEECH_FLOAT. + if (player_relations.checkPermissionSilently(sender_name, PlayerRelation::SPEECH_LOG)) + chatWindow->chatLog(chatMsg, BY_OTHER); + + chatMsg.erase(0, pos + 3); + trim(chatMsg); + + if (player_relations.hasPermission(sender_name, PlayerRelation::SPEECH_FLOAT)) + being->setSpeech(chatMsg, SPEECH_TIME); + break; + } + + case SMSG_PLAYER_CHAT: + case SMSG_GM_CHAT: { + chatMsgLength = msg.readInt16() - 4; + + if (chatMsgLength <= 0) + { + break; + } + + chatMsg = msg.readString(chatMsgLength); + std::string::size_type pos = chatMsg.find(" : ", 0); + + if (msg.getId() == SMSG_PLAYER_CHAT) + { + chatWindow->chatLog(chatMsg, BY_PLAYER); + + if (pos != std::string::npos) + chatMsg.erase(0, pos + 3); + + trim(chatMsg); + + player_node->setSpeech(chatMsg, SPEECH_TIME); + } + else + { + chatWindow->chatLog(chatMsg, BY_GM); + } + break; + } + + case SMSG_WHO_ANSWER: + chatWindow->chatLog("Online users: " + toString(msg.readInt32()), + BY_SERVER); + break; + + case 0x010c: + // Display MVP player + msg.readInt32(); // id + chatWindow->chatLog("MVP player", BY_SERVER); + break; + } +} diff --git a/src/net/ea/chathandler.h b/src/net/ea/chathandler.h new file mode 100644 index 00000000..8207b1d5 --- /dev/null +++ b/src/net/ea/chathandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_CHATHANDLER_H +#define NET_CHATHANDLER_H + +#include "../messagehandler.h" + +class ChatHandler : public MessageHandler +{ + public: + ChatHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/equipmenthandler.cpp b/src/net/ea/equipmenthandler.cpp new file mode 100644 index 00000000..19063daf --- /dev/null +++ b/src/net/ea/equipmenthandler.cpp @@ -0,0 +1,190 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "equipmenthandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../equipment.h" +#include "../../inventory.h" +#include "../../item.h" +#include "../../localplayer.h" +#include "../../log.h" + +#include "../../gui/chat.h" + +#include "../../utils/gettext.h" + +EquipmentHandler::EquipmentHandler() +{ + static const Uint16 _messages[] = { + SMSG_PLAYER_EQUIPMENT, + SMSG_PLAYER_EQUIP, + SMSG_PLAYER_UNEQUIP, + SMSG_PLAYER_ARROW_EQUIP, + SMSG_PLAYER_ATTACK_RANGE, + 0 + }; + handledMessages = _messages; +} + +void EquipmentHandler::handleMessage(MessageIn &msg) +{ + Sint32 itemCount; + Sint16 index, equipPoint, itemId; + Sint8 type; + int mask, position; + Item *item; + Inventory *inventory = player_node->getInventory(); + + switch (msg.getId()) + { + case SMSG_PLAYER_EQUIPMENT: + msg.readInt16(); // length + itemCount = (msg.getLength() - 4) / 20; + + for (int loop = 0; loop < itemCount; loop++) + { + index = msg.readInt16(); + itemId = msg.readInt16(); + msg.readInt8(); // type + msg.readInt8(); // identify flag + msg.readInt16(); // equip type + equipPoint = msg.readInt16(); + msg.readInt8(); // attribute + msg.readInt8(); // refine + msg.skip(8); // card + + inventory->setItem(index, itemId, 1, true); + + if (equipPoint) + { + mask = 1; + position = 0; + while (!(equipPoint & mask)) + { + mask <<= 1; + position++; + } + item = inventory->getItem(index); + player_node->mEquipment->setEquipment(position, index); + } + } + break; + + case SMSG_PLAYER_EQUIP: + index = msg.readInt16(); + equipPoint = msg.readInt16(); + type = msg.readInt8(); + + logger->log("Equipping: %i %i %i", index, equipPoint, type); + + if (!type) { + chatWindow->chatLog(_("Unable to equip."), BY_SERVER); + break; + } + + if (!equipPoint) { + // No point given, no point in searching + break; + } + + /* + * An item may occupy more than 1 slot. If so, it's + * only shown as equipped on the *first* slot. + */ + mask = 1; + position = 0; + while (!(equipPoint & mask)) { + mask <<= 1; + position++; + } + logger->log("Position %i", position); + + item = player_node->getInventory()->getItem(player_node->mEquipment->getEquipment(position)); + + // Unequip any existing equipped item in this position + if (item) { + item->setEquipped(false); + } + + item = inventory->getItem(index); + player_node->mEquipment->setEquipment(position, index); + break; + + case SMSG_PLAYER_UNEQUIP: + index = msg.readInt16(); + equipPoint = msg.readInt16(); + type = msg.readInt8(); + + if (!type) { + chatWindow->chatLog(_("Unable to unequip."), BY_SERVER); + break; + } + + if (!equipPoint) { + // No point given, no point in searching + break; + } + + mask = 1; + position = 0; + while (!(equipPoint & mask)) { + mask <<= 1; + position++; + } + + item = inventory->getItem(index); + if (!item) + break; + + item->setEquipped(false); + + if (equipPoint & 0x8000) { // Arrows + player_node->mEquipment->setArrows(0); + } + else { + player_node->mEquipment->removeEquipment(position); + } + logger->log("Unequipping: %i %i(%i) %i", + index, equipPoint, type, position); + break; + + case SMSG_PLAYER_ATTACK_RANGE: + player_node->setAttackRange(msg.readInt16()); + break; + + case SMSG_PLAYER_ARROW_EQUIP: + index = msg.readInt16(); + + if (index <= 1) + break; + + item = inventory->getItem(index); + + if (item) { + item->setEquipped(true); + player_node->mEquipment->setArrows(index); + logger->log("Arrows equipped: %i", index); + } + break; + } +} diff --git a/src/net/ea/equipmenthandler.h b/src/net/ea/equipmenthandler.h new file mode 100644 index 00000000..fe4a7ecc --- /dev/null +++ b/src/net/ea/equipmenthandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_EQUIPMENTHANDLER_H +#define NET_EQUIPMENTHANDLER_H + +#include "../messagehandler.h" + +class EquipmentHandler : public MessageHandler +{ + public: + EquipmentHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/inventoryhandler.cpp b/src/net/ea/inventoryhandler.cpp new file mode 100644 index 00000000..71eee291 --- /dev/null +++ b/src/net/ea/inventoryhandler.cpp @@ -0,0 +1,227 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <SDL_types.h> + +#include "inventoryhandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../inventory.h" +#include "../../item.h" +#include "../../itemshortcut.h" +#include "../../localplayer.h" +#include "../../log.h" + +#include "../../gui/chat.h" + +#include "../../resources/iteminfo.h" + +#include "../../utils/gettext.h" +#include "../../utils/strprintf.h" +#include "../../utils/stringutils.h" + +InventoryHandler::InventoryHandler() +{ + static const Uint16 _messages[] = { + SMSG_PLAYER_INVENTORY, + SMSG_PLAYER_INVENTORY_ADD, + SMSG_PLAYER_INVENTORY_REMOVE, + SMSG_PLAYER_INVENTORY_USE, + SMSG_ITEM_USE_RESPONSE, + SMSG_PLAYER_STORAGE_ITEMS, + SMSG_PLAYER_STORAGE_EQUIP, + SMSG_PLAYER_STORAGE_STATUS, + SMSG_PLAYER_STORAGE_ADD, + SMSG_PLAYER_STORAGE_REMOVE, + SMSG_PLAYER_STORAGE_CLOSE, + 0 + }; + handledMessages = _messages; +} + +void InventoryHandler::handleMessage(MessageIn &msg) +{ + Sint32 number; + Sint16 index, amount, itemId, equipType, arrow; + Sint16 identified, cards[4], itemType; + Inventory *inventory = player_node->getInventory(); + Inventory *storage = player_node->getStorage(); + + switch (msg.getId()) + { + case SMSG_PLAYER_INVENTORY: + case SMSG_PLAYER_STORAGE_ITEMS: + case SMSG_PLAYER_STORAGE_EQUIP: + switch (msg.getId()) { + case SMSG_PLAYER_INVENTORY: + // Clear inventory - this will be a complete refresh + inventory->clear(); + break; + case SMSG_PLAYER_STORAGE_ITEMS: + /* + * This packet will always be followed by a + * SMSG_PLAYER_STORAGE_EQUIP packet. The two packets + * together comprise a complete refresh of storage, so + * clear storage here + */ + storage->clear(); + logger->log("Received SMSG_PLAYER_STORAGE_ITEMS"); + break; + default: + logger->log("Received SMSG_PLAYER_STORAGE_EQUIP"); + break; + } + msg.readInt16(); // length + number = (msg.getLength() - 4) / 18; + + for (int loop = 0; loop < number; loop++) { + index = msg.readInt16(); + itemId = msg.readInt16(); + itemType = msg.readInt8(); + identified = msg.readInt8(); + if (msg.getId() == SMSG_PLAYER_STORAGE_EQUIP) { + amount = 1; + msg.readInt16(); // Equip Point? + } else { + amount = msg.readInt16(); + } + arrow = msg.readInt16(); + if (msg.getId() == SMSG_PLAYER_STORAGE_EQUIP) { + msg.readInt8(); // Attribute (broken) + msg.readInt8(); // Refine level + } + for (int i = 0; i < 4; i++) + cards[i] = msg.readInt16(); + + if (msg.getId() == SMSG_PLAYER_INVENTORY) { + inventory->setItem(index, itemId, amount, false); + + // Trick because arrows are not considered equipment + if (arrow & 0x8000) { + if (Item *item = inventory->getItem(index)) + item->setEquipment(true); + } + } else { + logger->log("Index:%d, ID:%d, Type:%d, Identified:%d, Qty:%d, Cards:%d, %d, %d, %d", + index, itemId, itemType, identified, amount, cards[0], cards[1], cards[2], cards[3]); + storage->setItem(index, itemId, amount, false); + } + } + break; + + case SMSG_PLAYER_INVENTORY_ADD: + index = msg.readInt16(); + amount = msg.readInt16(); + itemId = msg.readInt16(); + identified = msg.readInt8(); + msg.readInt8(); // attribute + msg.readInt8(); // refine + for (int i = 0; i < 4; i++) + cards[i] = msg.readInt16(); + equipType = msg.readInt16(); + itemType = msg.readInt8(); + + if (msg.readInt8() > 0) { + chatWindow->chatLog(_("Unable to pick up item"), BY_SERVER); + } else { + const ItemInfo &itemInfo = ItemDB::get(itemId); + const std::string amountStr = + (amount > 1) ? toString(amount) : "a"; + chatWindow->chatLog(strprintf(_("You picked up %s %s"), + amountStr.c_str(), itemInfo.getName().c_str()), BY_SERVER); + + if (Item *item = inventory->getItem(index)) { + item->setId(itemId); + item->increaseQuantity(amount); + } else { + inventory->setItem(index, itemId, amount, equipType != 0); + } + } + break; + + case SMSG_PLAYER_INVENTORY_REMOVE: + index = msg.readInt16(); + amount = msg.readInt16(); + if (Item *item = inventory->getItem(index)) { + item->increaseQuantity(-amount); + if (item->getQuantity() == 0) + inventory->removeItemAt(index); + } + break; + + case SMSG_PLAYER_INVENTORY_USE: + index = msg.readInt16(); + msg.readInt16(); // item id + msg.readInt32(); // id + amount = msg.readInt16(); + msg.readInt8(); // type + + if (Item *item = inventory->getItem(index)) + item->setQuantity(amount); + break; + + case SMSG_ITEM_USE_RESPONSE: + index = msg.readInt16(); + amount = msg.readInt16(); + + if (msg.readInt8() == 0) { + chatWindow->chatLog(_("Failed to use item"), BY_SERVER); + } else { + if (Item *item = inventory->getItem(index)) + item->setQuantity(amount); + } + break; + + case SMSG_PLAYER_STORAGE_STATUS: + /* + * Basic slots used vs total slots info + * We don't really need this information, but this is + * the closest we get to an "Open Storage" packet + * from the server. It always comes after the two + * SMSG_PLAYER_STORAGE_... packets that update + * storage contents. + */ + logger->log("Received SMSG_PLAYER_STORAGE_STATUS"); + player_node->setInStorage(true); + break; + + case SMSG_PLAYER_STORAGE_ADD: + /* + * Move an item into storage + */ + break; + + case SMSG_PLAYER_STORAGE_REMOVE: + /* + * Move an item out of storage + */ + break; + + case SMSG_PLAYER_STORAGE_CLOSE: + /* + * Storage access has been closed + */ + player_node->setInStorage(false); + logger->log("Received SMSG_PLAYER_STORAGE_CLOSE"); + break; + } +} diff --git a/src/net/ea/inventoryhandler.h b/src/net/ea/inventoryhandler.h new file mode 100644 index 00000000..b2e469fa --- /dev/null +++ b/src/net/ea/inventoryhandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_INVENTORYHANDLER_H +#define NET_INVENTORYHANDLER_H + +#include "../messagehandler.h" + +class InventoryHandler : public MessageHandler +{ + public: + InventoryHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/loginhandler.cpp b/src/net/ea/loginhandler.cpp new file mode 100644 index 00000000..3f58f2c0 --- /dev/null +++ b/src/net/ea/loginhandler.cpp @@ -0,0 +1,158 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "loginhandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../log.h" +#include "../../logindata.h" +#include "../../main.h" +#include "../../serverinfo.h" + +#include "../../utils/gettext.h" +#include "../../utils/strprintf.h" +#include "../../utils/stringutils.h" + +extern SERVER_INFO **server_info; + +LoginHandler::LoginHandler() +{ + static const Uint16 _messages[] = { + SMSG_CONNECTION_PROBLEM, + SMSG_UPDATE_HOST, + 0x0069, + 0x006a, + 0 + }; + handledMessages = _messages; +} + +void LoginHandler::handleMessage(MessageIn &msg) +{ + int code; + + switch (msg.getId()) + { + case SMSG_CONNECTION_PROBLEM: + code = msg.readInt8(); + logger->log("Connection problem: %i", code); + + switch (code) { + case 0: + errorMessage = _("Authentication failed"); + break; + case 1: + errorMessage = _("No servers available"); + break; + case 2: + errorMessage = _("This account is already logged in"); + break; + default: + errorMessage = _("Unknown connection error"); + break; + } + state = STATE_ERROR; + break; + + case SMSG_UPDATE_HOST: + int len; + + len = msg.readInt16() - 4; + mUpdateHost = msg.readString(len); + + logger->log("Received update host \"%s\" from login server", + mUpdateHost.c_str()); + break; + + case 0x0069: + // Skip the length word + msg.skip(2); + + n_server = (msg.getLength() - 47) / 32; + server_info = + (SERVER_INFO**) malloc(sizeof(SERVER_INFO*) * n_server); + + mLoginData->session_ID1 = msg.readInt32(); + mLoginData->account_ID = msg.readInt32(); + mLoginData->session_ID2 = msg.readInt32(); + msg.skip(30); // unknown + mLoginData->sex = msg.readInt8(); + + for (int i = 0; i < n_server; i++) + { + server_info[i] = new SERVER_INFO; + + server_info[i]->address = msg.readInt32(); + server_info[i]->port = msg.readInt16(); + server_info[i]->name = msg.readString(20); + server_info[i]->online_users = msg.readInt32(); + server_info[i]->updateHost = mUpdateHost; + msg.skip(2); // unknown + + logger->log("Network: Server: %s (%s:%d)", + server_info[i]->name.c_str(), + ipToString(server_info[i]->address), + server_info[i]->port); + } + state = STATE_CHAR_SERVER; + break; + + case 0x006a: + code = msg.readInt8(); + logger->log("Login::error code: %i", code); + + switch (code) { + case 0: + errorMessage = _("Unregistered ID"); + break; + case 1: + errorMessage = _("Wrong password"); + break; + case 2: + errorMessage = _("Account expired"); + break; + case 3: + errorMessage = _("Rejected from server"); + break; + case 4: + + errorMessage = _("You have been permanently banned from " + "the game. Please contact the GM Team."); + break; + case 6: + errorMessage = strprintf(_("You have been temporarily " + "banned from the game until " + "%s.\n Please contact the GM " + "team via the forums."), + msg.readString(20).c_str()); + break; + case 9: + errorMessage = _("This user name is already taken"); + break; + default: + errorMessage = _("Unknown error"); + break; + } + state = STATE_ERROR; + break; + } +} diff --git a/src/net/ea/loginhandler.h b/src/net/ea/loginhandler.h new file mode 100644 index 00000000..c2ba5083 --- /dev/null +++ b/src/net/ea/loginhandler.h @@ -0,0 +1,45 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_LOGINHANDLER_H +#define NET_LOGINHANDLER_H + +#include <string> + +#include "../messagehandler.h" + +struct LoginData; + +class LoginHandler : public MessageHandler +{ + public: + LoginHandler(); + + void handleMessage(MessageIn &msg); + + void setLoginData(LoginData *loginData) { mLoginData = loginData; }; + + private: + LoginData *mLoginData; + std::string mUpdateHost; +}; + +#endif diff --git a/src/net/ea/maploginhandler.cpp b/src/net/ea/maploginhandler.cpp new file mode 100644 index 00000000..6931024e --- /dev/null +++ b/src/net/ea/maploginhandler.cpp @@ -0,0 +1,76 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "maploginhandler.h" +#include "../messagein.h" +#include "protocol.h" + +#include "../../localplayer.h" +#include "../../log.h" +#include "../../main.h" + +#include "../../utils/gettext.h" + +MapLoginHandler::MapLoginHandler() +{ + static const Uint16 _messages[] = { + SMSG_CONNECTION_PROBLEM, + SMSG_LOGIN_SUCCESS, + 0 + }; + handledMessages = _messages; +} + +void MapLoginHandler::handleMessage(MessageIn &msg) +{ + int code; + unsigned char direction; + + switch (msg.getId()) + { + case SMSG_CONNECTION_PROBLEM: + code = msg.readInt8(); + logger->log("Connection problem: %i", code); + + switch (code) { + case 0: + errorMessage = _("Authentication failed"); + break; + case 2: + errorMessage = _("This account is already logged in"); + break; + default: + errorMessage = _("Unknown connection error"); + break; + } + state = STATE_ERROR; + break; + + case SMSG_LOGIN_SUCCESS: + msg.readInt32(); // server tick + msg.readCoordinates(player_node->mX, player_node->mY, direction); + msg.skip(2); // unknown + logger->log("Protocol: Player start position: (%d, %d), Direction: %d", + player_node->mX, player_node->mY, direction); + state = STATE_GAME; + break; + } +} diff --git a/src/net/ea/maploginhandler.h b/src/net/ea/maploginhandler.h new file mode 100644 index 00000000..1ce5ee79 --- /dev/null +++ b/src/net/ea/maploginhandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_MAPLOGINHANDLER_H +#define NET_MAPLOGINHANDLER_H + +#include "../messagehandler.h" + +class MapLoginHandler : public MessageHandler +{ + public: + MapLoginHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/network.cpp b/src/net/ea/network.cpp new file mode 100644 index 00000000..199e94da --- /dev/null +++ b/src/net/ea/network.cpp @@ -0,0 +1,436 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sstream> + +#include "../messagehandler.h" +#include "../messagein.h" +#include "network.h" + +#include "../../log.h" +#include "../../utils/stringutils.h" + +/** Warning: buffers and other variables are shared, + so there can be only one connection active at a time */ + +short packet_lengths[] = { + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// #0x0040 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -1, 55, 17, 3, 37, 46, -1, 23, -1, 3,108, 3, 2, + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +// #0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 23, -1, -1, -1, 0, + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, +// #0x00C0 + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 6, 2, 27, + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, +// #0x0100 + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, +// #0x0140 + 22, 14, 6, 10, 23, 19, 6, 39, 8, 9, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14,186,182, + 14, 30, 10, 3, -1, 6,106, -1, 4, 5, 4, -1, 6, 7, -1, -1, +// #0x0180 + 6, 3,106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, + 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, 4, 6, 2, 6, + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, + 11, 7, -1, 67, 12, 18,114, 6, 3, 6, 26, 26, 26, 26, 2, 3, +// #0x01C0 + 2, 14, 10, -1, 22, 22, 4, 2, 13, 97, 0, 9, 9, 29, 6, 28, + 8, 14, 10, 35, 6, 8, 4, 11, 54, 53, 60, 2, -1, 47, 33, 6, + 30, 8, 34, 14, 2, 6, 26, 2, 28, 81, 6, 10, 26, 2, -1, -1, + -1, -1, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56, -1, 4, 5, 10, +// #0x200 + 26, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const unsigned int BUFFER_SIZE = 65536; + +int networkThread(void *data) +{ + Network *network = static_cast<Network*>(data); + + if (!network->realConnect()) + return -1; + + network->receive(); + + return 0; +} + +Network::Network(): + mSocket(0), + mAddress(), mPort(0), + mInBuffer(new char[BUFFER_SIZE]), + mOutBuffer(new char[BUFFER_SIZE]), + mInSize(0), mOutSize(0), + mToSkip(0), + mState(IDLE), + mWorkerThread(0) +{ + mMutex = SDL_CreateMutex(); +} + +Network::~Network() +{ + clearHandlers(); + + if (mState != IDLE && mState != NET_ERROR) + disconnect(); + + SDL_DestroyMutex(mMutex); + + delete[] mInBuffer; + delete[] mOutBuffer; +} + +bool Network::connect(const std::string &address, short port) +{ + if (mState != IDLE && mState != NET_ERROR) + { + logger->log("Tried to connect an already connected socket!"); + return false; + } + + if (address.empty()) + { + setError("Empty address given to Network::connect()!"); + return false; + } + + logger->log("Network::Connecting to %s:%i", address.c_str(), port); + + mAddress = address; + mPort = port; + + // Reset to sane values + mOutSize = 0; + mInSize = 0; + mToSkip = 0; + + mState = CONNECTING; + mWorkerThread = SDL_CreateThread(networkThread, this); + if (!mWorkerThread) + { + setError("Unable to create network worker thread"); + return false; + } + + return true; +} + +void Network::disconnect() +{ + mState = IDLE; + + if (mWorkerThread) + { + SDL_WaitThread(mWorkerThread, NULL); + mWorkerThread = NULL; + } + + if (mSocket) + { + SDLNet_TCP_Close(mSocket); + mSocket = 0; + } +} + +void Network::registerHandler(MessageHandler *handler) +{ + for (const Uint16 *i = handler->handledMessages; *i; i++) + { + mMessageHandlers[*i] = handler; + } + + handler->setNetwork(this); +} + +void Network::unregisterHandler(MessageHandler *handler) +{ + for (const Uint16 *i = handler->handledMessages; *i; i++) + { + mMessageHandlers.erase(*i); + } + + handler->setNetwork(0); +} + +void Network::clearHandlers() +{ + MessageHandlerIterator i; + for (i = mMessageHandlers.begin(); i != mMessageHandlers.end(); i++) + { + i->second->setNetwork(0); + } + mMessageHandlers.clear(); +} + +void Network::dispatchMessages() +{ + while (messageReady()) + { + MessageIn msg = getNextMessage(); + + MessageHandlerIterator iter = mMessageHandlers.find(msg.getId()); + + if (iter != mMessageHandlers.end()) + iter->second->handleMessage(msg); + else + logger->log("Unhandled packet: %x", msg.getId()); + + skip(msg.getLength()); + } +} + +void Network::flush() +{ + if (!mOutSize || mState != CONNECTED) + return; + + int ret; + + + SDL_mutexP(mMutex); + ret = SDLNet_TCP_Send(mSocket, mOutBuffer, mOutSize); + if (ret < (int)mOutSize) + { + setError("Error in SDLNet_TCP_Send(): " + + std::string(SDLNet_GetError())); + } + mOutSize = 0; + SDL_mutexV(mMutex); +} + +void Network::skip(int len) +{ + SDL_mutexP(mMutex); + mToSkip += len; + if (!mInSize) + { + SDL_mutexV(mMutex); + return; + } + + if (mInSize >= mToSkip) + { + mInSize -= mToSkip; + memmove(mInBuffer, mInBuffer + mToSkip, mInSize); + mToSkip = 0; + } + else + { + mToSkip -= mInSize; + mInSize = 0; + } + SDL_mutexV(mMutex); +} + +bool Network::messageReady() +{ + int len = -1; + + SDL_mutexP(mMutex); + if (mInSize >= 2) + { + len = packet_lengths[readWord(0)]; + + if (len == -1 && mInSize > 4) + len = readWord(2); + + } + + bool ret = (mInSize >= static_cast<unsigned int>(len)); + SDL_mutexV(mMutex); + + return ret; +} + +MessageIn Network::getNextMessage() +{ + while (!messageReady()) + { + if (mState == NET_ERROR) + break; + } + + SDL_mutexP(mMutex); + int msgId = readWord(0); + int len = packet_lengths[msgId]; + + if (len == -1) + len = readWord(2); + +#ifdef DEBUG + logger->log("Received packet 0x%x of length %d", msgId, len); +#endif + + MessageIn msg(mInBuffer, len); + SDL_mutexV(mMutex); + + return msg; +} + +bool Network::realConnect() +{ + IPaddress ipAddress; + + if (SDLNet_ResolveHost(&ipAddress, mAddress.c_str(), mPort) == -1) + { + std::string error = "Unable to resolve host \"" + mAddress + "\""; + setError(error); + logger->log("SDLNet_ResolveHost: %s", error.c_str()); + return false; + } + + mState = CONNECTING; + + mSocket = SDLNet_TCP_Open(&ipAddress); + if (!mSocket) + { + logger->log("Error in SDLNet_TCP_Open(): %s", SDLNet_GetError()); + setError(SDLNet_GetError()); + return false; + } + + logger->log("Network::Started session with %s:%i", + ipToString(ipAddress.host), ipAddress.port); + + mState = CONNECTED; + + return true; +} + +void Network::receive() +{ + SDLNet_SocketSet set; + + if (!(set = SDLNet_AllocSocketSet(1))) + { + setError("Error in SDLNet_AllocSocketSet(): " + + std::string(SDLNet_GetError())); + return; + } + + if (SDLNet_TCP_AddSocket(set, mSocket) == -1) + { + setError("Error in SDLNet_AddSocket(): " + + std::string(SDLNet_GetError())); + } + + while (mState == CONNECTED) + { + // TODO Try to get this to block all the time while still being able + // to escape the loop + int numReady = SDLNet_CheckSockets(set, ((Uint32)500)); + int ret; + switch (numReady) + { + case -1: + logger->log("Error: SDLNet_CheckSockets"); + // FALLTHROUGH + case 0: + break; + + case 1: + // Receive data from the socket + SDL_mutexP(mMutex); + ret = SDLNet_TCP_Recv(mSocket, mInBuffer + mInSize, BUFFER_SIZE - mInSize); + + if (!ret) + { + // We got disconnected + mState = IDLE; + logger->log("Disconnected."); + } + else if (ret < 0) + { + setError("Error in SDLNet_TCP_Recv(): " + + std::string(SDLNet_GetError())); + } + else { + mInSize += ret; + if (mToSkip) + { + if (mInSize >= mToSkip) + { + mInSize -= mToSkip; + memmove(mInBuffer, mInBuffer + mToSkip, mInSize); + mToSkip = 0; + } + else + { + mToSkip -= mInSize; + mInSize = 0; + } + } + } + SDL_mutexV(mMutex); + break; + + default: + // more than one socket is ready.. + // this should not happen since we only listen once socket. + std::stringstream errorStream; + errorStream << "Error in SDLNet_TCP_Recv(), " << numReady + << " sockets are ready: " << SDLNet_GetError(); + setError(errorStream.str()); + break; + } + } + + if (SDLNet_TCP_DelSocket(set, mSocket) == -1) + { + logger->log("Error in SDLNet_DelSocket(): %s", SDLNet_GetError()); + } + + SDLNet_FreeSocketSet(set); +} + +void Network::setError(const std::string& error) +{ + logger->log("Network error: %s", error.c_str()); + mError = error; + mState = NET_ERROR; +} + +Uint16 Network::readWord(int pos) +{ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + return SDL_Swap16((*(Uint16*)(mInBuffer+(pos)))); +#else + return (*(Uint16*)(mInBuffer+(pos))); +#endif +} diff --git a/src/net/ea/network.h b/src/net/ea/network.h new file mode 100644 index 00000000..02fe7538 --- /dev/null +++ b/src/net/ea/network.h @@ -0,0 +1,118 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NETWORK_ +#define NETWORK_ + +#include <map> +#include <SDL_net.h> +#include <SDL_thread.h> +#include <string> + +/** + * Protocol version, reported to the eAthena char and mapserver who can adjust + * the protocol accordingly. + */ +#define CLIENT_PROTOCOL_VERSION 1 + +class MessageHandler; +class MessageIn; + +class Network; + +class Network +{ + public: + friend int networkThread(void *data); + friend class MessageOut; + + Network(); + + ~Network(); + + bool connect(const std::string &address, short port); + + void disconnect(); + + void registerHandler(MessageHandler *handler); + + void unregisterHandler(MessageHandler *handler); + + void clearHandlers(); + + int getState() const { return mState; } + + const std::string& getError() const { return mError; } + + bool isConnected() const { return mState == CONNECTED; } + + int getInSize() const { return mInSize; } + + void skip(int len); + + bool messageReady(); + + MessageIn getNextMessage(); + + void dispatchMessages(); + + void flush(); + + // ERROR replaced by NET_ERROR because already defined in Windows + enum { + IDLE, + CONNECTED, + CONNECTING, + DATA, + NET_ERROR + }; + + protected: + void setError(const std::string& error); + + Uint16 readWord(int pos); + + bool realConnect(); + + void receive(); + + TCPsocket mSocket; + + std::string mAddress; + short mPort; + + char *mInBuffer, *mOutBuffer; + unsigned int mInSize, mOutSize; + + unsigned int mToSkip; + + int mState; + std::string mError; + + SDL_Thread *mWorkerThread; + SDL_mutex *mMutex; + + typedef std::map<Uint16, MessageHandler*> MessageHandlers; + typedef MessageHandlers::iterator MessageHandlerIterator; + MessageHandlers mMessageHandlers; +}; + +#endif diff --git a/src/net/ea/npchandler.cpp b/src/net/ea/npchandler.cpp new file mode 100644 index 00000000..068a3be6 --- /dev/null +++ b/src/net/ea/npchandler.cpp @@ -0,0 +1,111 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "../messagein.h" +#include "npchandler.h" +#include "protocol.h" + +#include "../../beingmanager.h" +#include "../../localplayer.h" +#include "../../npc.h" + +#include "../../gui/npc_text.h" +#include "../../gui/npcintegerdialog.h" +#include "../../gui/npclistdialog.h" +#include "../../gui/npcstringdialog.h" + +extern NpcIntegerDialog *npcIntegerDialog; +extern NpcListDialog *npcListDialog; +extern NpcTextDialog *npcTextDialog; +extern NpcStringDialog *npcStringDialog; + +NPCHandler::NPCHandler() +{ + static const Uint16 _messages[] = { + SMSG_NPC_CHOICE, + SMSG_NPC_MESSAGE, + SMSG_NPC_NEXT, + SMSG_NPC_CLOSE, + SMSG_NPC_INT_INPUT, + SMSG_NPC_STR_INPUT, + 0 + }; + handledMessages = _messages; +} + +void NPCHandler::handleMessage(MessageIn &msg) +{ + int id; + + switch (msg.getId()) + { + case SMSG_NPC_CHOICE: + msg.readInt16(); // length + id = msg.readInt32(); + player_node->setAction(LocalPlayer::STAND); + current_npc = dynamic_cast<NPC*>(beingManager->findBeing(id)); + npcListDialog->parseItems(msg.readString(msg.getLength() - 8)); + npcListDialog->setVisible(true); + break; + + case SMSG_NPC_MESSAGE: + msg.readInt16(); // length + id = msg.readInt32(); + player_node->setAction(LocalPlayer::STAND); + current_npc = dynamic_cast<NPC*>(beingManager->findBeing(id)); + npcTextDialog->addText(msg.readString(msg.getLength() - 8)); + npcListDialog->setVisible(false); + npcTextDialog->setVisible(true); + break; + + case SMSG_NPC_CLOSE: + id = msg.readInt32(); + current_npc = dynamic_cast<NPC*>(beingManager->findBeing(id)); + npcTextDialog->showCloseButton(); + break; + + case SMSG_NPC_NEXT: + // Next button in NPC dialog, currently unused + id = msg.readInt32(); + current_npc = dynamic_cast<NPC*>(beingManager->findBeing(id)); + npcTextDialog->showNextButton(); + break; + + case SMSG_NPC_INT_INPUT: + // Request for an integer + id = msg.readInt32(); + current_npc = dynamic_cast<NPC*>(beingManager->findBeing(id)); + npcIntegerDialog->setRange(0, 2147483647); + npcIntegerDialog->setDefaultValue(0); + npcIntegerDialog->setVisible(true); + npcIntegerDialog->requestFocus(); + break; + + case SMSG_NPC_STR_INPUT: + // Request for a string + id = msg.readInt32(); + current_npc = dynamic_cast<NPC*>(beingManager->findBeing(id)); + npcStringDialog->setValue(""); + npcStringDialog->setVisible(true); + npcStringDialog->requestFocus(); + break; + } +} diff --git a/src/net/ea/npchandler.h b/src/net/ea/npchandler.h new file mode 100644 index 00000000..49df20c3 --- /dev/null +++ b/src/net/ea/npchandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_NPCHANDLER_H +#define NET_NPCHANDLER_H + +#include "../messagehandler.h" + +class NPCHandler : public MessageHandler +{ + public: + NPCHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/partyhandler.cpp b/src/net/ea/partyhandler.cpp new file mode 100644 index 00000000..d1d3b55e --- /dev/null +++ b/src/net/ea/partyhandler.cpp @@ -0,0 +1,122 @@ +/* + * The Mana World + * Copyright (C) 2008 Lloyd Bryant <lloyd_bryant@netzero.net> + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <guichan/actionlistener.hpp> + +#include "partyhandler.h" +#include "protocol.h" +#include "../messagein.h" + +#include "../../gui/chat.h" +#include "../../gui/confirm_dialog.h" + +#include "../../beingmanager.h" +#include "../../party.h" + +PartyHandler::PartyHandler(Party *party) : mParty(party) +{ + static const Uint16 _messages[] = { + SMSG_PARTY_CREATE, + SMSG_PARTY_INFO, + SMSG_PARTY_INVITE, + SMSG_PARTY_INVITED, + SMSG_PARTY_SETTINGS, + SMSG_PARTY_MEMBER_INFO, + SMSG_PARTY_LEAVE, + SMSG_PARTY_UPDATE_HP, + SMSG_PARTY_UPDATE_COORDS, + SMSG_PARTY_MESSAGE, + 0 + }; + handledMessages = _messages; +} + +void PartyHandler::handleMessage(MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_PARTY_CREATE: + mParty->createResponse(msg.readInt8()); + break; + case SMSG_PARTY_INFO: + break; + case SMSG_PARTY_INVITE: + { + std::string nick = msg.readString(24); + int status = msg.readInt8(); + mParty->inviteResponse(nick, status); + break; + } + case SMSG_PARTY_INVITED: + { + int id = msg.readInt32(); + Being *being = beingManager->findBeing(id); + if (!being) + { + break; + } + std::string nick; + int gender = 0; + std::string partyName = ""; + if (being->getType() != Being::PLAYER) + { + nick = ""; + } + else + { + nick = being->getName(); + gender = being->getGender(); + partyName = msg.readString(24); + } + mParty->invitedAsk(nick, gender, partyName); + break; + } + case SMSG_PARTY_SETTINGS: + break; + case SMSG_PARTY_MEMBER_INFO: + break; + case SMSG_PARTY_LEAVE: + { + /*int id = */msg.readInt32(); + std::string nick = msg.readString(24); + /*int fail = */msg.readInt8(); + mParty->leftResponse(nick); + break; + } + case SMSG_PARTY_UPDATE_HP: + break; + case SMSG_PARTY_UPDATE_COORDS: + break; + case SMSG_PARTY_MESSAGE: + { // new block to enable local variables + int msgLength = msg.readInt16() - 8; + if (msgLength <= 0) + { + return; + } + int id = msg.readInt32(); + Being *being = beingManager->findBeing(id); + std::string chatMsg = msg.readString(msgLength); + mParty->receiveChat(being, chatMsg); + } + break; + } +} diff --git a/src/net/ea/partyhandler.h b/src/net/ea/partyhandler.h new file mode 100644 index 00000000..5c10eb21 --- /dev/null +++ b/src/net/ea/partyhandler.h @@ -0,0 +1,39 @@ +/* + * The Mana World + * Copyright (C) 2008 Lloyd Bryant <lloyd_bryant@netzero.net> + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PARTYHANDLER_H +#define PARTYHANDLER_H + +#include "../messagehandler.h" + +class Party; + +class PartyHandler : public MessageHandler +{ + public: + PartyHandler(Party *party); + + void handleMessage(MessageIn &msg); + private: + Party *mParty; +}; + +#endif diff --git a/src/net/ea/playerhandler.cpp b/src/net/ea/playerhandler.cpp new file mode 100644 index 00000000..9f0acbb3 --- /dev/null +++ b/src/net/ea/playerhandler.cpp @@ -0,0 +1,417 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "../messagein.h" +#include "playerhandler.h" +#include "protocol.h" + +#include "../../engine.h" +#include "../../localplayer.h" +#include "../../log.h" +#include "../../npc.h" +#include "../../units.h" + +#include "../../gui/buy.h" +#include "../../gui/chat.h" +#include "../../gui/gui.h" +#include "../../gui/npclistdialog.h" +#include "../../gui/npc_text.h" +#include "../../gui/ok_dialog.h" +#include "../../gui/sell.h" +#include "../../gui/skill.h" +#include "../../gui/viewport.h" + +#include "../../utils/stringutils.h" +#include "../../utils/gettext.h" + +// TODO Move somewhere else +OkDialog *weightNotice = NULL; +OkDialog *deathNotice = NULL; + +extern NpcListDialog *npcListDialog; +extern NpcTextDialog *npcTextDialog; +extern BuyDialog *buyDialog; +extern SellDialog *sellDialog; +extern Window *buySellDialog; + +// Max. distance we are willing to scroll after a teleport; +// everything beyond will reset the port hard. +static const int MAP_TELEPORT_SCROLL_DISTANCE = 8; + +/** + * Listener used for handling the overweigth message. + */ +// TODO Move somewhere else +namespace { + struct WeightListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event) + { + weightNotice = NULL; + } + } weightListener; +} + +/** + * Listener used for handling death message. + */ +// TODO Move somewhere else +namespace { + struct DeathListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event) + { + player_node->revive(); + deathNotice = NULL; + npcListDialog->setVisible(false); + npcTextDialog->setVisible(false); + buyDialog->setVisible(false); + sellDialog->setVisible(false); + buySellDialog->setVisible(false); + if (current_npc) current_npc->handleDeath(); + } + } deathListener; +} + +PlayerHandler::PlayerHandler() +{ + static const Uint16 _messages[] = { + SMSG_WALK_RESPONSE, + SMSG_PLAYER_WARP, + SMSG_PLAYER_STAT_UPDATE_1, + SMSG_PLAYER_STAT_UPDATE_2, + SMSG_PLAYER_STAT_UPDATE_3, + SMSG_PLAYER_STAT_UPDATE_4, + SMSG_PLAYER_STAT_UPDATE_5, + SMSG_PLAYER_STAT_UPDATE_6, + SMSG_PLAYER_ARROW_MESSAGE, + 0 + }; + handledMessages = _messages; +} + +void PlayerHandler::handleMessage(MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_WALK_RESPONSE: + /* + * This client assumes that all walk messages succeed, + * and that the server will send a correction notice + * otherwise. + */ + break; + + case SMSG_PLAYER_WARP: + { + std::string mapPath = msg.readString(16); + bool nearby; + Uint16 x = msg.readInt16(); + Uint16 y = msg.readInt16(); + + logger->log("Warping to %s (%d, %d)", mapPath.c_str(), x, y); + + /* + * We must clear the local player's target *before* the call + * to changeMap, as it deletes all beings. + */ + player_node->stopAttack(); + + nearby = (engine->getCurrentMapName() == mapPath); + + // Switch the actual map, deleting the previous one if necessary + engine->changeMap(mapPath); + + if (current_npc) current_npc->handleDeath(); + + float scrollOffsetX = 0.0f; + float scrollOffsetY = 0.0f; + + /* Scroll if neccessary */ + if (!nearby + || (abs(x - player_node->mX) > MAP_TELEPORT_SCROLL_DISTANCE) + || (abs(y - player_node->mY) > MAP_TELEPORT_SCROLL_DISTANCE)) + { + scrollOffsetX = (x - player_node->mX) * 32; + scrollOffsetY = (y - player_node->mY) * 32; + } + + player_node->setAction(Being::STAND); + player_node->mFrame = 0; + player_node->mX = x; + player_node->mY = y; + + logger->log("Adjust scrolling by %d:%d", + (int)scrollOffsetX, + (int)scrollOffsetY); + + viewport->scrollBy(scrollOffsetX, scrollOffsetY); + } + break; + + case SMSG_PLAYER_STAT_UPDATE_1: + { + Sint16 type = msg.readInt16(); + Uint32 value = msg.readInt32(); + + switch (type) + { + //case 0x0000: + // player_node->setWalkSpeed(msg.readInt32()); + // break; + case 0x0005: player_node->setHp(value); break; + case 0x0006: player_node->setMaxHp(value); break; + case 0x0007: player_node->mMp = value; break; + case 0x0008: player_node->mMaxMp = value; break; + case 0x0009: + player_node->mStatsPointsToAttribute = value; + break; + case 0x000b: player_node->setLevel(value); break; + case 0x000c: + player_node->mSkillPoint = value; + skillDialog->update(); + break; + case 0x0018: + if ((int) value >= player_node->getMaxWeight() / 2 && + player_node->getTotalWeight() < + player_node->getMaxWeight() / 2) + { + weightNotice = new OkDialog(_("Message"), + _("You are carrying more than " + "half your weight. You are " + "unable to regain health.")); + weightNotice->addActionListener( + &weightListener); + } + player_node->setTotalWeight(value); + break; + case 0x0019: player_node->setMaxWeight(value); break; + case 0x0029: player_node->ATK = value; break; + case 0x002b: player_node->MATK = value; break; + case 0x002d: player_node->DEF = value; break; + case 0x002e: player_node->DEF_BONUS = value; break; + case 0x002f: player_node->MDEF = value; break; + case 0x0031: player_node->HIT = value; break; + case 0x0032: player_node->FLEE = value; break; + case 0x0035: player_node->mAttackSpeed = value; break; + case 0x0037: player_node->mJobLevel = value; break; + } + + if (player_node->getHp() == 0 && !deathNotice) + { + static char const *const deadMsg[] = + { + _("You are dead."), + _("We regret to inform you that your character was " + "killed in battle."), + _("You are not that alive anymore."), + _("The cold hands of the grim reaper are grabbing for " + "your soul."), + _("Game Over!"), + _("Insert coin to continue"), + _("No, kids. Your character did not really die. It... " + "err... went to a better place."), + _("Your plan of breaking your enemies weapon by " + "bashing it with your throat failed."), + _("I guess this did not run too well."), + // NetHack reference: + _("Do you want your possessions identified?"), + // Secret of Mana reference: + _("Sadly, no trace of you was ever found..."), + // Final Fantasy VI reference: + _("Annihilated."), + // Earthbound reference: + _("Looks like you got your head handed to you."), + // Leisure Suit Larry 1 reference: + _("You screwed up again, dump your body down the tubes " + "and get you another one."), + // Monty Python references (Dead Parrot sketch mostly): + _("You're not dead yet. You're just resting."), + _("You are no more."), + _("You have ceased to be."), + _("You've expired and gone to meet your maker."), + _("You're a stiff."), + _("Bereft of life, you rest in peace."), + _("If you weren't so animated, you'd be pushing up the " + "daisies."), + _("Your metabolic processes are now history."), + _("You're off the twig."), + _("You've kicked the bucket."), + _("You've shuffled off your mortal coil, run down the " + "curtain and joined the bleedin' choir invisibile."), + _("You are an ex-player."), + _("You're pining for the fjords.") + }; + std::string message(deadMsg[rand()%27]); + + deathNotice = new OkDialog(_("Message"), message); + deathNotice->addActionListener(&deathListener); + player_node->setAction(Being::DEAD); + } + } + break; + + case SMSG_PLAYER_STAT_UPDATE_2: + switch (msg.readInt16()) { + case 0x0001: + player_node->setXp(msg.readInt32()); + break; + case 0x0002: + player_node->mJobXp = msg.readInt32(); + break; + case 0x0014: { + int curGp = player_node->getMoney(); + player_node->setMoney(msg.readInt32()); + if (player_node->getMoney() > curGp) + chatWindow->chatLog(_("You picked up ") + + Units::formatCurrency(player_node->getMoney() + - curGp), BY_SERVER); + } + break; + case 0x0016: + player_node->mXpForNextLevel = msg.readInt32(); + break; + case 0x0017: + player_node->mJobXpForNextLevel = msg.readInt32(); + break; + } + break; + + case SMSG_PLAYER_STAT_UPDATE_3: + { + Sint32 type = msg.readInt32(); + Sint32 base = msg.readInt32(); + Sint32 bonus = msg.readInt32(); + Sint32 total = base + bonus; + + switch (type) { + case 0x000d: player_node->mAttr[LocalPlayer::STR] = total; + break; + case 0x000e: player_node->mAttr[LocalPlayer::AGI] = total; + break; + case 0x000f: player_node->mAttr[LocalPlayer::VIT] = total; + break; + case 0x0010: player_node->mAttr[LocalPlayer::INT] = total; + break; + case 0x0011: player_node->mAttr[LocalPlayer::DEX] = total; + break; + case 0x0012: player_node->mAttr[LocalPlayer::LUK] = total; + break; + } + } + break; + + case SMSG_PLAYER_STAT_UPDATE_4: + { + Sint16 type = msg.readInt16(); + Sint8 fail = msg.readInt8(); + Sint8 value = msg.readInt8(); + + if (fail != 1) + break; + + switch (type) { + case 0x000d: player_node->mAttr[LocalPlayer::STR] = value; + break; + case 0x000e: player_node->mAttr[LocalPlayer::AGI] = value; + break; + case 0x000f: player_node->mAttr[LocalPlayer::VIT] = value; + break; + case 0x0010: player_node->mAttr[LocalPlayer::INT] = value; + break; + case 0x0011: player_node->mAttr[LocalPlayer::DEX] = value; + break; + case 0x0012: player_node->mAttr[LocalPlayer::LUK] = value; + break; + } + } + break; + + // Updates stats and status points + case SMSG_PLAYER_STAT_UPDATE_5: + player_node->mStatsPointsToAttribute = msg.readInt16(); + player_node->mAttr[LocalPlayer::STR] = msg.readInt8(); + player_node->mAttrUp[LocalPlayer::STR] = msg.readInt8(); + player_node->mAttr[LocalPlayer::AGI] = msg.readInt8(); + player_node->mAttrUp[LocalPlayer::AGI] = msg.readInt8(); + player_node->mAttr[LocalPlayer::VIT] = msg.readInt8(); + player_node->mAttrUp[LocalPlayer::VIT] = msg.readInt8(); + player_node->mAttr[LocalPlayer::INT] = msg.readInt8(); + player_node->mAttrUp[LocalPlayer::INT] = msg.readInt8(); + player_node->mAttr[LocalPlayer::DEX] = msg.readInt8(); + player_node->mAttrUp[LocalPlayer::DEX] = msg.readInt8(); + player_node->mAttr[LocalPlayer::LUK] = msg.readInt8(); + player_node->mAttrUp[LocalPlayer::LUK] = msg.readInt8(); + player_node->ATK = msg.readInt16(); // ATK + player_node->ATK_BONUS = msg.readInt16(); // ATK bonus + player_node->MATK = msg.readInt16(); // MATK max + player_node->MATK_BONUS = msg.readInt16(); // MATK min + player_node->DEF = msg.readInt16(); // DEF + player_node->DEF_BONUS = msg.readInt16(); // DEF bonus + player_node->MDEF = msg.readInt16(); // MDEF + player_node->MDEF_BONUS = msg.readInt16(); // MDEF bonus + player_node->HIT = msg.readInt16(); // HIT + player_node->FLEE = msg.readInt16(); // FLEE + player_node->FLEE_BONUS = msg.readInt16(); // FLEE bonus + msg.readInt16(); // critical + msg.readInt16(); // unknown + break; + + case SMSG_PLAYER_STAT_UPDATE_6: + switch (msg.readInt16()) { + case 0x0020: + player_node->mAttrUp[LocalPlayer::STR] = msg.readInt8(); + break; + case 0x0021: + player_node->mAttrUp[LocalPlayer::AGI] = msg.readInt8(); + break; + case 0x0022: + player_node->mAttrUp[LocalPlayer::VIT] = msg.readInt8(); + break; + case 0x0023: + player_node->mAttrUp[LocalPlayer::INT] = msg.readInt8(); + break; + case 0x0024: + player_node->mAttrUp[LocalPlayer::DEX] = msg.readInt8(); + break; + case 0x0025: + player_node->mAttrUp[LocalPlayer::LUK] = msg.readInt8(); + break; + } + break; + + case SMSG_PLAYER_ARROW_MESSAGE: + { + Sint16 type = msg.readInt16(); + + switch (type) { + case 0: + chatWindow->chatLog(_("Equip arrows first"), + BY_SERVER); + break; + default: + logger->log("0x013b: Unhandled message %i", type); + break; + } + } + break; + } +} diff --git a/src/net/ea/playerhandler.h b/src/net/ea/playerhandler.h new file mode 100644 index 00000000..f3352289 --- /dev/null +++ b/src/net/ea/playerhandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_PLAYERHANDLER_H +#define NET_PLAYERHANDLER_H + +#include "../messagehandler.h" + +class PlayerHandler : public MessageHandler +{ + public: + PlayerHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/protocol.cpp b/src/net/ea/protocol.cpp new file mode 100644 index 00000000..69d69901 --- /dev/null +++ b/src/net/ea/protocol.cpp @@ -0,0 +1,77 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "protocol.h" + +#define LOBYTE(w) ((unsigned char)(w)) +#define HIBYTE(w) ((unsigned char)(((unsigned short)(w)) >> 8)) + +void set_coordinates(char *data, + unsigned short x, + unsigned short y, + unsigned char direction) +{ + short temp; + temp = x; + temp <<= 6; + data[0] = 0; + data[1] = 1; + data[2] = 2; + data[0] = HIBYTE(temp); + data[1] = (unsigned char)(temp); + temp = y; + temp <<= 4; + data[1] |= HIBYTE(temp); + data[2] = LOBYTE(temp); + + // Translate direction to eAthena format + switch (direction) + { + case 1: + direction = 0; + break; + case 3: + direction = 1; + break; + case 2: + direction = 2; + break; + case 6: + direction = 3; + break; + case 4: + direction = 4; + break; + case 12: + direction = 5; + break; + case 8: + direction = 6; + break; + case 9: + direction = 7; + break; + default: + // OOPSIE! Impossible or unknown + direction = (unsigned char)-1; + } + data[2] |= direction; +} diff --git a/src/net/ea/protocol.h b/src/net/ea/protocol.h new file mode 100644 index 00000000..55c0d8b6 --- /dev/null +++ b/src/net/ea/protocol.h @@ -0,0 +1,162 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +/********************************* + * Packets from server to client * + *********************************/ +#define SMSG_LOGIN_SUCCESS 0x0073 /**< Contains starting location */ +#define SMSG_SERVER_PING 0x007f /**< Contains server tick */ +#define SMSG_CONNECTION_PROBLEM 0x0081 +#define SMSG_UPDATE_HOST 0x0063 /**< Custom update host packet */ +#define SMSG_PLAYER_UPDATE_1 0x01d8 +#define SMSG_PLAYER_UPDATE_2 0x01d9 +#define SMSG_PLAYER_MOVE 0x01da /**< A nearby player moves */ +#define SMSG_PLAYER_STOP 0x0088 /**< Stop walking, set position */ +#define SMSG_PLAYER_MOVE_TO_ATTACK 0x0139 /**< Move to within attack range */ +#define SMSG_PLAYER_STAT_UPDATE_1 0x00b0 +#define SMSG_PLAYER_STAT_UPDATE_2 0x00b1 +#define SMSG_PLAYER_STAT_UPDATE_3 0x0141 +#define SMSG_PLAYER_STAT_UPDATE_4 0x00bc +#define SMSG_PLAYER_STAT_UPDATE_5 0x00bd +#define SMSG_PLAYER_STAT_UPDATE_6 0x00be +#define SMSG_WHO_ANSWER 0x00c2 +#define SMSG_PLAYER_WARP 0x0091 /**< Warp player to map/location */ +#define SMSG_PLAYER_INVENTORY 0x01ee +#define SMSG_PLAYER_INVENTORY_ADD 0x00a0 +#define SMSG_PLAYER_INVENTORY_REMOVE 0x00af +#define SMSG_PLAYER_INVENTORY_USE 0x01c8 +#define SMSG_PLAYER_EQUIPMENT 0x00a4 +#define SMSG_PLAYER_EQUIP 0x00aa +#define SMSG_PLAYER_UNEQUIP 0x00ac +#define SMSG_PLAYER_ATTACK_RANGE 0x013a +#define SMSG_PLAYER_ARROW_EQUIP 0x013c +#define SMSG_PLAYER_ARROW_MESSAGE 0x013b +#define SMSG_PLAYER_SKILLS 0x010f +#define SMSG_SKILL_FAILED 0x0110 +#define SMSG_ITEM_USE_RESPONSE 0x00a8 +#define SMSG_ITEM_VISIBLE 0x009d /**< An item is on the floor */ +#define SMSG_ITEM_DROPPED 0x009e /**< An item is dropped */ +#define SMSG_ITEM_REMOVE 0x00a1 /**< An item disappers */ +#define SMSG_BEING_VISIBLE 0x0078 +#define SMSG_BEING_MOVE 0x007b /**< A nearby monster moves */ +#define SMSG_BEING_SPAWN 0x007c /**< A being spawns nearby */ +#define SMSG_BEING_MOVE2 0x0086 /**< New eAthena being moves */ +#define SMSG_BEING_REMOVE 0x0080 +#define SMSG_BEING_CHANGE_LOOKS 0x00c3 +#define SMSG_BEING_CHANGE_LOOKS2 0x01d7 /**< Same as 0x00c3, but 16 bit ID */ +#define SMSG_BEING_SELFEFFECT 0x019b +#define SMSG_BEING_EMOTION 0x00c0 +#define SMSG_BEING_ACTION 0x008a /**< Attack, sit, stand up, ... */ +#define SMSG_BEING_CHAT 0x008d /**< A being talks */ +#define SMSG_BEING_NAME_RESPONSE 0x0095 /**< Has to be requested */ + +#define SMSG_NPC_MESSAGE 0x00b4 +#define SMSG_NPC_NEXT 0x00b5 +#define SMSG_NPC_CLOSE 0x00b6 +#define SMSG_NPC_CHOICE 0x00b7 /**< Display a choice */ +#define SMSG_NPC_BUY_SELL_CHOICE 0x00c4 +#define SMSG_NPC_BUY 0x00c6 +#define SMSG_NPC_SELL 0x00c7 +#define SMSG_NPC_BUY_RESPONSE 0x00ca +#define SMSG_NPC_SELL_RESPONSE 0x00cb +#define SMSG_NPC_INT_INPUT 0x0142 /**< Integer input */ +#define SMSG_NPC_STR_INPUT 0x01d4 /**< String input */ +#define SMSG_PLAYER_CHAT 0x008e /**< Player talks */ +#define SMSG_WHISPER 0x0097 /**< Whisper Recieved */ +#define SMSG_WHISPER_RESPONSE 0x0098 +#define SMSG_GM_CHAT 0x009a /**< GM announce */ +#define SMSG_WALK_RESPONSE 0x0087 + +#define SMSG_TRADE_REQUEST 0x00e5 /**< Receiving a request to trade */ +#define SMSG_TRADE_RESPONSE 0x00e7 +#define SMSG_TRADE_ITEM_ADD 0x00e9 +#define SMSG_TRADE_ITEM_ADD_RESPONSE 0x01b1 /**< Not standard eAthena! */ +#define SMSG_TRADE_OK 0x00ec +#define SMSG_TRADE_CANCEL 0x00ee +#define SMSG_TRADE_COMPLETE 0x00f0 + +#define SMSG_PARTY_CREATE 0x00fa +#define SMSG_PARTY_INFO 0x00fb +#define SMSG_PARTY_INVITE 0x00fd +#define SMSG_PARTY_INVITED 0x00fe +#define SMSG_PARTY_SETTINGS 0x0102 +#define SMSG_PARTY_MEMBER_INFO 0x0104 +#define SMSG_PARTY_LEAVE 0x0105 +#define SMSG_PARTY_UPDATE_HP 0x0106 +#define SMSG_PARTY_UPDATE_COORDS 0x0107 +#define SMSG_PARTY_MESSAGE 0x0109 + +#define SMSG_PLAYER_STORAGE_ITEMS 0x01f0 /**< Item list for storage */ +#define SMSG_PLAYER_STORAGE_EQUIP 0x00a6 /**< Equipment list for storage */ +#define SMSG_PLAYER_STORAGE_STATUS 0x00f2 /**< Slots used and total slots */ +#define SMSG_PLAYER_STORAGE_ADD 0x00f4 /**< Add item/equip to storage */ +#define SMSG_PLAYER_STORAGE_REMOVE 0x00f6 /**< Remove item/equip from storage */ +#define SMSG_PLAYER_STORAGE_CLOSE 0x00f8 /**< Storage access closed */ + +/********************************** + * Packets from client to server * + **********************************/ +#define CMSG_CLIENT_PING 0x007e /**< Send to server with tick */ +#define CMSG_TRADE_RESPONSE 0x00e6 +#define CMSG_ITEM_PICKUP 0x009f +#define CMSG_MAP_LOADED 0x007d +#define CMSG_NPC_BUY_REQUEST 0x00c8 +#define CMSG_NPC_BUY_SELL_REQUEST 0x00c5 +#define CMSG_CHAT_MESSAGE 0x008c +#define CMSG_CHAT_WHISPER 0x0096 +#define CMSG_CHAT_ANNOUNCE 0x0099 +#define CMSG_CHAT_WHO 0x00c1 +#define CMSG_NPC_LIST_CHOICE 0x00b8 +#define CMSG_NPC_NEXT_REQUEST 0x00b9 +#define CMSG_NPC_SELL_REQUEST 0x00c9 +#define CMSG_NPC_INT_RESPONSE 0x0143 +#define CMSG_NPC_STR_RESPONSE 0x01d5 +#define CMSG_SKILL_LEVELUP_REQUEST 0x0112 +#define CMSG_STAT_UPDATE_REQUEST 0x00bb +#define CMSG_TRADE_ITEM_ADD_REQUEST 0x00e8 +#define CMSG_TRADE_CANCEL_REQUEST 0x00ed +#define CMSG_TRADE_ADD_COMPLETE 0x00eb +#define CMSG_TRADE_OK 0x00ef +#define CMSG_NPC_TALK 0x0090 +#define CMSG_TRADE_REQUEST 0x00e4 +#define CMSG_PLAYER_INVENTORY_USE 0x00a7 +#define CMSG_PLAYER_INVENTORY_DROP 0x00a2 +#define CMSG_PLAYER_EQUIP 0x00a9 +#define CMSG_PLAYER_UNEQUIP 0x00ab + +#define CMSG_PARTY_CREATE 0x00f9 +#define CMSG_PARTY_INVITE 0x00fc +#define CMSG_PARTY_INVITED 0x00ff +#define CMSG_PARTY_LEAVE 0x0100 /** Undocumented */ +#define CMSG_PARTY_SETTINGS 0x0101 +#define CMSG_PARTY_MESSAGE 0x0108 + +#define CMSG_MOVE_TO_STORAGE 0x00f3 /** Move item to storage */ +#define CSMG_MOVE_FROM_STORAGE 0x00f5 /** Remove item from storage */ +#define CMSG_CLOSE_STORAGE 0x00f7 /** Request storage close */ + +/** Encodes coords and direction in 3 bytes data */ +void set_coordinates(char *data, unsigned short x, unsigned short y, unsigned char direction); + +#endif diff --git a/src/net/ea/skillhandler.cpp b/src/net/ea/skillhandler.cpp new file mode 100644 index 00000000..6e766008 --- /dev/null +++ b/src/net/ea/skillhandler.cpp @@ -0,0 +1,93 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "../messagein.h" +#include "protocol.h" +#include "skillhandler.h" + +#include "../../log.h" + +#include "../../gui/chat.h" +#include "../../gui/skill.h" + +SkillHandler::SkillHandler() +{ + static const Uint16 _messages[] = { + SMSG_PLAYER_SKILLS, + SMSG_SKILL_FAILED, + 0 + }; + handledMessages = _messages; +} + +void SkillHandler::handleMessage(MessageIn &msg) +{ + int skillCount; + + switch (msg.getId()) + { + case SMSG_PLAYER_SKILLS: + msg.readInt16(); // length + skillCount = (msg.getLength() - 4) / 37; + skillDialog->cleanList(); + + for (int k = 0; k < skillCount; k++) + { + Sint16 skillId = msg.readInt16(); + msg.readInt16(); // target type + msg.readInt16(); // unknown + Sint16 level = msg.readInt16(); + Sint16 sp = msg.readInt16(); + msg.readInt16(); // range + std::string skillName = msg.readString(24); + Sint8 up = msg.readInt8(); + + if (level != 0 || up != 0) + { + if (skillDialog->hasSkill(skillId)) { + skillDialog->setSkill(skillId, level, sp); + } + else { + skillDialog->addSkill(skillId, level, sp); + } + } + } + skillDialog->update(); + break; + + case SMSG_SKILL_FAILED: + // Action failed (ex. sit because you have not reached the + // right level) + CHATSKILL action; + action.skill = msg.readInt16(); + action.bskill = msg.readInt16(); + action.unused = msg.readInt16(); // unknown + action.success = msg.readInt8(); + action.reason = msg.readInt8(); + if (action.success != SKILL_FAILED && + action.bskill == BSKILL_EMOTE) + { + logger->log("Action: %d/%d", action.bskill, action.success); + } + chatWindow->chatLog(action); + break; + } +} diff --git a/src/net/ea/skillhandler.h b/src/net/ea/skillhandler.h new file mode 100644 index 00000000..57d68f47 --- /dev/null +++ b/src/net/ea/skillhandler.h @@ -0,0 +1,35 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_SKILLHANDLER_H +#define NET_SKILLHANDLER_H + +#include "../messagehandler.h" + +class SkillHandler : public MessageHandler +{ + public: + SkillHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif diff --git a/src/net/ea/tradehandler.cpp b/src/net/ea/tradehandler.cpp new file mode 100644 index 00000000..6c953a11 --- /dev/null +++ b/src/net/ea/tradehandler.cpp @@ -0,0 +1,220 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "../messagein.h" +#include "protocol.h" +#include "tradehandler.h" + +#include "../../inventory.h" +#include "../../item.h" +#include "../../localplayer.h" +#include "../../player_relations.h" + +#include "../../gui/chat.h" +#include "../../gui/confirm_dialog.h" +#include "../../gui/trade.h" + +#include "../../utils/gettext.h" + +std::string tradePartnerName; + +/** + * Listener for request trade dialogs + */ +namespace { + struct RequestTradeListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event) + { + player_node->tradeReply(event.getId() == "yes"); + }; + } listener; +} + +TradeHandler::TradeHandler() +{ + static const Uint16 _messages[] = { + SMSG_TRADE_REQUEST, + SMSG_TRADE_RESPONSE, + SMSG_TRADE_ITEM_ADD, + SMSG_TRADE_ITEM_ADD_RESPONSE, + SMSG_TRADE_OK, + SMSG_TRADE_CANCEL, + SMSG_TRADE_COMPLETE, + 0 + }; + handledMessages = _messages; +} + + +void TradeHandler::handleMessage(MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_TRADE_REQUEST: + // If a trade window or request window is already open, send a + // trade cancel to any other trade request. + // + // Note that it would be nice if the server would prevent this + // situation, and that the requesting player would get a + // special message about the player being occupied. + tradePartnerName = msg.readString(24); + + if (player_relations.hasPermission(tradePartnerName, PlayerRelation::TRADE)) + { + if (!player_node->tradeRequestOk()) + { + player_node->tradeReply(false); + break; + } + + player_node->setTrading(true); + ConfirmDialog *dlg; + dlg = new ConfirmDialog(_("Request for trade"), + tradePartnerName + + _(" wants to trade with you, do you accept?")); + dlg->addActionListener(&listener); + } + else + { + player_node->tradeReply(false); + break; + } + break; + + case SMSG_TRADE_RESPONSE: + switch (msg.readInt8()) + { + case 0: // Too far away + chatWindow->chatLog(_("Trading isn't possible. Trade partner is too far away."), + BY_SERVER); + break; + case 1: // Character doesn't exist + chatWindow->chatLog(_("Trading isn't possible. Character doesn't exist."), + BY_SERVER); + break; + case 2: // Invite request check failed... + chatWindow->chatLog(_("Trade cancelled due to an unknown reason."), + BY_SERVER); + break; + case 3: // Trade accepted + tradeWindow->reset(); + tradeWindow->setCaption( + _("Trade: You and ") + tradePartnerName); + tradeWindow->setVisible(true); + break; + case 4: // Trade cancelled + if (player_relations.hasPermission(tradePartnerName, + PlayerRelation::SPEECH_LOG)) + chatWindow->chatLog(_("Trade with ") + tradePartnerName + + _(" cancelled"), BY_SERVER); + // otherwise ignore silently + + tradeWindow->setVisible(false); + player_node->setTrading(false); + break; + default: // Shouldn't happen as well, but to be sure + chatWindow->chatLog(_("Unhandled trade cancel packet"), + BY_SERVER); + break; + } + break; + + case SMSG_TRADE_ITEM_ADD: + { + Sint32 amount = msg.readInt32(); + Sint16 type = msg.readInt16(); + msg.readInt8(); // identified flag + msg.readInt8(); // attribute + msg.readInt8(); // refine + msg.skip(8); // card (4 shorts) + + // TODO: handle also identified, etc + if (type == 0) { + tradeWindow->setMoney(amount); + } else { + tradeWindow->addItem(type, false, amount, false); + } + } + break; + + case SMSG_TRADE_ITEM_ADD_RESPONSE: + // Trade: New Item add response (was 0x00ea, now 01b1) + { + const int index = msg.readInt16(); + Item *item = player_node->getInventory()->getItem(index); + if (!item) + { + tradeWindow->receivedOk(true); + return; + } + Sint16 quantity = msg.readInt16(); + + switch (msg.readInt8()) + { + case 0: + // Successfully added item + if (item->isEquipment() && item->isEquipped()) + { + player_node->unequipItem(item); + } + tradeWindow->addItem(item->getId(), true, quantity, + item->isEquipment()); + item->increaseQuantity(-quantity); + break; + case 1: + // Add item failed - player overweighted + chatWindow->chatLog(_("Failed adding item. Trade partner is over weighted."), + BY_SERVER); + break; + case 2: + // Add item failed - player has no free slot + chatWindow->chatLog(_("Failed adding item. Trade partner has no free slot."), + BY_SERVER); + break; + default: + chatWindow->chatLog(_("Failed adding item for unknown reason."), + BY_SERVER); + break; + } + } + break; + + case SMSG_TRADE_OK: + // 0 means ok from myself, 1 means ok from other; + tradeWindow->receivedOk(msg.readInt8() == 0); + break; + + case SMSG_TRADE_CANCEL: + chatWindow->chatLog(_("Trade canceled."), BY_SERVER); + tradeWindow->setVisible(false); + tradeWindow->reset(); + player_node->setTrading(false); + break; + + case SMSG_TRADE_COMPLETE: + chatWindow->chatLog(_("Trade completed."), BY_SERVER); + tradeWindow->setVisible(false); + tradeWindow->reset(); + player_node->setTrading(false); + break; + } +} diff --git a/src/net/ea/tradehandler.h b/src/net/ea/tradehandler.h new file mode 100644 index 00000000..04335069 --- /dev/null +++ b/src/net/ea/tradehandler.h @@ -0,0 +1,37 @@ +/* + * The Mana World + * Copyright (C) 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef NET_TRADEHANDLER_H +#define NET_TRADEHANDLER_H + +#include "../messagehandler.h" + +class Network; + +class TradeHandler : public MessageHandler +{ + public: + TradeHandler(); + + void handleMessage(MessageIn &msg); +}; + +#endif |