summaryrefslogtreecommitdiffstats
path: root/src/game-server
diff options
context:
space:
mode:
authorGuillaume Melquiond <guillaume.melquiond@gmail.com>2007-07-29 17:15:40 +0000
committerGuillaume Melquiond <guillaume.melquiond@gmail.com>2007-07-29 17:15:40 +0000
commit18c0302e49c761b2a3b531a0ac2683e5f1135c5e (patch)
tree5ed10d14ce764e8252d3e23f95aa62b6aad76ac4 /src/game-server
parentfd2cecb5c45cd6f69b193c97b7c78121f6b5b30f (diff)
downloadmanaserv-18c0302e49c761b2a3b531a0ac2683e5f1135c5e.tar.gz
manaserv-18c0302e49c761b2a3b531a0ac2683e5f1135c5e.tar.xz
manaserv-18c0302e49c761b2a3b531a0ac2683e5f1135c5e.zip
Added support for trading.
Diffstat (limited to 'src/game-server')
-rw-r--r--src/game-server/character.cpp7
-rw-r--r--src/game-server/character.hpp31
-rw-r--r--src/game-server/gamehandler.cpp84
-rw-r--r--src/game-server/inventory.cpp142
-rw-r--r--src/game-server/inventory.hpp33
-rw-r--r--src/game-server/state.cpp7
-rw-r--r--src/game-server/trade.cpp169
-rw-r--r--src/game-server/trade.hpp90
8 files changed, 489 insertions, 74 deletions
diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp
index 4ab26c9..1fb9b2f 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 15c72a3..efb93e1 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<unsigned short> 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 8010374..34988ed 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 7cefd72..2d3a66c 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 1ae3cbb..a05f166 100644
--- a/src/game-server/inventory.hpp
+++ b/src/game-server/inventory.hpp
@@ -65,19 +65,31 @@ 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.
*/
void sendFull() const;
@@ -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 fa51972..19c3ea6 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 0000000..c6cd842
--- /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 <algorithm>
+#include <cassert>
+
+#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 0000000..ec5239d
--- /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 <vector>
+
+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