[Quest API] Implement eq.handin() and quest::handin() (#4718)

* [Quest API] Implement eq.handin() and quest::handin()

* Fix MQ using new API style
This commit is contained in:
Chris Miles 2025-02-28 15:22:39 -06:00 committed by GitHub
parent 875df8e64a
commit 425d24c1f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 97 additions and 4 deletions

View File

@ -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<std::string, uint32> handin_map;
for (auto e: handin) {
if (!e.first) {
continue;
}
if (Strings::EqualFold(e.first, "0")) {
continue;
}
const uint32 count = static_cast<uint32>(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);

View File

@ -5642,6 +5642,31 @@ Lua_Zone lua_get_zone()
return Lua_Zone(zone);
}
bool lua_handin(luabind::adl::object handin_table)
{
std::map<std::string, uint32> 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<std::string>(i.key());
}
else if (luabind::type(i.key()) == LUA_TNUMBER) {
key = fmt::format("{}", luabind::object_cast<int>(i.key()));
}
else {
LogError("Handin key type [{}] not supported", luabind::type(i.key()));
}
if (!key.empty()) {
handin_map[key] = luabind::object_cast<uint32>(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
*/

View File

@ -4351,6 +4351,10 @@ bool NPC::CheckHandin(
h = m_hand_in;
}
if (IsMultiQuestEnabled()) {
LogNpcHandin("{} Multi-Quest hand-in enabled", log_handin_prefix);
}
std::vector<std::pair<const std::map<std::string, uint32>&, 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

View File

@ -4606,3 +4606,18 @@ void QuestManager::SpawnGrid(uint32 npc_id, glm::vec4 position, float spacing, u
}
}
}
bool QuestManager::handin(std::map<std::string, uint32> 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, {});
}

View File

@ -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<std::string, uint32> required);
private:
std::stack<running_quest> quests_running_;

View File

@ -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<std::string, uint32> 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