Index: sql/mangos.sql =================================================================== --- sql/mangos.sql (revision 5478) +++ sql/mangos.sql (working copy) @@ -659,6 +659,8 @@ `civilian` tinyint(3) unsigned NOT NULL default '0', `flag1` int(11) unsigned default '0', `lootid` int(10) unsigned NOT NULL default '0', + `lootMinCount` tinyint(3) unsigned NOT NULL default '0', + `lootMaxCount` tinyint(3) unsigned NOT NULL default '0', `pickpocketloot` int(10) unsigned NOT NULL default '0', `skinloot` int(10) unsigned NOT NULL default '0', `resistance1` int(10) unsigned NOT NULL default '0', Index: sql/updates/5XXX_mangos_creature_template.sql =================================================================== --- sql/updates/5478_mangos_creature_template.sql (revision 0) +++ sql/updates/5478_mangos_creature_template.sql (revision 0) @@ -0,0 +1,6 @@ +ALTER TABLE creature_template ADD COLUMN lootMinCount tinyint(3) unsigned NOT NULL default '0' AFTER `lootid`; +ALTER TABLE creature_template ADD COLUMN lootMaxCount tinyint(3) unsigned NOT NULL default '0' AFTER `lootMinCount`; + +INSERT INTO `mangos_string` (`entry`, `content_default`) values('580','Loot count range for creature entry #%u has been modified to (%u,%u)'); +INSERT INTO `mangos_string` (`entry`, `content_default`) values('581','Invalid value for loot count range'); +INSERT INTO `command` (`name`, `security`, `help`) values('setlootrange','3','Syntax: .setlootrange #max_count [#min_count]\r\n Set the lootMaxCount and lootMinCount in creature_template for selected creature. min_count default is 0.'); Index: src/game/Chat.cpp =================================================================== --- src/game/Chat.cpp (revision 5478) +++ src/game/Chat.cpp (working copy) @@ -392,7 +392,7 @@ { "setvalue", SEC_ADMINISTRATOR, &ChatHandler::HandleSetValue, "", NULL }, { "getvalue", SEC_ADMINISTRATOR, &ChatHandler::HandleGetValue, "", NULL }, { "Mod32Value", SEC_ADMINISTRATOR, &ChatHandler::HandleMod32Value, "", NULL }, - + { "setlootrange",SEC_ADMINISTRATOR, &ChatHandler::HandleSetLootRangeCommand, "", NULL }, { NULL, 0, NULL, "", NULL } }; Index: src/game/Chat.h =================================================================== --- src/game/Chat.h (revision 5478) +++ src/game/Chat.h (working copy) @@ -339,6 +339,7 @@ bool HandleRemoveQuest(const char * args); bool HandleSaveAllCommand(const char* args); bool HandleGetItemState(const char * args); + bool HandleSetLootRangeCommand(const char* arg); Player* getSelectedPlayer(); Creature* getSelectedCreature(); Index: src/game/Creature.h =================================================================== --- src/game/Creature.h (revision 5478) +++ src/game/Creature.h (working copy) @@ -162,7 +162,7 @@ uint32 faction_H; uint32 npcflag; float speed; - float scale; + float scale; uint32 rank; float mindmg; float maxdmg; @@ -184,6 +184,8 @@ bool civilian; uint32 flag1; uint32 lootid; + uint32 lootMinCount; + uint32 lootMaxCount; uint32 pickpocketLootId; uint32 SkinLootId; uint32 resistance1; Index: src/game/Language.h =================================================================== --- src/game/Language.h (revision 5478) +++ src/game/Language.h (working copy) @@ -481,6 +481,8 @@ LANG_INVISIBLE_INVISIBLE = 577, LANG_INVISIBLE_VISIBLE = 578, LANG_SELECTED_TARGET_NOT_HAVE_VICTIM = 579, + LANG_CREATURE_LOOT_RANGE_MODIFIED = 580, + LANG_CREATURE_LOOT_RANGE_INVALID = 581, // Battleground LANG_BG_A_WINS = 600, Index: src/game/Level3.cpp =================================================================== --- src/game/Level3.cpp (revision 5478) +++ src/game/Level3.cpp (working copy) @@ -44,6 +44,50 @@ #include "SystemConfig.h" #include "Config/ConfigEnv.h" + + + + + +bool ChatHandler::HandleSetLootRangeCommand(const char* args) +{ + if(!*args) + return false; + + Creature* pCreature = getSelectedCreature(); + if (!pCreature) + { + PSendSysMessage(LANG_SELECT_CREATURE); + return false; + } + + char* pMax = strtok((char*)args, " "); + char* pMin = strtok(NULL, " "); + int iMax, iMin = 0; + + if ( !pMax ) + { + SendSysMessage(LANG_CMD_SYNTAX); + return false; + } + + iMax = atoi( pMax ); + if ( pMin ) iMin = atoi( pMin ); + + if ( iMax < iMin || iMax<0 || iMin<0 ) + { + SendSysMessage( LANG_CREATURE_LOOT_RANGE_INVALID ); + return false; + } + + WorldDatabase.PExecuteLog("UPDATE creature_template SET lootMinCount = '%d', lootMaxCount = '%d' WHERE entry = '%u'", iMin, iMax, pCreature->GetEntry()); + const_cast(pCreature->GetCreatureInfo())->lootMinCount = iMin; + const_cast(pCreature->GetCreatureInfo())->lootMaxCount = iMax; + PSendSysMessage( LANG_CREATURE_LOOT_RANGE_MODIFIED, pCreature->GetEntry(), iMin, iMax ); + return true; +} + + //reload commands bool ChatHandler::HandleReloadCommand(const char* arg) { Index: src/game/LootMgr.cpp =================================================================== --- src/game/LootMgr.cpp (revision 5478) +++ src/game/LootMgr.cpp (working copy) @@ -74,7 +74,7 @@ sLog.outString( "%s :", tablename); - // 0 1 2 3 4 5 6 7 8 + // 0 1 2 3 4 5 6 7 8 9 QueryResult *result = WorldDatabase.PQuery("SELECT entry, item, ChanceOrRef, QuestChanceOrGroup, mincount, maxcount, freeforall, lootcondition, condition_value1, condition_value2 FROM %s",tablename); if (result) @@ -254,6 +254,21 @@ LoadLootTable(LootTemplates_Prospecting, "prospecting_loot_template"); } + +struct LootedItem { + float value; + LootStoreItem *item; + + LootedItem(LootStoreItem *_item, float _value) : value(_value), item(_item) {}; + bool operator<(const LootedItem &_2) const + { + return value < _2.value; + } +}; + +typedef std::set LootItems; + + struct HasChance { struct GroupChance @@ -267,10 +282,12 @@ typedef std::vector GroupChances; LootStore* m_store; + LootItems &m_items; + int m_min, m_max; GroupChances groupChance; - explicit HasChance(LootStore* _store) : m_store(_store) + explicit HasChance(LootStore* _store, LootItems &_items, int _min, int _max) : m_store(_store), m_items(_items), m_min(_min), m_max(_max) { } @@ -284,8 +301,43 @@ if (itm.questChanceOrGroup == 0) { // reference non group loot ( itm.chanceOrRef < 0) handled separatly - if ( itm.chanceOrRef > 0 && roll_chance_f(itm.chanceOrRef * sWorld.getRate(RATE_DROP_ITEMS)) ) - return &itm; + if ( itm.chanceOrRef > 0 ) + { + float chance = rand_chance(); + float itm_chance = itm.chanceOrRef * sWorld.getRate(RATE_DROP_ITEMS); + if ( m_min==0 && m_max==0 ) + { + //sLog.outDebug("HasChance: min=max=0 returning item as usual"); + if ( chance < itm_chance ) + return &itm; + return NULL; + } + + float n_chance = chance / itm_chance; // normalize chance + if ( chance > itm_chance ) n_chance *= -1; + //sLog.outDebug("HasChance: item %u has normalized chance: %f", itm.itemid, n_chance); + if ( m_items.size() < m_min ) + { + //sLog.outDebug("HasChance: itemcount: %d, min: %d not reached", m_items.size(), m_min); + m_items.insert( LootedItem(&itm, n_chance) ); + } + else if ( m_items.size() >= m_max || m_items.begin()->value < 0 ) + { + //sLog.outDebug("HasChance: itemcount: %d, max: %d reached or begin->value < 0 (%f)", m_items.size(), m_max, m_items.begin()->value); + // see if the lowest value in the set is lower than this, in this case replace the item + if ( m_items.begin()->value < n_chance ) + { + //sLog.outDebug("HasChance: lowest value (%f) is smaller than chance %f, dropping %u, adding %u", m_items.begin()->value, n_chance, m_items.begin()->item->itemid, itm.itemid); + m_items.erase( m_items.begin() ); + m_items.insert( LootedItem(&itm, n_chance) ); + } + } else { + //sLog.outDebug("HasChance:%d count. min (%d) reached, max (%d) not. Adding %u as ususal. %f<%f ? nchance: %f,min_nchance: %f", m_items.size(), m_min, m_max, itm.itemid, chance, itm_chance, n_chance, m_items.begin()->value); + if ( chance < itm_chance ) + m_items.insert( LootedItem(&itm, n_chance) ); + } + // return NULL, no to add item to lootlist yet + } return NULL; } @@ -349,7 +401,7 @@ inline bool operator() ( LootStoreItem &itm ) { return roll_chance_i(itm.questChanceOrGroup); } }; -void FillLoot(Loot* loot, uint32 loot_id, LootStore& store, Player* loot_owner, bool referenced) +void FillLoot(Loot* loot, uint32 loot_id, LootStore& store, Player* loot_owner, bool referenced, int min, int max) { LootStore::iterator tab = store.find(loot_id); @@ -363,7 +415,8 @@ loot->quest_items.reserve(min(tab->second.size(),MAX_NR_QUEST_ITEMS)); // fill loot with all normal and quest items that have a chance - HasChance hasChance(&store); + LootItems items; + HasChance hasChance(&store, items, min, max); HasQuestChance hasQuestChance; for(LootStoreItemList::iterator item_iter = tab->second.begin(); item_iter != tab->second.end(); ++item_iter) @@ -383,7 +436,7 @@ // Reference to another loot int LootId = -int(item_iter->chanceOrRef); - FillLoot(loot,LootId,store,loot_owner,true); + FillLoot(loot,LootId,store,loot_owner,true, item_iter->mincount, item_iter->maxcount); continue; } @@ -405,6 +458,26 @@ } } + if ( items.size() ) + { + //sLog.outDebug("FillLoot: items.size() is not 0"); + for (LootItems::iterator it = items.begin(); it != items.end(); ++it) + { + //sLog.outDebug("FillLoot: adding 'items' to loot: %u (had value:%f)", it->item->itemid, it->value); + loot->items.push_back( + LootItem(*it->item, urand(it->item->mincount, it->item->maxcount), + GenerateEnchSuffixFactor(it->item->itemid), + Item::GenerateItemRandomPropertyId(it->item->itemid)) + ); + + // non-conditional one-player only items are counted here, + // free for all items are counted in FillFFALoot(), + // non-ffa conditionals are counted in FillNonQuestNonFFAConditionalLoot() + if( ! it->item->freeforall && ! it->item->condition) + ++loot->unlootedCount; + } + } + if(referenced) return; if(!loot_owner) Index: src/game/LootMgr.h =================================================================== --- src/game/LootMgr.h (revision 5478) +++ src/game/LootMgr.h (working copy) @@ -258,7 +258,7 @@ QuestItemList* FillFFALoot(Player* player, Loot *loot); QuestItemList* FillQuestLoot(Player* player, Loot *loot); QuestItemList* FillNonQuestNonFFAConditionalLoot(Player* player, Loot *loot); -void FillLoot(Loot *loot, uint32 loot_id, LootStore& store, Player* loot_owner, bool referenced = false); +void FillLoot(Loot *loot, uint32 loot_id, LootStore& store, Player* loot_owner, bool referenced = false, int min_loot_count = 0, int max_loot_count = 0); void LoadLootTables(); void LoadLootTable(LootStore& lootstore,char const* tablename); Index: src/game/ObjectMgr.cpp =================================================================== --- src/game/ObjectMgr.cpp (revision 5478) +++ src/game/ObjectMgr.cpp (working copy) @@ -541,6 +541,12 @@ const_cast(cInfo)->scale = ScaleEntry ? ScaleEntry->scale : 1.0f; } + if ( cInfo->lootMaxCount < cInfo->lootMinCount ) + { + sLog.outErrorDb("Creature (Entry: %u) has wrong minLoot,maxLoot value (%u, %u), set to 0,0.",cInfo->Entry,cInfo->lootMinCount, cInfo->lootMaxCount); + const_cast(cInfo)->lootMinCount = 0; + const_cast(cInfo)->lootMaxCount = 0; + } } } Index: src/game/Player.cpp =================================================================== --- src/game/Player.cpp (revision 5478) +++ src/game/Player.cpp (working copy) @@ -6897,7 +6897,7 @@ if (!creature->HasFlag(UNIT_NPC_FLAGS,UNIT_NPC_FLAG_VENDOR) && lootid) { loot->clear(); - FillLoot(loot, lootid, LootTemplates_Creature, recipient); + FillLoot(loot, lootid, LootTemplates_Creature, recipient, false, creature->GetCreatureInfo()->lootMinCount, creature->GetCreatureInfo()->lootMaxCount); } loot->generateMoneyLoot(creature->GetCreatureInfo()->mingold,creature->GetCreatureInfo()->maxgold); Index: src/shared/Database/SQLStorage.cpp =================================================================== --- src/shared/Database/SQLStorage.cpp (revision 5478) +++ src/shared/Database/SQLStorage.cpp (working copy) @@ -27,7 +27,7 @@ extern DatabaseMysql WorldDatabase; #endif -const char CreatureInfofmt[]="iiiiisssiiiiiiiiiiffiffiiiiiiiiiiiffiiliiiiiiiiiiiiiiiisiilliis"; +const char CreatureInfofmt[]="iiiiisssiiiiiiiiiiffiffiiiiiiiiiiiffiiliiiiiiiiiiiiiiiiiisiilliis"; const char CreatureDataAddonInfofmt[]="iiiiiis"; const char CreatureModelfmt[]="iffii"; const char CreatureInfoAddonInfofmt[]="iiiiiis";