/*
* Copyright (C) 2005-2008 MaNGOS
*
* Copyright (C) 2008 Trinity
*
* 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
* (at your option) 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 "Common.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "World.h"
#include "ObjectAccessor.h"
#include "Log.h"
#include "Opcodes.h"
#include "Player.h"
#include "Item.h"
#include "SocialMgr.h"
#include "Language.h"
enum TradeStatus
{
TRADE_STATUS_BUSY = 0,
TRADE_STATUS_BEGIN_TRADE = 1,
TRADE_STATUS_OPEN_WINDOW = 2,
TRADE_STATUS_TRADE_CANCELED = 3,
TRADE_STATUS_TRADE_ACCEPT = 4,
TRADE_STATUS_BUSY_2 = 5,
TRADE_STATUS_NO_TARGET = 6,
TRADE_STATUS_BACK_TO_TRADE = 7,
TRADE_STATUS_TRADE_COMPLETE = 8,
// 9?
TRADE_STATUS_TARGET_TO_FAR = 10,
TRADE_STATUS_WRONG_FACTION = 11,
TRADE_STATUS_CLOSE_WINDOW = 12,
// 13?
TRADE_STATUS_IGNORE_YOU = 14,
TRADE_STATUS_YOU_STUNNED = 15,
TRADE_STATUS_TARGET_STUNNED = 16,
TRADE_STATUS_YOU_DEAD = 17,
TRADE_STATUS_TARGET_DEAD = 18,
TRADE_STATUS_YOU_LOGOUT = 19,
TRADE_STATUS_TARGET_LOGOUT = 20,
TRADE_STATUS_TRIAL_ACCOUNT = 21, // Trial accounts can not perform that action
TRADE_STATUS_ONLY_CONJURED = 22 // You can only trade conjured items... (cross realm BG related).
};
void WorldSession::SendTradeStatus(uint32 status)
{
WorldPacket data;
switch(status)
{
case TRADE_STATUS_BEGIN_TRADE:
data.Initialize(SMSG_TRADE_STATUS, 4+8);
data << uint32(status);
data << uint64(0);
break;
case TRADE_STATUS_OPEN_WINDOW:
data.Initialize(SMSG_TRADE_STATUS, 4+4);
data << uint32(status);
data << uint32(0); // added in 2.4.0
break;
case TRADE_STATUS_CLOSE_WINDOW:
data.Initialize(SMSG_TRADE_STATUS, 4+4+1+4);
data << uint32(status);
data << uint32(0);
data << uint8(0);
data << uint32(0);
break;
case TRADE_STATUS_ONLY_CONJURED:
data.Initialize(SMSG_TRADE_STATUS, 4+1);
data << uint32(status);
data << uint8(0);
break;
default:
data.Initialize(SMSG_TRADE_STATUS, 4);
data << uint32(status);
break;
}
SendPacket(&data);
}
void WorldSession::HandleIgnoreTradeOpcode(WorldPacket& /*recvPacket*/)
{
sLog.outDebug( "WORLD: Ignore Trade %u",_player->GetGUIDLow());
// recvPacket.print_storage();
}
void WorldSession::HandleBusyTradeOpcode(WorldPacket& /*recvPacket*/)
{
sLog.outDebug( "WORLD: Busy Trade %u",_player->GetGUIDLow());
// recvPacket.print_storage();
}
void WorldSession::SendUpdateTrade()
{
Item *item = NULL;
if( !_player || !_player->pTrader )
return;
// reset trade status
if (_player->acceptTrade)
{
_player->acceptTrade = false;
SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
}
if (_player->pTrader->acceptTrade)
{
_player->pTrader->acceptTrade = false;
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
}
WorldPacket data(SMSG_TRADE_STATUS_EXTENDED, (100)); // guess size
data << (uint8 ) 1; // can be different (only seen 0 and 1)
data << (uint32) 0; // added in 2.4.0, this value must be equal to value from TRADE_STATUS_OPEN_WINDOW status packet (different value for different players to block multiple trades?)
data << (uint32) TRADE_SLOT_COUNT; // trade slots count/number?, = next field in most cases
data << (uint32) TRADE_SLOT_COUNT; // trade slots count/number?, = prev field in most cases
data << (uint32) _player->pTrader->tradeGold; // trader gold
data << (uint32) 0; // spell casted on lowest slot item
for(uint8 i = 0; i < TRADE_SLOT_COUNT; i++)
{
item = (_player->pTrader->tradeItems[i] != NULL_SLOT ? _player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i] ) : NULL);
data << (uint8) i; // trade slot number, if not specified, then end of packet
if(item)
{
data << (uint32) item->GetProto()->ItemId; // entry
// display id
data << (uint32) item->GetProto()->DisplayInfoID;
// stack count
data << (uint32) item->GetUInt32Value(ITEM_FIELD_STACK_COUNT);
data << (uint32) 0; // probably gift=1, created_by=0?
// gift creator
data << (uint64) item->GetUInt64Value(ITEM_FIELD_GIFTCREATOR);
data << (uint32) item->GetEnchantmentId(PERM_ENCHANTMENT_SLOT);
for(uint8 j = 0; j < 3; ++j)
data << (uint32) 0; // enchantment id (permanent/gems?)
// creator
data << (uint64) item->GetUInt64Value(ITEM_FIELD_CREATOR);
data << (uint32) item->GetSpellCharges(); // charges
data << (uint32) item->GetItemSuffixFactor(); // SuffixFactor
// random properties id
data << (uint32) item->GetItemRandomPropertyId();
data << (uint32) item->GetProto()->LockID; // lock id
// max durability
data << (uint32) item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
// durability
data << (uint32) item->GetUInt32Value(ITEM_FIELD_DURABILITY);
}
else
{
for(uint8 j = 0; j < 18; j++)
data << uint32(0);
}
}
SendPacket(&data);
}
//==============================================================
// transfer the items to the players
void WorldSession::moveItems(Item* myItems[], Item* hisItems[])
{
for(int i=0; ipTrader->CanStoreItem( NULL_BAG, NULL_SLOT, traderDst, myItems[i], false ) == EQUIP_ERR_OK);
bool playerCanTrade = (hisItems[i]==NULL || _player->CanStoreItem( NULL_BAG, NULL_SLOT, playerDst, hisItems[i], false ) == EQUIP_ERR_OK);
if(traderCanTrade && playerCanTrade )
{
// Ok, if trade item exists and can be stored
// If we trade in both directions we had to check, if the trade will work before we actually do it
// A roll back is not possible after we stored it
if(myItems[i])
{
// logging
sLog.outDebug("partner storing: %u",myItems[i]->GetGUIDLow());
if( _player->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) )
sLog.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)",
_player->GetName(),_player->GetSession()->GetAccountId(),
myItems[i]->GetProto()->Name1,myItems[i]->GetEntry(),myItems[i]->GetCount(),
_player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId());
// store
_player->pTrader->MoveItemToInventory( traderDst, myItems[i], true, true);
}
if(hisItems[i])
{
// logging
sLog.outDebug("player storing: %u",hisItems[i]->GetGUIDLow());
if( _player->pTrader->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) )
sLog.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)",
_player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId(),
hisItems[i]->GetProto()->Name1,hisItems[i]->GetEntry(),hisItems[i]->GetCount(),
_player->GetName(),_player->GetSession()->GetAccountId());
// store
_player->MoveItemToInventory( playerDst, hisItems[i], true, true);
}
}
else
{
// in case of fatal error log error message
// return the already removed items to the original owner
if(myItems[i])
{
if(!traderCanTrade)
sLog.outError("trader can't store item: %u",myItems[i]->GetGUIDLow());
if(_player->CanStoreItem( NULL_BAG, NULL_SLOT, playerDst, myItems[i], false ) == EQUIP_ERR_OK)
_player->MoveItemToInventory(playerDst, myItems[i], true, true);
else
sLog.outError("player can't take item back: %u",myItems[i]->GetGUIDLow());
}
// return the already removed items to the original owner
if(hisItems[i])
{
if(!playerCanTrade)
sLog.outError("player can't store item: %u",hisItems[i]->GetGUIDLow());
if(_player->pTrader->CanStoreItem( NULL_BAG, NULL_SLOT, traderDst, hisItems[i], false ) == EQUIP_ERR_OK)
_player->pTrader->MoveItemToInventory(traderDst, hisItems[i], true, true);
else
sLog.outError("trader can't take item back: %u",hisItems[i]->GetGUIDLow());
}
}
}
}
//==============================================================
void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
{
Item *myItems[TRADE_SLOT_TRADED_COUNT] = { NULL, NULL, NULL, NULL, NULL, NULL };
Item *hisItems[TRADE_SLOT_TRADED_COUNT] = { NULL, NULL, NULL, NULL, NULL, NULL };
bool myCanCompleteTrade=true,hisCanCompleteTrade=true;
if ( !GetPlayer()->pTrader )
return;
// not accept case incorrect money amount
if( _player->tradeGold > _player->GetMoney() )
{
SendNotification(LANG_NOT_ENOUGH_GOLD);
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
_player->acceptTrade = false;
return;
}
// not accept case incorrect money amount
if( _player->pTrader->tradeGold > _player->pTrader->GetMoney() )
{
_player->pTrader->GetSession( )->SendNotification(LANG_NOT_ENOUGH_GOLD);
SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
_player->pTrader->acceptTrade = false;
return;
}
// not accept if some items now can't be trade (cheating)
for(int i=0; itradeItems[i] != NULL_SLOT )
{
if(Item* item =_player->GetItemByPos( _player->tradeItems[i] ))
{
if(!item->CanBeTraded())
{
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
return;
}
}
}
if(_player->pTrader->tradeItems[i] != NULL_SLOT)
{
if(Item* item =_player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i]) )
{
if(!item->CanBeTraded())
{
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
return;
}
}
}
}
_player->acceptTrade = true;
if (_player->pTrader->acceptTrade )
{
// inform partner client
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT);
// store items in local list and set 'in-trade' flag
for(int i=0; itradeItems[i] != NULL_SLOT )
{
sLog.outDebug("player trade item bag: %u slot: %u",_player->tradeItems[i] >> 8, _player->tradeItems[i] & 255 );
//Can return NULL
myItems[i]=_player->GetItemByPos( _player->tradeItems[i] );
if (myItems[i])
myItems[i]->SetInTrade();
}
if(_player->pTrader->tradeItems[i] != NULL_SLOT)
{
sLog.outDebug("partner trade item bag: %u slot: %u",_player->pTrader->tradeItems[i] >> 8,_player->pTrader->tradeItems[i] & 255);
//Can return NULL
hisItems[i]=_player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i]);
if(hisItems[i])
hisItems[i]->SetInTrade();
}
}
// test if item will fit in each inventory
hisCanCompleteTrade = (_player->pTrader->CanStoreItems( myItems,TRADE_SLOT_TRADED_COUNT )== EQUIP_ERR_OK);
myCanCompleteTrade = (_player->CanStoreItems( hisItems,TRADE_SLOT_TRADED_COUNT ) == EQUIP_ERR_OK);
// clear 'in-trade' flag
for(int i=0; iSetInTrade(false);
if(hisItems[i]) hisItems[i]->SetInTrade(false);
}
// in case of missing space report error
if(!myCanCompleteTrade)
{
SendNotification(LANG_NOT_FREE_TRADE_SLOTS);
GetPlayer( )->pTrader->GetSession( )->SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS);
SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
return;
}
else if (!hisCanCompleteTrade)
{
SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS);
GetPlayer()->pTrader->GetSession()->SendNotification(LANG_NOT_FREE_TRADE_SLOTS);
SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
return;
}
// execute trade: 1. remove
for(int i=0; iSetUInt64Value( ITEM_FIELD_GIFTCREATOR,_player->GetGUID());
_player->MoveItemFromInventory(_player->tradeItems[i] >> 8, _player->tradeItems[i] & 255, true);
}
if(hisItems[i])
{
hisItems[i]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR,_player->pTrader->GetGUID());
_player->pTrader->MoveItemFromInventory(_player->pTrader->tradeItems[i] >> 8, _player->pTrader->tradeItems[i] & 255, true);
}
}
// execute trade: 2. store
moveItems(myItems, hisItems);
// logging money
if(sWorld.getConfig(CONFIG_GM_LOG_TRADE))
{
if( _player->GetSession()->GetSecurity() > SEC_PLAYER && _player->tradeGold > 0)
sLog.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)",
_player->GetName(),_player->GetSession()->GetAccountId(),
_player->tradeGold,
_player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId());
if( _player->pTrader->GetSession()->GetSecurity() > SEC_PLAYER && _player->pTrader->tradeGold > 0)
sLog.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)",
_player->pTrader->GetName(),_player->pTrader->GetSession()->GetAccountId(),
_player->pTrader->tradeGold,
_player->GetName(),_player->GetSession()->GetAccountId());
}
// update money
_player->ModifyMoney( -int32(_player->tradeGold) );
_player->ModifyMoney(_player->pTrader->tradeGold );
_player->pTrader->ModifyMoney( -int32(_player->pTrader->tradeGold) );
_player->pTrader->ModifyMoney(_player->tradeGold );
_player->ClearTrade();
_player->pTrader->ClearTrade();
// desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards)
CharacterDatabase.BeginTransaction();
_player->SaveInventoryAndGoldToDB();
_player->pTrader->SaveInventoryAndGoldToDB();
CharacterDatabase.CommitTransaction();
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE);
SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE);
_player->pTrader->pTrader = NULL;
_player->pTrader = NULL;
}
else
{
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT);
}
}
void WorldSession::HandleUnacceptTradeOpcode(WorldPacket& /*recvPacket*/)
{
if ( !GetPlayer()->pTrader )
return;
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
_player->acceptTrade = false;
}
void WorldSession::HandleBeginTradeOpcode(WorldPacket& /*recvPacket*/)
{
if(!_player->pTrader)
return;
_player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_OPEN_WINDOW);
_player->pTrader->ClearTrade();
SendTradeStatus(TRADE_STATUS_OPEN_WINDOW);
_player->ClearTrade();
}
void WorldSession::SendCancelTrade()
{
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
}
void WorldSession::HandleCancelTradeOpcode(WorldPacket& /*recvPacket*/)
{
// sended also after LOGOUT COMPLETE
if(_player) // needed because STATUS_AUTHED
_player->TradeCancel(true);
}
void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket)
{
CHECK_PACKET_SIZE(recvPacket,8);
if( GetPlayer()->pTrader )
return;
uint64 ID;
if( !GetPlayer()->isAlive() )
{
SendTradeStatus(TRADE_STATUS_YOU_DEAD);
return;
}
if( GetPlayer()->hasUnitState(UNIT_STAT_STUNNED) )
{
SendTradeStatus(TRADE_STATUS_YOU_STUNNED);
return;
}
if( isLogingOut() )
{
SendTradeStatus(TRADE_STATUS_YOU_LOGOUT);
return;
}
if( GetPlayer()->isInFlight() )
{
SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
return;
}
recvPacket >> ID;
Player* pOther = ObjectAccessor::FindPlayer( ID );
if( !pOther )
{
SendTradeStatus(TRADE_STATUS_NO_TARGET);
return;
}
if( pOther == GetPlayer() || pOther->pTrader )
{
SendTradeStatus(TRADE_STATUS_BUSY);
return;
}
if( !pOther->isAlive() )
{
SendTradeStatus(TRADE_STATUS_TARGET_DEAD);
return;
}
if( pOther->isInFlight() )
{
SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
return;
}
if( pOther->hasUnitState(UNIT_STAT_STUNNED) )
{
SendTradeStatus(TRADE_STATUS_TARGET_STUNNED);
return;
}
if( pOther->GetSession()->isLogingOut() )
{
SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT);
return;
}
if( pOther->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow()) )
{
SendTradeStatus(TRADE_STATUS_IGNORE_YOU);
return;
}
if(pOther->GetTeam() !=_player->GetTeam() )
{
SendTradeStatus(TRADE_STATUS_WRONG_FACTION);
return;
}
if( pOther->GetDistance2d( _player ) > 10.0f )
{
SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
return;
}
// OK start trade
_player->pTrader = pOther;
pOther->pTrader =_player;
WorldPacket data(SMSG_TRADE_STATUS, 12);
data << (uint32) TRADE_STATUS_BEGIN_TRADE;
data << (uint64)_player->GetGUID();
_player->pTrader->GetSession()->SendPacket(&data);
}
void WorldSession::HandleSetTradeGoldOpcode(WorldPacket& recvPacket)
{
CHECK_PACKET_SIZE(recvPacket,4);
if(!_player->pTrader)
return;
uint32 gold;
recvPacket >> gold;
// gold can be incorrect, but this is checked at trade finished.
_player->tradeGold = gold;
_player->pTrader->GetSession()->SendUpdateTrade();
}
void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket)
{
CHECK_PACKET_SIZE(recvPacket,1+1+1);
if(!_player->pTrader)
return;
// send update
uint8 tradeSlot;
uint8 bag;
uint8 slot;
recvPacket >> tradeSlot;
recvPacket >> bag;
recvPacket >> slot;
// invalid slot number
if(tradeSlot >= TRADE_SLOT_COUNT)
{
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
return;
}
// check cheating, can't fail with correct client operations
Item* item = _player->GetItemByPos(bag,slot);
if(!item || tradeSlot!=TRADE_SLOT_NONTRADED && !item->CanBeTraded())
{
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
return;
}
uint16 pos = (bag << 8) | slot;
// prevent place single item into many trade slots using cheating and client bugs
for(int i = 0; i < TRADE_SLOT_COUNT; ++i)
{
if(_player->tradeItems[i]==pos)
{
// cheating attempt
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
return;
}
}
_player->tradeItems[tradeSlot] = pos;
_player->pTrader->GetSession()->SendUpdateTrade();
}
void WorldSession::HandleClearTradeItemOpcode(WorldPacket& recvPacket)
{
CHECK_PACKET_SIZE(recvPacket,1);
if(!_player->pTrader)
return;
uint8 tradeSlot;
recvPacket >> tradeSlot;
// invalid slot number
if(tradeSlot >= TRADE_SLOT_COUNT)
return;
_player->tradeItems[tradeSlot] = NULL_SLOT;
_player->pTrader->GetSession()->SendUpdateTrade();
}