2 * Copyright (C) 2005-2008 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
) )
196 sLog
.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)",
197 _player
->GetName(),_player
->GetSession()->GetAccountId(),
198 myItems
[i
]->GetProto()->Name1
,myItems
[i
]->GetEntry(),myItems
[i
]->GetCount(),
199 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId());
202 _player
->pTrader
->MoveItemToInventory( traderDst
, myItems
[i
], true, true);
207 sLog
.outDebug("player storing: %u",hisItems
[i
]->GetGUIDLow());
208 if( _player
->pTrader
->GetSession()->GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_GM_LOG_TRADE
) )
209 sLog
.outCommand("GM %s (Account: %u) trade: %s (Entry: %d Count: %u) to player: %s (Account: %u)",
210 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId(),
211 hisItems
[i
]->GetProto()->Name1
,hisItems
[i
]->GetEntry(),hisItems
[i
]->GetCount(),
212 _player
->GetName(),_player
->GetSession()->GetAccountId());
215 _player
->MoveItemToInventory( playerDst
, hisItems
[i
], true, true);
220 // in case of fatal error log error message
221 // return the already removed items to the original owner
225 sLog
.outError("trader can't store item: %u",myItems
[i
]->GetGUIDLow());
226 if(_player
->CanStoreItem( NULL_BAG
, NULL_SLOT
, playerDst
, myItems
[i
], false ) == EQUIP_ERR_OK
)
227 _player
->MoveItemToInventory(playerDst
, myItems
[i
], true, true);
229 sLog
.outError("player can't take item back: %u",myItems
[i
]->GetGUIDLow());
231 // return the already removed items to the original owner
235 sLog
.outError("player can't store item: %u",hisItems
[i
]->GetGUIDLow());
236 if(_player
->pTrader
->CanStoreItem( NULL_BAG
, NULL_SLOT
, traderDst
, hisItems
[i
], false ) == EQUIP_ERR_OK
)
237 _player
->pTrader
->MoveItemToInventory(traderDst
, hisItems
[i
], true, true);
239 sLog
.outError("trader can't take item back: %u",hisItems
[i
]->GetGUIDLow());
245 //==============================================================
247 void WorldSession::HandleAcceptTradeOpcode(WorldPacket
& /*recvPacket*/)
249 Item
*myItems
[TRADE_SLOT_TRADED_COUNT
] = { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
250 Item
*hisItems
[TRADE_SLOT_TRADED_COUNT
] = { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
251 bool myCanCompleteTrade
=true,hisCanCompleteTrade
=true;
253 if ( !GetPlayer()->pTrader
)
256 // not accept case incorrect money amount
257 if( _player
->tradeGold
> _player
->GetMoney() )
259 SendNotification(LANG_NOT_ENOUGH_GOLD
);
260 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
261 _player
->acceptTrade
= false;
265 // not accept case incorrect money amount
266 if( _player
->pTrader
->tradeGold
> _player
->pTrader
->GetMoney() )
268 _player
->pTrader
->GetSession( )->SendNotification(LANG_NOT_ENOUGH_GOLD
);
269 SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
270 _player
->pTrader
->acceptTrade
= false;
274 // not accept if some items now can't be trade (cheating)
275 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; i
++)
277 if(_player
->tradeItems
[i
] != NULL_SLOT
)
279 if(Item
* item
=_player
->GetItemByPos( _player
->tradeItems
[i
] ))
281 if(!item
->CanBeTraded())
283 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
288 if(_player
->pTrader
->tradeItems
[i
] != NULL_SLOT
)
290 if(Item
* item
=_player
->pTrader
->GetItemByPos( _player
->pTrader
->tradeItems
[i
]) )
292 if(!item
->CanBeTraded())
294 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
301 _player
->acceptTrade
= true;
302 if (_player
->pTrader
->acceptTrade
)
304 // inform partner client
305 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT
);
307 // store items in local list and set 'in-trade' flag
308 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; i
++)
310 if(_player
->tradeItems
[i
] != NULL_SLOT
)
312 sLog
.outDebug("player trade item bag: %u slot: %u",_player
->tradeItems
[i
] >> 8, _player
->tradeItems
[i
] & 255 );
314 myItems
[i
]=_player
->GetItemByPos( _player
->tradeItems
[i
] );
316 myItems
[i
]->SetInTrade();
318 if(_player
->pTrader
->tradeItems
[i
] != NULL_SLOT
)
320 sLog
.outDebug("partner trade item bag: %u slot: %u",_player
->pTrader
->tradeItems
[i
] >> 8,_player
->pTrader
->tradeItems
[i
] & 255);
322 hisItems
[i
]=_player
->pTrader
->GetItemByPos( _player
->pTrader
->tradeItems
[i
]);
324 hisItems
[i
]->SetInTrade();
328 // test if item will fit in each inventory
329 hisCanCompleteTrade
= (_player
->pTrader
->CanStoreItems( myItems
,TRADE_SLOT_TRADED_COUNT
)== EQUIP_ERR_OK
);
330 myCanCompleteTrade
= (_player
->CanStoreItems( hisItems
,TRADE_SLOT_TRADED_COUNT
) == EQUIP_ERR_OK
);
332 // clear 'in-trade' flag
333 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; i
++)
335 if(myItems
[i
]) myItems
[i
]->SetInTrade(false);
336 if(hisItems
[i
]) hisItems
[i
]->SetInTrade(false);
339 // in case of missing space report error
340 if(!myCanCompleteTrade
)
342 SendNotification(LANG_NOT_FREE_TRADE_SLOTS
);
343 GetPlayer( )->pTrader
->GetSession( )->SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS
);
344 SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
345 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
348 else if (!hisCanCompleteTrade
)
350 SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS
);
351 GetPlayer()->pTrader
->GetSession()->SendNotification(LANG_NOT_FREE_TRADE_SLOTS
);
352 SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
353 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
357 // execute trade: 1. remove
358 for(int i
=0; i
<TRADE_SLOT_TRADED_COUNT
; i
++)
362 myItems
[i
]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR
,_player
->GetGUID());
363 _player
->MoveItemFromInventory(_player
->tradeItems
[i
] >> 8, _player
->tradeItems
[i
] & 255, true);
367 hisItems
[i
]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR
,_player
->pTrader
->GetGUID());
368 _player
->pTrader
->MoveItemFromInventory(_player
->pTrader
->tradeItems
[i
] >> 8, _player
->pTrader
->tradeItems
[i
] & 255, true);
372 // execute trade: 2. store
373 moveItems(myItems
, hisItems
);
376 if(sWorld
.getConfig(CONFIG_GM_LOG_TRADE
))
378 if( _player
->GetSession()->GetSecurity() > SEC_PLAYER
&& _player
->tradeGold
> 0)
379 sLog
.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)",
380 _player
->GetName(),_player
->GetSession()->GetAccountId(),
382 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId());
383 if( _player
->pTrader
->GetSession()->GetSecurity() > SEC_PLAYER
&& _player
->pTrader
->tradeGold
> 0)
384 sLog
.outCommand("GM %s (Account: %u) give money (Amount: %u) to player: %s (Account: %u)",
385 _player
->pTrader
->GetName(),_player
->pTrader
->GetSession()->GetAccountId(),
386 _player
->pTrader
->tradeGold
,
387 _player
->GetName(),_player
->GetSession()->GetAccountId());
391 _player
->ModifyMoney( -int32(_player
->tradeGold
) );
392 _player
->ModifyMoney(_player
->pTrader
->tradeGold
);
393 _player
->pTrader
->ModifyMoney( -int32(_player
->pTrader
->tradeGold
) );
394 _player
->pTrader
->ModifyMoney(_player
->tradeGold
);
396 _player
->ClearTrade();
397 _player
->pTrader
->ClearTrade();
399 // desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards)
400 CharacterDatabase
.BeginTransaction();
401 _player
->SaveInventoryAndGoldToDB();
402 _player
->pTrader
->SaveInventoryAndGoldToDB();
403 CharacterDatabase
.CommitTransaction();
405 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE
);
406 SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE
);
408 _player
->pTrader
->pTrader
= NULL
;
409 _player
->pTrader
= NULL
;
413 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT
);
417 void WorldSession::HandleUnacceptTradeOpcode(WorldPacket
& /*recvPacket*/)
419 if ( !GetPlayer()->pTrader
)
422 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE
);
423 _player
->acceptTrade
= false;
426 void WorldSession::HandleBeginTradeOpcode(WorldPacket
& /*recvPacket*/)
428 if(!_player
->pTrader
)
431 _player
->pTrader
->GetSession()->SendTradeStatus(TRADE_STATUS_OPEN_WINDOW
);
432 _player
->pTrader
->ClearTrade();
434 SendTradeStatus(TRADE_STATUS_OPEN_WINDOW
);
435 _player
->ClearTrade();
438 void WorldSession::SendCancelTrade()
440 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
443 void WorldSession::HandleCancelTradeOpcode(WorldPacket
& /*recvPacket*/)
445 // sended also after LOGOUT COMPLETE
446 if(_player
) // needed because STATUS_AUTHED
447 _player
->TradeCancel(true);
450 void WorldSession::HandleInitiateTradeOpcode(WorldPacket
& recvPacket
)
452 CHECK_PACKET_SIZE(recvPacket
,8);
454 if( GetPlayer()->pTrader
)
459 if( !GetPlayer()->isAlive() )
461 SendTradeStatus(TRADE_STATUS_YOU_DEAD
);
465 if( GetPlayer()->hasUnitState(UNIT_STAT_STUNNED
) )
467 SendTradeStatus(TRADE_STATUS_YOU_STUNNED
);
473 SendTradeStatus(TRADE_STATUS_YOU_LOGOUT
);
477 if( GetPlayer()->isInFlight() )
479 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR
);
485 Player
* pOther
= ObjectAccessor::FindPlayer( ID
);
489 SendTradeStatus(TRADE_STATUS_NO_TARGET
);
493 if( pOther
== GetPlayer() || pOther
->pTrader
)
495 SendTradeStatus(TRADE_STATUS_BUSY
);
499 if( !pOther
->isAlive() )
501 SendTradeStatus(TRADE_STATUS_TARGET_DEAD
);
505 if( pOther
->isInFlight() )
507 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR
);
511 if( pOther
->hasUnitState(UNIT_STAT_STUNNED
) )
513 SendTradeStatus(TRADE_STATUS_TARGET_STUNNED
);
517 if( pOther
->GetSession()->isLogingOut() )
519 SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT
);
523 if( pOther
->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow()) )
525 SendTradeStatus(TRADE_STATUS_IGNORE_YOU
);
529 if(pOther
->GetTeam() !=_player
->GetTeam() )
531 SendTradeStatus(TRADE_STATUS_WRONG_FACTION
);
535 if( pOther
->GetDistance2d( _player
) > 10.0f
)
537 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR
);
542 _player
->pTrader
= pOther
;
543 pOther
->pTrader
=_player
;
545 WorldPacket
data(SMSG_TRADE_STATUS
, 12);
546 data
<< (uint32
) TRADE_STATUS_BEGIN_TRADE
;
547 data
<< (uint64
)_player
->GetGUID();
548 _player
->pTrader
->GetSession()->SendPacket(&data
);
551 void WorldSession::HandleSetTradeGoldOpcode(WorldPacket
& recvPacket
)
553 CHECK_PACKET_SIZE(recvPacket
,4);
555 if(!_player
->pTrader
)
562 // gold can be incorrect, but this is checked at trade finished.
563 _player
->tradeGold
= gold
;
565 _player
->pTrader
->GetSession()->SendUpdateTrade();
568 void WorldSession::HandleSetTradeItemOpcode(WorldPacket
& recvPacket
)
570 CHECK_PACKET_SIZE(recvPacket
,1+1+1);
572 if(!_player
->pTrader
)
580 recvPacket
>> tradeSlot
;
584 // invalid slot number
585 if(tradeSlot
>= TRADE_SLOT_COUNT
)
587 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
591 // check cheating, can't fail with correct client operations
592 Item
* item
= _player
->GetItemByPos(bag
,slot
);
593 if(!item
|| tradeSlot
!=TRADE_SLOT_NONTRADED
&& !item
->CanBeTraded())
595 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
599 uint16 pos
= (bag
<< 8) | slot
;
601 // prevent place single item into many trade slots using cheating and client bugs
602 for(int i
= 0; i
< TRADE_SLOT_COUNT
; ++i
)
604 if(_player
->tradeItems
[i
]==pos
)
607 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED
);
612 _player
->tradeItems
[tradeSlot
] = pos
;
614 _player
->pTrader
->GetSession()->SendUpdateTrade();
617 void WorldSession::HandleClearTradeItemOpcode(WorldPacket
& recvPacket
)
619 CHECK_PACKET_SIZE(recvPacket
,1);
621 if(!_player
->pTrader
)
625 recvPacket
>> tradeSlot
;
627 // invalid slot number
628 if(tradeSlot
>= TRADE_SLOT_COUNT
)
631 _player
->tradeItems
[tradeSlot
] = NULL_SLOT
;
633 _player
->pTrader
->GetSession()->SendUpdateTrade();