[6982] Implemented gmlevel-based command security
[getmangos.git] / src / game / TradeHandler.cpp
blob601807eef8e67ae4066b0622264730a6cd773d1b
1 /*
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
19 #include "Common.h"
20 #include "WorldPacket.h"
21 #include "WorldSession.h"
22 #include "World.h"
23 #include "ObjectAccessor.h"
24 #include "Log.h"
25 #include "Opcodes.h"
26 #include "Player.h"
27 #include "Item.h"
28 #include "SocialMgr.h"
29 #include "Language.h"
31 enum TradeStatus
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,
42 // 9?
43 TRADE_STATUS_TARGET_TO_FAR = 10,
44 TRADE_STATUS_WRONG_FACTION = 11,
45 TRADE_STATUS_CLOSE_WINDOW = 12,
46 // 13?
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)
60 WorldPacket data;
62 switch(status)
64 case TRADE_STATUS_BEGIN_TRADE:
65 data.Initialize(SMSG_TRADE_STATUS, 4+8);
66 data << uint32(status);
67 data << uint64(0);
68 break;
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
73 break;
74 case TRADE_STATUS_CLOSE_WINDOW:
75 data.Initialize(SMSG_TRADE_STATUS, 4+4+1+4);
76 data << uint32(status);
77 data << uint32(0);
78 data << uint8(0);
79 data << uint32(0);
80 break;
81 case TRADE_STATUS_ONLY_CONJURED:
82 data.Initialize(SMSG_TRADE_STATUS, 4+1);
83 data << uint32(status);
84 data << uint8(0);
85 break;
86 default:
87 data.Initialize(SMSG_TRADE_STATUS, 4);
88 data << uint32(status);
89 break;
92 SendPacket(&data);
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()
109 Item *item = NULL;
111 if( !_player || !_player->pTrader )
112 return;
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
141 if(item)
143 data << (uint32) item->GetProto()->ItemId; // entry
144 // display id
145 data << (uint32) item->GetProto()->DisplayInfoID;
146 // stack count
147 data << (uint32) item->GetUInt32Value(ITEM_FIELD_STACK_COUNT);
148 data << (uint32) 0; // probably gift=1, created_by=0?
149 // gift creator
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?)
154 // creator
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
161 // max durability
162 data << (uint32) item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
163 // durability
164 data << (uint32) item->GetUInt32Value(ITEM_FIELD_DURABILITY);
166 else
168 for(uint8 j = 0; j < 18; j++)
169 data << uint32(0);
172 SendPacket(&data);
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
191 if(myItems[i])
193 // logging
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());
203 // store
204 _player->pTrader->MoveItemToInventory( traderDst, myItems[i], true, true);
206 if(hisItems[i])
208 // logging
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());
218 // store
219 _player->MoveItemToInventory( playerDst, hisItems[i], true, true);
222 else
224 // in case of fatal error log error message
225 // return the already removed items to the original owner
226 if(myItems[i])
228 if(!traderCanTrade)
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);
232 else
233 sLog.outError("player can't take item back: %u",myItems[i]->GetGUIDLow());
235 // return the already removed items to the original owner
236 if(hisItems[i])
238 if(!playerCanTrade)
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);
242 else
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 )
258 return;
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;
266 return;
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;
275 return;
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);
288 return;
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);
299 return;
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 );
317 //Can return NULL
318 myItems[i]=_player->GetItemByPos( _player->tradeItems[i] );
319 if (myItems[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);
325 //Can return NULL
326 hisItems[i]=_player->pTrader->GetItemByPos( _player->pTrader->tradeItems[i]);
327 if(hisItems[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);
350 return;
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);
358 return;
361 // execute trade: 1. remove
362 for(int i=0; i<TRADE_SLOT_TRADED_COUNT; i++)
364 if(myItems[i])
366 myItems[i]->SetUInt64Value( ITEM_FIELD_GIFTCREATOR,_player->GetGUID());
367 _player->MoveItemFromInventory(_player->tradeItems[i] >> 8, _player->tradeItems[i] & 255, true);
369 if(hisItems[i])
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);
379 // logging money
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(),
386 _player->tradeGold,
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());
398 // update money
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;
419 else
421 _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT);
425 void WorldSession::HandleUnacceptTradeOpcode(WorldPacket& /*recvPacket*/)
427 if ( !GetPlayer()->pTrader )
428 return;
430 _player->pTrader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE);
431 _player->acceptTrade = false;
434 void WorldSession::HandleBeginTradeOpcode(WorldPacket& /*recvPacket*/)
436 if(!_player->pTrader)
437 return;
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 )
463 return;
465 uint64 ID;
467 if( !GetPlayer()->isAlive() )
469 SendTradeStatus(TRADE_STATUS_YOU_DEAD);
470 return;
473 if( GetPlayer()->hasUnitState(UNIT_STAT_STUNNED) )
475 SendTradeStatus(TRADE_STATUS_YOU_STUNNED);
476 return;
479 if( isLogingOut() )
481 SendTradeStatus(TRADE_STATUS_YOU_LOGOUT);
482 return;
485 if( GetPlayer()->isInFlight() )
487 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
488 return;
491 recvPacket >> ID;
493 Player* pOther = ObjectAccessor::FindPlayer( ID );
495 if( !pOther )
497 SendTradeStatus(TRADE_STATUS_NO_TARGET);
498 return;
501 if( pOther == GetPlayer() || pOther->pTrader )
503 SendTradeStatus(TRADE_STATUS_BUSY);
504 return;
507 if( !pOther->isAlive() )
509 SendTradeStatus(TRADE_STATUS_TARGET_DEAD);
510 return;
513 if( pOther->isInFlight() )
515 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
516 return;
519 if( pOther->hasUnitState(UNIT_STAT_STUNNED) )
521 SendTradeStatus(TRADE_STATUS_TARGET_STUNNED);
522 return;
525 if( pOther->GetSession()->isLogingOut() )
527 SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT);
528 return;
531 if( pOther->GetSocial()->HasIgnore(GetPlayer()->GetGUIDLow()) )
533 SendTradeStatus(TRADE_STATUS_IGNORE_YOU);
534 return;
537 if(pOther->GetTeam() !=_player->GetTeam() )
539 SendTradeStatus(TRADE_STATUS_WRONG_FACTION);
540 return;
543 if( pOther->GetDistance2d( _player ) > 10.0f )
545 SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
546 return;
549 // OK start trade
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)
564 return;
566 uint32 gold;
568 recvPacket >> gold;
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)
581 return;
583 // send update
584 uint8 tradeSlot;
585 uint8 bag;
586 uint8 slot;
588 recvPacket >> tradeSlot;
589 recvPacket >> bag;
590 recvPacket >> slot;
592 // invalid slot number
593 if(tradeSlot >= TRADE_SLOT_COUNT)
595 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
596 return;
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);
604 return;
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)
614 // cheating attempt
615 SendTradeStatus(TRADE_STATUS_TRADE_CANCELED);
616 return;
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)
630 return;
632 uint8 tradeSlot;
633 recvPacket >> tradeSlot;
635 // invalid slot number
636 if(tradeSlot >= TRADE_SLOT_COUNT)
637 return;
639 _player->tradeItems[tradeSlot] = NULL_SLOT;
641 _player->pTrader->GetSession()->SendUpdateTrade();