2 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include "WorldPacket.h"
21 #include "WorldSession.h"
23 #include "ObjectAccessor.h"
28 #include "SocialMgr.h"
33 TRADE_STATUS_BUSY
= 0,
34 TRADE_STATUS_BEGIN_TRADE
= 1,
35 TRADE_STATUS_OPEN_WINDOW
= 2,
36 TRADE_STATUS_TRADE_CANCELED
= 3,
37 TRADE_STATUS_TRADE_ACCEPT
= 4,
38 TRADE_STATUS_BUSY_2
= 5,
39 TRADE_STATUS_NO_TARGET
= 6,
40 TRADE_STATUS_BACK_TO_TRADE
= 7,
41 TRADE_STATUS_TRADE_COMPLETE
= 8,
43 TRADE_STATUS_TARGET_TO_FAR
= 10,
44 TRADE_STATUS_WRONG_FACTION
= 11,
45 TRADE_STATUS_CLOSE_WINDOW
= 12,
47 TRADE_STATUS_IGNORE_YOU
= 14,
48 TRADE_STATUS_YOU_STUNNED
= 15,
49 TRADE_STATUS_TARGET_STUNNED
= 16,
50 TRADE_STATUS_YOU_DEAD
= 17,
51 TRADE_STATUS_TARGET_DEAD
= 18,
52 TRADE_STATUS_YOU_LOGOUT
= 19,
53 TRADE_STATUS_TARGET_LOGOUT
= 20,
54 TRADE_STATUS_TRIAL_ACCOUNT
= 21, // Trial accounts can not perform that action
55 TRADE_STATUS_ONLY_CONJURED
= 22 // You can only trade conjured items... (cross realm BG related).
58 void WorldSession::SendTradeStatus(uint32 status
)
64 case TRADE_STATUS_BEGIN_TRADE
:
65 data
.Initialize(SMSG_TRADE_STATUS
, 4+8);
66 data
<< uint32(status
);
69 case TRADE_STATUS_OPEN_WINDOW
:
70 data
.Initialize(SMSG_TRADE_STATUS
, 4+4);
71 data
<< uint32(status
);
72 data
<< uint32(0); // added in 2.4.0
74 case TRADE_STATUS_CLOSE_WINDOW
:
75 data
.Initialize(SMSG_TRADE_STATUS
, 4+4+1+4);
76 data
<< uint32(status
);
81 case TRADE_STATUS_ONLY_CONJURED
:
82 data
.Initialize(SMSG_TRADE_STATUS
, 4+1);
83 data
<< uint32(status
);
87 data
.Initialize(SMSG_TRADE_STATUS
, 4);
88 data
<< uint32(status
);
95 void WorldSession::HandleIgnoreTradeOpcode(WorldPacket
& /*recvPacket*/)
97 sLog
.outDebug( "WORLD: Ignore Trade %u",_player
->GetGUIDLow());
98 // recvPacket.print_storage();
101 void WorldSession::HandleBusyTradeOpcode(WorldPacket
& /*recvPacket*/)
103 sLog
.outDebug( "WORLD: Busy Trade %u",_player
->GetGUIDLow());
104 // recvPacket.print_storage();
107 void WorldSession::SendUpdateTrade()
111 if( !_player
|| !_player
->pTrader
)
114 // reset trade status
115 if (_player
->acceptTrade
)
117 _player
->acceptTrade
= false;
118 SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
121 if (_player
->pTrader
->acceptTrade
)
123 _player
->pTrader
->acceptTrade
= false;
124 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
127 WorldPacket
data(SMSG_TRADE_STATUS_EXTENDED
, (100)); // guess size
128 data
<< (uint8
) 1; // can be different (only seen 0 and 1)
129 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?)
130 data
<< (uint32
) TRADE_SLOT_COUNT
; // trade slots count/number?, = next field in most cases
131 data
<< (uint32
) TRADE_SLOT_COUNT
; // trade slots count/number?, = prev field in most cases
132 data
<< (uint32
) _player
->pTrader
->tradeGold
; // trader gold
133 data
<< (uint32
) 0; // spell casted on lowest slot item
135 for(uint8 i
= 0; i
< TRADE_SLOT_COUNT
; ++i
)
137 item
= (_player
->pTrader
->tradeItems
[i
] != NULL_SLOT
? _player
->pTrader
->GetItemByPos( _player
->pTrader
->tradeItems
[i
] ) : NULL
);
139 data
<< (uint8
) i
; // trade slot number, if not specified, then end of packet
143 data
<< (uint32
) item
->GetProto()->ItemId
; // entry
145 data
<< (uint32
) item
->GetProto()->DisplayInfoID
;
147 data
<< (uint32
) item
->GetUInt32Value(ITEM_FIELD_STACK_COUNT
);
148 data
<< (uint32
) 0; // probably gift=1, created_by=0?
150 data
<< (uint64
) item
->GetUInt64Value(ITEM_FIELD_GIFTCREATOR
);
151 data
<< (uint32
) item
->GetEnchantmentId(PERM_ENCHANTMENT_SLOT
);
152 for(uint8 j
= 0; j
< 3; ++j
)
153 data
<< (uint32
) 0; // enchantment id (permanent/gems?)
155 data
<< (uint64
) item
->GetUInt64Value(ITEM_FIELD_CREATOR
);
156 data
<< (uint32
) item
->GetSpellCharges(); // charges
157 data
<< (uint32
) item
->GetItemSuffixFactor(); // SuffixFactor
158 // random properties id
159 data
<< (uint32
) item
->GetItemRandomPropertyId();
160 data
<< (uint32
) item
->GetProto()->LockID
; // lock id
162 data
<< (uint32
) item
->GetUInt32Value(ITEM_FIELD_MAXDURABILITY
);
164 data
<< (uint32
) item
->GetUInt32Value(ITEM_FIELD_DURABILITY
);
168 for(uint8 j
= 0; j
< 18; j
++)
175 //==============================================================
176 // transfer the items to the players
178 void WorldSession::moveItems(Item
* myItems
[], Item
* hisItems
[])
180 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; ++i
)
182 ItemPosCountVec traderDst
;
183 ItemPosCountVec playerDst
;
184 bool traderCanTrade
= (myItems
[i
]==NULL
|| _player
->pTrader
->CanStoreItem( NULL_BAG
, NULL_SLOT
, traderDst
, myItems
[i
], false ) == EQUIP_ERR_OK
);
185 bool playerCanTrade
= (hisItems
[i
]==NULL
|| _player
->CanStoreItem( NULL_BAG
, NULL_SLOT
, playerDst
, hisItems
[i
], false ) == EQUIP_ERR_OK
);
186 if(traderCanTrade
&& playerCanTrade
)
188 // Ok, if trade item exists and can be stored
189 // If we trade in both directions we had to check, if the trade will work before we actually do it
190 // A roll back is not possible after we stored it
194 sLog
.outDebug("partner storing: %u",myItems
[i
]->GetGUIDLow());
195 if( _player
->GetSession()->GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_GM_LOG_TRADE
) )
197 sLog
.outCommand(_player
->GetSession()->GetAccountId(),"GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)",
198 _player
->GetName(),_player
->GetSession()->GetAccountId(),
199 myItems
[i
]->GetProto()->Name1
,myItems
[i
]->GetEntry(),myItems
[i
]->GetCount(),
200 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId());
204 _player
->pTrader
->MoveItemToInventory( traderDst
, myItems
[i
], true, true);
209 sLog
.outDebug("player storing: %u",hisItems
[i
]->GetGUIDLow());
210 if( _player
->pTrader
->GetSession()->GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_GM_LOG_TRADE
) )
212 sLog
.outCommand(_player
->pTrader
->GetSession()->GetAccountId(),"GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)",
213 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId(),
214 hisItems
[i
]->GetProto()->Name1
,hisItems
[i
]->GetEntry(),hisItems
[i
]->GetCount(),
215 _player
->GetName(),_player
->GetSession()->GetAccountId());
219 _player
->MoveItemToInventory( playerDst
, hisItems
[i
], true, true);
224 // in case of fatal error log error message
225 // return the already removed items to the original owner
229 sLog
.outError("trader can't store item: %u",myItems
[i
]->GetGUIDLow());
230 if(_player
->CanStoreItem( NULL_BAG
, NULL_SLOT
, playerDst
, myItems
[i
], false ) == EQUIP_ERR_OK
)
231 _player
->MoveItemToInventory(playerDst
, myItems
[i
], true, true);
233 sLog
.outError("player can't take item back: %u",myItems
[i
]->GetGUIDLow());
235 // return the already removed items to the original owner
239 sLog
.outError("player can't store item: %u",hisItems
[i
]->GetGUIDLow());
240 if(_player
->pTrader
->CanStoreItem( NULL_BAG
, NULL_SLOT
, traderDst
, hisItems
[i
], false ) == EQUIP_ERR_OK
)
241 _player
->pTrader
->MoveItemToInventory(traderDst
, hisItems
[i
], true, true);
243 sLog
.outError("trader can't take item back: %u",hisItems
[i
]->GetGUIDLow());
249 //==============================================================
251 void WorldSession::HandleAcceptTradeOpcode(WorldPacket
& /*recvPacket*/)
253 Item
*myItems
[TRADE_SLOT_TRADED_COUNT
] = { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
254 Item
*hisItems
[TRADE_SLOT_TRADED_COUNT
] = { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
255 bool myCanCompleteTrade
=true,hisCanCompleteTrade
=true;
257 if ( !GetPlayer()->pTrader
)
260 // not accept case incorrect money amount
261 if( _player
->tradeGold
> _player
->GetMoney() )
263 SendNotification(LANG_NOT_ENOUGH_GOLD
);
264 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
265 _player
->acceptTrade
= false;
269 // not accept case incorrect money amount
270 if( _player
->pTrader
->tradeGold
> _player
->pTrader
->GetMoney() )
272 _player
->pTrader
->GetSession( )->SendNotification(LANG_NOT_ENOUGH_GOLD
);
273 SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
274 _player
->pTrader
->acceptTrade
= false;
278 // not accept if some items now can't be trade (cheating)
279 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; ++i
)
281 if(_player
->tradeItems
[i
] != NULL_SLOT
)
283 if(Item
* item
=_player
->GetItemByPos( _player
->tradeItems
[i
] ))
285 if(!item
->CanBeTraded())
287 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
292 if(_player
->pTrader
->tradeItems
[i
] != NULL_SLOT
)
294 if(Item
* item
=_player
->pTrader
->GetItemByPos( _player
->pTrader
->tradeItems
[i
]) )
296 if(!item
->CanBeTraded())
298 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
305 _player
->acceptTrade
= true;
306 if (_player
->pTrader
->acceptTrade
)
308 // inform partner client
309 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT
);
311 // store items in local list and set 'in-trade' flag
312 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; ++i
)
314 if(_player
->tradeItems
[i
] != NULL_SLOT
)
316 sLog
.outDebug("player trade item bag: %u slot: %u",_player
->tradeItems
[i
] >> 8, _player
->tradeItems
[i
] & 255 );
318 myItems
[i
]=_player
->GetItemByPos( _player
->tradeItems
[i
] );
320 myItems
[i
]->SetInTrade();
322 if(_player
->pTrader
->tradeItems
[i
] != NULL_SLOT
)
324 sLog
.outDebug("partner trade item bag: %u slot: %u",_player
->pTrader
->tradeItems
[i
] >> 8,_player
->pTrader
->tradeItems
[i
] & 255);
326 hisItems
[i
]=_player
->pTrader
->GetItemByPos( _player
->pTrader
->tradeItems
[i
]);
328 hisItems
[i
]->SetInTrade();
332 // test if item will fit in each inventory
333 hisCanCompleteTrade
= (_player
->pTrader
->CanStoreItems( myItems
,TRADE_SLOT_TRADED_COUNT
)== EQUIP_ERR_OK
);
334 myCanCompleteTrade
= (_player
->CanStoreItems( hisItems
,TRADE_SLOT_TRADED_COUNT
) == EQUIP_ERR_OK
);
336 // clear 'in-trade' flag
337 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; ++i
)
339 if(myItems
[i
]) myItems
[i
]->SetInTrade(false);
340 if(hisItems
[i
]) hisItems
[i
]->SetInTrade(false);
343 // in case of missing space report error
344 if(!myCanCompleteTrade
)
346 SendNotification(LANG_NOT_FREE_TRADE_SLOTS
);
347 GetPlayer( )->pTrader
->GetSession( )->SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS
);
348 SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
349 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
352 else if (!hisCanCompleteTrade
)
354 SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS
);
355 GetPlayer()->pTrader
->GetSession()->SendNotification(LANG_NOT_FREE_TRADE_SLOTS
);
356 SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
357 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
361 // execute trade: 1. remove
362 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; ++i
)
366 myItems
[i
]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR
,_player
->GetGUID());
367 _player
->MoveItemFromInventory(_player
->tradeItems
[i
] >> 8, _player
->tradeItems
[i
] & 255, true);
371 hisItems
[i
]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR
,_player
->pTrader
->GetGUID());
372 _player
->pTrader
->MoveItemFromInventory(_player
->pTrader
->tradeItems
[i
] >> 8, _player
->pTrader
->tradeItems
[i
] & 255, true);
376 // execute trade: 2. store
377 moveItems(myItems
, hisItems
);
380 if(sWorld
.getConfig(CONFIG_GM_LOG_TRADE
))
382 if( _player
->GetSession()->GetSecurity() > SEC_PLAYER
&& _player
->tradeGold
> 0)
384 sLog
.outCommand(_player
->GetSession()->GetAccountId(),"GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)",
385 _player
->GetName(),_player
->GetSession()->GetAccountId(),
387 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId());
389 if( _player
->pTrader
->GetSession()->GetSecurity() > SEC_PLAYER
&& _player
->pTrader
->tradeGold
> 0)
391 sLog
.outCommand(_player
->pTrader
->GetSession()->GetAccountId(),"GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)",
392 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId(),
393 _player
->pTrader
->tradeGold
,
394 _player
->GetName(),_player
->GetSession()->GetAccountId());
399 _player
->ModifyMoney( -int32(_player
->tradeGold
) );
400 _player
->ModifyMoney(_player
->pTrader
->tradeGold
);
401 _player
->pTrader
->ModifyMoney( -int32(_player
->pTrader
->tradeGold
) );
402 _player
->pTrader
->ModifyMoney(_player
->tradeGold
);
404 _player
->ClearTrade();
405 _player
->pTrader
->ClearTrade();
407 // desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards)
408 CharacterDatabase
.BeginTransaction();
409 _player
->SaveInventoryAndGoldToDB();
410 _player
->pTrader
->SaveInventoryAndGoldToDB();
411 CharacterDatabase
.CommitTransaction();
413 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE
);
414 SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE
);
416 _player
->pTrader
->pTrader
= NULL
;
417 _player
->pTrader
= NULL
;
421 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT
);
425 void WorldSession::HandleUnacceptTradeOpcode(WorldPacket
& /*recvPacket*/)
427 if ( !GetPlayer()->pTrader
)
430 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
431 _player
->acceptTrade
= false;
434 void WorldSession::HandleBeginTradeOpcode(WorldPacket
& /*recvPacket*/)
436 if(!_player
->pTrader
)
439 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_OPEN_WINDOW
);
440 _player
->pTrader
->ClearTrade();
442 SendTradeStatus(TRADE_STATUS_OPEN_WINDOW
);
443 _player
->ClearTrade();
446 void WorldSession::SendCancelTrade()
448 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
451 void WorldSession::HandleCancelTradeOpcode(WorldPacket
& /*recvPacket*/)
453 // sended also after LOGOUT COMPLETE
454 if(_player
) // needed because STATUS_AUTHED
455 _player
->TradeCancel(true);
458 void WorldSession::HandleInitiateTradeOpcode(WorldPacket
& recvPacket
)
460 CHECK_PACKET_SIZE(recvPacket
,8);
462 if (GetPlayer()->pTrader
)
467 if (!GetPlayer()->isAlive())
469 SendTradeStatus(TRADE_STATUS_YOU_DEAD
);
473 if (GetPlayer()->hasUnitState(UNIT_STAT_STUNNED
))
475 SendTradeStatus(TRADE_STATUS_YOU_STUNNED
);
481 SendTradeStatus(TRADE_STATUS_YOU_LOGOUT
);
485 if (GetPlayer()->isInFlight())
487 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR
);
493 Player
* pOther
= ObjectAccessor::FindPlayer( ID
);
497 SendTradeStatus(TRADE_STATUS_NO_TARGET
);
501 if (pOther
== GetPlayer() || pOther
->pTrader
)
503 SendTradeStatus(TRADE_STATUS_BUSY
);
507 if (!pOther
->isAlive())
509 SendTradeStatus(TRADE_STATUS_TARGET_DEAD
);
513 if (pOther
->isInFlight())
515 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR
);
519 if (pOther
->hasUnitState(UNIT_STAT_STUNNED
))
521 SendTradeStatus(TRADE_STATUS_TARGET_STUNNED
);
525 if (pOther
->GetSession()->isLogingOut())
527 SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT
);
531 if (pOther
->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow()))
533 SendTradeStatus(TRADE_STATUS_IGNORE_YOU
);
537 if (pOther
->GetTeam() !=_player
->GetTeam() )
539 SendTradeStatus(TRADE_STATUS_WRONG_FACTION
);
543 if (!pOther
->IsWithinDistInMap(_player
,10.0f
,false))
545 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR
);
550 _player
->pTrader
= pOther
;
551 pOther
->pTrader
=_player
;
553 WorldPacket
data(SMSG_TRADE_STATUS
, 12);
554 data
<< (uint32
) TRADE_STATUS_BEGIN_TRADE
;
555 data
<< (uint64
)_player
->GetGUID();
556 _player
->pTrader
->GetSession()->SendPacket(&data
);
559 void WorldSession::HandleSetTradeGoldOpcode(WorldPacket
& recvPacket
)
561 CHECK_PACKET_SIZE(recvPacket
,4);
563 if(!_player
->pTrader
)
570 // gold can be incorrect, but this is checked at trade finished.
571 _player
->tradeGold
= gold
;
573 _player
->pTrader
->GetSession()->SendUpdateTrade();
576 void WorldSession::HandleSetTradeItemOpcode(WorldPacket
& recvPacket
)
578 CHECK_PACKET_SIZE(recvPacket
,1+1+1);
580 if(!_player
->pTrader
)
588 recvPacket
>> tradeSlot
;
592 // invalid slot number
593 if(tradeSlot
>= TRADE_SLOT_COUNT
)
595 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
599 // check cheating, can't fail with correct client operations
600 Item
* item
= _player
->GetItemByPos(bag
,slot
);
601 if(!item
|| tradeSlot
!=TRADE_SLOT_NONTRADED
&& !item
->CanBeTraded())
603 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
607 uint16 pos
= (bag
<< 8) | slot
;
609 // prevent place single item into many trade slots using cheating and client bugs
610 for(int i
= 0; i
< TRADE_SLOT_COUNT
; ++i
)
612 if(_player
->tradeItems
[i
]==pos
)
615 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
620 _player
->tradeItems
[tradeSlot
] = pos
;
622 _player
->pTrader
->GetSession()->SendUpdateTrade();
625 void WorldSession::HandleClearTradeItemOpcode(WorldPacket
& recvPacket
)
627 CHECK_PACKET_SIZE(recvPacket
,1);
629 if(!_player
->pTrader
)
633 recvPacket
>> tradeSlot
;
635 // invalid slot number
636 if(tradeSlot
>= TRADE_SLOT_COUNT
)
639 _player
->tradeItems
[tradeSlot
] = NULL_SLOT
;
641 _player
->pTrader
->GetSession()->SendUpdateTrade();