From ea309597a60319cfd75f463c90bf6fe056907a6b Mon Sep 17 00:00:00 2001 From: Vayle <76063792+Valorith@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:52:27 -0400 Subject: [PATCH] Fix task delivery hand-ins on quest NPCs --- common/item_instance.cpp | 1 + zone/cli/tests/cli_npc_handins.cpp | 29 +++++++++++++++++ zone/npc.cpp | 50 +++++++++++++++++++++++++----- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 64ae0b4cc..3c18c2cc7 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -148,6 +148,7 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_exp = copy.m_exp; m_evolveLvl = copy.m_evolveLvl; + m_task_delivered_count = copy.m_task_delivered_count; if (copy.m_scaledItem) { m_scaledItem = new ItemData(*copy.m_scaledItem); diff --git a/zone/cli/tests/cli_npc_handins.cpp b/zone/cli/tests/cli_npc_handins.cpp index a51dbd873..2b692e841 100644 --- a/zone/cli/tests/cli_npc_handins.cpp +++ b/zone/cli/tests/cli_npc_handins.cpp @@ -20,6 +20,7 @@ void RunTest(const std::string& test_name, int expected, int actual); struct HandinEntry { std::string item_id = "0"; uint32 count = 0; + int task_delivered_count = 0; const EQ::ItemInstance *item = nullptr; bool is_multiquest_item = false; // state }; @@ -407,6 +408,32 @@ void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::stri }, .handin_check_result = true, }, + TestCase{ + .description = "Test task-delivered non-stack item is not returned", + .hand_in = { + .items = { + HandinEntry{.item_id = "1001", .count = 1, .task_delivered_count = 1}, + }, + }, + .required = {}, + .returned = {}, + .handin_check_result = false, + }, + TestCase{ + .description = "Test task-delivered stack only returns undelivered charges", + .hand_in = { + .items = { + HandinEntry{.item_id = "13005", .count = 20, .task_delivered_count = 10}, + }, + }, + .required = {}, + .returned = { + .items = { + HandinEntry{.item_id = "13005", .count = 10}, + }, + }, + .handin_check_result = false, + }, TestCase{ .description = "Test handing in Soulfire that has 5 charges and have it count as 1 item", .hand_in = { @@ -455,6 +482,8 @@ void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::stri inst->SetCharges(inst->GetItem()->MaxCharges); } + inst->SetTaskDeliveredCount(hand_in.task_delivered_count); + hand_ins[hand_in.item_id] = inst->GetCharges(); items.push_back(inst); } diff --git a/zone/npc.cpp b/zone/npc.cpp index 64b61d33b..c77df393b 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4630,7 +4630,7 @@ NPC::Handin NPC::ReturnHandinItems(Client *c) } } - auto returned = m_hand_in; + auto returned = Handin{}; // check if any money was handed in if (m_hand_in.original_money.platinum > 0 || @@ -4669,26 +4669,61 @@ NPC::Handin NPC::ReturnHandinItems(Client *c) m_hand_in.items.end(), [&](HandinEntry &i) { if (i.item && i.item->GetItem() && !i.is_multiquest_item && !returned_items_already) { + const uint32 task_delivered_count = std::min( + i.count, + std::max(i.item->GetTaskDeliveredCount(), 0) + ); + const uint32 return_count = i.count - task_delivered_count; + + if (task_delivered_count > 0) { + LogNpcHandin( + "Task delivery consumed item [{}] ({}) count [{}], remaining return count [{}]", + i.item->GetItem()->Name, + i.item_id, + task_delivered_count, + return_count + ); + } + + if (return_count == 0) { + return true; // Task delivery already consumed this hand-in item + } + + const uint16 charges_to_return = i.item->IsStackable() ? + static_cast(return_count) : + std::max(static_cast(i.item->GetCharges()), static_cast(1)); + return_items.emplace_back( PlayerEvent::HandinEntry{ .item_id = i.item->GetID(), .item_name = i.item->GetItem()->Name, .augment_ids = i.item->GetAugmentIDs(), .augment_names = i.item->GetAugmentNames(), - .charges = std::max(static_cast(i.item->GetCharges()), static_cast(1)) + .charges = charges_to_return } ); - // If the item is stackable and the new charges don't match the original count - // set the charges to the original count - if (i.item->IsStackable() && i.item->GetCharges() != i.count) { - i.item->SetCharges(i.count); + returned.items.emplace_back( + HandinEntry{ + .item_id = i.item_id, + .count = return_count, + .item = i.item, + .is_multiquest_item = i.is_multiquest_item + } + ); + + // Return only the remaining portion that was not consumed by either + // quest hand-ins or task delivery updates. + if (i.item->IsStackable() && i.item->GetCharges() != charges_to_return) { + i.item->SetCharges(charges_to_return); } + i.item->SetTaskDeliveredCount(0); c->PushItemOnCursor(*i.item, true); LogNpcHandin( - "Hand-in failed, returning item [{}] i.is_multiquest_item [{}]", + "Hand-in failed, returning item [{}] count [{}] i.is_multiquest_item [{}]", i.item->GetItem()->Name, + return_count, i.is_multiquest_item ); @@ -4737,6 +4772,7 @@ NPC::Handin NPC::ReturnHandinItems(Client *c) return_money.silver = m_hand_in.money.silver; return_money.gold = m_hand_in.money.gold; return_money.platinum = m_hand_in.money.platinum; + returned.money = m_hand_in.money; // if multi-quest and we returned money, reset the hand-in bucket if (IsMultiQuestEnabled()) {