/*
* Ascent MMORPG Server
* Copyright (C) 2005-2007 Ascent Team
*
* 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 3 of the License, or
* 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, see .
*
*/
#include "StdAfx.h"
initialiseSingleton(LootMgr);
struct loot_tb
{
uint32 itemid;
float chance;
};
bool Rand(float chance)
{
int32 val = sRand.randInt(10000);
int32 p = int32(chance * 100);
return p >= val;
}
template // works for anything that has the field 'chance' and is stored in plain array
const T& RandomChoice( const T* variant, int count )
{
float totalChance = 0;
for( int i = 0; i < count; i++)
totalChance += variant[i].chance;
float val = sRand.rand(totalChance);
for( int i = 0; i < count; i++)
{
val -= variant[i].chance;
if (val <= 0) return variant[i];
}
// should not come here, buf if it does, we should return something reasonable
return variant[count-1];
}
LootMgr::LootMgr()
{}
void LootMgr::LoadLoot()
{
//THIS MUST BE CALLED AFTER LOADING OF ITEMS
LoadLootProp(0);
LoadLootTables("creatureloot",&CreatureLoot);
LoadLootTables("objectloot",&GOLoot);
LoadLootTables("skinningloot",&SkinningLoot);
LoadLootTables("fishingloot",&FishingLoot);
LoadLootTables("itemloot", &ItemLoot);
LoadLootTables("prospectingloot", &ProspectingLoot);
LoadLootTables("pickpocketingloot", &PickpocketingLoot);
}
void LootMgr::LoadLootProp(uint32 id)
{
QueryResult * result = WorldDatabase.Query("SELECT * FROM lootrandomprop ORDER BY entryid");
if(!result)return;
Field *fields = result->Fetch();
map > > propcache;
map > >::iterator itr;
uint32 i = 0;
uint32 m = result->GetRowCount();
do
{
propcache[fields[0].GetUInt32()].push_back( make_pair( fields[1].GetUInt32(), fields[2].GetFloat() ) );
} while(result->NextRow());
delete result;
i = 0;
m = propcache.size();
for(itr = propcache.begin(); itr != propcache.end(); ++itr)
{
LootPropTable * prop = new LootPropTable;
prop->iPropsCount = itr->second.size();
prop->pProps = new LootProp[prop->iPropsCount];
vector< pair >::iterator it2 = itr->second.begin();
uint32 j = 0;
for(; it2 != itr->second.end(); ++it2, ++j)
{
prop->pProps[j].chance = it2->second;
prop->pProps[j].prop = it2->first;
}
LootProperties[itr->first] = prop;
}
}
LootMgr::~LootMgr()
{
sLog.outString(" Deleting Loot Tables...");
for(LootStore::iterator iter=CreatureLoot.begin(); iter != CreatureLoot.end(); ++iter)
delete [] iter->second.items;
for(LootStore::iterator iter=FishingLoot.begin(); iter != FishingLoot.end(); ++iter)
delete [] iter->second.items;
for(LootStore::iterator iter=SkinningLoot.begin(); iter != SkinningLoot.end(); ++iter)
delete [] iter->second.items;
for(LootStore::iterator iter=GOLoot.begin(); iter != GOLoot.end(); ++iter)
delete [] iter->second.items;
for(LootStore::iterator iter=ItemLoot.begin(); iter != ItemLoot.end(); ++iter)
delete [] iter->second.items;
for(LootStore::iterator iter=ProspectingLoot.begin(); iter != ProspectingLoot.end(); ++iter)
delete [] iter->second.items;
for(LootStore::iterator iter=PickpocketingLoot.begin(); iter != PickpocketingLoot.end(); ++iter)
delete [] iter->second.items;
for(PropStore::iterator iter = LootProperties.begin(); iter != LootProperties.end(); ++iter)
{
delete iter->second->pProps;
delete iter->second;
}
}
void LootMgr::LoadLootTables(const char * szTableName,LootStore * LootTable)
{
/* DBCFile *dbc = new DBCFile();
dbc->open("DBC/ItemRandomProperties.dbc");
_propCount = dbc->getRecordCount();
delete dbc;*/
//HM_NAMESPACE::hash_map > loot_db;
//HM_NAMESPACE::hash_map >::iterator itr;
vector< pair< uint32, vector< tempy > > > db_cache;
vector< pair< uint32, vector< tempy > > >::iterator itr;
db_cache.reserve(10000);
LootStore::iterator tab;
QueryResult *result =WorldDatabase.Query("SELECT * FROM %s ORDER BY entryid ASC",szTableName);
if(!result)
{
sLog.outError("\rWARNING: Loading loot from table %s failed.", szTableName);
return;
}
uint32 entry_id = 0;
uint32 last_entry = 0;
uint32 total =(uint32) result->GetRowCount();
int pos = 0;
vector< tempy > ttab;
tempy t;
bool d = false;
do
{
Field *fields = result->Fetch();
entry_id = fields[1].GetUInt32();
if(entry_id < last_entry)
{
sLog.outError("WARNING: Out of order loot table being loaded.\n");
return;
}
if(entry_id != last_entry)
{
if(last_entry != 0)
db_cache.push_back( make_pair( last_entry, ttab) );
ttab.clear();
}
if(result->GetFieldCount() > 4)
{
t.itemid = fields[2].GetUInt32();
t.chance = fields[3].GetFloat();
t.chance_2 = fields[4].GetFloat();
t.mincount = fields[5].GetUInt32();
t.maxcount = fields[6].GetUInt32();
}
else
{
if(!d)
{
Log.Warning("LootMgr", "Loot table %s is using old loot structure (without heroic chances column)!", szTableName);
Log.Warning("LootMgr", "This will be deprecated soon!");
d=true;
}
t.itemid = fields[2].GetUInt32();
t.chance = fields[3].GetFloat();
t.chance_2 = t.chance;
t.mincount = t.maxcount = 1;
}
ttab.push_back( t );
/*loot_tb t;
t.itemid = fields[1].GetUInt32();
t.chance = fields[2].GetFloat();
loot_db[fields[0].GetUInt32()].push_back(t);*/
last_entry = entry_id;
} while( result->NextRow() );
//last list was not pushed in
if(last_entry != 0 && ttab.size())
db_cache.push_back( make_pair( last_entry, ttab) );
pos = 0;
total = db_cache.size();
ItemPrototype* proto;
uint32 itemid;
//for(itr=loot_db.begin();itr!=loot_db.end();++itr)
for( itr = db_cache.begin(); itr != db_cache.end(); ++itr)
{
entry_id = (*itr).first;
if(LootTable->end()==LootTable->find(entry_id))
{
StoreLootList list;
//list.count = itr->second.size();
list.count = (*itr).second.size();
list.items=new StoreLootItem[list.count];
uint32 ind=0;
//for(std::vector::iterator itr2=itr->second.begin();itr2!=itr->second.end();++itr2)
for(vector< tempy >::iterator itr2 = (*itr).second.begin(); itr2 != (*itr).second.end(); ++itr2)
{
//Omit items that are not in db to prevent future bugs
//uint32 itemid=(*itr2).itemid;
itemid = itr2->itemid;
proto=ItemPrototypeStorage.LookupEntry(itemid);
if(!proto)
{
list.items[ind].item.itemid=0;
sLog.outDetail("WARNING: Loot %u contains item that does not exist in the DB.",entry_id);
}
else
{
list.items[ind].item.itemid=itemid;
list.items[ind].item.displayid=proto->DisplayInfoID;
//list.items[ind].chance=(*itr2).chance;
list.items[ind].chance= itr2->chance;
list.items[ind].chance2 = itr2->chance_2;
list.items[ind].mincount = itr2->mincount;
list.items[ind].maxcount = itr2->maxcount;
PropStore::iterator ptab =LootProperties.find(itemid);
if( LootProperties.end()==ptab)
list.items[ind].prop=NULL;
else
list.items[ind].prop=ptab->second;
if(LootTable == &GOLoot)
{
if(proto->Class == ITEM_CLASS_QUEST)
{
//printf("Quest item \"%s\" allocated to quest ", proto->Name1.c_str());
sQuestMgr.SetGameObjectLootQuest(itr->first, itemid);
quest_loot_go[entry_id].insert(proto->ItemId);
}
}
}
ind++;
}
(*LootTable)[entry_id]=list;
}
}
//sLog.outString(" %d loot templates loaded from %s", db_cache.size(), szTableName);
// loot_db.clear();
delete result;
}
void LootMgr::PushLoot(StoreLootList *list,Loot * loot, bool heroic)
{
uint32 i;
uint32 count;
for(uint32 x =0; xcount;x++)
if(list->items[x].item.itemid)// this check is needed until loot DB is fixed
{
float chance = heroic ? list->items[x].chance2 : list->items[x].chance;
if(chance == 0.0f) continue;
ItemPrototype *itemproto = ItemPrototypeStorage.LookupEntry(list->items[x].item.itemid);
if(Rand(chance * sWorld.getRate(RATE_DROP0 + itemproto->Quality)) )//|| itemproto->Class == ITEM_CLASS_QUEST)
{
if(list->items[x].mincount == list->items[x].maxcount)
count = list->items[x].maxcount;
else
count = sRand.randInt(list->items[x].maxcount - list->items[x].mincount) + list->items[x].mincount;
for(i = 0; i < loot->items.size(); ++i)
{
//itemid rand match a already placed item, if item is stackable and unique(stack), increment it, otherwise skips
if((loot->items[i].item.itemid == list->items[x].item.itemid) && itemproto->MaxCount && ((loot->items[i].iItemsCount + count) < itemproto->MaxCount))
{
if(itemproto->Unique && ((loot->items[i].iItemsCount+count) < itemproto->Unique))
{
loot->items[i].iItemsCount += count;
break;
}
else if (!itemproto->Unique)
{
loot->items[i].iItemsCount += count;
break;
}
}
}
if(i != loot->items.size())
continue;
__LootItem itm;
itm.item =list->items[x].item;
itm.iItemsCount = count;
itm.iRandomProperty=0;
itm.roll = NULL;
itm.passed = false;
if (list->items[x].prop && itemproto->Quality > 1)
itm.iRandomProperty = RandomChoice(list->items[x].prop->pProps, list->items[x].prop->iPropsCount).prop;
loot->items.push_back(itm);
}
}
}
void LootMgr::FillCreatureLoot(Loot * loot,uint32 loot_id, bool heroic)
{
loot->items.clear ();
loot->gold =0;
LootStore::iterator tab =CreatureLoot.find(loot_id);
if( CreatureLoot.end()==tab)return;
else PushLoot(&tab->second,loot, heroic);
}
void LootMgr::FillGOLoot(Loot * loot,uint32 loot_id, bool heroic)
{
loot->items.clear ();
loot->gold =0;
LootStore::iterator tab =GOLoot.find(loot_id);
if( GOLoot.end()==tab)return;
else PushLoot(&tab->second,loot, heroic);
}
void LootMgr::FillPickpocketingLoot(Loot * loot,uint32 loot_id)
{
loot->items.clear ();
loot->gold =0;
LootStore::iterator tab =PickpocketingLoot.find(loot_id);
if( PickpocketingLoot.end()==tab)return;
else PushLoot(&tab->second,loot,false);
}
//Puts 1 item always, no random properties
void LootMgr::FillProfessionLoot(LootStore * store,Loot * loot,uint32 loot_id)
{
loot->items.clear ();
loot->gold =0;
LootStore::iterator tab =store->find(loot_id);
if( store->end()==tab)return;
StoreLootList *list=&(tab->second);
// TODO: fix infinite loop with ' while(true) '
while(true)
for(uint32 x =0,pass=0; xcount; x++,pass++)
{
if(list->items[x].item.itemid)// this check is needed until loot DB is fixed
{
// ItemPrototype *itemproto = ItemPrototypeStorage.LookupEntry(list->items[x].item.itemid);
if(Rand(list->items[x].chance))// || itemproto->Class == ITEM_CLASS_QUEST)
{
__LootItem itm;
itm.item =list->items[x].item;
itm.iItemsCount=1;
itm.passed = false;
itm.roll = 0;
/*if(itemproto->MaxCount>1)
{
uint32 c=1 + sRand.randInt(itemproto->MaxCount);
if(Rand(float(100.0/c)))
itm.iItemsCount=c;
}*/
itm.iRandomProperty=0;
loot->items.push_back(itm);
return;
}
}
if(pass>100)
{
sLog.outError("WARNING: Loot %u has too low chances",loot_id);
return;
}
}
}
bool LootMgr::CanGODrop(uint32 LootId,uint32 itemid)
{
LootStore::iterator tab =GOLoot.find(LootId);
if( GOLoot.end()==tab)
return false;
StoreLootList *list=&(tab->second);
for(uint32 x=0;xcount;x++)
if(list->items[x].item.itemid==itemid)
return true;
return false;
}
//THIS should be cached
bool LootMgr::IsPickpocketable(uint32 creatureId)
{
LootStore::iterator tab =PickpocketingLoot.find(creatureId);
if( PickpocketingLoot.end()==tab)return false;
else return true;
}
//THIS should be cached
bool LootMgr::IsSkinnable(uint32 creatureId)
{
LootStore::iterator tab =SkinningLoot.find(creatureId);
if( SkinningLoot.end()==tab)return false;
else return true;
}
//THIS should be cached
bool LootMgr::IsFishable(uint32 zoneid)
{
LootStore::iterator tab =FishingLoot.find(zoneid);
return tab!=FishingLoot.end();
}
#define NEED 1
#define GREED 2
LootRoll::LootRoll(uint32 timer, uint32 groupcount, uint64 guid, uint32 slotid, uint32 itemid, uint32 itemunk1, uint32 itemunk2, MapMgr * mgr) : EventableObject()
{
_mgr = mgr;
sEventMgr.AddEvent(this, &LootRoll::Finalize, EVENT_LOOT_ROLL_FINALIZE, 60000, 1,0);
_groupcount = groupcount;
_guid = guid;
_slotid = slotid;
_itemid = itemid;
_itemunk1 = itemunk1;
_itemunk2 = itemunk2;
_remaining = groupcount;
_passedGuid = 0;
}
LootRoll::~LootRoll()
{
}
void LootRoll::Finalize()
{
sEventMgr.RemoveEvents(this);
// this we will have to finalize with groups types.. for now
// we'll just assume need before greed. person with highest roll
// in need gets the item.
uint32 highest = 0;
int8 hightype = -1;
uint64 player = 0;
WorldPacket data(34);
/* grab any player */
Player * gplr = NULL;
for(std::map::iterator itr = NeedRolls.begin(); itr != NeedRolls.end(); ++itr)
{
gplr = _mgr->GetPlayer((uint32)itr->first);
if(gplr) break;
}
if(!gplr)
{
for(std::map::iterator itr = GreedRolls.begin(); itr != GreedRolls.end(); ++itr)
{
gplr = _mgr->GetPlayer((uint32)itr->first);
if(gplr) break;
}
}
for(std::map::iterator itr = NeedRolls.begin(); itr != NeedRolls.end(); ++itr)
{
if(itr->second > highest)
{
highest = itr->second;
player = itr->first;
hightype = NEED;
}
data.Initialize(SMSG_LOOT_ROLL);
data << _guid << _slotid << itr->first;
data << _itemid << _itemunk1 << _itemunk2;
data << uint8(itr->second) << uint8(NEED);
if(gplr && gplr->GetGroup())
gplr->GetGroup()->SendPacketToAll(&data);
}
for(std::map::iterator itr = GreedRolls.begin(); itr != GreedRolls.end(); ++itr)
{
if(!highest && itr->second > highest)
{
highest = itr->second;
player = itr->first;
hightype = GREED;
}
data.Initialize(SMSG_LOOT_ROLL);
data << _guid << _slotid << itr->first;
data << _itemid << _itemunk1 << _itemunk2;
data << uint8(itr->second) << uint8(GREED);
if(gplr && gplr->GetGroup())
gplr->GetGroup()->SendPacketToAll(&data);
}
Loot * pLoot = 0;
if(GUID_HIPART(_guid) == HIGHGUID_UNIT)
{
Creature * pc = _mgr->GetCreature(_guid);
if(pc) pLoot = &pc->loot;
}
else if(GUID_HIPART(_guid) == HIGHGUID_GAMEOBJECT)
{
GameObject * go = _mgr->GetGameObject(_guid);
if(go) pLoot = &go->loot;
}
if(!pLoot)
{
delete this;
return;
}
if(_slotid >= pLoot->items.size())
{
delete this;
return;
}
pLoot->items.at(_slotid).roll = NULL;
uint32 itemid = pLoot->items.at(_slotid).item.itemid;
uint32 amt = pLoot->items.at(_slotid).iItemsCount;
if(!amt)
{
delete this;
return;
}
Player * _player = (player) ? _mgr->GetPlayer(player) : 0;
if(!player || !_player)
{
_player = _mgr->GetPlayer(_passedGuid);
if(_player)
{
/* all passed */
data.Initialize(SMSG_LOOT_ALL_PASSED);
data << _guid << _groupcount << _itemid << _itemunk1 << _itemunk2;
if(_player->InGroup())
_player->GetGroup()->SendPacketToAll(&data);
else
_player->GetSession()->SendPacket(&data);
}
/* item can now be looted by anyone :) */
pLoot->items.at(_slotid).passed = true;
delete this;
return;
}
pLoot->items.at(_slotid).roll = 0;
data.Initialize(SMSG_LOOT_ROLL_WON);
data << _guid << _slotid << _itemid << _itemunk1 << _itemunk2;
data << _player->GetGUID() << uint8(highest) << uint8(hightype);
if(_player->InGroup())
_player->GetGroup()->SendPacketToAll(&data);
else
_player->GetSession()->SendPacket(&data);
ItemPrototype* it = ItemPrototypeStorage.LookupEntry(itemid);
int8 error;
if((error = _player->GetItemInterface()->CanReceiveItem(it, 1)))
{
_player->GetItemInterface()->BuildInventoryChangeError(NULL, NULL, error);
return;
}
Item * add = _player->GetItemInterface()->FindItemLessMax(itemid, amt, false);
if (!add)
{
SlotResult slotresult = _player->GetItemInterface()->FindFreeInventorySlot(it);
if(!slotresult.Result)
{
_player->GetItemInterface()->BuildInventoryChangeError(NULL, NULL, INV_ERR_INVENTORY_FULL);
return;
}
sLog.outDebug("AutoLootItem MISC");
Item *item = objmgr.CreateItem( itemid, _player);
item->SetUInt32Value(ITEM_FIELD_STACK_COUNT,amt);
uint32 rndprop=pLoot->items.at(_slotid).iRandomProperty;
if(rndprop)
item->SetUInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID,rndprop);
item->ApplyRandomProperties();
_player->GetItemInterface()->SafeAddItem(item,slotresult.ContainerSlot, slotresult.Slot);
sQuestMgr.OnPlayerItemPickup(_player,item);
}
else
{
add->SetCount(add->GetUInt32Value(ITEM_FIELD_STACK_COUNT) + amt);
add->m_isDirty = true;
sQuestMgr.OnPlayerItemPickup(_player,add);
}
pLoot->items.at(_slotid).iItemsCount=0;
// this gets sent to all looters
data.Initialize(SMSG_LOOT_REMOVED);
data << uint8(_slotid);
Player * plr;
for(LooterSet::iterator itr = pLoot->looters.begin(); itr != pLoot->looters.end(); ++itr)
{
if((plr = _player->GetMapMgr()->GetPlayer(*itr)))
plr->GetSession()->SendPacket(&data);
}
WorldPacket idata(45);
_player->GetSession()->BuildItemPushResult(&idata, _player->GetGUID(), ITEM_PUSH_TYPE_LOOT, amt, itemid, pLoot->items.at(_slotid).iRandomProperty);
if(_player->InGroup())
_player->GetGroup()->SendPacketToAll(&idata);
else
_player->GetSession()->SendPacket(&idata);
delete this;
}
void LootRoll::PlayerRolled(Player *player, uint8 choice)
{
if(NeedRolls.find(player->GetGUID()) != NeedRolls.end() || GreedRolls.find(player->GetGUID()) != GreedRolls.end())
return; // dont allow cheaters
std::map* rmap = 0;
if(choice == NEED) {
rmap = &NeedRolls;
} else if(choice == GREED) {
rmap = &GreedRolls;
}
int roll = sRand.randInt(99)+1;
// create packet
WorldPacket data(34);
data.SetOpcode(SMSG_LOOT_ROLL);
data << _guid << _slotid << player->GetGUID();
data << _itemid << _itemunk1 << _itemunk2;
if(rmap)
{
rmap->insert ( std::make_pair(player->GetGUID(), roll) );
if(choice == GREED)
data << uint8(0xF9) << uint8(0x00);
else
data << uint8(0xC1) << uint8(0x00);
}
else
{
if(!_passedGuid)
_passedGuid = player->GetGUID();
data << uint8(128) << uint8(128);
}
data << uint8(roll) << uint8(choice);
if(player->InGroup())
player->GetGroup()->SendPacketToAll(&data);
else
player->GetSession()->SendPacket(&data);
// check for early completion
if(!--_remaining)
{
// kill event early
//sEventMgr.RemoveEvents(this);
Finalize();
}
}
void LootMgr::FillItemLoot(Loot *loot, uint32 loot_id)
{
loot->items.clear ();
loot->gold =0;
LootStore::iterator tab =ItemLoot.find(loot_id);
if( ItemLoot.end()==tab)return;
else PushLoot(&tab->second,loot,false);
}
int32 LootRoll::event_GetInstanceID()
{
return _mgr->GetInstanceID();
}