diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 0bd59d360..d6e8b0c97 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -5341,18 +5341,20 @@ struct MercenaryMerchantResponse_Struct { struct ServerLootItem_Struct { uint32 item_id; // uint32 item_id; - int16 equip_slot; // int16 equip_slot; - uint16 charges; // uint8 charges; - uint16 lootslot; // uint16 lootslot; - uint32 aug_1; // uint32 aug_1; - uint32 aug_2; // uint32 aug_2; - uint32 aug_3; // uint32 aug_3; - uint32 aug_4; // uint32 aug_4; - uint32 aug_5; // uint32 aug_5; - uint32 aug_6; // uint32 aug_5; - uint8 attuned; - uint8 min_level; - uint8 max_level; + int16 equip_slot; // int16 equip_slot; + uint16 charges; // uint8 charges; + uint16 lootslot; // uint16 lootslot; + uint32 aug_1; // uint32 aug_1; + uint32 aug_2; // uint32 aug_2; + uint32 aug_3; // uint32 aug_3; + uint32 aug_4; // uint32 aug_4; + uint32 aug_5; // uint32 aug_5; + uint32 aug_6; // uint32 aug_5; + uint8 attuned; + uint16 trivial_min_level; + uint16 trivial_max_level; + uint16 npc_min_level; + uint16 npc_max_level; }; //Found in client near a ref to the string: diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 6c3aca086..80cf9851f 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -117,6 +117,7 @@ namespace Logs { HotReload, Merchants, ZonePoints, + Loot, MaxCategoryID /* Don't Remove this */ }; @@ -192,7 +193,8 @@ namespace Logs { "Aura", "HotReload", "Merchants", - "ZonePoints" + "ZonePoints", + "Loot" }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index ceb45c1a7..514f37e21 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -591,6 +591,16 @@ OutF(LogSys, Logs::Detail, Logs::ZonePoints, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogLoot(message, ...) do {\ + if (LogSys.log_settings[Logs::Loot].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::Loot, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogLootDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::Loot].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::Loot, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ diff --git a/common/loottable.h b/common/loottable.h index d046c0c08..2b9a13926 100644 --- a/common/loottable.h +++ b/common/loottable.h @@ -39,13 +39,15 @@ struct LootTable_Struct { }; struct LootDropEntries_Struct { - uint32 item_id; - int8 item_charges; - uint8 equip_item; - float chance; - uint8 minlevel; - uint8 maxlevel; - uint8 multiplier; + uint32 item_id; + int8 item_charges; + uint8 equip_item; + float chance; + uint16 trivial_min_level; + uint16 trivial_max_level; + uint16 npc_min_level; + uint16 npc_max_level; + uint8 multiplier; }; struct LootDrop_Struct { diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 6c9723e7f..d26d2d820 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -2142,7 +2142,7 @@ void SharedDatabase::LoadLootDrops(void *data, uint32 size) { EQ::FixedMemoryVariableHashSet hash(reinterpret_cast(data), size); uint8 loot_drop[sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)]; - LootDrop_Struct *ld = reinterpret_cast(loot_drop); + LootDrop_Struct *p_loot_drop_struct = reinterpret_cast(loot_drop); const std::string query = fmt::format( SQL( @@ -2152,8 +2152,10 @@ void SharedDatabase::LoadLootDrops(void *data, uint32 size) { lootdrop_entries.item_charges, lootdrop_entries.equip_item, lootdrop_entries.chance, - lootdrop_entries.minlevel, - lootdrop_entries.maxlevel, + lootdrop_entries.trivial_min_level, + lootdrop_entries.trivial_max_level, + lootdrop_entries.npc_min_level, + lootdrop_entries.npc_max_level, lootdrop_entries.multiplier FROM lootdrop @@ -2171,37 +2173,44 @@ void SharedDatabase::LoadLootDrops(void *data, uint32 size) { return; } - uint32 current_id = 0; - uint32 current_entry = 0; + uint32 current_id = 0; + uint32 current_entry = 0; - for (auto row = results.begin(); row != results.end(); ++row) { - uint32 id = static_cast(atoul(row[0])); - if(id != current_id) { - if(current_id != 0) - hash.insert(current_id, loot_drop, (sizeof(LootDrop_Struct) +(sizeof(LootDropEntries_Struct) * ld->NumEntries))); + for (auto row = results.begin(); row != results.end(); ++row) { + auto id = static_cast(atoul(row[0])); + if (id != current_id) { + if (current_id != 0) { + hash.insert( + current_id, + loot_drop, + (sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * p_loot_drop_struct->NumEntries))); + } - memset(loot_drop, 0, sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)); + memset(loot_drop, 0, sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * 1260)); current_entry = 0; - current_id = id; - } + current_id = id; + } - if(current_entry >= 1260) - continue; + if (current_entry >= 1260) { + continue; + } - ld->Entries[current_entry].item_id = static_cast(atoul(row[1])); - ld->Entries[current_entry].item_charges = static_cast(atoi(row[2])); - ld->Entries[current_entry].equip_item = static_cast(atoi(row[3])); - ld->Entries[current_entry].chance = static_cast(atof(row[4])); - ld->Entries[current_entry].minlevel = static_cast(atoi(row[5])); - ld->Entries[current_entry].maxlevel = static_cast(atoi(row[6])); - ld->Entries[current_entry].multiplier = static_cast(atoi(row[7])); + p_loot_drop_struct->Entries[current_entry].item_id = static_cast(atoul(row[1])); + p_loot_drop_struct->Entries[current_entry].item_charges = static_cast(atoi(row[2])); + p_loot_drop_struct->Entries[current_entry].equip_item = static_cast(atoi(row[3])); + p_loot_drop_struct->Entries[current_entry].chance = static_cast(atof(row[4])); + p_loot_drop_struct->Entries[current_entry].trivial_min_level = static_cast(atoi(row[5])); + p_loot_drop_struct->Entries[current_entry].trivial_max_level = static_cast(atoi(row[6])); + p_loot_drop_struct->Entries[current_entry].npc_min_level = static_cast(atoi(row[7])); + p_loot_drop_struct->Entries[current_entry].npc_max_level = static_cast(atoi(row[8])); + p_loot_drop_struct->Entries[current_entry].multiplier = static_cast(atoi(row[9])); - ++(ld->NumEntries); - ++current_entry; - } + ++(p_loot_drop_struct->NumEntries); + ++current_entry; + } - if(current_id != 0) - hash.insert(current_id, loot_drop, (sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * ld->NumEntries))); + if(current_id != 0) + hash.insert(current_id, loot_drop, (sizeof(LootDrop_Struct) + (sizeof(LootDropEntries_Struct) * p_loot_drop_struct->NumEntries))); } diff --git a/common/version.h b/common/version.h index fb60b794e..0dd15a90f 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9154 +#define CURRENT_BINARY_DATABASE_VERSION 9155 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9027 diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 4adc4bca0..383293eac 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -408,6 +408,7 @@ 9152|2020_03_09_convert_myisam_to_innodb.sql|SELECT * FROM db_version WHERE version >= 9152|empty| 9153|2020_05_09_items_subtype.sql|SHOW COLUMNS from `items` LIKE 'UNK219'|not_empty| 9154|2020_04_11_expansions_content_filters.sql|SHOW COLUMNS from `zone` LIKE 'min_expansion'|empty| +9155|2020_08_15_lootdrop_level_filtering.sql|SHOW COLUMNS from `lootdrop_entries` LIKE 'trivial_min_level'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2020_08_15_lootdrop_level_filtering.sql b/utils/sql/git/required/2020_08_15_lootdrop_level_filtering.sql new file mode 100644 index 000000000..00084a59f --- /dev/null +++ b/utils/sql/git/required/2020_08_15_lootdrop_level_filtering.sql @@ -0,0 +1,7 @@ +ALTER TABLE `lootdrop_entries` CHANGE `minlevel` `trivial_min_level` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT ''; +ALTER TABLE `lootdrop_entries` CHANGE `maxlevel` `trivial_max_level` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT ''; +ALTER TABLE `lootdrop_entries` ADD COLUMN `npc_min_level` smallint unsigned NOT NULL DEFAULT '0' COMMENT ''; +ALTER TABLE `lootdrop_entries` ADD COLUMN `npc_max_level` smallint unsigned NOT NULL DEFAULT '0' COMMENT ''; +ALTER TABLE `lootdrop_entries` CHANGE `trivial_min_level` `trivial_min_level` smallint(5) unsigned NOT NULL DEFAULT 0 COMMENT ''; +ALTER TABLE `lootdrop_entries` CHANGE `trivial_max_level` `trivial_max_level` smallint(5) unsigned NOT NULL DEFAULT 0 COMMENT ''; +UPDATE `lootdrop_entries` SET `trivial_max_level` = 0 WHERE `trivial_max_level` = 127; \ No newline at end of file diff --git a/zone/aa.cpp b/zone/aa.cpp index ec66d517f..110885fef 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -448,7 +448,7 @@ void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration) sitem = CorpseToUse->GetWornItem(x); if(sitem){ const EQ::ItemData * itm = database.GetItem(sitem); - npca->AddLootDrop(itm, &npca->itemlist, 1, 1, 255, true, true); + npca->AddLootDrop(itm, &npca->itemlist, LootDropEntries_Struct{ .equip_item = true }, true); } } diff --git a/zone/attack.cpp b/zone/attack.cpp index 88dbc7bd4..86c326e53 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2464,7 +2464,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy killer = killer->GetOwner(); if (killer->IsClient() && !killer->CastToClient()->GetGM()) - this->CheckMinMaxLevel(killer); + this->CheckTrivialMinMaxLevelDrop(killer); } entity_list.RemoveFromAutoXTargets(this); diff --git a/zone/loottables.cpp b/zone/loottables.cpp index b64a9f7e0..0f8bd0ab6 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -29,6 +29,7 @@ #include "zone_store.h" #include "global_loot_manager.h" #include "../common/repositories/criteria/content_filter_criteria.h" +#include "../common/say_link.h" #include #include @@ -116,110 +117,127 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite // Called by AddLootTableToNPC // maxdrops = size of the array npcd -void ZoneDatabase::AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop) { - const LootDrop_Struct* lds = GetLootDrop(lootdrop_id); - if (!lds) { +void ZoneDatabase::AddLootDropToNPC(NPC *npc, uint32 lootdrop_id, ItemList *item_list, uint8 droplimit, uint8 mindrop) +{ + const LootDrop_Struct *loot_drop = GetLootDrop(lootdrop_id); + if (!loot_drop) { return; } - if(lds->NumEntries == 0) + if (loot_drop->NumEntries == 0) { return; + } - if(droplimit == 0 && mindrop == 0) { - for(uint32 i = 0; i < lds->NumEntries; ++i) { - int charges = lds->Entries[i].multiplier; - for(int j = 0; j < charges; ++j) { - if(zone->random.Real(0.0, 100.0) <= lds->Entries[i].chance) { - const EQ::ItemData* dbitem = GetItem(lds->Entries[i].item_id); - npc->AddLootDrop(dbitem, itemlist, lds->Entries[i].item_charges, lds->Entries[i].minlevel, - lds->Entries[i].maxlevel, lds->Entries[i].equip_item > 0 ? true : false, false); + if (droplimit == 0 && mindrop == 0) { + for (uint32 i = 0; i < loot_drop->NumEntries; ++i) { + int charges = loot_drop->Entries[i].multiplier; + for (int j = 0; j < charges; ++j) { + if (zone->random.Real(0.0, 100.0) <= loot_drop->Entries[i].chance && npc->MeetsLevelRequirements(loot_drop->Entries[i])) { + const EQ::ItemData *database_item = GetItem(loot_drop->Entries[i].item_id); + npc->AddLootDrop( + database_item, + item_list, + loot_drop->Entries[i] + ); } } } return; } - if(lds->NumEntries > 100 && droplimit == 0) { + if (loot_drop->NumEntries > 100 && droplimit == 0) { droplimit = 10; } - if(droplimit < mindrop) { + if (droplimit < mindrop) { droplimit = mindrop; } - float roll_t = 0.0f; - float roll_t_min = 0.0f; - bool active_item_list = false; - for(uint32 i = 0; i < lds->NumEntries; ++i) { - const EQ::ItemData* db_item = GetItem(lds->Entries[i].item_id); - if(db_item) { - roll_t += lds->Entries[i].chance; + float roll_t = 0.0f; + float roll_t_min = 0.0f; + bool active_item_list = false; + for (uint32 i = 0; i < loot_drop->NumEntries; ++i) { + const EQ::ItemData *db_item = GetItem(loot_drop->Entries[i].item_id); + if (db_item) { + roll_t += loot_drop->Entries[i].chance; active_item_list = true; } } roll_t_min = roll_t; - roll_t = EQ::ClampLower(roll_t, 100.0f); + roll_t = EQ::ClampLower(roll_t, 100.0f); - if(!active_item_list) { + if (!active_item_list) { return; } - for(int i = 0; i < mindrop; ++i) { - float roll = (float)zone->random.Real(0.0, roll_t_min); - for(uint32 j = 0; j < lds->NumEntries; ++j) { - const EQ::ItemData* db_item = GetItem(lds->Entries[j].item_id); - if(db_item) { - if(roll < lds->Entries[j].chance) { - npc->AddLootDrop(db_item, itemlist, lds->Entries[j].item_charges, lds->Entries[j].minlevel, - lds->Entries[j].maxlevel, lds->Entries[j].equip_item > 0 ? true : false, false); + for (int i = 0; i < mindrop; ++i) { + float roll = (float) zone->random.Real(0.0, roll_t_min); + for (uint32 j = 0; j < loot_drop->NumEntries; ++j) { + const EQ::ItemData *db_item = GetItem(loot_drop->Entries[j].item_id); + if (db_item) { + if (roll < loot_drop->Entries[j].chance && npc->MeetsLevelRequirements(loot_drop->Entries[j])) { + npc->AddLootDrop( + db_item, + item_list, + loot_drop->Entries[j] + ); - int charges = (int)lds->Entries[i].multiplier; + int charges = (int) loot_drop->Entries[i].multiplier; charges = EQ::ClampLower(charges, 1); - for(int k = 1; k < charges; ++k) { - float c_roll = (float)zone->random.Real(0.0, 100.0); - if(c_roll <= lds->Entries[i].chance) { - npc->AddLootDrop(db_item, itemlist, lds->Entries[j].item_charges, lds->Entries[j].minlevel, - lds->Entries[j].maxlevel, lds->Entries[j].equip_item > 0 ? true : false, false); + for (int k = 1; k < charges; ++k) { + float c_roll = (float) zone->random.Real(0.0, 100.0); + if (c_roll <= loot_drop->Entries[i].chance) { + npc->AddLootDrop( + db_item, + item_list, + loot_drop->Entries[i] + ); } } - j = lds->NumEntries; + j = loot_drop->NumEntries; break; } else { - roll -= lds->Entries[j].chance; + roll -= loot_drop->Entries[j].chance; } } } } - for(int i = mindrop; i < droplimit; ++i) { - float roll = (float)zone->random.Real(0.0, roll_t); - for(uint32 j = 0; j < lds->NumEntries; ++j) { - const EQ::ItemData* db_item = GetItem(lds->Entries[j].item_id); - if(db_item) { - if(roll < lds->Entries[j].chance) { - npc->AddLootDrop(db_item, itemlist, lds->Entries[j].item_charges, lds->Entries[j].minlevel, - lds->Entries[j].maxlevel, lds->Entries[j].equip_item > 0 ? true : false, false); + for (int i = mindrop; i < droplimit; ++i) { + float roll = (float) zone->random.Real(0.0, roll_t); + for (uint32 j = 0; j < loot_drop->NumEntries; ++j) { + const EQ::ItemData *db_item = GetItem(loot_drop->Entries[j].item_id); + if (db_item) { + if (roll < loot_drop->Entries[j].chance && npc->MeetsLevelRequirements(loot_drop->Entries[j])) { + npc->AddLootDrop( + db_item, + item_list, + loot_drop->Entries[j] + ); - int charges = (int)lds->Entries[i].multiplier; + int charges = (int) loot_drop->Entries[i].multiplier; charges = EQ::ClampLower(charges, 1); - for(int k = 1; k < charges; ++k) { - float c_roll = (float)zone->random.Real(0.0, 100.0); - if(c_roll <= lds->Entries[i].chance) { - npc->AddLootDrop(db_item, itemlist, lds->Entries[j].item_charges, lds->Entries[j].minlevel, - lds->Entries[j].maxlevel, lds->Entries[j].equip_item > 0 ? true : false, false); + for (int k = 1; k < charges; ++k) { + float c_roll = (float) zone->random.Real(0.0, 100.0); + if (c_roll <= loot_drop->Entries[i].chance) { + npc->AddLootDrop( + db_item, + item_list, + loot_drop->Entries[i] + ); } } - j = lds->NumEntries; + j = loot_drop->NumEntries; break; } else { - roll -= lds->Entries[j].chance; + roll -= loot_drop->Entries[j].chance; } } } @@ -231,43 +249,86 @@ void ZoneDatabase::AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* iteml // npc->SendAppearancePacket(AT_Light, npc->GetActiveLightValue()); } -//if itemlist is null, just send wear changes -void NPC::AddLootDrop(const EQ::ItemData *item2, ItemList* itemlist, int16 charges, uint8 minlevel, uint8 maxlevel, bool equipit, bool wearchange, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, uint32 aug6) { - if(item2 == nullptr) - return; - - //make sure we are doing something... - if(!itemlist && !wearchange) - return; - - auto item = new ServerLootItem_Struct; -#if EQDEBUG>=11 - LogDebug("Adding drop to npc: [{}], Item: [{}]", GetName(), item2->ID); -#endif - - EQApplicationPacket* outapp = nullptr; - WearChange_Struct* wc = nullptr; - if(wearchange) { - outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); - wc = (WearChange_Struct*)outapp->pBuffer; - wc->spawn_id = GetID(); - wc->material=0; +bool NPC::MeetsLevelRequirements(LootDropEntries_Struct loot_drop) +{ + if (loot_drop.npc_min_level > 0 && GetLevel() < loot_drop.npc_min_level) { + return false; } - item->item_id = item2->ID; - item->charges = charges; - item->aug_1 = aug1; - item->aug_2 = aug2; - item->aug_3 = aug3; - item->aug_4 = aug4; - item->aug_5 = aug5; - item->aug_6 = aug6; - item->attuned = 0; - item->min_level = minlevel; - item->max_level = maxlevel; - item->equip_slot = EQ::invslot::SLOT_INVALID; + if (loot_drop.npc_max_level > 0 && GetLevel() > loot_drop.npc_max_level) { + return false; + } - if (equipit) { + return true; +} + +//if itemlist is null, just send wear changes +void NPC::AddLootDrop( + const EQ::ItemData *item2, + ItemList *itemlist, + LootDropEntries_Struct loot_drop, + bool wear_change, + uint32 aug1, + uint32 aug2, + uint32 aug3, + uint32 aug4, + uint32 aug5, + uint32 aug6 +) +{ + if (item2 == nullptr) { + return; + } + + //make sure we are doing something... + if (!itemlist && !wear_change) { + return; + } + + auto item = new ServerLootItem_Struct; + + if (LogSys.log_settings[Logs::Loot].is_category_enabled == 1) { + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemData); + linker.SetItemData(item2); + + LogLoot( + "[NPC::AddLootDrop] NPC [{}] Item ({}) [{}] charges [{}] chance [{}] trivial min/max [{}/{}] npc min/max [{}/{}]", + GetName(), + item2->ID, + linker.GenerateLink(), + loot_drop.item_charges, + loot_drop.chance, + loot_drop.trivial_min_level, + loot_drop.trivial_max_level, + loot_drop.npc_min_level, + loot_drop.npc_max_level + ); + } + + EQApplicationPacket *outapp = nullptr; + WearChange_Struct *p_wear_change_struct = nullptr; + if (wear_change) { + outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); + p_wear_change_struct = (WearChange_Struct *) outapp->pBuffer; + p_wear_change_struct->spawn_id = GetID(); + p_wear_change_struct->material = 0; + } + + item->item_id = item2->ID; + item->charges = loot_drop.item_charges; + item->aug_1 = aug1; + item->aug_2 = aug2; + item->aug_3 = aug3; + item->aug_4 = aug4; + item->aug_5 = aug5; + item->aug_6 = aug6; + item->attuned = 0; + item->trivial_min_level = loot_drop.trivial_min_level; + item->trivial_max_level = loot_drop.trivial_max_level; + item->equip_slot = EQ::invslot::SLOT_INVALID; + + if (loot_drop.equip_item > 0) { uint8 eslot = 0xFF; char newid[20]; const EQ::ItemData* compitem = nullptr; @@ -403,9 +464,9 @@ void NPC::AddLootDrop(const EQ::ItemData *item2, ItemList* itemlist, int16 charg //if we found an open slot it goes in... if(eslot != 0xFF) { - if(wearchange) { - wc->wear_slot_id = eslot; - wc->material = emat; + if(wear_change) { + p_wear_change_struct->wear_slot_id = eslot; + p_wear_change_struct->material = emat; } } @@ -415,24 +476,25 @@ void NPC::AddLootDrop(const EQ::ItemData *item2, ItemList* itemlist, int16 charg } } - if(itemlist != nullptr) + if (itemlist != nullptr) { itemlist->push_back(item); - else - safe_delete(item); + } + else safe_delete(item); - if(wearchange && outapp) { + if (wear_change && outapp) { entity_list.QueueClients(this, outapp); safe_delete(outapp); } UpdateEquipmentLight(); - if (UpdateActiveLight()) + if (UpdateActiveLight()) { SendAppearancePacket(AT_Light, GetActiveLightType()); + } } void NPC::AddItem(const EQ::ItemData* item, uint16 charges, bool equipitem) { //slot isnt needed, its determined from the item. - AddLootDrop(item, &itemlist, charges, 1, 255, equipitem, equipitem); + AddLootDrop(item, &itemlist, LootDropEntries_Struct{ .equip_item = equipitem }, true); } void NPC::AddItem(uint32 itemid, uint16 charges, bool equipitem, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, uint32 aug6) { @@ -440,7 +502,7 @@ void NPC::AddItem(uint32 itemid, uint16 charges, bool equipitem, uint32 aug1, ui const EQ::ItemData * i = database.GetItem(itemid); if(i == nullptr) return; - AddLootDrop(i, &itemlist, charges, 1, 255, equipitem, equipitem, aug1, aug2, aug3, aug4, aug5, aug6); + AddLootDrop(i, &itemlist, LootDropEntries_Struct{ .equip_item = equipitem }, true, aug1, aug2, aug3, aug4, aug5, aug6); } void NPC::AddLootTable() { diff --git a/zone/npc.cpp b/zone/npc.cpp index 278c7dc7c..b0b9e65f7 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -576,25 +576,33 @@ void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) { } } -void NPC::CheckMinMaxLevel(Mob *them) +void NPC::CheckTrivialMinMaxLevelDrop(Mob *killer) { - if(them == nullptr || !them->IsClient()) + if (killer == nullptr || !killer->IsClient()) { return; + } - uint16 themlevel = them->GetLevel(); - uint8 material; + uint16 killer_level = killer->GetLevel(); + uint8 material; auto cur = itemlist.begin(); - while(cur != itemlist.end()) - { - if(!(*cur)) + while (cur != itemlist.end()) { + if (!(*cur)) { return; + } - if(themlevel < (*cur)->min_level || themlevel > (*cur)->max_level) - { + uint16 trivial_min_level = (*cur)->trivial_min_level; + uint16 trivial_max_level = (*cur)->trivial_max_level; + bool fits_trivial_criteria = ( + (trivial_min_level > 0 && killer_level < trivial_min_level) || + (trivial_max_level > 0 && killer_level > trivial_max_level) + ); + + if (fits_trivial_criteria) { material = EQ::InventoryProfile::CalcMaterialFromSlot((*cur)->equip_slot); - if (material != EQ::textures::materialInvalid) + if (material != EQ::textures::materialInvalid) { SendWearChange(material); + } cur = itemlist.erase(cur); continue; @@ -603,8 +611,9 @@ void NPC::CheckMinMaxLevel(Mob *them) } UpdateEquipmentLight(); - if (UpdateActiveLight()) + if (UpdateActiveLight()) { SendAppearancePacket(AT_Light, GetActiveLightType()); + } } void NPC::ClearItemList() { @@ -647,8 +656,8 @@ void NPC::QueryLoot(Client* to) item_count, linker.GenerateLink().c_str(), (*cur)->item_id, - (*cur)->min_level, - (*cur)->max_level + (*cur)->trivial_min_level, + (*cur)->trivial_max_level ); } diff --git a/zone/npc.h b/zone/npc.h index ba9ab54b3..4ea8b6ecd 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -25,6 +25,7 @@ #include "zonedb.h" #include "zone_store.h" #include "zonedump.h" +#include "../common/loottable.h" #include #include @@ -193,7 +194,7 @@ public: void CheckGlobalLootTables(); void DescribeAggro(Client *towho, Mob *mob, bool verbose); void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0); - void CheckMinMaxLevel(Mob *them); + void CheckTrivialMinMaxLevelDrop(Mob *killer); void ClearItemList(); ServerLootItem_Struct* GetItem(int slot_id); void AddCash(uint16 in_copper, uint16 in_silver, uint16 in_gold, uint16 in_platinum); @@ -291,7 +292,22 @@ public: void PickPocket(Client* thief); void Disarm(Client* client, int chance); void StartSwarmTimer(uint32 duration) { swarm_timer.Start(duration); } - void AddLootDrop(const EQ::ItemData*dbitem, ItemList* itemlistconst, int16 charges, uint8 minlevel, uint8 maxlevel, bool equipit, bool wearchange = false, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0); + + void AddLootDrop( + const EQ::ItemData *item2, + ItemList *itemlist, + LootDropEntries_Struct loot_drop, + bool wear_change = false, + uint32 aug1 = 0, + uint32 aug2 = 0, + uint32 aug3 = 0, + uint32 aug4 = 0, + uint32 aug5 = 0, + uint32 aug6 = 0 + ); + + bool MeetsLevelRequirements(LootDropEntries_Struct loot_drop); + virtual void DoClassAttacks(Mob *target); void CheckSignal(); inline bool IsNotTargetableWithHotkey() const { return no_target_hotkey; } @@ -620,12 +636,12 @@ protected: bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup - private: uint32 loottable_id; bool skip_global_loot; bool skip_auto_scale; bool p_depop; + }; #endif diff --git a/zone/pets.cpp b/zone/pets.cpp index 32c0dae1e..f8d0a6c37 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -394,7 +394,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, for (int i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; i++) if (petinv[i]) { item = database.GetItem(petinv[i]); - npc->AddLootDrop(item, &npc->itemlist, 0, 1, 127, true, true); + npc->AddLootDrop(item, &npc->itemlist, LootDropEntries_Struct{ .equip_item = true }, true); } } @@ -625,7 +625,7 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { _CLIENTPET(this) && GetPetType() <= petOther); if (!noDrop || petCanHaveNoDrop) { - AddLootDrop(item2, &itemlist, 0, 1, 255, true, true); + AddLootDrop(item2, &itemlist, LootDropEntries_Struct{.equip_item = true }, true); } } } diff --git a/zone/trading.cpp b/zone/trading.cpp index 91ac17870..b192ae8fe 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -899,8 +899,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (baginst) { const EQ::ItemData* bagitem = baginst->GetItem(); if (bagitem && (GetGM() || (bagitem->NoDrop != 0 && baginst->IsAttuned() == false))) { - tradingWith->CastToNPC()->AddLootDrop(bagitem, &tradingWith->CastToNPC()->itemlist, - baginst->GetCharges(), 1, 127, true, true); + tradingWith->CastToNPC()->AddLootDrop( + bagitem, + &tradingWith->CastToNPC()->itemlist, + LootDropEntries_Struct{.item_charges = static_cast(baginst->GetCharges()), .equip_item = true }, + true + ); } else if (RuleB(NPC, ReturnNonQuestNoDropItems)) { PushItemOnCursor(*baginst, true); @@ -909,8 +913,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st } } - tradingWith->CastToNPC()->AddLootDrop(item, &tradingWith->CastToNPC()->itemlist, - inst->GetCharges(), 1, 127, true, true); + tradingWith->CastToNPC()->AddLootDrop( + item, + &tradingWith->CastToNPC()->itemlist, + LootDropEntries_Struct{.item_charges = static_cast(inst->GetCharges()), .equip_item = true }, + true + ); } // Return NO DROP and Attuned items being handed into a non-quest NPC if the rule is true else if (RuleB(NPC, ReturnNonQuestNoDropItems)) { diff --git a/zone/zonedb.h b/zone/zonedb.h index 1ed875a02..f3766c05c 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -453,7 +453,7 @@ public: bool GetPoweredPetEntry(const char *pet_type, int16 petpower, PetRecord *into); bool GetBasePetItems(int32 equipmentset, uint32 *items); void AddLootTableToNPC(NPC* npc, uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat); - void AddLootDropToNPC(NPC* npc, uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop); + void AddLootDropToNPC(NPC* npc, uint32 lootdrop_id, ItemList* item_list, uint8 droplimit, uint8 mindrop); uint32 GetMaxNPCSpellsID(); uint32 GetMaxNPCSpellsEffectsID(); bool GetAuraEntry(uint16 spell_id, AuraRecord &record);