From 425d24c1f4b99ba80b71753163b46d9df1507574 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Fri, 28 Feb 2025 15:22:39 -0600 Subject: [PATCH] [Quest API] Implement eq.handin() and quest::handin() (#4718) * [Quest API] Implement eq.handin() and quest::handin() * Fix MQ using new API style --- zone/embparser_api.cpp | 23 +++++++++++++++++++++++ zone/lua_general.cpp | 26 ++++++++++++++++++++++++++ zone/npc.cpp | 12 ++++++++++-- zone/questmgr.cpp | 15 +++++++++++++++ zone/questmgr.h | 1 + zone/trading.cpp | 24 ++++++++++++++++++++++-- 6 files changed, 97 insertions(+), 4 deletions(-) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 97800a396..8f23083f1 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -5973,6 +5973,28 @@ void Perl__SpawnGrid(uint32 npc_id, float x, float y, float z, float heading, fl quest_manager.SpawnGrid(npc_id, glm::vec4(x, y, z, heading), spacing, spawn_count); } +bool Perl__handin(perl::reference handin_ref) +{ + perl::hash handin = handin_ref; + + std::map handin_map; + + for (auto e: handin) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } + + const uint32 count = static_cast(handin.at(e.first)); + handin_map[e.first] = count; + } + + return quest_manager.handin(handin_map); +} + void perl_register_quest() { perl::interpreter perl(PERL_GET_THX); @@ -6698,6 +6720,7 @@ void perl_register_quest() package.add("gmsay", (void(*)(const char*, int, bool))&Perl__gmsay); package.add("gmsay", (void(*)(const char*, int, bool, int))&Perl__gmsay); package.add("gmsay", (void(*)(const char*, int, bool, int, int))&Perl__gmsay); + package.add("handin", &Perl__handin); package.add("has_zone_flag", &Perl__has_zone_flag); package.add("hasrecipelearned", &Perl__hasrecipelearned); package.add("hastimer", &Perl__hastimer); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index c41fcd1fb..a381a1b14 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -5642,6 +5642,31 @@ Lua_Zone lua_get_zone() return Lua_Zone(zone); } +bool lua_handin(luabind::adl::object handin_table) +{ + std::map handin_map; + + for (luabind::iterator i(handin_table), end; i != end; i++) { + std::string key; + if (luabind::type(i.key()) == LUA_TSTRING) { + key = luabind::object_cast(i.key()); + } + else if (luabind::type(i.key()) == LUA_TNUMBER) { + key = fmt::format("{}", luabind::object_cast(i.key())); + } + else { + LogError("Handin key type [{}] not supported", luabind::type(i.key())); + } + + if (!key.empty()) { + handin_map[key] = luabind::object_cast(handin_table[i.key()]); + LogNpcHandinDetail("Handin key [{}] value [{}]", key, handin_map[key]); + } + } + + return quest_manager.handin(handin_map); +} + #define LuaCreateNPCParse(name, c_type, default_value) do { \ cur = table[#name]; \ if(luabind::type(cur) != LUA_TNIL) { \ @@ -6450,6 +6475,7 @@ luabind::scope lua_register_general() { luabind::def("spawn_circle", &lua_spawn_circle), luabind::def("spawn_grid", &lua_spawn_grid), luabind::def("get_zone", &lua_get_zone), + luabind::def("handin", &lua_handin), /* Cross Zone */ diff --git a/zone/npc.cpp b/zone/npc.cpp index 60f917610..9541fda46 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4351,6 +4351,10 @@ bool NPC::CheckHandin( h = m_hand_in; } + if (IsMultiQuestEnabled()) { + LogNpcHandin("{} Multi-Quest hand-in enabled", log_handin_prefix); + } + std::vector&, Handin&>> datasets = {}; // if we've already started the hand-in process, we don't want to re-process the hand-in data @@ -4432,7 +4436,7 @@ bool NPC::CheckHandin( // multi-quest if (IsMultiQuestEnabled()) { - for (auto &h_item: h.items) { + for (auto &h_item: m_hand_in.items) { for (const auto &r_item: r.items) { if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { h_item.is_multiquest_item = true; @@ -4777,7 +4781,11 @@ NPC::Handin NPC::ReturnHandinItems(Client *c) } c->PushItemOnCursor(*i.item, true); - LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); + LogNpcHandin( + "Hand-in failed, returning item [{}] i.is_multiquest_item [{}]", + i.item->GetItem()->Name, + i.is_multiquest_item + ); returned_handin = true; return true; // Mark this item for removal diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 7e8a4fa2f..7f60e1021 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -4606,3 +4606,18 @@ void QuestManager::SpawnGrid(uint32 npc_id, glm::vec4 position, float spacing, u } } } + +bool QuestManager::handin(std::map required) { + QuestManagerCurrentQuestVars(); + if (!owner || !initiator) { + LogQuests("QuestManager::handin called with nullptr owner. Probably syntax error in quest file"); + return false; + } + + if (owner && !owner->IsNPC()) { + LogQuests("QuestManager::handin called with non-NPC owner. Probably syntax error in quest file"); + return false; + } + + return owner->CastToNPC()->CheckHandin(initiator, {}, required, {}); +} diff --git a/zone/questmgr.h b/zone/questmgr.h index c73596f4e..255871105 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -376,6 +376,7 @@ public: bool botquest(); bool createBot(const char *name, const char *lastname, uint8 level, uint16 race, uint8 botclass, uint8 gender); + bool handin(std::map required); private: std::stack quests_running_; diff --git a/zone/trading.cpp b/zone/trading.cpp index f3dc839d3..0cdb17bbf 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -618,16 +618,36 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st item_list.emplace_back(inst); } + auto handin_npc = tradingWith->CastToNPC(); + m_external_handin_money_returned = {}; m_external_handin_items_returned = {}; bool has_aggro = tradingWith->CheckAggro(this); if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE) && !has_aggro) { + // This CheckHandin call enables eq.handin and quest::handin to recognize the hand-in context. + // It initializes the first hand-in bucket, which is then reused for the EVENT_TRADE subroutine. + std::map handin = { + {"copper", trade->cp}, + {"silver", trade->sp}, + {"gold", trade->gp}, + {"platinum", trade->pp} + }; + + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + + std::string item_id = fmt::format("{}", inst->GetItem()->ID); + handin[item_id] += inst->GetCharges(); + } + + handin_npc->CheckHandin(this, handin, {}, items); + parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list); LogNpcHandinDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID()); } - auto handin_npc = tradingWith->CastToNPC(); - // this is a catch-all return for items that weren't consumed by the EVENT_TRADE subroutine // it's possible we have a quest NPC that doesn't have an EVENT_TRADE subroutine // we can't double fire the ReturnHandinItems() event, so we need to check if it's already been processed from EVENT_TRADE