#include "../common/global_define.h" #include "../common/data_verification.h" #include "../common/loot.h" #include "client.h" #include "entity.h" #include "mob.h" #include "npc.h" #include "zonedb.h" #include "global_loot_manager.h" #include "../common/repositories/criteria/content_filter_criteria.h" #include "../common/repositories/global_loot_repository.h" #include "quest_parser_collection.h" #ifdef _WINDOWS #define snprintf _snprintf #endif void NPC::AddLootTable(uint32 loottable_id, bool is_global) { // check if it's a GM spawn if (!npctype_id) { return; } if (!is_global) { m_loot_copper = 0; m_loot_silver = 0; m_loot_gold = 0; m_loot_platinum = 0; } zone->LoadLootTable(loottable_id); const auto *l = zone->GetLootTable(loottable_id); if (!l) { return; } LogLootDetail( "Attempting to load loot [{}] loottable [{}] ({}) is_global [{}]", GetCleanName(), loottable_id, l->name, is_global ); auto content_flags = ContentFlags{ .min_expansion = l->min_expansion, .max_expansion = l->max_expansion, .content_flags = l->content_flags, .content_flags_disabled = l->content_flags_disabled }; if (!content_service.DoesPassContentFiltering(content_flags)) { return; } uint32 min_cash = l->mincash; uint32 max_cash = l->maxcash; if (min_cash > max_cash) { const uint32 t = min_cash; min_cash = max_cash; max_cash = t; } uint32 cash = 0; if (!is_global) { if (max_cash > 0 && l->avgcoin > 0 && EQ::ValueWithin(l->avgcoin, min_cash, max_cash)) { const float upper_chance = static_cast(l->avgcoin - min_cash) / static_cast(max_cash - min_cash); const float avg_cash_roll = static_cast(zone->random.Real(0.0, 1.0)); if (avg_cash_roll < upper_chance) { cash = zone->random.Int(l->avgcoin, max_cash); } else { cash = zone->random.Int(min_cash, l->avgcoin); } } else { cash = zone->random.Int(min_cash, max_cash); } } if (cash != 0) { m_loot_platinum = cash / 1000; cash -= m_loot_platinum * 1000; m_loot_gold = cash / 100; cash -= m_loot_gold * 100; m_loot_silver = cash / 10; cash -= m_loot_silver * 10; m_loot_copper = cash; } const uint32 global_loot_multiplier = RuleI(Zone, GlobalLootMultiplier); for (auto <e: zone->GetLootTableEntries(loottable_id)) { for (uint32 k = 1; k <= (lte.multiplier * global_loot_multiplier); k++) { const uint8 drop_limit = lte.droplimit; const uint8 minimum_drop = lte.mindrop; const float probability = lte.probability; float drop_chance = 0.0f; if (EQ::ValueWithin(probability, 0.0f, 100.0f)) { drop_chance = static_cast(zone->random.Real(0.0, 100.0)); } if (probability != 0.0 && (probability == 100.0 || drop_chance <= probability)) { AddLootDropTable(lte.lootdrop_id, drop_limit, minimum_drop); } } } LogLootDetail( "Loaded [{}] Loot Table [{}] is_global [{}]", GetCleanName(), loottable_id, is_global ); } void NPC::AddLootDropTable(uint32 lootdrop_id, uint8 drop_limit, uint8 min_drop) { const auto l = zone->GetLootdrop(lootdrop_id); const auto le = zone->GetLootdropEntries(lootdrop_id); auto content_flags = ContentFlags{ .min_expansion = l.min_expansion, .max_expansion = l.max_expansion, .content_flags = l.content_flags, .content_flags_disabled = l.content_flags_disabled }; if (l.id == 0 || le.empty() || !content_service.DoesPassContentFiltering(content_flags)) { return; } // if this lootdrop is droplimit=0 and mindrop 0, scan list once and return if (drop_limit == 0 && min_drop == 0) { for (const auto &e: le) { LogLootDetail( "-- NPC [{}] Lootdrop [{}] Item [{}] ({}_ Chance [{}] Multiplier [{}]", GetCleanName(), lootdrop_id, database.GetItem(e.item_id)->Name, e.item_id, e.chance, e.multiplier ); for (int j = 0; j < e.multiplier; ++j) { if (zone->random.Real(0.0, 100.0) <= e.chance && MeetsLootDropLevelRequirements(e, true)) { const EQ::ItemData *database_item = database.GetItem(e.item_id); AddLootDrop(database_item, e); LogLootDetail( "---- NPC (Rolled) [{}] Lootdrop [{}] Item [{}] ({}) Chance [{}] Multiplier [{}]", GetCleanName(), lootdrop_id, database_item->Name, e.item_id, e.chance, e.multiplier ); } } } return; } if (le.size() > 100 && drop_limit == 0) { drop_limit = 10; } if (drop_limit < min_drop) { drop_limit = min_drop; } float roll_t = 0.0f; float no_loot_prob = 1.0f; bool roll_table_chance_bypass = false; bool active_item_list = false; for (const auto &e: le) { const EQ::ItemData *db_item = database.GetItem(e.item_id); if (db_item && MeetsLootDropLevelRequirements(e)) { roll_t += e.chance; if (e.chance >= 100) { roll_table_chance_bypass = true; } else { no_loot_prob *= (100 - e.chance) / 100.0f; } active_item_list = true; } } if (!active_item_list) { return; } // This will pick one item per iteration until mindrop. // Don't let the compare against chance fool you. // The roll isn't 0-100, its 0-total and it picks the item, we're just // looping to find the lucky item, descremening otherwise. This is ok, // items with chance 60 are 6 times more likely than items chance 10. int drops = 0; // translate above for loop using l and le for (int i = 0; i < drop_limit; ++i) { if (drops < min_drop || roll_table_chance_bypass || (float) zone->random.Real(0.0, 1.0) >= no_loot_prob) { float roll = (float) zone->random.Real(0.0, roll_t); for (const auto &e: le) { const auto *db_item = database.GetItem(e.item_id); if (db_item) { // if it doesn't meet the requirements do nothing if (!MeetsLootDropLevelRequirements(e)) { continue; } if (roll < e.chance) { AddLootDrop(db_item, e); drops++; uint8 charges = e.multiplier; charges = EQ::ClampLower(charges, static_cast(1)); for (int k = 1; k < charges; ++k) { float c_roll = static_cast(zone->random.Real(0.0, 100.0)); if (c_roll <= e.chance) { AddLootDrop(db_item, e); } } break; } else { roll -= e.chance; } } } } } UpdateEquipmentLight(); } bool NPC::MeetsLootDropLevelRequirements(LootdropEntriesRepository::LootdropEntries loot_drop, bool verbose) { if (loot_drop.npc_min_level > 0 && GetLevel() < loot_drop.npc_min_level) { if (verbose) { LogLootDetail( "NPC [{}] does not meet loot_drop level requirements (min_level) level [{}] current [{}] for item [{}]", GetCleanName(), loot_drop.npc_min_level, GetLevel(), database.CreateItemLink(loot_drop.item_id) ); } return false; } if (loot_drop.npc_max_level > 0 && GetLevel() > loot_drop.npc_max_level) { if (verbose) { LogLootDetail( "NPC [{}] does not meet loot_drop level requirements (max_level) level [{}] current [{}] for item [{}]", GetCleanName(), loot_drop.npc_max_level, GetLevel(), database.CreateItemLink(loot_drop.item_id) ); } return false; } return true; } //if itemlist is null, just send wear changes void NPC::AddLootDrop( const EQ::ItemData *item2, LootdropEntriesRepository::LootdropEntries loot_drop, bool wear_change, bool quest, bool pet, uint32 augment_one, uint32 augment_two, uint32 augment_three, uint32 augment_four, uint32 augment_five, uint32 augment_six ) { if (!item2) { return; } if (CountQuestItems() >= MAX_NPC_QUEST_INVENTORY) { return; } auto item = new LootItem; if (LogSys.log_settings[Logs::Loot].is_category_enabled == 1) { EQ::SayLinkEngine linker; linker.SetLinkType(EQ::saylink::SayLinkItemData); linker.SetItemData(item2); LogLoot( "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 ); } if (quest || pet) { LogLoot("Adding {} to npc: {}. Wearchange: {} Multiquest: {} Pet: {}", item2->Name, GetName(), wear_change, quest, pet); } 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 = augment_one; item->aug_2 = augment_two; item->aug_3 = augment_three; item->aug_4 = augment_four; item->aug_5 = augment_five; item->aug_6 = augment_six; item->attuned = false; 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; item->quest = quest; item->pet = pet; // unsure if required to equip, YOLO for now if (item2->ItemType == EQ::item::ItemTypeBow) { SetBowEquipped(true); } if (item2->ItemType == EQ::item::ItemTypeArrow) { SetArrowEquipped(true); } if (pet && quest) { LogLoot("Error: Item {} is being added to {} as both a pet and a quest.", item2->Name, GetName()); item->pet = 0; } bool found = false; // track if we found an empty slot we fit into int found_slot = INVALID_INDEX; // for multi-slot items auto *inst = database.CreateItem( item2->ID, loot_drop.item_charges, augment_one, augment_two, augment_three, augment_four, augment_five, augment_six ); if (!inst) { return; } if (loot_drop.equip_item > 0) { uint8 equipment_slot = UINT8_MAX; const EQ::ItemData *compitem = nullptr; // Equip rules are as follows: // If the item has the NoPet flag set it will not be equipped. // An empty slot takes priority. The first empty one that an item can // fit into will be the one picked for the item. // AC is the primary choice for which item gets picked for a slot. // If AC is identical HP is considered next. // If an item can fit into multiple slots we'll pick the last one where // it is an improvement. if (!item2->NoPet) { for (int i = EQ::invslot::EQUIPMENT_BEGIN; !found && i <= EQ::invslot::EQUIPMENT_END; i++) { const uint32 slots = (1 << i); if (item2->Slots & slots) { if (equipment[i]) { compitem = database.GetItem(equipment[i]); if (item2->AC > compitem->AC || (item2->AC == compitem->AC && item2->HP > compitem->HP)) { // item would be an upgrade // check if we're multi-slot, if yes then we have to keep // looking in case any of the other slots we can fit into are empty. if (item2->Slots != slots) { found_slot = i; } else { // Unequip old item auto *old_item = GetItem(i); old_item->equip_slot = EQ::invslot::SLOT_INVALID; equipment[i] = item2->ID; found_slot = i; found = true; } } } else { equipment[i] = item2->ID; found_slot = i; found = true; } } } } // Possible slot was found but not selected. Pick it now. if (!found && found_slot >= 0) { equipment[found_slot] = item2->ID; found = true; } uint32 equipment_material; if ( item2->Material <= 0 || ( item2->Slots & ( (1 << EQ::invslot::slotPrimary) | (1 << EQ::invslot::slotSecondary) ) ) ) { equipment_material = Strings::ToUnsignedInt(&item2->IDFile[2]); } else { equipment_material = item2->Material; } if (found_slot == EQ::invslot::slotPrimary) { equipment_slot = EQ::textures::weaponPrimary; if (item2->Damage > 0) { SendAddPlayerState(PlayerState::PrimaryWeaponEquipped); if (!RuleB(Combat, ClassicNPCBackstab)) { SetFacestab(true); } } if (item2->IsType2HWeapon()) { SetTwoHanderEquipped(true); } } else if ( found_slot == EQ::invslot::slotSecondary && ( GetOwner() || (CanThisClassDualWield() && zone->random.Roll(NPC_DW_CHANCE)) || item2->Damage == 0 ) && ( item2->IsType1HWeapon() || item2->ItemType == EQ::item::ItemTypeShield || item2->ItemType == EQ::item::ItemTypeLight ) ) { equipment_slot = EQ::textures::weaponSecondary; if (item2->Damage > 0) { SendAddPlayerState(PlayerState::SecondaryWeaponEquipped); } } else if (found_slot == EQ::invslot::slotHead) { equipment_slot = EQ::textures::armorHead; } else if (found_slot == EQ::invslot::slotChest) { equipment_slot = EQ::textures::armorChest; } else if (found_slot == EQ::invslot::slotArms) { equipment_slot = EQ::textures::armorArms; } else if (EQ::ValueWithin(found_slot, EQ::invslot::slotWrist1, EQ::invslot::slotWrist2)) { equipment_slot = EQ::textures::armorWrist; } else if (found_slot == EQ::invslot::slotHands) { equipment_slot = EQ::textures::armorHands; } else if (found_slot == EQ::invslot::slotLegs) { equipment_slot = EQ::textures::armorLegs; } else if (found_slot == EQ::invslot::slotFeet) { equipment_slot = EQ::textures::armorFeet; } if (equipment_slot != UINT8_MAX) { if (wear_change) { p_wear_change_struct->wear_slot_id = equipment_slot; p_wear_change_struct->material = equipment_material; } } if (found) { item->equip_slot = found_slot; } } if (found_slot != INVALID_INDEX) { GetInv().PutItem(found_slot, *inst); } if (parse->HasQuestSub(GetNPCTypeID(), EVENT_LOOT_ADDED)) { std::vector args = {inst}; parse->EventNPC(EVENT_LOOT_ADDED, this, nullptr, "", 0, &args); } m_loot_items.push_back(item); if (found) { CalcBonuses(); } if (IsRecordLootStats()) { m_rolled_items.emplace_back(item->item_id); } if (wear_change && outapp) { entity_list.QueueClients(this, outapp); safe_delete(outapp); } UpdateEquipmentLight(); if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } safe_delete(inst); } void NPC::AddItem(const EQ::ItemData *item, uint16 charges, bool equip_item, bool quest) { auto l = LootdropEntriesRepository::NewNpcEntity(); l.equip_item = static_cast(equip_item ? 1 : 0); l.item_charges = charges; AddLootDrop(item, l, equip_item, quest); } void NPC::AddItem( uint32 item_id, uint16 charges, bool equip_item, bool quest, uint32 augment_one, uint32 augment_two, uint32 augment_three, uint32 augment_four, uint32 augment_five, uint32 augment_six ) { const auto *item = database.GetItem(item_id); if (!item) { return; } auto l = LootdropEntriesRepository::NewNpcEntity(); l.equip_item = static_cast(equip_item ? 1 : 0); l.item_charges = charges; AddLootDrop( item, l, equip_item, quest, augment_one, augment_two, augment_three, augment_four, augment_five, augment_six ); } void NPC::AddLootTable() { AddLootTable(m_loottable_id); } void NPC::CheckGlobalLootTables() { const auto &l = zone->GetGlobalLootTables(this); for (const auto &e: l) { AddLootTable(e, true); } } void ZoneDatabase::LoadGlobalLoot() { const auto &l = GlobalLootRepository::GetWhere( *this, fmt::format( "`enabled` = 1 {}", ContentFilterCriteria::apply() ) ); if (l.empty()) { return; } LogInfo( "Loaded [{}] Global Loot Entr{}.", Strings::Commify(l.size()), l.size() != 1 ? "ies" : "y" ); const std::string &zone_id = std::to_string(zone->GetZoneID()); for (const auto &e: l) { if (!e.zone.empty()) { const auto &zones = Strings::Split(e.zone, "|"); if (!Strings::Contains(zones, zone_id)) { continue; } } GlobalLootEntry gle(e.id, e.loottable_id, e.description); if (e.min_level) { gle.AddRule(GlobalLoot::RuleTypes::LevelMin, e.min_level); } if (e.max_level) { gle.AddRule(GlobalLoot::RuleTypes::LevelMax, e.max_level); } if (e.rare) { gle.AddRule(GlobalLoot::RuleTypes::Rare, e.rare); } if (e.raid) { gle.AddRule(GlobalLoot::RuleTypes::Raid, e.raid); } if (!e.race.empty()) { const auto &races = Strings::Split(e.race, "|"); for (const auto &r: races) { gle.AddRule(GlobalLoot::RuleTypes::Race, Strings::ToInt(r)); } } if (!e.class_.empty()) { const auto &classes = Strings::Split(e.class_, "|"); for (const auto &c: classes) { gle.AddRule(GlobalLoot::RuleTypes::Class, Strings::ToInt(c)); } } if (!e.bodytype.empty()) { const auto &bodytypes = Strings::Split(e.bodytype, "|"); for (const auto &b: bodytypes) { gle.AddRule(GlobalLoot::RuleTypes::BodyType, Strings::ToInt(b)); } } if (e.hot_zone) { gle.AddRule(GlobalLoot::RuleTypes::HotZone, e.hot_zone); } zone->AddGlobalLootEntry(gle); } } LootItem *NPC::GetItem(int slot_id) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *item = *cur; if (item->equip_slot == slot_id) { return item; } } return (nullptr); } LootItem *NPC::GetItemByItemID(int16 item_id) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *item = *cur; if (item->item_id == item_id) { return item; } } return(nullptr); } void NPC::RemoveItem(LootItem *item_data, uint8 quantity) { if (!item_data) { return; } LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *item = *cur; if (item != item_data) { continue; } if (!item) { return; } if (item->charges <= quantity) { m_loot_items.erase(cur); UpdateEquipmentLight(); if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } } else { item->charges -= quantity; } return; } } void NPC::RemoveItem(uint32 item_id, uint16 quantity, uint16 slot) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *item = *cur; if (item->item_id == item_id && slot <= 0 && quantity <= 0) { m_loot_items.erase(cur); UpdateEquipmentLight(); if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } return; } else if (item->item_id == item_id && item->equip_slot == slot && quantity >= 1) { if (item->charges <= quantity) { m_loot_items.erase(cur); UpdateEquipmentLight(); if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } } else { item->charges -= quantity; } return; } } } void NPC::CheckTrivialMinMaxLevelDrop(Mob *killer) { if (killer == nullptr || !killer->IsClient()) { return; } uint16 killer_level = killer->GetLevel(); uint8 material; auto cur = m_loot_items.begin(); while (cur != m_loot_items.end()) { if (!(*cur)) { return; } 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) { SendWearChange(material); } cur = m_loot_items.erase(cur); continue; } ++cur; } UpdateEquipmentLight(); if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } } void NPC::ClearLootItems() { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *item = *cur; safe_delete(item); } m_loot_items.clear(); UpdateEquipmentLight(); if (UpdateActiveLight()) { SendAppearancePacket(AppearanceType::Light, GetActiveLightType()); } } void NPC::QueryLoot(Client *to, bool is_pet_query) { if (!m_loot_items.empty()) { if (!is_pet_query) { to->Message( Chat::White, fmt::format( "Loot | {} ({}) ID: {} Loottable ID: {}", GetName(), GetID(), GetNPCTypeID(), GetLoottableID() ).c_str() ); } int item_count = 0; for (auto current_item: m_loot_items) { int item_number = (item_count + 1); if (!current_item) { LogError("ItemList error, null item."); continue; } if (!current_item->item_id || !database.GetItem(current_item->item_id)) { LogError("Database error, invalid item."); continue; } EQ::SayLinkEngine linker; linker.SetLinkType(EQ::saylink::SayLinkLootItem); linker.SetLootData(current_item); to->Message( Chat::White, fmt::format( "Item {} | {} ({}){}", item_number, linker.GenerateLink().c_str(), current_item->item_id, ( current_item->charges > 1 ? fmt::format( " Amount: {}", current_item->charges ) : "" ) ).c_str() ); item_count++; } } if (!is_pet_query) { if (m_loot_platinum || m_loot_gold || m_loot_silver || m_loot_copper) { to->Message( Chat::White, fmt::format( "Money | {}", Strings::Money( m_loot_platinum, m_loot_gold, m_loot_silver, m_loot_copper ) ).c_str() ); } } } bool NPC::HasItem(uint32 item_id) { if (!database.GetItem(item_id)) { return false; } for (auto loot_item: m_loot_items) { if (!loot_item) { LogError("NPC::HasItem() - ItemList error, null item"); continue; } if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { LogError("NPC::HasItem() - Database error, invalid item"); continue; } if (loot_item->item_id == item_id) { return true; } } return false; } uint16 NPC::CountItem(uint32 item_id) { uint16 item_count = 0; if (!database.GetItem(item_id)) { return item_count; } for (auto loot_item: m_loot_items) { if (!loot_item) { LogError("NPC::CountItem() - ItemList error, null item"); continue; } if (!loot_item->item_id || !database.GetItem(loot_item->item_id)) { LogError("NPC::CountItem() - Database error, invalid item"); continue; } if (loot_item->item_id == item_id) { item_count += loot_item->charges > 0 ? loot_item->charges : 1; } } return item_count; } uint32 NPC::GetLootItemIDBySlot(uint16 loot_slot) { for (auto loot_item: m_loot_items) { if (loot_item->lootslot == loot_slot) { return loot_item->item_id; } } return 0; } uint16 NPC::GetFirstLootSlotByItemID(uint32 item_id) { for (auto loot_item: m_loot_items) { if (loot_item->item_id == item_id) { return loot_item->lootslot; } } return 0; } void NPC::AddLootCash( uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_platinum ) { m_loot_copper = in_copper >= 0 ? in_copper : 0; m_loot_silver = in_silver >= 0 ? in_silver : 0; m_loot_gold = in_gold >= 0 ? in_gold : 0; m_loot_platinum = in_platinum >= 0 ? in_platinum : 0; } void NPC::RemoveLootCash() { m_loot_copper = 0; m_loot_silver = 0; m_loot_gold = 0; m_loot_platinum = 0; } bool NPC::HasQuestLootItem(int16 itemid) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *quest_item = *cur; if (quest_item && quest_item->quest == 1 && quest_item->item_id == itemid) { return true; } } return false; } bool NPC::HasQuestLoot() { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *quest_loot = *cur; if (quest_loot && quest_loot->quest == 1) { return true; } } return false; } bool NPC::RemoveQuestLootItems(int16 itemid) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *quest_item = *cur; if (quest_item && quest_item->quest == 1) { if (itemid == 0 || itemid == quest_item->item_id) { RemoveItem(quest_item); return true; } } } return false; } bool NPC::HasRequiredQuestLoot(int16 itemid1, int16 itemid2, int16 itemid3, int16 itemid4) { if (itemid2 == 0 && itemid3 == 0 && itemid4 == 0) { return true; } uint8 item2count = 0, item3count = 0, item4count = 0, item1npc = 0, item2npc = 0, item3npc = 0, item4npc = 0; uint8 item1count = 1; if (itemid2 > 0) { item2count = 1; } if (itemid3 > 0) { item3count = 1; } if (itemid4 > 0) { item4count = 1; } if (itemid1 == itemid2 && itemid2 > 0) { item2count = item1count; ++item1count; ++item2count; } if (itemid1 == itemid3 && itemid3 > 0) { item3count = item1count; ++item1count; ++item3count; } if (itemid1 == itemid4 && itemid4 > 0) { item4count = item1count; ++item1count; ++item4count; } if (itemid2 == itemid3 && itemid2 > 0 && itemid3 > 0) { item3count = item2count; ++item2count; ++item3count; } if (itemid2 == itemid4 && itemid2 > 0 && itemid4 > 0) { item4count = item2count; ++item2count; ++item4count; } if (itemid3 == itemid4 && itemid3 > 0 && itemid4 > 0) { item4count = item3count; ++item3count; ++item4count; } LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *sitem = *cur; if (sitem && sitem->quest == 1) { if (sitem->item_id == itemid1) { ++item1npc; } if (sitem->item_id == itemid2 && itemid2 > 0) { ++item2npc; } if (sitem->item_id == itemid3 && itemid3 > 0) { ++item3npc; } if (sitem->item_id == itemid4 && itemid4 > 0) { ++item4npc; } } } if (item1npc < item1count) { return false; } if (itemid2 > 0 && item2npc < item2count) { return false; } if (itemid3 > 0 && item3npc < item3count) { return false; } if (itemid4 > 0 && item4npc < item4count) { return false; } return true; } void NPC::CleanQuestLootItems() { //Removes nodrop or multiple quest loot items from a NPC before sending the corpse items to the client. LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); uint8 count = 0; for (; cur != end; ++cur) { LootItem *quest_item = *cur; if (quest_item && (quest_item->quest == 1 || quest_item->pet == 1)) { uint8 count = CountQuestItem(quest_item->item_id); if (count > 1 && quest_item->pet != 1) { RemoveItem(quest_item); return; } else { const EQ::ItemData *item = database.GetItem(quest_item->item_id); if (item && item->NoDrop == 0) { RemoveItem(quest_item); return; } } } } } uint8 NPC::CountQuestItem(uint16 itemid) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); uint8 count = 0; for (; cur != end; ++cur) { LootItem *quest_item = *cur; if (quest_item && quest_item->item_id == itemid) { ++count; } } return count; } uint8 NPC::CountQuestItems() { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); uint8 count = 0; for (; cur != end; ++cur) { LootItem *quest_item = *cur; if (quest_item && quest_item->quest == 1) { ++count; } } return count; } bool NPC::AddQuestLoot(int16 itemid, int8 charges) { auto l = LootdropEntriesRepository::NewNpcEntity(); const EQ::ItemData *item = database.GetItem(itemid); if (item) { l.item_charges = charges; l.equip_item = 0; AddLootDrop(item, l, false, false, true); LogLoot("Adding item {} to the NPC's loot marked as quest.", itemid); if (itemid > 0 && HasPetLootItem(itemid)) { LogLoot("Deleting quest item {} from NPC's pet loot.", itemid); RemovePetLootItems(itemid); } } else return false; return true; } void NPC::DeleteQuestLoot(int16 itemid1, int16 itemid2, int16 itemid3, int16 itemid4) { int16 items = m_loot_items.size(); for (int i = 0; i < items; ++i) { if (itemid1 == 0) { if (!RemoveQuestLootItems(itemid1)) break; } else { if (itemid1 != 0) { RemoveQuestLootItems(itemid1); } if (itemid2 != 0) { RemoveQuestLootItems(itemid2); } if (itemid3 != 0) { RemoveQuestLootItems(itemid3); } if (itemid4 != 0) { RemoveQuestLootItems(itemid4); } } } } void NPC::DeleteInvalidQuestLoot() { int16 items = m_loot_items.size(); for (int i = 0; i < items; ++i) { CleanQuestLootItems(); } } bool NPC::AddPetLoot(int16 itemid, int8 charges, bool fromquest) { auto l = LootdropEntriesRepository::NewNpcEntity(); const EQ::ItemData *item = database.GetItem(itemid); bool IsCharmedPet = IsPet() && IsCharmed(); if (!item) { return false; } bool valid = (item->NoDrop != 0 && ( !IsCharmedPet || (IsCharmedPet && CountQuestItem(item->ID) == 0))); if (!fromquest || valid) { if (item) { l.item_charges = charges; AddLootDrop(item, l, true, true, false, true); LogLoot("Adding item {} to the NPC's loot marked as pet.", itemid); return true; } } else { LogLoot("Item {} is a duplicate or no drop. Deleting...", itemid); return false; } return false; } bool NPC::HasPetLootItem(int16 itemid) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *quest_item = *cur; if (quest_item && quest_item->pet == 1 && quest_item->item_id == itemid) { return true; } } return false; } bool NPC::RemovePetLootItems(int16 itemid) { LootItems::iterator cur, end; cur = m_loot_items.begin(); end = m_loot_items.end(); for (; cur != end; ++cur) { LootItem *quest_item = *cur; if (quest_item && quest_item->pet == 1) { if (itemid == 0 || itemid == quest_item->item_id) { RemoveItem(quest_item); return true; } } } return false; }