2008-10-02 16:23:55 -05:00
/*
2009-02-04 12:42:26 +01:00
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
2008-10-14 11:57:03 -05:00
*
2009-02-04 12:04:12 +01:00
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
2008-10-02 16:23:55 -05:00
*
* 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
2008-10-14 11:57:03 -05:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2008-10-02 16:23:55 -05:00
* 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
2008-10-14 11:57:03 -05:00
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2008-10-02 16:23:55 -05:00
*/
#include "Common.h"
#include "WorldPacket.h"
#include "Log.h"
#include "Corpse.h"
#include "GameObject.h"
#include "Player.h"
#include "ObjectAccessor.h"
#include "WorldSession.h"
#include "LootMgr.h"
#include "Object.h"
#include "Group.h"
#include "World.h"
#include "Util.h"
void WorldSession :: HandleAutostoreLootItemOpcode ( WorldPacket & recv_data )
{
CHECK_PACKET_SIZE ( recv_data , 1 );
sLog . outDebug ( "WORLD: CMSG_AUTOSTORE_LOOT_ITEM" );
Player * player = GetPlayer ();
uint64 lguid = player -> GetLootGUID ();
Loot * loot ;
uint8 lootSlot ;
recv_data >> lootSlot ;
if ( IS_GAMEOBJECT_GUID ( lguid ))
{
2009-04-20 20:28:19 -05:00
GameObject * go = player -> GetMap () -> GetGameObject ( lguid );
2008-10-02 16:23:55 -05:00
// not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO
if ( ! go || ( go -> GetOwnerGUID () != _player -> GetGUID () && go -> GetGoType () != GAMEOBJECT_TYPE_FISHINGHOLE ) && ! go -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE ))
{
player -> SendLootRelease ( lguid );
return ;
}
loot = & go -> loot ;
}
else if ( IS_ITEM_GUID ( lguid ))
{
Item * pItem = player -> GetItemByGuid ( lguid );
if ( ! pItem )
{
player -> SendLootRelease ( lguid );
return ;
}
loot = & pItem -> loot ;
}
2008-11-21 19:45:49 -06:00
else if ( IS_CORPSE_GUID ( lguid ))
{
Corpse * bones = ObjectAccessor :: GetCorpse ( * player , lguid );
if ( ! bones )
{
player -> SendLootRelease ( lguid );
return ;
}
loot = & bones -> loot ;
}
2008-10-02 16:23:55 -05:00
else
{
2009-04-20 20:28:19 -05:00
Creature * pCreature = GetPlayer () -> GetMap () -> GetCreature ( lguid );
2008-10-02 16:23:55 -05:00
bool ok_loot = pCreature && pCreature -> isAlive () == ( player -> getClass () == CLASS_ROGUE && pCreature -> lootForPickPocketed );
if ( ! ok_loot || ! pCreature -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE ) )
{
player -> SendLootRelease ( lguid );
return ;
}
loot = & pCreature -> loot ;
}
QuestItem * qitem = NULL ;
QuestItem * ffaitem = NULL ;
QuestItem * conditem = NULL ;
LootItem * item = loot -> LootItemInSlot ( lootSlot , player , & qitem , & ffaitem , & conditem );
if ( ! item )
{
player -> SendEquipError ( EQUIP_ERR_ALREADY_LOOTED , NULL , NULL );
return ;
}
// questitems use the blocked field for other purposes
if ( ! qitem && item -> is_blocked )
{
player -> SendLootRelease ( lguid );
return ;
}
ItemPosCountVec dest ;
uint8 msg = player -> CanStoreNewItem ( NULL_BAG , NULL_SLOT , dest , item -> itemid , item -> count );
if ( msg == EQUIP_ERR_OK )
{
Item * newitem = player -> StoreNewItem ( dest , item -> itemid , true , item -> randomPropertyId );
if ( qitem )
{
qitem -> is_looted = true ;
//freeforall is 1 if everyone's supposed to get the quest item.
if ( item -> freeforall || loot -> GetPlayerQuestItems (). size () == 1 )
player -> SendNotifyLootItemRemoved ( lootSlot );
else
loot -> NotifyQuestItemRemoved ( qitem -> index );
}
else
{
if ( ffaitem )
{
//freeforall case, notify only one player of the removal
ffaitem -> is_looted = true ;
player -> SendNotifyLootItemRemoved ( lootSlot );
}
else
{
//not freeforall, notify everyone
if ( conditem )
conditem -> is_looted = true ;
loot -> NotifyItemRemoved ( lootSlot );
}
}
//if only one person is supposed to loot the item, then set it to looted
if ( ! item -> freeforall )
item -> is_looted = true ;
-- loot -> unlootedCount ;
player -> SendNewItem ( newitem , uint32 ( item -> count ), false , false , true );
2008-12-24 09:58:26 -06:00
player -> GetAchievementMgr (). UpdateAchievementCriteria ( ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM , item -> itemid , item -> count );
2008-10-02 16:23:55 -05:00
}
else
player -> SendEquipError ( msg , NULL , NULL );
}
void WorldSession :: HandleLootMoneyOpcode ( WorldPacket & /*recv_data*/ )
{
sLog . outDebug ( "WORLD: CMSG_LOOT_MONEY" );
Player * player = GetPlayer ();
uint64 guid = player -> GetLootGUID ();
if ( ! guid )
return ;
Loot * pLoot = NULL ;
switch ( GUID_HIPART ( guid ))
{
case HIGHGUID_GAMEOBJECT :
{
2009-04-20 20:28:19 -05:00
GameObject * pGameObject = GetPlayer () -> GetMap () -> GetGameObject ( guid );
2008-10-02 16:23:55 -05:00
// not check distance for GO in case owned GO (fishing bobber case, for example)
if ( pGameObject && ( pGameObject -> GetOwnerGUID () == _player -> GetGUID () || pGameObject -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE )) )
pLoot = & pGameObject -> loot ;
break ;
}
case HIGHGUID_CORPSE : // remove insignia ONLY in BG
{
Corpse * bones = ObjectAccessor :: GetCorpse ( * GetPlayer (), guid );
if ( bones && bones -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE ) )
pLoot = & bones -> loot ;
break ;
}
case HIGHGUID_ITEM :
{
if ( Item * item = GetPlayer () -> GetItemByGuid ( guid ))
pLoot = & item -> loot ;
break ;
}
case HIGHGUID_UNIT :
{
2009-04-20 20:28:19 -05:00
Creature * pCreature = GetPlayer () -> GetMap () -> GetCreature ( guid );
2008-10-02 16:23:55 -05:00
bool ok_loot = pCreature && pCreature -> isAlive () == ( player -> getClass () == CLASS_ROGUE && pCreature -> lootForPickPocketed );
if ( ok_loot && pCreature -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE ) )
pLoot = & pCreature -> loot ;
break ;
}
default :
return ; // unlootable type
}
if ( pLoot )
{
if ( ! IS_ITEM_GUID ( guid ) && player -> GetGroup ()) //item can be looted only single player
{
Group * group = player -> GetGroup ();
std :: vector < Player *> playersNear ;
for ( GroupReference * itr = group -> GetFirstMember (); itr != NULL ; itr = itr -> next ())
{
Player * playerGroup = itr -> getSource ();
if ( ! playerGroup )
continue ;
if ( player -> GetDistance2d ( playerGroup ) < sWorld . getConfig ( CONFIG_GROUP_XP_DISTANCE ))
playersNear . push_back ( playerGroup );
}
uint32 money_per_player = uint32 (( pLoot -> gold ) / ( playersNear . size ()));
2009-04-29 00:26:07 -05:00
for ( std :: vector < Player *>:: const_iterator i = playersNear . begin (); i != playersNear . end (); ++ i )
2008-10-02 16:23:55 -05:00
{
( * i ) -> ModifyMoney ( money_per_player );
2009-03-05 09:22:43 -06:00
( * i ) -> GetAchievementMgr (). UpdateAchievementCriteria ( ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY , money_per_player );
2008-10-02 16:23:55 -05:00
//Offset surely incorrect, but works
WorldPacket data ( SMSG_LOOT_MONEY_NOTIFY , 4 );
data << uint32 ( money_per_player );
( * i ) -> GetSession () -> SendPacket ( & data );
}
}
else
2009-03-05 09:22:43 -06:00
{
2008-10-02 16:23:55 -05:00
player -> ModifyMoney ( pLoot -> gold );
2009-03-05 09:22:43 -06:00
player -> GetAchievementMgr (). UpdateAchievementCriteria ( ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY , pLoot -> gold );
}
2008-10-02 16:23:55 -05:00
pLoot -> gold = 0 ;
pLoot -> NotifyMoneyRemoved ();
}
}
void WorldSession :: HandleLootOpcode ( WorldPacket & recv_data )
{
CHECK_PACKET_SIZE ( recv_data , 8 );
sLog . outDebug ( "WORLD: CMSG_LOOT" );
uint64 guid ;
recv_data >> guid ;
GetPlayer () -> SendLoot ( guid , LOOT_CORPSE );
}
void WorldSession :: HandleLootReleaseOpcode ( WorldPacket & recv_data )
{
CHECK_PACKET_SIZE ( recv_data , 8 );
sLog . outDebug ( "WORLD: CMSG_LOOT_RELEASE" );
// cheaters can modify lguid to prevent correct apply loot release code and re-loot
// use internal stored guid
//uint64 lguid;
//recv_data >> lguid;
if ( uint64 lguid = GetPlayer () -> GetLootGUID ())
DoLootRelease ( lguid );
}
void WorldSession :: DoLootRelease ( uint64 lguid )
{
Player * player = GetPlayer ();
Loot * loot ;
player -> SetLootGUID ( 0 );
player -> SendLootRelease ( lguid );
player -> RemoveFlag ( UNIT_FIELD_FLAGS , UNIT_FLAG_LOOTING );
2009-04-20 20:28:19 -05:00
if ( ! player -> IsInWorld ())
return ;
2008-10-02 16:23:55 -05:00
if ( IS_GAMEOBJECT_GUID ( lguid ))
{
2009-04-20 20:28:19 -05:00
GameObject * go = GetPlayer () -> GetMap () -> GetGameObject ( lguid );
2008-10-02 16:23:55 -05:00
// not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO
if ( ! go || ( go -> GetOwnerGUID () != _player -> GetGUID () && go -> GetGoType () != GAMEOBJECT_TYPE_FISHINGHOLE ) && ! go -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE ))
return ;
loot = & go -> loot ;
if ( go -> GetGoType () == GAMEOBJECT_TYPE_DOOR )
{
// locked doors are opened with spelleffect openlock, prevent remove its as looted
go -> UseDoorOrButton ();
}
else if ( loot -> isLooted () || go -> GetGoType () == GAMEOBJECT_TYPE_FISHINGNODE )
{
// GO is mineral vein? so it is not removed after its looted
if ( go -> GetGoType () == GAMEOBJECT_TYPE_CHEST )
{
uint32 go_min = go -> GetGOInfo () -> chest . minSuccessOpens ;
uint32 go_max = go -> GetGOInfo () -> chest . maxSuccessOpens ;
// only vein pass this check
if ( go_min != 0 && go_max > go_min )
{
float amount_rate = sWorld . getRate ( RATE_MINING_AMOUNT );
float min_amount = go_min * amount_rate ;
float max_amount = go_max * amount_rate ;
go -> AddUse ();
float uses = float ( go -> GetUseCount ());
if ( uses < max_amount )
{
if ( uses >= min_amount )
{
float chance_rate = sWorld . getRate ( RATE_MINING_NEXT );
int32 ReqValue = 175 ;
LockEntry const * lockInfo = sLockStore . LookupEntry ( go -> GetGOInfo () -> chest . lockId );
if ( lockInfo )
2008-12-24 09:58:26 -06:00
ReqValue = lockInfo -> Skill [ 0 ];
2008-10-02 16:23:55 -05:00
float skill = float ( player -> GetSkillValue ( SKILL_MINING )) / ( ReqValue + 25 );
double chance = pow ( 0.8 * chance_rate , 4 * ( 1 / double ( max_amount )) * double ( uses ));
if ( roll_chance_f ( 100 * chance + skill ))
{
go -> SetLootState ( GO_READY );
}
else // not have more uses
go -> SetLootState ( GO_JUST_DEACTIVATED );
}
else // 100% chance until min uses
go -> SetLootState ( GO_READY );
}
else // max uses already
go -> SetLootState ( GO_JUST_DEACTIVATED );
}
else // not vein
go -> SetLootState ( GO_JUST_DEACTIVATED );
}
else if ( go -> GetGoType () == GAMEOBJECT_TYPE_FISHINGHOLE )
{ // The fishing hole used once more
go -> AddUse (); // if the max usage is reached, will be despawned in next tick
if ( go -> GetUseCount () >= irand ( go -> GetGOInfo () -> fishinghole . minSuccessOpens , go -> GetGOInfo () -> fishinghole . maxSuccessOpens ))
{
go -> SetLootState ( GO_JUST_DEACTIVATED );
}
else
go -> SetLootState ( GO_READY );
}
else // not chest (or vein/herb/etc)
go -> SetLootState ( GO_JUST_DEACTIVATED );
loot -> clear ();
}
else
// not fully looted object
go -> SetLootState ( GO_ACTIVATED );
}
else if ( IS_CORPSE_GUID ( lguid )) // ONLY remove insignia at BG
{
Corpse * corpse = ObjectAccessor :: GetCorpse ( * player , lguid );
if ( ! corpse || ! corpse -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE ) )
return ;
loot = & corpse -> loot ;
if ( loot -> isLooted ())
{
loot -> clear ();
corpse -> RemoveFlag ( CORPSE_FIELD_DYNAMIC_FLAGS , CORPSE_DYNFLAG_LOOTABLE );
}
}
else if ( IS_ITEM_GUID ( lguid ))
{
Item * pItem = player -> GetItemByGuid ( lguid );
if ( ! pItem )
return ;
2009-01-12 10:53:53 -06:00
ItemPrototype const * proto = pItem -> GetProto ();
// destroy only 5 items from stack in case prospecting and milling
if ( ( proto -> BagFamily & ( BAG_FAMILY_MASK_MINING_SUPP | BAG_FAMILY_MASK_HERBS )) &&
proto -> Class == ITEM_CLASS_TRADE_GOODS )
2008-10-02 16:23:55 -05:00
{
pItem -> m_lootGenerated = false ;
pItem -> loot . clear ();
2009-01-12 10:53:53 -06:00
uint32 count = pItem -> GetCount ();
// >=5 checked in spell code, but will work for cheating cases also with removing from another stacks.
if ( count > 5 )
count = 5 ;
2008-10-02 16:23:55 -05:00
player -> DestroyItemCount ( pItem , count , true );
}
else
// FIXME: item don't must be deleted in case not fully looted state. But this pre-request implement loot saving in DB at item save. Or checting possible.
player -> DestroyItem ( pItem -> GetBagSlot (), pItem -> GetSlot (), true );
return ; // item can be looted only single player
}
else
{
2009-04-20 20:28:19 -05:00
Creature * pCreature = GetPlayer () -> GetMap () -> GetCreature ( lguid );
2008-10-02 16:23:55 -05:00
bool ok_loot = pCreature && pCreature -> isAlive () == ( player -> getClass () == CLASS_ROGUE && pCreature -> lootForPickPocketed );
if ( ! ok_loot || ! pCreature -> IsWithinDistInMap ( _player , INTERACTION_DISTANCE ) )
return ;
loot = & pCreature -> loot ;
// update next looter
if ( Player * recipient = pCreature -> GetLootRecipient ())
if ( Group * group = recipient -> GetGroup ())
if ( group -> GetLooterGuid () == player -> GetGUID ())
group -> UpdateLooterGuid ( pCreature );
if ( loot -> isLooted ())
{
// skip pickpocketing loot for speed, skinning timer redunction is no-op in fact
if ( ! pCreature -> isAlive ())
pCreature -> AllLootRemovedFromCorpse ();
pCreature -> RemoveFlag ( UNIT_DYNAMIC_FLAGS , UNIT_DYNFLAG_LOOTABLE );
loot -> clear ();
}
}
//Player is not looking at loot list, he doesn't need to see updates on the loot list
loot -> RemoveLooter ( player -> GetGUID ());
}
void WorldSession :: HandleLootMasterGiveOpcode ( WorldPacket & recv_data )
{
CHECK_PACKET_SIZE ( recv_data , 8 + 1 + 8 );
uint8 slotid ;
uint64 lootguid , target_playerguid ;
recv_data >> lootguid >> slotid >> target_playerguid ;
if ( ! _player -> GetGroup () || _player -> GetGroup () -> GetLooterGuid () != _player -> GetGUID ())
{
_player -> SendLootRelease ( GetPlayer () -> GetLootGUID ());
return ;
}
Player * target = ObjectAccessor :: FindPlayer ( MAKE_NEW_GUID ( target_playerguid , 0 , HIGHGUID_PLAYER ));
if ( ! target )
return ;
sLog . outDebug ( "WorldSession::HandleLootMasterGiveOpcode (CMSG_LOOT_MASTER_GIVE, 0x02A3) Target = [%s]." , target -> GetName ());
if ( _player -> GetLootGUID () != lootguid )
return ;
Loot * pLoot = NULL ;
if ( IS_CREATURE_GUID ( GetPlayer () -> GetLootGUID ()))
{
2009-04-20 20:28:19 -05:00
Creature * pCreature = GetPlayer () -> GetMap () -> GetCreature ( lootguid );
2008-10-02 16:23:55 -05:00
if ( ! pCreature )
return ;
pLoot = & pCreature -> loot ;
}
else if ( IS_GAMEOBJECT_GUID ( GetPlayer () -> GetLootGUID ()))
{
2009-04-20 20:28:19 -05:00
GameObject * pGO = GetPlayer () -> GetMap () -> GetGameObject ( lootguid );
2008-10-02 16:23:55 -05:00
if ( ! pGO )
return ;
pLoot = & pGO -> loot ;
}
if ( ! pLoot )
return ;
if ( slotid > pLoot -> items . size ())
{
2009-03-24 17:38:14 -06:00
sLog . outDebug ( "AutoLootItem: Player %s might be using a hack! (slot %d, size %lu)" , GetPlayer () -> GetName (), slotid , ( unsigned long ) pLoot -> items . size ());
2008-10-02 16:23:55 -05:00
return ;
}
LootItem & item = pLoot -> items [ slotid ];
ItemPosCountVec dest ;
uint8 msg = target -> CanStoreNewItem ( NULL_BAG , NULL_SLOT , dest , item . itemid , item . count );
if ( msg != EQUIP_ERR_OK )
{
target -> SendEquipError ( msg , NULL , NULL );
_player -> SendEquipError ( msg , NULL , NULL ); // send duplicate of error massage to master looter
return ;
}
// not move item from loot to target inventory
Item * newitem = target -> StoreNewItem ( dest , item . itemid , true , item . randomPropertyId );
target -> SendNewItem ( newitem , uint32 ( item . count ), false , false , true );
2008-12-24 09:58:26 -06:00
target -> GetAchievementMgr (). UpdateAchievementCriteria ( ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM , item . itemid , item . count );
2008-10-02 16:23:55 -05:00
// mark as looted
item . count = 0 ;
item . is_looted = true ;
pLoot -> NotifyItemRemoved ( slotid );
-- pLoot -> unlootedCount ;
}
2009-02-17 20:07:49 -06:00