From 18c0302e49c761b2a3b531a0ac2683e5f1135c5e Mon Sep 17 00:00:00 2001 From: Guillaume Melquiond Date: Sun, 29 Jul 2007 17:15:40 +0000 Subject: Added support for trading. --- ChangeLog | 8 ++ src/Makefile.am | 2 + src/defines.h | 10 +++ src/game-server/character.cpp | 7 +- src/game-server/character.hpp | 31 +++++--- src/game-server/gamehandler.cpp | 84 +++++++++++++++++--- src/game-server/inventory.cpp | 142 ++++++++++++++++++++++----------- src/game-server/inventory.hpp | 33 ++++++-- src/game-server/state.cpp | 7 ++ src/game-server/trade.cpp | 169 ++++++++++++++++++++++++++++++++++++++++ src/game-server/trade.hpp | 90 +++++++++++++++++++++ src/net/messageout.cpp | 7 ++ src/net/messageout.hpp | 5 ++ 13 files changed, 521 insertions(+), 74 deletions(-) create mode 100644 src/game-server/trade.cpp create mode 100644 src/game-server/trade.hpp diff --git a/ChangeLog b/ChangeLog index 23e2411c..8ea10462 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,14 @@ src/game-server/map.hpp, src/game-server/mapcomposite.cpp: Revert to the old pathfinding system without collision with beings, as the new one is too cpu intensive. + * src/net/messageout.hpp, src/net/messageout.cpp: Allowed cancellation + of outgoing messages. + * src/game-server/inventory.hpp, src/game-server/inventory.hpp: Allowed + delayed changes of inventories. + * src/defines.h, src/Makefile.am, src/game-server/trade.cpp, + src/game-server/trade.hpp, src/game-server/state.cpp, + src/game-server/character.cpp, src/game-server/character.hpp, + src/game-server/gamehandler.cpp: Added support for trading. 2007-07-28 Guillaume Melquiond diff --git a/src/Makefile.am b/src/Makefile.am index 00efc9dc..9826bb1d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -119,6 +119,8 @@ tmwserv_game_SOURCES = \ game-server/state.cpp \ game-server/testing.cpp \ game-server/thing.hpp \ + game-server/trade.hpp \ + game-server/trade.cpp \ game-server/trigger.hpp \ game-server/trigger.cpp \ net/connection.hpp \ diff --git a/src/defines.h b/src/defines.h index c522889c..41bf12a3 100644 --- a/src/defines.h +++ b/src/defines.h @@ -169,6 +169,16 @@ enum { PGMSG_NPC_TALK = 0x02B2, // W being id PGMSG_NPC_TALK_NEXT = 0x02B3, // W being id PGMSG_NPC_SELECT = 0x02B4, // W being id, B choice + PGMSG_TRADE_REQUEST = 0x02C0, // W being id + GPMSG_TRADE_REQUEST = 0x02C1, // W being id + GPMSG_TRADE_START = 0x02C2, // - + GPMSG_TRADE_COMPLETE = 0x02C3, // - + PGMSG_TRADE_CANCEL = 0x02C4, // - + GPMSG_TRADE_CANCEL = 0x02C5, // - + PGMSG_TRADE_ACCEPT = 0x02C6, // - + GPMSG_TRADE_ACCEPT = 0x02C7, // - + PGMSG_TRADE_ADD_ITEM = 0x02C8, // B slot, B amount + GPMSG_TRADE_ADD_ITEM = 0x02C9, // W item id, B amount PGMSG_USE_ITEM = 0x0300, // L item id GPMSG_USE_RESPONSE = 0x0301, // B error GPMSG_BEINGS_DAMAGE = 0x0310, // { W being id, W amount }* diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index 4ab26c96..1fb9b2fe 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -32,10 +32,9 @@ Character::Character(MessageIn & msg): Being(OBJECT_CHARACTER, 65535), - mClient(NULL), - mAttributesChanged(true), - mDatabaseID(-1), mName(), mGender(0), mHairStyle(0), mHairColor(0), - mLevel(0), mMoney(0) + mClient(NULL), mTrading(NULL), mDatabaseID(-1), + mMoney(0), mGender(0), mHairStyle(0), mHairColor(0), mLevel(0), + mAttributesChanged(true) { // prepare attributes vector mAttributes.resize(NB_ATTRIBUTES_CHAR, 1); diff --git a/src/game-server/character.hpp b/src/game-server/character.hpp index 15c72a3d..efb93e1e 100644 --- a/src/game-server/character.hpp +++ b/src/game-server/character.hpp @@ -33,6 +33,7 @@ class GameClient; class MessageIn; class MessageOut; class Point; +class Trade; /** * The representation of a player's character in the game world. @@ -76,6 +77,17 @@ class Character : public Being Possessions &getPossessions() { return mPossessions; } + /** + * Gets the trade object the character is involved in. + */ + Trade *getTrading() const + { return mTrading; } + + /** + * Sets the trade object the character is involved in. + */ + void setTrading(Trade *t) + { mTrading = t; } /* * Character data: @@ -105,7 +117,7 @@ class Character : public Being /** Gets the gender of the character (male or female). */ int getGender() const - { return mGender;} + { return mGender; } /** Sets the gender of the character (male or female). */ void @@ -200,25 +212,26 @@ class Character : public Being Character &operator=(Character const &); GameClient *mClient; /**< Client computer. */ + Trade *mTrading; /**< Trade object the character is involved in. */ /** Atributes as the client should currently know them. */ std::vector mOldAttributes; - /** - * true when one or more attributes might have changed since the - * client has been updated about them. - */ - bool mAttributesChanged; + Possessions mPossessions; /**< Possesssions of the character. */ - int mDatabaseID; /**< Character's database ID. */ std::string mName; /**< Name of the character. */ + int mDatabaseID; /**< Character's database ID. */ + unsigned short mMoney; /**< Wealth of the character. */ unsigned char mGender; /**< Gender of the character. */ unsigned char mHairStyle; /**< Hair Style of the character. */ unsigned char mHairColor; /**< Hair Color of the character. */ unsigned char mLevel; /**< Level of the character. */ - unsigned int mMoney; /**< Wealth of the being. */ - Possessions mPossessions; /**< Possesssions of the character. */ + /** + * true when one or more attributes might have changed since the + * client has been updated about them. + */ + bool mAttributesChanged; }; #endif // _TMWSERV_CHARACTER_HPP_ diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp index 8010374b..34988edc 100644 --- a/src/game-server/gamehandler.cpp +++ b/src/game-server/gamehandler.cpp @@ -34,6 +34,7 @@ #include "game-server/mapcomposite.hpp" #include "game-server/npc.hpp" #include "game-server/state.hpp" +#include "game-server/trade.hpp" #include "net/messagein.hpp" #include "net/messageout.hpp" #include "net/netcomputer.hpp" @@ -116,6 +117,34 @@ void GameHandler::process() ConnectionHandler::process(); } +static MovingObject *findBeingNear(Object *p, int id) +{ + MapComposite *map = p->getMap(); + Point const &ppos = p->getPosition(); + // TODO: use a less arbitrary value. + for (MovingObjectIterator i(map->getAroundPointIterator(ppos, 48)); i; ++i) + { + MovingObject *o = *i; + if (o->getPublicID() != id) continue; + return ppos.inRangeOf(o->getPosition(), 48) ? o : NULL; + } + return NULL; +} + +static Character *findCharacterNear(Object *p, int id) +{ + MapComposite *map = p->getMap(); + Point const &ppos = p->getPosition(); + // TODO: use a less arbitrary value. + for (CharacterIterator i(map->getAroundPointIterator(ppos, 48)); i; ++i) + { + Character *o = *i; + if (o->getPublicID() != id) continue; + return ppos.inRangeOf(o->getPosition(), 48) ? o : NULL; + } + return NULL; +} + void GameHandler::processMessage(NetComputer *comp, MessageIn &message) { GameClient &computer = *static_cast< GameClient * >(comp); @@ -148,18 +177,7 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) case PGMSG_NPC_SELECT: { int id = message.readShort(); - MapComposite *map = computer.character->getMap(); - MovingObject *o = NULL; - Point ppos = computer.character->getPosition(); - // TODO: use a less arbitrary value. - for (MovingObjectIterator i(map->getAroundPointIterator(ppos, 48)); i; ++i) - { - if ((*i)->getPublicID() == id) - { - o = *i; - break; - } - } + MovingObject *o = findBeingNear(computer.character, id); if (!o || o->getType() != OBJECT_NPC) break; NPC *q = static_cast< NPC * >(o); @@ -290,6 +308,48 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) computer.status = CLIENT_LOGIN; } break; + case PGMSG_TRADE_REQUEST: + { + int id = message.readShort(); + + if (Trade *t = computer.character->getTrading()) + { + if (t->request(computer.character, id)) break; + } + + Character *q = findCharacterNear(computer.character, id); + if (!q || q->getTrading()) + { + result.writeShort(GPMSG_TRADE_CANCEL); + break; + } + + new Trade(computer.character, q); + } break; + + case PGMSG_TRADE_CANCEL: + case PGMSG_TRADE_ACCEPT: + case PGMSG_TRADE_ADD_ITEM: + { + Trade *t = computer.character->getTrading(); + if (!t) break; + + switch (message.getId()) + { + case PGMSG_TRADE_CANCEL: + t->cancel(computer.character); + break; + case PGMSG_TRADE_ACCEPT : + t->accept(computer.character); + break; + case PGMSG_TRADE_ADD_ITEM: + int slot = message.readByte(); + t->addItem(computer.character, slot, message.readByte()); + break; + } + } break; + + // The following messages should be handled by the chat server, not the game server. #if 0 diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index 7cefd72b..2d3a66c6 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -31,15 +31,62 @@ #include "game-server/itemmanager.hpp" #include "net/messageout.hpp" -Inventory::Inventory(Character *p) - : poss(p->getPossessions()), msg(GPMSG_INVENTORY), client(p) -{} +Inventory::Inventory(Character *p, bool d): + mPoss(&p->getPossessions()), msg(GPMSG_INVENTORY), mClient(p), mDelayed(d) +{ +} Inventory::~Inventory() { if (msg.getLength() > 2) { - gameHandler->sendTo(client, msg); + if (mDelayed) + { + Possessions &poss = mClient->getPossessions(); + if (mPoss != &poss) + { + poss = *mPoss; + delete mPoss; + } + } + gameHandler->sendTo(mClient, msg); + } +} + +void Inventory::cancel() +{ + msg.clear(); + msg.writeShort(GPMSG_INVENTORY); + mPoss = &mClient->getPossessions(); +} + +void Inventory::commit() +{ + if (msg.getLength() > 2) + { + if (mDelayed) + { + Possessions &poss = mClient->getPossessions(); + if (mPoss != &poss) + { + poss = *mPoss; + delete mPoss; + mPoss = &poss; + } + } + gameHandler->sendTo(mClient, msg); + msg.clear(); + msg.writeShort(GPMSG_INVENTORY); + } +} + +void Inventory::prepare() +{ + if (!mDelayed) return; + Possessions &poss = mClient->getPossessions(); + if (mPoss == &poss) + { + mPoss = new Possessions(poss); } } @@ -49,7 +96,7 @@ void Inventory::sendFull() const for (int i = 0; i < EQUIPMENT_SLOTS; ++i) { - if (int id = poss.equipment[i]) + if (int id = mPoss->equipment[i]) { m.writeByte(i); m.writeShort(id); @@ -57,8 +104,8 @@ void Inventory::sendFull() const } int slot = EQUIP_CLIENT_INVENTORY; - for (std::vector< InventoryItem >::iterator i = poss.inventory.begin(), - i_end = poss.inventory.end(); i != i_end; ++i) + for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), + i_end = mPoss->inventory.end(); i != i_end; ++i) { if (i->itemId) { @@ -73,13 +120,13 @@ void Inventory::sendFull() const } } - gameHandler->sendTo(client, m); + gameHandler->sendTo(mClient, m); } int Inventory::getItem(int slot) const { - for (std::vector< InventoryItem >::iterator i = poss.inventory.begin(), - i_end = poss.inventory.end(); i != i_end; ++i) + for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), + i_end = mPoss->inventory.end(); i != i_end; ++i) { if (slot == 0) { @@ -99,8 +146,8 @@ int Inventory::getItem(int slot) const int Inventory::getIndex(int slot) const { int index = 0; - for (std::vector< InventoryItem >::iterator i = poss.inventory.begin(), - i_end = poss.inventory.end(); i != i_end; ++i, ++index) + for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), + i_end = mPoss->inventory.end(); i != i_end; ++i, ++index) { if (slot == 0) { @@ -120,8 +167,8 @@ int Inventory::getIndex(int slot) const int Inventory::getSlot(int index) const { int slot = 0; - for (std::vector< InventoryItem >::iterator i = poss.inventory.begin(), - i_end = poss.inventory.begin() + index; i != i_end; ++i) + for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), + i_end = mPoss->inventory.begin() + index; i != i_end; ++i) { slot += i->itemId ? 1 : i->amount; } @@ -131,9 +178,9 @@ int Inventory::getSlot(int index) const int Inventory::fillFreeSlot(int itemId, int amount, int maxPerSlot) { int slot = 0; - for (int i = 0, i_end = poss.inventory.size(); i < i_end; ++i) + for (int i = 0, i_end = mPoss->inventory.size(); i < i_end; ++i) { - InventoryItem &it = poss.inventory[i]; + InventoryItem &it = mPoss->inventory[i]; if (it.itemId == 0) { int nb = std::min(amount, maxPerSlot); @@ -146,7 +193,7 @@ int Inventory::fillFreeSlot(int itemId, int amount, int maxPerSlot) { --it.amount; InventoryItem iu = { itemId, nb }; - poss.inventory.insert(poss.inventory.begin() + i, iu); + mPoss->inventory.insert(mPoss->inventory.begin() + i, iu); ++i_end; } @@ -168,7 +215,7 @@ int Inventory::fillFreeSlot(int itemId, int amount, int maxPerSlot) int nb = std::min(amount, maxPerSlot); amount -= nb; InventoryItem it = { itemId, nb }; - poss.inventory.push_back(it); + mPoss->inventory.push_back(it); msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); msg.writeShort(itemId); @@ -181,11 +228,13 @@ int Inventory::fillFreeSlot(int itemId, int amount, int maxPerSlot) int Inventory::insert(int itemId, int amount) { + prepare(); + int slot = 0; int maxPerSlot = ItemManager::getItem(itemId)->getMaxPerSlot(); - for (std::vector< InventoryItem >::iterator i = poss.inventory.begin(), - i_end = poss.inventory.end(); i != i_end; ++i) + for (std::vector< InventoryItem >::iterator i = mPoss->inventory.begin(), + i_end = mPoss->inventory.end(); i != i_end; ++i) { if (i->itemId == itemId) { @@ -216,8 +265,8 @@ int Inventory::count(int itemId) const { int nb = 0; - for (std::vector< InventoryItem >::iterator i = poss.inventory.begin(), - i_end = poss.inventory.end(); i != i_end; ++i) + for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), + i_end = mPoss->inventory.end(); i != i_end; ++i) { if (i->itemId == itemId) { @@ -230,17 +279,17 @@ int Inventory::count(int itemId) const void Inventory::freeIndex(int i) { - InventoryItem &it = poss.inventory[i]; + InventoryItem &it = mPoss->inventory[i]; - if (i == (int)poss.inventory.size() - 1) + if (i == (int)mPoss->inventory.size() - 1) { - poss.inventory.pop_back(); + mPoss->inventory.pop_back(); } - else if (poss.inventory[i + 1].itemId == 0) + else if (mPoss->inventory[i + 1].itemId == 0) { it.itemId = 0; - it.amount = poss.inventory[i + 1].amount + 1; - poss.inventory.erase(poss.inventory.begin() + i + 1); + it.amount = mPoss->inventory[i + 1].amount + 1; + mPoss->inventory.erase(mPoss->inventory.begin() + i + 1); } else { @@ -248,19 +297,21 @@ void Inventory::freeIndex(int i) it.amount = 1; } - if (i > 0 && poss.inventory[i - 1].itemId == 0) + if (i > 0 && mPoss->inventory[i - 1].itemId == 0) { // Note: "it" is no longer a valid iterator. - poss.inventory[i - 1].amount += poss.inventory[i].amount; - poss.inventory.erase(poss.inventory.begin() + i); + mPoss->inventory[i - 1].amount += mPoss->inventory[i].amount; + mPoss->inventory.erase(mPoss->inventory.begin() + i); } } int Inventory::remove(int itemId, int amount) { - for (int i = poss.inventory.size() - 1; i >= 0; --i) + prepare(); + + for (int i = mPoss->inventory.size() - 1; i >= 0; --i) { - InventoryItem &it = poss.inventory[i]; + InventoryItem &it = mPoss->inventory[i]; if (it.itemId == itemId) { int nb = std::min((int)it.amount, amount); @@ -290,13 +341,14 @@ int Inventory::remove(int itemId, int amount) int Inventory::removeFromSlot(int slot, int amount) { int i = getIndex(slot); - if (i < 0) { return amount; } - InventoryItem &it = poss.inventory[i]; + prepare(); + + InventoryItem &it = mPoss->inventory[i]; int nb = std::min((int)it.amount, amount); it.amount -= nb; amount -= nb; @@ -322,6 +374,8 @@ bool Inventory::equip(int slot) return false; } + prepare(); + int availableSlots = 0, firstSlot = 0, secondSlot = 0; switch (ItemManager::getItem(itemId)->getType()) @@ -330,13 +384,13 @@ bool Inventory::equip(int slot) { // Special case 1, the two one-handed weapons are to be placed back // in the inventory, if there are any. - int id = poss.equipment[EQUIP_FIGHT1_SLOT]; + int id = mPoss->equipment[EQUIP_FIGHT1_SLOT]; if (id && !insert(id, 1)) { return false; } - id = poss.equipment[EQUIP_FIGHT2_SLOT]; + id = mPoss->equipment[EQUIP_FIGHT2_SLOT]; if (id && !insert(id, 1)) { return false; @@ -346,8 +400,8 @@ bool Inventory::equip(int slot) msg.writeShort(itemId); msg.writeByte(EQUIP_FIGHT2_SLOT); msg.writeShort(0); - poss.equipment[EQUIP_FIGHT1_SLOT] = itemId; - poss.equipment[EQUIP_FIGHT2_SLOT] = 0; + mPoss->equipment[EQUIP_FIGHT1_SLOT] = itemId; + mPoss->equipment[EQUIP_FIGHT2_SLOT] = 0; removeFromSlot(slot, 1); return true; } @@ -355,7 +409,7 @@ bool Inventory::equip(int slot) case ITEM_EQUIPMENT_PROJECTILE: msg.writeByte(EQUIP_PROJECTILE_SLOT); msg.writeShort(itemId); - poss.equipment[EQUIP_PROJECTILE_SLOT] = itemId; + mPoss->equipment[EQUIP_PROJECTILE_SLOT] = itemId; return true; case ITEM_EQUIPMENT_ONE_HAND_WEAPON: @@ -400,19 +454,19 @@ bool Inventory::equip(int slot) return false; } - int id = poss.equipment[firstSlot]; + int id = mPoss->equipment[firstSlot]; switch (availableSlots) { case 2: - if (id && !poss.equipment[secondSlot] && + if (id && !mPoss->equipment[secondSlot] && ItemManager::getItem(id)->getType() != ITEM_EQUIPMENT_TWO_HANDS_WEAPON) { // The first slot is full and the second slot is empty. msg.writeByte(secondSlot); msg.writeShort(itemId); - poss.equipment[secondSlot] = itemId; + mPoss->equipment[secondSlot] = itemId; removeFromSlot(slot, 1); return true; } @@ -425,7 +479,7 @@ bool Inventory::equip(int slot) } msg.writeByte(firstSlot); msg.writeShort(itemId); - poss.equipment[firstSlot] = itemId; + mPoss->equipment[firstSlot] = itemId; removeFromSlot(slot, 1); return true; diff --git a/src/game-server/inventory.hpp b/src/game-server/inventory.hpp index 1ae3cbb5..a05f166b 100644 --- a/src/game-server/inventory.hpp +++ b/src/game-server/inventory.hpp @@ -65,18 +65,30 @@ class GameClient; */ class Inventory { - Possessions &poss; - MessageOut msg; - Character *client; - public: - Inventory(Character *); /** + * Creates a view on the possessions of a character. + * @param delayed true if changes have to be cancelable. + */ + Inventory(Character *, bool delayed = false); + + /** + * Commits delayed changes. * Sends the update message to the client. */ ~Inventory(); + /** + * Commits delayed changes. + */ + void commit(); + + /** + * Cancels delayed changes. + */ + void cancel(); + /** * Sends a complete inventory update to the client. */ @@ -122,6 +134,12 @@ class Inventory int getItem(int slot) const; private: + + /** + * Ensures we are working on a copy in delayed mode. + */ + void prepare(); + /** * Fills some slots with items. * @return number of items not inserted. @@ -142,6 +160,11 @@ class Inventory * Gets the slot number of an inventory index. */ int getSlot(int index) const; + + Possessions *mPoss; /**< Pointer to the modified possessions. */ + MessageOut msg; /**< Update message containing all the changes. */ + Character *mClient; /**< Character to notify. */ + bool mDelayed; /**< Delayed changes. */ }; diff --git a/src/game-server/state.cpp b/src/game-server/state.cpp index fa51972c..19c3ea60 100644 --- a/src/game-server/state.cpp +++ b/src/game-server/state.cpp @@ -37,6 +37,7 @@ #include "game-server/mapmanager.hpp" #include "game-server/monster.hpp" #include "game-server/npc.hpp" +#include "game-server/trade.hpp" #include "net/messageout.hpp" #include "utils/logger.h" @@ -499,6 +500,12 @@ void GameState::remove(Thing *ptr) if (ptr->canMove()) { + if (ptr->getType() == OBJECT_CHARACTER) + { + Character *ch = static_cast< Character * >(ptr); + if (Trade *t = ch->getTrading()) t->cancel(ch); + } + MovingObject *obj = static_cast< MovingObject * >(ptr); MessageOut msg(GPMSG_BEING_LEAVE); msg.writeShort(obj->getPublicID()); diff --git a/src/game-server/trade.cpp b/src/game-server/trade.cpp new file mode 100644 index 00000000..c6cd8425 --- /dev/null +++ b/src/game-server/trade.cpp @@ -0,0 +1,169 @@ +/* + * The Mana World Server + * Copyright 2007 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include +#include + +#include "game-server/trade.hpp" + +#include "defines.h" +#include "game-server/character.hpp" +#include "game-server/gamehandler.hpp" +#include "game-server/inventory.hpp" +#include "net/messageout.hpp" + +Trade::Trade(Character *c1, Character *c2): + mChar1(c1), mChar2(c2), mState(TRADE_INIT) +{ + MessageOut msg(GPMSG_TRADE_REQUEST); + msg.writeShort(c1->getPublicID()); + c2->getClient()->send(msg); + c1->setTrading(this); + c2->setTrading(this); +} + +Trade::~Trade() +{ + mChar1->setTrading(NULL); + mChar2->setTrading(NULL); +} + +void Trade::cancel(Character *c) +{ + MessageOut msg(GPMSG_TRADE_CANCEL); + if (c != mChar1) mChar1->getClient()->send(msg); + if (c != mChar2) mChar2->getClient()->send(msg); + delete this; +} + +bool Trade::request(Character *c, int id) +{ + if (mState != TRADE_INIT || c != mChar2 || mChar1->getPublicID() != id) + { + /* This is not an ack for the current transaction. So assume + a new one is about to start and cancel the current one. */ + cancel(c); + return false; + } + + // Starts trading. + mState = TRADE_RUN; + MessageOut msg(GPMSG_TRADE_START); + mChar1->getClient()->send(msg); + mChar2->getClient()->send(msg); + return true; +} + +static bool performTrade(TradedItems items, Inventory &inv1, Inventory &inv2) +{ + for (TradedItems::const_iterator i = items.begin(), + i_end = items.end(); i != i_end; ++i) + { + if (i->id != inv1.getItem(i->slot) || + inv1.removeFromSlot(i->slot, i->amount) != 0 || + inv2.insert(i->id, i->amount) != 0) + { + return false; + } + } + return true; +} + +void Trade::accept(Character *c) +{ + if (mState == TRADE_RUN) + { + if (c == mChar2) + { + std::swap(mChar1, mChar2); + std::swap(mItems1, mItems2); + } + assert(c == mChar1); + // First player agrees. + mState = TRADE_EXIT; + MessageOut msg(GPMSG_TRADE_ACCEPT); + mChar2->getClient()->send(msg); + return; + } + + if (mState != TRADE_EXIT || c != mChar2) + { + // First player has already agreed. We only care about the second one. + return; + } + + Inventory v1(mChar1, true), v2(mChar2, true); + if (!performTrade(mItems1, v1, v2) || !performTrade(mItems2, v2, v1)) + { + v1.cancel(); + v2.cancel(); + cancel(NULL); + return; + } + + MessageOut msg(GPMSG_TRADE_COMPLETE); + mChar1->getClient()->send(msg); + mChar2->getClient()->send(msg); + delete this; +} + +void Trade::addItem(Character *c, int slot, int amount) +{ + if (mState == TRADE_INIT) return; + + Character *other; + TradedItems *items; + if (c == mChar1) + { + other = mChar2; + items = &mItems1; + } + else + { + assert(c == mChar2); + other = mChar1; + items = &mItems2; + } + + // Arbitrary limit to prevent a client from DOSing the server. + if (items->size() >= 50) return; + + Inventory inv(c, true); + int id = inv.getItem(slot); + if (id == 0) return; + + /* Checking now if there is enough items is useless as it can change + later on. At worst, the transaction will be cancelled at the end if + the client lied. */ + + TradedItem ti = { id, slot, amount }; + items->push_back(ti); + + MessageOut msg(GPMSG_TRADE_ADD_ITEM); + msg.writeShort(id); + msg.writeByte(amount); + other->getClient()->send(msg); + + // Go back to normal run. + mState = TRADE_RUN; +} diff --git a/src/game-server/trade.hpp b/src/game-server/trade.hpp new file mode 100644 index 00000000..ec5239d0 --- /dev/null +++ b/src/game-server/trade.hpp @@ -0,0 +1,90 @@ +/* + * The Mana World Server + * Copyright 2007 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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. + * + * The Mana World 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 The Mana World; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#ifndef _TMSERV_GAMESERVER_TRADE_HPP_ +#define _TMSERV_GAMESERVER_TRADE_HPP_ + +#include + +class Character; + +enum TradeState +{ + TRADE_INIT = 0, /**< Waiting for an ack from player 2. */ + TRADE_RUN, /**< Currently trading. */ + TRADE_EXIT /**< Waiting for an ack from player 2. */ +}; + +struct TradedItem +{ + unsigned short id; + unsigned char slot, amount; +}; + +typedef std::vector< TradedItem > TradedItems; + +class Trade +{ + public: + + /** + * Sets up a trade between two characters. + * Asks for an acknowledgment from the second one. + */ + Trade(Character *, Character *); + + ~Trade(); + + /** + * Cancels a trade by a given character (optional). + * Warns the other character the trade is cancelled. + * Takes care of cleaning afterwards. + */ + void cancel(Character *); + + /** + * Requests a trade to start with given public ID. + * Continues the current trade if the ID is correct, cancels it + * otherwise. + * @return true if the current trade keeps going. + */ + bool request(Character *, int); + + /** + * Agrees to complete the trade. + */ + void accept(Character *); + + /** + * Adds some items to the trade. + */ + void addItem(Character *, int slot, int amount); + + private: + + Character *mChar1, *mChar2; /**< Characters involved. */ + TradedItems mItems1, mItems2; /**< Traded items. */ + TradeState mState; +}; + +#endif diff --git a/src/net/messageout.cpp b/src/net/messageout.cpp index 929514a6..57b2db78 100644 --- a/src/net/messageout.cpp +++ b/src/net/messageout.cpp @@ -57,6 +57,13 @@ MessageOut::~MessageOut() free(mData); } +void MessageOut::clear() +{ + mData = (char *) realloc(mData, INITIAL_DATA_CAPACITY); + mDataSize = INITIAL_DATA_CAPACITY; + mPos = 0; +} + void MessageOut::expand(size_t bytes) { diff --git a/src/net/messageout.hpp b/src/net/messageout.hpp index bb252c24..3515476f 100644 --- a/src/net/messageout.hpp +++ b/src/net/messageout.hpp @@ -47,6 +47,11 @@ class MessageOut */ ~MessageOut(); + /** + * Clears current message. + */ + void clear(); + void writeByte(char value); /**< Writes a byte. */ -- cgit v1.2.3-60-g2f50