diff --git a/common/ruletypes.h b/common/ruletypes.h index c92e4c4dc..077d86503 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -543,6 +543,7 @@ RULE_INT(NPC, LastFightingDelayMovingMin, 10000, "Minimum time before mob goes h RULE_INT(NPC, LastFightingDelayMovingMax, 20000, "Maximum time before mob goes home after all aggro loss (milliseconds)") RULE_BOOL(NPC, SmartLastFightingDelayMoving, true, "When true, mobs that started going home previously will do so again immediately if still on FD hate list") RULE_BOOL(NPC, ReturnNonQuestNoDropItems, false, "Returns NO DROP items on NPC that don't have an EVENT_TRADE sub in their script") +RULE_BOOL(NPC, ReturnQuestItemsFromNonQuestNPCs, false, "Returns Quest items traded to NPCs that are not flagged as a Quest NPC") RULE_INT(NPC, StartEnrageValue, 9, " Percentage HP that an NPC will begin to enrage") RULE_BOOL(NPC, LiveLikeEnrage, false, "If set to true then only player controlled pets will enrage") RULE_BOOL(NPC, EnableMeritBasedFaction, false, "If set to true, faction will be given in the same way as experience (solo/group/raid)") diff --git a/zone/npc.cpp b/zone/npc.cpp index 40accb01f..864cc99d0 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -199,6 +199,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi CHA = npc_type_data->CHA; npc_mana = npc_type_data->Mana; m_is_underwater_only = npc_type_data->underwater; + m_is_quest_npc = npc_type_data->is_quest_npc; //quick fix of ordering if they screwed it up in the DB if (max_dmg < min_dmg) { diff --git a/zone/npc.h b/zone/npc.h index 77025f168..83e954e44 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -443,6 +443,7 @@ public: const bool HasPrivateCorpse() const { return NPCTypedata_ours ? NPCTypedata_ours->private_corpse : NPCTypedata->private_corpse; } virtual const bool IsUnderwaterOnly() const { return m_is_underwater_only; } + virtual const bool IsQuestNPC() const { return m_is_quest_npc; } const char* GetRawNPCTypeName() const { return NPCTypedata_ours ? NPCTypedata_ours->name : NPCTypedata->name; } virtual int GetKillExpMod() const { return NPCTypedata_ours ? NPCTypedata_ours->exp_mod : NPCTypedata->exp_mod; } @@ -670,6 +671,7 @@ protected: uint32 adventure_template_id; bool m_is_underwater_only = false; + bool m_is_quest_npc = false; //mercenary stuff std::list mercTypeList; diff --git a/zone/trading.cpp b/zone/trading.cpp index eebdcf720..da90dd91d 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -776,24 +776,32 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st const EQ::ItemData* item = inst->GetItem(); const bool is_pet = _CLIENTPET(tradingWith) && tradingWith->GetPetType()<=petOther; + const bool is_quest_npc = tradingWith->CastToNPC()->IsQuestNPC(); + const bool restrict_quest_items_to_quest_npc = RuleB(NPC, ReturnQuestItemsFromNonQuestNPCs); const bool pets_can_take_quest_items = RuleB(Pets, CanTakeQuestItems); const bool is_pet_and_can_have_nodrop_items = (RuleB(Pets, CanTakeNoDrop) && is_pet); const bool is_pet_and_can_have_quest_items = (pets_can_take_quest_items && is_pet); // if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it if (GetGM() || + (!restrict_quest_items_to_quest_npc || (is_quest_npc && item->IsQuestItem()) || !item->IsQuestItem()) && // If rule is enabled, return any quest items given to non-quest NPCs (((item->NoDrop != 0 && !inst->IsAttuned()) || is_pet_and_can_have_nodrop_items) && ((!item->IsQuestItem() || is_pet_and_can_have_quest_items || !is_pet)))) { // pets need to look inside bags and try to equip items found there if (item->IsClassBag() && item->BagSlots > 0) { for (int16 bslot = EQ::invbag::SLOT_BEGIN; bslot < item->BagSlots; bslot++) { - const EQ::ItemInstance* baginst = inst->GetItem(bslot); + const EQ::ItemInstance *baginst = inst->GetItem(bslot); if (baginst) { - const EQ::ItemData* bagitem = baginst->GetItem(); - if (bagitem && (GetGM() || ((bagitem->NoDrop != 0 && !baginst->IsAttuned()) || is_pet_and_can_have_nodrop_items) && - ((!bagitem->IsQuestItem()|| is_pet_and_can_have_quest_items || !is_pet)))) { + const EQ::ItemData *bagitem = baginst->GetItem(); + if (bagitem && (GetGM() || + (!restrict_quest_items_to_quest_npc || + (is_quest_npc && bagitem->IsQuestItem()) || !bagitem->IsQuestItem()) && + // If rule is enabled, return any quest items given to non-quest NPCs (inside bags) + (bagitem->NoDrop != 0 && !baginst->IsAttuned()) && + ((is_pet && (!bagitem->IsQuestItem() || pets_can_take_quest_items) || + !is_pet)))) { auto loot_drop_entry = NPC::NewLootDropEntry(); - loot_drop_entry.equip_item = 1; + loot_drop_entry.equip_item = 1; loot_drop_entry.item_charges = static_cast(baginst->GetCharges()); tradingWith->CastToNPC()->AddLootDrop( @@ -802,13 +810,17 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st loot_drop_entry, true ); - } - else if (is_pet && bagitem->IsQuestItem()) { + // Return quest items being traded to non-quest NPC when the rule is true + } else if (restrict_quest_items_to_quest_npc && (!is_quest_npc && bagitem->IsQuestItem())) { + tradingWith->SayString(TRADE_BACK, GetCleanName()); + PushItemOnCursor(*baginst, true); + Message(Chat::Red, "You can only trade quest items to quest NPCs."); + // Return quest items being traded to player pet when not allowed + } else if (is_pet && bagitem->IsQuestItem() && !pets_can_take_quest_items) { tradingWith->SayString(TRADE_BACK, GetCleanName()); PushItemOnCursor(*baginst, true); Message(Chat::Red, "You cannot trade quest items with your pet."); - } - else if (RuleB(NPC, ReturnNonQuestNoDropItems)) { + } else if (RuleB(NPC, ReturnNonQuestNoDropItems)) { tradingWith->SayString(TRADE_BACK, GetCleanName()); PushItemOnCursor(*baginst, true); } @@ -817,7 +829,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st } auto new_loot_drop_entry = NPC::NewLootDropEntry(); - new_loot_drop_entry.equip_item = 1; + new_loot_drop_entry.equip_item = 1; new_loot_drop_entry.item_charges = static_cast(inst->GetCharges()); tradingWith->CastToNPC()->AddLootDrop( @@ -827,6 +839,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st true ); } + // Return quest items being traded to non-quest NPC when the rule is true + else if (restrict_quest_items_to_quest_npc && (!is_quest_npc && item->IsQuestItem())) { + tradingWith->SayString(TRADE_BACK, GetCleanName()); + PushItemOnCursor(*inst, true); + Message(Chat::Red, "You can only trade quest items to quest NPCs."); + } // Return quest items being traded to player pet when not allowed else if (is_pet && item->IsQuestItem()) { tradingWith->SayString(TRADE_BACK, GetCleanName()); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 08d6950ee..f02e085fa 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1991,6 +1991,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load t->ranged_type = n.ranged_type; t->runspeed = n.runspeed; t->findable = n.findable != 0; + t->is_quest_npc = n.isquest != 0; t->trackable = n.trackable != 0; t->hp_regen = n.hp_regen_rate; t->mana_regen = n.mana_regen_rate; diff --git a/zone/zonedump.h b/zone/zonedump.h index 1ab90468f..2d72730d4 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -124,6 +124,7 @@ struct NPCType int avoidance_rating; // flat bonus before mods bool findable; //can be found with find command bool trackable; + bool is_quest_npc; int16 slow_mitigation; uint8 maxlevel; uint32 scalerate;