From 3dafffe8d7a4596f6c75991b0e7307d10e1dcf49 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Sun, 13 Oct 2024 22:42:28 -0400 Subject: [PATCH 01/44] Initial --- zone/client.cpp | 39 ++++++++++++++++++++++++++++++++ zone/client.h | 7 ++++++ zone/lua_client.cpp | 46 ++++++++++++++++++++++++++++++++++++++ zone/lua_client.h | 6 +++++ zone/lua_parser_events.cpp | 11 ++++++++- zone/perl_client.cpp | 40 +++++++++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 1 deletion(-) diff --git a/zone/client.cpp b/zone/client.cpp index d0a8d97b0b..80372dcf18 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13075,3 +13075,42 @@ void Client::ClientToNpcAggroProcess() LogAggro("Checking Reverse Aggro (client->npc) scanned_npcs ([{}])", npc_scan_count); } } + +bool Client::CheckHandin( + NPC* n, + std::map handin, + std::map required, + std::vector items +) +{ + bool success = true; + + if (handin.size() != required.size()) { + success = false; + } + + if (success) { + for (int i = 0; i < handin.size(); i++) { + if (handin[std::to_string(i)] != required[std::to_string(i)]) { + success = false; + break; + } + } + } + + if (!success) { + for (int i = 0; i < items.size(); i++) { + if (const EQ::ItemInstance* item = items[i]) { + n->Say( + fmt::format( + "I have no need for this {}, you can have it back.", + GetCleanName() + ).c_str() + ); + PushItemOnCursor(*item, true); + } + } + } + + return success; +} diff --git a/zone/client.h b/zone/client.h index 5acc388b3e..beec190248 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1732,6 +1732,13 @@ class Client : public Mob void DuplicateLoreMessage(uint32 ItemID); void GarbleMessage(char *, uint8); + bool CheckHandin( + NPC* n, + std::map handin, + std::map required, + std::vector items + ); + void ItemTimerCheck(); void TryItemTimer(int slot); void SendItemScale(EQ::ItemInstance *inst); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index f8d5ca64d0..f895f90d16 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3436,6 +3436,51 @@ void Lua_Client::AreaTaunt(float range, int bonus_hate) entity_list.AETaunt(self, range, bonus_hate); } +bool Lua_Client::CheckHandin( + Lua_NPC n, + luabind::adl::object handin_table, + luabind::adl::object required_table, + luabind::adl::object items_table +) +{ + Lua_Safe_Call_Bool(); + + if ( + luabind::type(handin_table) != LUA_TTABLE || + luabind::type(required_table) != LUA_TTABLE || + luabind::type(items_table) != LUA_TTABLE + ) { + return false; + } + + std::map handin_map; + std::map required_map; + std::vector items; + + for (luabind::iterator i(handin_table), end; i != end; i++) { + uint32 key = luabind::object_cast(i.key()); + handin_map[std::to_string(key)] = luabind::object_cast(handin_table[i.key()]); + LogError("handin_map | key [{}]", key); + } + + for (luabind::iterator i(required_table), end; i != end; i++) { + uint32 key = luabind::object_cast(i.key()); + required_map[std::to_string(key)] = luabind::object_cast(required_table[i.key()]); + LogError("required_map | key [{}]", key); + } + + for (luabind::iterator i(items_table), end; i != end; i++) { + auto item = luabind::object_cast(items_table[i.key()]); + + items.emplace_back(item); + if (item && item.GetItem()) { + LogError("items | id [{}] name [{}]", item.GetID(), item.GetName()); + } + } + + return self->CheckHandin(n, handin_map, required_map, items); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -3505,6 +3550,7 @@ luabind::scope lua_register_client() { .def("CashReward", &Lua_Client::CashReward) .def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName) .def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID) + .def("CheckHandin", (bool(Lua_Client::*)(Lua_NPC,luabind::adl::object,luabind::adl::object,luabind::adl::object))&Lua_Client::CheckHandin) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill) .def("CheckSpecializeIncrease", (void(Lua_Client::*)(int))&Lua_Client::CheckSpecializeIncrease) diff --git a/zone/lua_client.h b/zone/lua_client.h index faff8b1572..27006eb819 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -582,6 +582,12 @@ class Lua_Client : public Lua_Mob bool RemoveAAPoints(uint32 points); bool RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount); bool AreTasksCompleted(luabind::object task_ids); + bool CheckHandin( + Lua_NPC n, + luabind::adl::object handin_table, + luabind::adl::object required_table, + luabind::adl::object items_table + ); void DialogueWindow(std::string markdown); diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index fb6cb87084..f208d922a3 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -56,6 +56,11 @@ void handle_npc_event_trade( uint32 extra_data, std::vector *extra_pointers ) { + Lua_NPC l_npc(reinterpret_cast(npc)); + luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); + l_npc_o.push(L); + lua_setfield(L, -2, "self"); + Lua_Client l_client(reinterpret_cast(init)); luabind::adl::object l_client_o = luabind::adl::object(L, l_client); l_client_o.push(L); @@ -102,7 +107,11 @@ void handle_npc_event_trade( lua_pushinteger(L, money_value); lua_setfield(L, -2, "copper"); - // set a reference to the client inside of the trade object as well for plugins to process + // set a reference to the NPC inside the trade object as well for plugins to process + l_npc_o.push(L); + lua_setfield(L, -2, "self"); + + // set a reference to the client inside the trade object as well for plugins to process l_client_o.push(L); lua_setfield(L, -2, "other"); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 8bb39afda2..90d7427139 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3212,6 +3212,45 @@ Merc* Perl_Client_GetMerc(Client* self) return self->GetMerc(); } +bool Perl_Client_CheckHandin( + Client* self, + NPC* n, + perl::reference handin_ref, + perl::reference required_ref, + perl::array items_ref +) +{ + perl::hash handin = handin_ref; + perl::hash required = required_ref; + + std::map handin_map; + std::map required_map; + std::vector items; + + bool success = true; + + for (auto e : handin) { + const uint32 item_id = Strings::ToUnsignedInt(e.first); + const uint32 item_count = static_cast(handin.at(e.first)); + + handin_map[std::to_string(item_id)] = item_count; + } + + for (auto e : required) { + const uint32 item_id = Strings::ToUnsignedInt(e.first); + const uint32 item_count = static_cast(handin.at(e.first)); + + required_map[std::to_string(item_id)] = item_count; + } + + for (auto e : items_ref) { + const EQ::ItemInstance* i = static_cast(e); + items.emplace_back(i); + } + + return self->CheckHandin(n, handin_map, required_map, items); +} + void perl_register_client() { perl::interpreter perl(PERL_GET_THX); @@ -3284,6 +3323,7 @@ void perl_register_client() package.add("CashReward", &Perl_Client_CashReward); package.add("ChangeLastName", &Perl_Client_ChangeLastName); package.add("CharacterID", &Perl_Client_CharacterID); + package.add("CheckHandin", &Perl_Client_CheckHandin); package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill); package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill); package.add("CheckSpecializeIncrease", &Perl_Client_CheckSpecializeIncrease); From 45cab22748986cb0a8c97f9e50aaf8ca67564638 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 13 Oct 2024 22:51:03 -0500 Subject: [PATCH 02/44] Push it --- zone/client.cpp | 85 ++++++++++++++++++++++++++++++++------------ zone/lua_client.cpp | 21 +++++++---- zone/perl_client.cpp | 43 +++++++++++++++++----- 3 files changed, 110 insertions(+), 39 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 80372dcf18..9b7c347993 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13083,34 +13083,73 @@ bool Client::CheckHandin( std::vector items ) { - bool success = true; - - if (handin.size() != required.size()) { - success = false; - } + // loop through handin + for (const auto& [key, value] : handin) { + LogTradingDetail("Handin key [{}] value [{}]", key, value); + + // items + if (Strings::IsNumber(key)) { + if (required.find(key) == required.end()) { + LogTradingDetail("Handin (item) key [{}] value [{}] (not required)", key, value); + } else { + if (value < required[key]) { + LogTradingDetail("Handin (item) key [{}] value [{}] (not enough) required [{}]", key, value, required[key]); + } else { + LogTradingDetail("Handin (item) key [{}] value [{}] (success)", key, value); + // add item to npc list + } + } + } else if (!key.empty()) { + std::vector moneys = { + "platinum", + "gold", + "silver", + "copper" + }; - if (success) { - for (int i = 0; i < handin.size(); i++) { - if (handin[std::to_string(i)] != required[std::to_string(i)]) { - success = false; - break; + for (const auto& money : moneys) { + if (key == money) { + if (required.find(key) == required.end()) { + LogTradingDetail("Handin (money) key [{}] value [{}] (not required)", key, value); + } else { + if (value < required[key]) { + LogTradingDetail("Handin (money) key [{}] value [{}] (not enough) required [{}]", key, value, required[key]); + } else { + LogTradingDetail("Handin (money) key [{}] value [{}] (success)", key, value); + // add money to npc list + } + } + } } } } - if (!success) { - for (int i = 0; i < items.size(); i++) { - if (const EQ::ItemInstance* item = items[i]) { - n->Say( - fmt::format( - "I have no need for this {}, you can have it back.", - GetCleanName() - ).c_str() - ); - PushItemOnCursor(*item, true); - } - } + for (const auto& [key, value] : required) { + LogTradingDetail("Required key [{}] value [{}]", key, value); } - return success; +// if (success) { +// for (int i = 0; i < handin.size(); i++) { +// if (handin[std::to_string(i)] != required[std::to_string(i)]) { +// success = false; +// break; +// } +// } +// } + +// if (!success) { +// for (int i = 0; i < items.size(); i++) { +// if (const EQ::ItemInstance* item = items[i]) { +// n->Say( +// fmt::format( +// "I have no need for this {}, you can have it back.", +// GetCleanName() +// ).c_str() +// ); +// PushItemOnCursor(*item, true); +// } +// } +// } + + return true; } diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index f895f90d16..11d41dce38 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3458,23 +3458,30 @@ bool Lua_Client::CheckHandin( std::vector items; for (luabind::iterator i(handin_table), end; i != end; i++) { - uint32 key = luabind::object_cast(i.key()); - handin_map[std::to_string(key)] = luabind::object_cast(handin_table[i.key()]); - LogError("handin_map | key [{}]", key); + std::string key = luabind::object_cast(i.key()); + handin_map[key] = luabind::object_cast(handin_table[i.key()]); + LogTradingDetail("Handin key [{}] value [{}]", key, handin_map[key]); } for (luabind::iterator i(required_table), end; i != end; i++) { - uint32 key = luabind::object_cast(i.key()); - required_map[std::to_string(key)] = luabind::object_cast(required_table[i.key()]); - LogError("required_map | key [{}]", key); + std::string key = luabind::object_cast(i.key()); + required_map[key] = luabind::object_cast(required_table[i.key()]); + LogTradingDetail("Required key [{}] value [{}]", key, handin_map[key]); } for (luabind::iterator i(items_table), end; i != end; i++) { auto item = luabind::object_cast(items_table[i.key()]); - items.emplace_back(item); if (item && item.GetItem()) { LogError("items | id [{}] name [{}]", item.GetID(), item.GetName()); + + LogTradingDetail( + "Item instance [{}] ({}) added to handin list", + item.GetName(), + item.GetID() + ); + + items.emplace_back(item); } } diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 90d7427139..e145ba7cbd 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3227,25 +3227,50 @@ bool Perl_Client_CheckHandin( std::map required_map; std::vector items; - bool success = true; + for (auto e: handin) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } - for (auto e : handin) { - const uint32 item_id = Strings::ToUnsignedInt(e.first); - const uint32 item_count = static_cast(handin.at(e.first)); + LogTradingDetail("Handin key [{}] value [{}]", e.first, handin.at(e.first).c_str()); - handin_map[std::to_string(item_id)] = item_count; + const uint32 count = static_cast(handin.at(e.first)); + handin_map[e.first] = count; } - for (auto e : required) { - const uint32 item_id = Strings::ToUnsignedInt(e.first); - const uint32 item_count = static_cast(handin.at(e.first)); + for (auto e: required) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } + + LogTradingDetail("Required key [{}] value [{}]", e.first, required.at(e.first).c_str()); - required_map[std::to_string(item_id)] = item_count; + const uint32 count = static_cast(required.at(e.first)); + required_map[e.first] = count; } for (auto e : items_ref) { const EQ::ItemInstance* i = static_cast(e); + if (!i) { + continue; + } + items.emplace_back(i); + + LogTradingDetail( + "Item instance [{}] ({}) UUID ({}) added to handin list", + i->GetItem()->Name, + i->GetItem()->ID, + i->GetSerialNumber() + ); } return self->CheckHandin(n, handin_map, required_map, items); From 498a13809ead80534c42ee49d5bf41884a9a7cc7 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 13 Oct 2024 23:57:24 -0500 Subject: [PATCH 03/44] More work --- zone/lua_client.cpp | 47 ++++++++++++++++++++++++++++++++----------- zone/lua_client.h | 2 +- zone/lua_iteminst.cpp | 7 +++++++ zone/lua_iteminst.h | 1 + 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 11d41dce38..d25681d73e 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3436,7 +3436,7 @@ void Lua_Client::AreaTaunt(float range, int bonus_hate) entity_list.AETaunt(self, range, bonus_hate); } -bool Lua_Client::CheckHandin( +bool Lua_Client::LuaCheckHandin( Lua_NPC n, luabind::adl::object handin_table, luabind::adl::object required_table, @@ -3458,27 +3458,50 @@ bool Lua_Client::CheckHandin( std::vector items; for (luabind::iterator i(handin_table), end; i != end; i++) { - std::string key = luabind::object_cast(i.key()); - handin_map[key] = luabind::object_cast(handin_table[i.key()]); - LogTradingDetail("Handin key [{}] value [{}]", key, handin_map[key]); + 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()]); + LogTradingDetail("Handin key [{}] value [{}]", key, handin_map[key]); + } } for (luabind::iterator i(required_table), end; i != end; i++) { - std::string key = luabind::object_cast(i.key()); - required_map[key] = luabind::object_cast(required_table[i.key()]); - LogTradingDetail("Required key [{}] value [{}]", key, handin_map[key]); + 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("Required key type [{}] not supported", luabind::type(i.key())); + } + + if (!key.empty()) { + required_map[key] = luabind::object_cast(required_table[i.key()]); + LogTradingDetail("Required key [{}] value [{}]", key, required_map[key]); + } } for (luabind::iterator i(items_table), end; i != end; i++) { auto item = luabind::object_cast(items_table[i.key()]); if (item && item.GetItem()) { - LogError("items | id [{}] name [{}]", item.GetID(), item.GetName()); - LogTradingDetail( - "Item instance [{}] ({}) added to handin list", + "Item instance [{}] ({}) UUID ({}) added to handin list", item.GetName(), - item.GetID() + item.GetID(), + item.GetSerialNumber() ); items.emplace_back(item); @@ -3557,7 +3580,7 @@ luabind::scope lua_register_client() { .def("CashReward", &Lua_Client::CashReward) .def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName) .def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID) - .def("CheckHandin", (bool(Lua_Client::*)(Lua_NPC,luabind::adl::object,luabind::adl::object,luabind::adl::object))&Lua_Client::CheckHandin) + .def("CheckHandin", (bool(Lua_Client::*)(Lua_NPC,luabind::adl::object,luabind::adl::object,luabind::adl::object))&Lua_Client::LuaCheckHandin) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill) .def("CheckSpecializeIncrease", (void(Lua_Client::*)(int))&Lua_Client::CheckSpecializeIncrease) diff --git a/zone/lua_client.h b/zone/lua_client.h index 27006eb819..6b601a69c7 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -582,7 +582,7 @@ class Lua_Client : public Lua_Mob bool RemoveAAPoints(uint32 points); bool RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount); bool AreTasksCompleted(luabind::object task_ids); - bool CheckHandin( + bool LuaCheckHandin( Lua_NPC n, luabind::adl::object handin_table, luabind::adl::object required_table, diff --git a/zone/lua_iteminst.cpp b/zone/lua_iteminst.cpp index ad9ed14054..f3b5dfab13 100644 --- a/zone/lua_iteminst.cpp +++ b/zone/lua_iteminst.cpp @@ -305,6 +305,12 @@ std::string Lua_ItemInst::GetName() { return self->GetItem()->Name; } +int Lua_ItemInst::GetSerialNumber() +{ + Lua_Safe_Call_Int(); + return self->GetSerialNumber(); +} + void Lua_ItemInst::ItemSay(const char* text) // @categories Inventory and Items { Lua_Safe_Call_Void(); @@ -379,6 +385,7 @@ luabind::scope lua_register_iteminst() { .def("GetKillsNeeded", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetKillsNeeded) .def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl) .def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName) + .def("GetSerialNumber", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetSerialNumber) .def("GetPrice", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetPrice) .def("GetTaskDeliveredCount", &Lua_ItemInst::GetTaskDeliveredCount) .def("GetTotalItemCount", (uint8(Lua_ItemInst::*)(void))&Lua_ItemInst::GetTotalItemCount) diff --git a/zone/lua_iteminst.h b/zone/lua_iteminst.h index 2ff99165c2..6fd3a93ac1 100644 --- a/zone/lua_iteminst.h +++ b/zone/lua_iteminst.h @@ -87,6 +87,7 @@ class Lua_ItemInst : public Lua_Ptr int GetTaskDeliveredCount(); int RemoveTaskDeliveredItems(); std::string GetName(); + int GetSerialNumber(); void ItemSay(const char* text); void ItemSay(const char* text, uint8 language_id); luabind::object GetAugmentIDs(lua_State* L); From 61433e354895710c65ec8e671dcdd5dd0640696e Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 00:47:38 -0500 Subject: [PATCH 04/44] Handin logic --- zone/client.cpp | 159 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 115 insertions(+), 44 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 9b7c347993..d42da4f45f 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13083,22 +13083,37 @@ bool Client::CheckHandin( std::vector items ) { + struct HandinEntry { + std::string item_id; + uint32 count; + }; + + struct HandinMoney { + uint32 platinum; + uint32 gold; + uint32 silver; + uint32 copper; + }; + + struct Handin { + std::vector items; + HandinMoney money; + }; + + auto h = Handin{}; + // loop through handin for (const auto& [key, value] : handin) { LogTradingDetail("Handin key [{}] value [{}]", key, value); // items if (Strings::IsNumber(key)) { - if (required.find(key) == required.end()) { - LogTradingDetail("Handin (item) key [{}] value [{}] (not required)", key, value); - } else { - if (value < required[key]) { - LogTradingDetail("Handin (item) key [{}] value [{}] (not enough) required [{}]", key, value, required[key]); - } else { - LogTradingDetail("Handin (item) key [{}] value [{}] (success)", key, value); - // add item to npc list - } + const EQ::ItemData* item = database.GetItem(Strings::ToUnsignedInt(key)); + if (!item) { + continue; } + + h.items.emplace_back(HandinEntry{.item_id = key, .count = value}); } else if (!key.empty()) { std::vector moneys = { "platinum", @@ -13107,49 +13122,105 @@ bool Client::CheckHandin( "copper" }; - for (const auto& money : moneys) { - if (key == money) { - if (required.find(key) == required.end()) { - LogTradingDetail("Handin (money) key [{}] value [{}] (not required)", key, value); - } else { - if (value < required[key]) { - LogTradingDetail("Handin (money) key [{}] value [{}] (not enough) required [{}]", key, value, required[key]); - } else { - LogTradingDetail("Handin (money) key [{}] value [{}] (success)", key, value); - // add money to npc list - } - } + // money + if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { + if (key == "platinum") { + h.money.platinum = value; + } else if (key == "gold") { + h.money.gold = value; + } else if (key == "silver") { + h.money.silver = value; + } else if (key == "copper") { + h.money.copper = value; } } } } + auto r = Handin{}; for (const auto& [key, value] : required) { LogTradingDetail("Required key [{}] value [{}]", key, value); + + // items + if (Strings::IsNumber(key)) { + const EQ::ItemData* item = database.GetItem(Strings::ToUnsignedInt(key)); + if (!item) { + continue; + } + + r.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + } else if (!key.empty()) { + std::vector moneys = { + "platinum", + "gold", + "silver", + "copper" + }; + + // money + if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { + if (key == "platinum") { + r.money.platinum = value; + } else if (key == "gold") { + r.money.gold = value; + } else if (key == "silver") { + r.money.silver = value; + } else if (key == "copper") { + r.money.copper = value; + } + } + } } -// if (success) { -// for (int i = 0; i < handin.size(); i++) { -// if (handin[std::to_string(i)] != required[std::to_string(i)]) { -// success = false; -// break; -// } -// } -// } - -// if (!success) { -// for (int i = 0; i < items.size(); i++) { -// if (const EQ::ItemInstance* item = items[i]) { -// n->Say( -// fmt::format( -// "I have no need for this {}, you can have it back.", -// GetCleanName() -// ).c_str() -// ); -// PushItemOnCursor(*item, true); -// } -// } -// } + // compare hand-in to required, the item_id can be in any slot + bool success = true; + if (h.items.size() == r.items.size()) { + for (const auto& h_item : h.items) { + bool found = false; + for (const auto& r_item : r.items) { + if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { + found = true; + break; + } + } + + if (!found) { + success = false; + break; + } + } + } else { + success = false; + } - return true; + // compare hand-in money to required + if (success) { + if (h.money.platinum != r.money.platinum || h.money.gold != r.money.gold || h.money.silver != r.money.silver || h.money.copper != r.money.copper) { + success = false; + } + } + + if (!success) { + for (auto i : items) { + if (i) { + PushItemOnCursor(*i, true); + LogTradingDetail("Handin failed, returning item [{}]", i->GetItem()->Name); + } + } + + n->Say( + fmt::format( + "I have no need for this {}, you can have it back.", + GetCleanName() + ).c_str() + ); + + // check if any money was handed in + if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { + AddMoneyToPP(h.money.copper, h.money.silver, h.money.gold, h.money.platinum, true); + LogTradingDetail("Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + } + } + + return success; } From 35967d85dfa9bb17ee27120d09bec633bbc7f0fd Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 03:05:16 -0500 Subject: [PATCH 05/44] More --- common/ruletypes.h | 4 +- zone/client.cpp | 19 +++-- zone/npc.cpp | 20 +++++ zone/npc.h | 3 + zone/trading.cpp | 185 ++++++++++++++++++--------------------------- 5 files changed, 111 insertions(+), 120 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index a428325271..c1abbe836e 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -283,10 +283,9 @@ RULE_CATEGORY(Pets) RULE_REAL(Pets, AttackCommandRange, 150, "Range at which a pet will respond to attack commands") RULE_BOOL(Pets, UnTargetableSwarmPet, false, "Setting whether swarm pets should be targetable") RULE_REAL(Pets, PetPowerLevelCap, 10, "Maximum number of levels a player pet can go up with pet power") -RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets") -RULE_BOOL(Pets, CanTakeQuestItems, true, "Setting whether anyone can give quest items to pets") RULE_BOOL(Pets, LivelikeBreakCharmOnInvis, true, "Default: true will break charm on any type of invis (hide/ivu/iva/etc) false will only break if the pet can not see you (ex. you have an undead pet and cast IVU") RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep client pet's last names from being owner_name's pet") +RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets") RULE_INT(Pets, PetTauntRange, 150, "Range at which a pet will taunt targets.") RULE_CATEGORY_END() @@ -657,7 +656,6 @@ RULE_BOOL(NPC, EnableNPCQuestJournal, false, "Setting whether the NPC Quest Jour RULE_INT(NPC, LastFightingDelayMovingMin, 10000, "Minimum time before mob goes home after all aggro loss (milliseconds)") 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") diff --git a/zone/client.cpp b/zone/client.cpp index d42da4f45f..d19b3c3366 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13174,7 +13174,7 @@ bool Client::CheckHandin( // compare hand-in to required, the item_id can be in any slot bool success = true; - if (h.items.size() == r.items.size()) { + if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { for (const auto& h_item : h.items) { bool found = false; for (const auto& r_item : r.items) { @@ -13200,26 +13200,31 @@ bool Client::CheckHandin( } } + bool handed_back = false; if (!success) { for (auto i : items) { if (i) { PushItemOnCursor(*i, true); LogTradingDetail("Handin failed, returning item [{}]", i->GetItem()->Name); + handed_back = true; } } + // check if any money was handed in + if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { + AddMoneyToPP(h.money.copper, h.money.silver, h.money.gold, h.money.platinum, true); + handed_back = true; + LogTradingDetail("Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + } + } + + if (handed_back) { n->Say( fmt::format( "I have no need for this {}, you can have it back.", GetCleanName() ).c_str() ); - - // check if any money was handed in - if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { - AddMoneyToPP(h.money.copper, h.money.silver, h.money.gold, h.money.platinum, true); - LogTradingDetail("Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); - } } return success; diff --git a/zone/npc.cpp b/zone/npc.cpp index 2172a529e6..e575dd4fec 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4232,3 +4232,23 @@ void NPC::DoNpcToNpcAggroScan() false ); } + +bool NPC::CanPetTakeItem(const EQ::ItemInstance *inst) +{ + if (!inst) { + return false; + } + + if (!IsPetOwnerClient()) { + return false; + } + + const bool can_take_nodrop = RuleB(Pets, CanTakeNoDrop) || inst->GetItem()->NoDrop != 0; + const bool can_pet_take_item = !inst->GetItem()->IsQuestItem() && can_take_nodrop && !inst->IsAttuned(); + + if (!can_pet_take_item) { + return false; + } + + return true; +} diff --git a/zone/npc.h b/zone/npc.h index 20b6f74f6f..92a41dcf08 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -558,6 +558,9 @@ class NPC : public Mob bool CanPathTo(float x, float y, float z); void DoNpcToNpcAggroScan(); + + bool CanPetTakeItem(const EQ::ItemInstance *inst); + protected: void HandleRoambox(); diff --git a/zone/trading.cpp b/zone/trading.cpp index ac36f014fb..92ff512aca 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -320,7 +320,11 @@ void Client::ResetTrade() { } void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list* event_details) { - if(tradingWith && tradingWith->IsClient()) { + if (!tradingWith) { + return; + } + + if (tradingWith->IsClient()) { Client * other = tradingWith->CastToClient(); PlayerLogTrade_Struct * qs_audit = nullptr; bool qs_log = false; @@ -663,7 +667,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st //Do not reset the trade here, done by the caller. } } - else if(tradingWith && tradingWith->IsNPC()) { + else if(tradingWith->IsNPC()) { NPCHandinEventLog(trade, tradingWith->CastToNPC()); QSPlayerLogHandin_Struct* qs_audit = nullptr; @@ -741,7 +745,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st bool quest_npc = false; if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) { - // This is a quest NPC quest_npc = true; } @@ -757,19 +760,18 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (RuleB(TaskSystem, EnableTaskSystem)) { if (UpdateTasksOnDeliver(items, *trade, tradingWith->CastToNPC())) { - if (!tradingWith->IsMoving()) + if (!tradingWith->IsMoving()) { tradingWith->FaceTarget(this); + } EVENT_ITEM_ScriptStopReturn(); - } } // Regardless of quest or non-quest NPC - No in combat trade completion // is allowed. - if (tradingWith->CheckAggro(this)) - { - for (EQ::ItemInstance* inst : items) { + if (tradingWith->CheckAggro(this)) { + for (EQ::ItemInstance *inst: items) { if (!inst || !inst->GetItem()) { continue; } @@ -780,9 +782,8 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st } // Only enforce trade rules if the NPC doesn't have an EVENT_TRADE // subroutine. That overrides all. - else if (!quest_npc) - { - for (EQ::ItemInstance* inst : items) { + else if (!quest_npc) { + for (auto &inst: items) { if (!inst || !inst->GetItem()) { continue; } @@ -796,115 +797,61 @@ 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)))) { + auto with = tradingWith->CastToNPC(); + const EQ::ItemData *item = inst->GetItem(); + + if (with->IsPetOwnerClient() && with->CanPetTakeItem(inst)) { // 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++) { + // if an item inside the bag can't be given to the pet, keep the bag + bool keep_bag = false; + int item_count = 0; + for (int16 bslot = EQ::invbag::SLOT_BEGIN; bslot < item->BagSlots; bslot++) { const EQ::ItemInstance *baginst = inst->GetItem(bslot); - if (baginst) { - 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)))) { - - if (GetGM()) { - const std::string& item_link = database.CreateItemLink(bagitem->ID); - Message( - Chat::White, - fmt::format( - "Your GM flag allows you to give {} to {}.", - item_link, - GetTargetDescription(tradingWith) - ).c_str() - ); - } - - auto lde = LootdropEntriesRepository::NewNpcEntity(); - lde.equip_item = 1; - lde.item_charges = static_cast(baginst->GetCharges()); - - tradingWith->CastToNPC()->AddLootDrop( - bagitem, - lde, - 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 && 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)) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*baginst, true); - } + if (baginst && baginst->GetItem() && with->CanPetTakeItem(baginst)) { + // add item to pet's inventory + auto lde = LootdropEntriesRepository::NewNpcEntity(); + lde.equip_item = 1; + lde.item_charges = static_cast(baginst->GetCharges()); + with->AddLootDrop(baginst->GetItem(), lde, true); + inst->DeleteItem(bslot); + item_count++; + } + else { + keep_bag = true; } } - } else { + + // add item to pet's inventory + if (!keep_bag || item_count == 0) { + auto lde = LootdropEntriesRepository::NewNpcEntity(); + lde.equip_item = 1; + lde.item_charges = static_cast(inst->GetCharges()); + with->AddLootDrop(item, lde, true); + inst = nullptr; + } + } + else { + // add item to pet's inventory auto lde = LootdropEntriesRepository::NewNpcEntity(); lde.equip_item = 1; lde.item_charges = static_cast(inst->GetCharges()); - - tradingWith->CastToNPC()->AddLootDrop( - item, - lde, - true - ); + with->AddLootDrop(item, lde, true); + inst = nullptr; } } - // 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()); - PushItemOnCursor(*inst, true); - Message(Chat::Red, "You cannot trade quest items with your pet."); - } - // Return NO DROP and Attuned items being handed into a non-quest NPC if the rule is true - else if (RuleB(NPC, ReturnNonQuestNoDropItems)) { - tradingWith->SayString(TRADE_BACK, GetCleanName()); - PushItemOnCursor(*inst, true); - } } } - char temp1[100] = { 0 }; - char temp2[100] = { 0 }; - snprintf(temp1, 100, "copper.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->cp); - parse->AddVar(temp1, temp2); - snprintf(temp1, 100, "silver.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->sp); - parse->AddVar(temp1, temp2); - snprintf(temp1, 100, "gold.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->gp); - parse->AddVar(temp1, temp2); - snprintf(temp1, 100, "platinum.%d", tradingWith->GetNPCTypeID()); - snprintf(temp2, 100, "%u", trade->pp); - parse->AddVar(temp1, temp2); + std::string currencies[] = {"copper", "silver", "gold", "platinum"}; + int32 amounts[] = {trade->cp, trade->sp, trade->gp, trade->pp}; + + for (int i = 0; i < 4; ++i) { + parse->AddVar( + fmt::format("{}.{}", currencies[i], tradingWith->GetNPCTypeID()).c_str(), + fmt::format("{}", amounts[i]).c_str() + ); + } if(tradingWith->GetAppearance() != eaDead) { tradingWith->FaceTarget(this); @@ -913,11 +860,29 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) { std::vector item_list(items.begin(), items.end()); parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list); + LogTradingDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID()); + } + else { + std::vector item_list; + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + item_list.push_back(inst); + } + std::map handin = { + {"copper", trade->cp}, + {"silver", trade->sp}, + {"gold", trade->gp}, + {"platinum", trade->pp} + }; + CheckHandin(tradingWith->CastToNPC(), handin, {}, item_list); + LogTradingDetail("CheckHandin() called for NPC [{}]", tradingWith->GetNPCTypeID()); } - for(int i = 0; i < 4; ++i) { - if(insts[i]) { - safe_delete(insts[i]); + for (auto &inst: insts) { + if (inst) { + safe_delete(inst); } } } From 58b9dfc76324e056062efca7c07c4fe9c0f80579 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 03:54:01 -0500 Subject: [PATCH 06/44] Make sure we always hand back but never double process --- zone/client.cpp | 2 ++ zone/client.h | 7 +++++++ zone/client_packet.cpp | 10 ++++++++++ zone/trading.cpp | 23 ++++++++++++++++++----- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index d19b3c3366..4f9e8b8dad 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13227,5 +13227,7 @@ bool Client::CheckHandin( ); } + m_processed_handin = true; + return success; } diff --git a/zone/client.h b/zone/client.h index beec190248..1d1d2e6ec4 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1816,6 +1816,10 @@ class Client : public Mob void RecordKilledNPCEvent(NPC *n); uint32 GetEXPForLevel(uint16 check_level); + + bool HasProcessedHandin() const; + void SetProcessedHandin(bool processed); + protected: friend class Mob; void CalcEdibleBonuses(StatBonuses* newbon); @@ -2239,6 +2243,9 @@ class Client : public Mob // full and partial mail key cache std::string m_mail_key_full; std::string m_mail_key; + + bool m_processed_handin = false; + public: const std::string &GetMailKeyFull() const; const std::string &GetMailKey() const; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index c880e310f8..ead892e42b 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -17226,3 +17226,13 @@ void Client::Handle_OP_ShopRetrieveParcel(const EQApplicationPacket *app) auto parcel_in = (ParcelRetrieve_Struct *)app->pBuffer; DoParcelRetrieve(*parcel_in); } + +bool Client::HasProcessedHandin() const +{ + return m_processed_handin; +} + +void Client::SetProcessedHandin(bool processed) +{ + m_processed_handin = processed; +} diff --git a/zone/trading.cpp b/zone/trading.cpp index 92ff512aca..748c3b6f43 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -857,18 +857,28 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st tradingWith->FaceTarget(this); } + std::vector item_list(items.begin(), items.end()); + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + item_list.emplace_back(inst); + } + if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) { - std::vector item_list(items.begin(), items.end()); parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list); LogTradingDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID()); } - else { - std::vector item_list; + + // it's possible we have a quest NPC that doesn't have an EVENT_TRADE subroutine + // we can't double fire the CheckHandin() event, so we need to check if it's already been processed + if (!HasProcessedHandin()) { + std::vector remaining_item_list; for (EQ::ItemInstance *inst: items) { if (!inst || !inst->GetItem()) { continue; } - item_list.push_back(inst); + remaining_item_list.push_back(inst); } std::map handin = { {"copper", trade->cp}, @@ -876,10 +886,13 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st {"gold", trade->gp}, {"platinum", trade->pp} }; - CheckHandin(tradingWith->CastToNPC(), handin, {}, item_list); + CheckHandin(tradingWith->CastToNPC(), handin, {}, remaining_item_list); LogTradingDetail("CheckHandin() called for NPC [{}]", tradingWith->GetNPCTypeID()); } + // reset the handin + SetProcessedHandin(false); + for (auto &inst: insts) { if (inst) { safe_delete(inst); From 935c8637cb3e8fb05c1be44cd9f1fae52bfc22b3 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 03:56:05 -0500 Subject: [PATCH 07/44] Update trading.cpp --- zone/trading.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/trading.cpp b/zone/trading.cpp index 748c3b6f43..5d080cdba5 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -870,8 +870,9 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st LogTradingDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID()); } + // 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 CheckHandin() event, so we need to check if it's already been processed + // we can't double fire the CheckHandin() event, so we need to check if it's already been processed from EVENT_TRADE if (!HasProcessedHandin()) { std::vector remaining_item_list; for (EQ::ItemInstance *inst: items) { From 09295fb0a16c04878c4598f3eb51bb2d481dfb30 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Mon, 14 Oct 2024 05:15:36 -0400 Subject: [PATCH 08/44] Push --- .../events/player_event_discord_formatter.cpp | 12 ++++ common/item_instance.cpp | 12 ++++ common/item_instance.h | 1 + zone/client.cpp | 70 +++++++++++++++++-- zone/lua_npc.cpp | 14 ++++ zone/lua_npc.h | 2 + zone/npc.h | 4 ++ zone/perl_npc.cpp | 14 +++- 8 files changed, 123 insertions(+), 6 deletions(-) diff --git a/common/events/player_event_discord_formatter.cpp b/common/events/player_event_discord_formatter.cpp index 578ec672bc..65bda760a0 100644 --- a/common/events/player_event_discord_formatter.cpp +++ b/common/events/player_event_discord_formatter.cpp @@ -714,6 +714,18 @@ std::string PlayerEventDiscordFormatter::FormatNPCHandinEvent( h.charges > 1 ? fmt::format(" Charges: {}", h.charges) : "", h.attuned ? " (Attuned)" : "" ); + + for (int i = 0; i < h.augment_ids.size(); i++) { + if (!Strings::EqualFold(h.augment_names[i], "None")) { + const uint8 slot_id = (i + 1); + handin_items_info += fmt::format( + "Augment {}: {} ({})\n", + slot_id, + h.augment_names[i], + h.augment_ids[i] + ); + } + } } } diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 6aeb32223b..6462b1c489 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -1806,6 +1806,18 @@ std::vector EQ::ItemInstance::GetAugmentIDs() const return augments; } +std::vector EQ::ItemInstance::GetAugmentNames() const +{ + std::vector augment_names; + + for (uint8 slot_id = invaug::SOCKET_BEGIN; slot_id <= invaug::SOCKET_END; slot_id++) { + const auto augment = GetAugment(slot_id); + augment_names.push_back(augment ? augment->GetItem()->Name : "None"); + } + + return augment_names; +} + int EQ::ItemInstance::GetItemRegen(bool augments) const { int stat = 0; diff --git a/common/item_instance.h b/common/item_instance.h index 928a6aabc3..63081ef58d 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -309,6 +309,7 @@ namespace EQ int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const; uint32 GetItemGuildFavor() const; std::vector GetAugmentIDs() const; + std::vector GetAugmentNames() const; static void AddGUIDToMap(uint64 existing_serial_number); static void ClearGUIDMap(); diff --git a/zone/client.cpp b/zone/client.cpp index 4f9e8b8dad..104b5d8050 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13100,6 +13100,11 @@ bool Client::CheckHandin( HandinMoney money; }; + std::vector handin_items; + PlayerEvent::HandinMoney handin_money; + std::vector return_items; + PlayerEvent::HandinMoney return_money; + auto h = Handin{}; // loop through handin @@ -13125,13 +13130,17 @@ bool Client::CheckHandin( // money if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { if (key == "platinum") { - h.money.platinum = value; + h.money.platinum = value; + handin_money.platinum = value; } else if (key == "gold") { - h.money.gold = value; + h.money.gold = value; + handin_money.gold = value; } else if (key == "silver") { - h.money.silver = value; + h.money.silver = value; + handin_money.silver = value; } else if (key == "copper") { - h.money.copper = value; + h.money.copper = value; + handin_money.copper = value; } } } @@ -13200,10 +13209,34 @@ bool Client::CheckHandin( } } + for (auto i : items) { + if (i && i->GetItem()) { + handin_items.emplace_back( + PlayerEvent::HandinEntry{ + .item_id = i->GetID(), + .item_name = i->GetItem()->Name, + .augment_ids = i->GetAugmentIDs(), + .augment_names = i->GetAugmentNames(), + .charges = static_cast(i->GetCharges()) + } + ); + } + } + bool handed_back = false; if (!success) { for (auto i : items) { - if (i) { + if (i && i->GetItem()) { + return_items.emplace_back( + PlayerEvent::HandinEntry{ + .item_id = i->GetID(), + .item_name = i->GetItem()->Name, + .augment_ids = i->GetAugmentIDs(), + .augment_names = i->GetAugmentNames(), + .charges = static_cast(i->GetCharges()) + } + ); + PushItemOnCursor(*i, true); LogTradingDetail("Handin failed, returning item [{}]", i->GetItem()->Name); handed_back = true; @@ -13212,6 +13245,11 @@ bool Client::CheckHandin( // check if any money was handed in if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { + return_money.copper = h.money.copper; + return_money.silver = h.money.silver; + return_money.gold = h.money.gold; + return_money.platinum = h.money.platinum; + AddMoneyToPP(h.money.copper, h.money.silver, h.money.gold, h.money.platinum, true); handed_back = true; LogTradingDetail("Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); @@ -13229,5 +13267,27 @@ bool Client::CheckHandin( m_processed_handin = true; + const bool handed_in_money = ( + handin_money.platinum > 0 || + handin_money.gold > 0 || + handin_money.silver > 0 || + handin_money.copper > 0 + ); + const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; + + if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { + auto e = PlayerEvent::HandinEvent{ + .npc_id = n->GetNPCTypeID(), + .npc_name = n->GetCleanName(), + .handin_items = handin_items, + .handin_money = handin_money, + .return_items = return_items, + .return_money = return_money, + .is_quest_handin = parse->HasQuestSub(n->GetNPCTypeID(), EVENT_TRADE) + }; + + RecordPlayerEventLogWithClient(this, PlayerEvent::NPC_HANDIN, e); + } + return success; } diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index b17804edce..40bc2a4606 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -837,6 +837,18 @@ void Lua_NPC::DescribeSpecialAbilities(Lua_Client c) self->DescribeSpecialAbilities(c); } +bool Lua_NPC::IsMultiQuest() +{ + Lua_Safe_Call_Bool(); + return self->IsMultiQuest(); +} + +void Lua_NPC::SetMultiQuest(bool b) +{ + Lua_Safe_Call_Void(); + self->SetMultiQuest(b); +} + luabind::scope lua_register_npc() { return luabind::class_("NPC") .def(luabind::constructor<>()) @@ -930,6 +942,7 @@ luabind::scope lua_register_npc() { .def("IsAnimal", (bool(Lua_NPC::*)(void))&Lua_NPC::IsAnimal) .def("IsGuarding", (bool(Lua_NPC::*)(void))&Lua_NPC::IsGuarding) .def("IsLDoNLocked", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNLocked) + .def("IsMultiQuest", (bool(Lua_NPC::*)(void))&Lua_NPC::IsMultiQuest) .def("IsLDoNTrapped", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapped) .def("IsLDoNTrapDetected", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapDetected) .def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist) @@ -969,6 +982,7 @@ luabind::scope lua_register_npc() { .def("SetGold", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetGold) .def("SetGrid", (void(Lua_NPC::*)(int))&Lua_NPC::SetGrid) .def("SetKeepsSoldItems", (void(Lua_NPC::*)(bool))&Lua_NPC::SetKeepsSoldItems) + .def("SetMultiQuest", (void(Lua_NPC::*)(bool))&Lua_NPC::SetMultiQuest) .def("SetLDoNLocked", (void(Lua_NPC::*)(bool))&Lua_NPC::SetLDoNLocked) .def("SetLDoNLockedSkill", (void(Lua_NPC::*)(uint16))&Lua_NPC::SetLDoNLockedSkill) .def("SetLDoNTrapped", (void(Lua_NPC::*)(bool))&Lua_NPC::SetLDoNTrapped) diff --git a/zone/lua_npc.h b/zone/lua_npc.h index ec916593ad..84af0a8a97 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -186,6 +186,8 @@ class Lua_NPC : public Lua_Mob void SetNPCAggro(bool in_npc_aggro); uint32 GetNPCSpellsEffectsID(); void DescribeSpecialAbilities(Lua_Client c); + bool IsMultiQuest(); + void SetMultiQuest(bool b); }; #endif diff --git a/zone/npc.h b/zone/npc.h index 92a41dcf08..893aeaa3c0 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -561,6 +561,9 @@ class NPC : public Mob bool CanPetTakeItem(const EQ::ItemInstance *inst); + bool IsMultiQuest() { return m_is_multiquest_npc; } + void SetMultiQuest(bool b) { m_is_multiquest_npc = b; } + protected: void HandleRoambox(); @@ -702,6 +705,7 @@ class NPC : public Mob bool raid_target; bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup + bool m_is_multiquest_npc; private: uint32 m_loottable_id; diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index b47b8c0e8d..690ac4f177 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -796,6 +796,16 @@ void Perl_NPC_DescribeSpecialAbilities(NPC* self, Client* c) self->DescribeSpecialAbilities(c); } +bool Perl_NPC_IsMultiQuest(NPC* self) +{ + return self->IsMultiQuest(); +} + +void Perl_NPC_SetMultiQuest(NPC* self, bool b) +{ + self->SetMultiQuest(b); +} + void perl_register_npc() { perl::interpreter perl(PERL_GET_THX); @@ -892,7 +902,8 @@ void perl_register_npc() package.add("IsGuarding", &Perl_NPC_IsGuarding); package.add("IsLDoNLocked", &Perl_NPC_IsLDoNLocked); package.add("IsLDoNTrapped", &Perl_NPC_IsLDoNTrapped); - package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected);; + package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected); + package.add("IsMultiQuest", &Perl_NPC_IsMultiQuest); package.add("IsOnHatelist", &Perl_NPC_IsOnHatelist); package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget); package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn); @@ -935,6 +946,7 @@ void perl_register_npc() package.add("SetLDoNTrapDetected", &Perl_NPC_SetLDoNTrapDetected); package.add("SetLDoNTrapSpellID", &Perl_NPC_SetLDoNTrapSpellID); package.add("SetLDoNTrapType", &Perl_NPC_SetLDoNTrapType); + package.add("SetMultiQuest", &Perl_NPC_SetMultiQuest); package.add("SetNPCAggro", &Perl_NPC_SetNPCAggro); package.add("SetGold", &Perl_NPC_SetGold); package.add("SetGrid", &Perl_NPC_SetGrid); From 111c7958270e868d02460ba37d4d3b2c17fa8809 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Mon, 14 Oct 2024 05:20:59 -0400 Subject: [PATCH 09/44] Push --- zone/client.cpp | 242 ----------------------------------------------- zone/client.h | 1 - zone/trading.cpp | 1 - 3 files changed, 244 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 104b5d8050..880b306847 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12320,248 +12320,6 @@ void Client::PlayerTradeEventLog(Trade *t, Trade *t2) RecordPlayerEventLogWithClient(trader2, PlayerEvent::TRADE, e); } -void Client::NPCHandinEventLog(Trade* t, NPC* n) -{ - Client* c = t->GetOwner()->CastToClient(); - - std::vector hi = {}; - std::vector ri = {}; - PlayerEvent::HandinMoney hm{}; - PlayerEvent::HandinMoney rm{}; - - if ( - c->EntityVariableExists("HANDIN_ITEMS") && - c->EntityVariableExists("HANDIN_MONEY") && - c->EntityVariableExists("RETURN_ITEMS") && - c->EntityVariableExists("RETURN_MONEY") - ) { - const std::string& handin_items = c->GetEntityVariable("HANDIN_ITEMS"); - const std::string& return_items = c->GetEntityVariable("RETURN_ITEMS"); - const std::string& handin_money = c->GetEntityVariable("HANDIN_MONEY"); - const std::string& return_money = c->GetEntityVariable("RETURN_MONEY"); - - // Handin Items - if (!handin_items.empty()) { - if (Strings::Contains(handin_items, ",")) { - const auto handin_data = Strings::Split(handin_items, ","); - for (const auto& h : handin_data) { - const auto item_data = Strings::Split(h, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - if (item_id != 0) { - const auto* item = database.GetItem(item_id); - - if (item) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } - } else if (Strings::Contains(handin_items, "|")) { - const auto item_data = Strings::Split(handin_items, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - const auto* item = database.GetItem(item_id); - - if (item) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } - - // Handin Money - if (!handin_money.empty()) { - const auto hms = Strings::Split(handin_money, "|"); - - hm.copper = Strings::ToUnsignedInt(hms[0]); - hm.silver = Strings::ToUnsignedInt(hms[1]); - hm.gold = Strings::ToUnsignedInt(hms[2]); - hm.platinum = Strings::ToUnsignedInt(hms[3]); - } - - // Return Items - if (!return_items.empty()) { - if (Strings::Contains(return_items, ",")) { - const auto return_data = Strings::Split(return_items, ","); - for (const auto& r : return_data) { - const auto item_data = Strings::Split(r, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - const auto* item = database.GetItem(item_id); - - if (item) { - ri.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } else if (Strings::Contains(return_items, "|")) { - const auto item_data = Strings::Split(return_items, "|"); - if ( - item_data.size() == 3 && - Strings::IsNumber(item_data[0]) && - Strings::IsNumber(item_data[1]) && - Strings::IsNumber(item_data[2]) - ) { - const uint32 item_id = Strings::ToUnsignedInt(item_data[0]); - const auto* item = database.GetItem(item_id); - - if (item) { - ri.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = item_id, - .item_name = item->Name, - .charges = static_cast(Strings::ToUnsignedInt(item_data[1])), - .attuned = Strings::ToInt(item_data[2]) ? true : false - } - ); - } - } - } - } - - // Return Money - if (!return_money.empty()) { - const auto rms = Strings::Split(return_money, "|"); - rm.copper = static_cast(Strings::ToUnsignedInt(rms[0])); - rm.silver = static_cast(Strings::ToUnsignedInt(rms[1])); - rm.gold = static_cast(Strings::ToUnsignedInt(rms[2])); - rm.platinum = static_cast(Strings::ToUnsignedInt(rms[3])); - } - - c->DeleteEntityVariable("HANDIN_ITEMS"); - c->DeleteEntityVariable("HANDIN_MONEY"); - c->DeleteEntityVariable("RETURN_ITEMS"); - c->DeleteEntityVariable("RETURN_MONEY"); - - const bool handed_in_money = hm.platinum > 0 || hm.gold > 0 || hm.silver > 0 || hm.copper > 0; - - const bool event_has_data_to_record = ( - !hi.empty() || handed_in_money - ); - - if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { - auto e = PlayerEvent::HandinEvent{ - .npc_id = n->GetNPCTypeID(), - .npc_name = n->GetCleanName(), - .handin_items = hi, - .handin_money = hm, - .return_items = ri, - .return_money = rm, - .is_quest_handin = true - }; - - RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); - } - - return; - } - - uint8 item_count = 0; - - hm.platinum = t->pp; - hm.gold = t->gp; - hm.silver = t->sp; - hm.copper = t->cp; - - for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_NPC_END; i++) { - if (c->GetInv().GetItem(i)) { - item_count++; - } - } - - hi.reserve(item_count); - - if (item_count > 0) { - for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_NPC_END; i++) { - const EQ::ItemInstance* inst = c->GetInv().GetItem(i); - if (inst) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .attuned = inst->IsAttuned() - } - ); - - if (inst->IsClassBag()) { - for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) { - inst = c->GetInv().GetItem(i, j); - if (inst) { - hi.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = inst->GetItem()->ID, - .item_name = inst->GetItem()->Name, - .charges = static_cast(inst->GetCharges()), - .attuned = inst->IsAttuned() - } - ); - } - } - } - } - } - } - - const bool handed_in_money = hm.platinum > 0 || hm.gold > 0 || hm.silver > 0 || hm.copper > 0; - - ri = hi; - rm = hm; - - const bool event_has_data_to_record = !hi.empty() || handed_in_money; - - if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { - auto e = PlayerEvent::HandinEvent{ - .npc_id = n->GetNPCTypeID(), - .npc_name = n->GetCleanName(), - .handin_items = hi, - .handin_money = hm, - .return_items = ri, - .return_money = rm, - .is_quest_handin = false - }; - - RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); - } -} - void Client::ShowSpells(Client* c, ShowSpellType show_spell_type) { std::string spell_string; diff --git a/zone/client.h b/zone/client.h index 1d1d2e6ec4..ff79655fea 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2238,7 +2238,6 @@ class Client : public Mob bool CanTradeFVNoDropItem(); void SendMobPositions(); void PlayerTradeEventLog(Trade *t, Trade *t2); - void NPCHandinEventLog(Trade* t, NPC* n); // full and partial mail key cache std::string m_mail_key_full; diff --git a/zone/trading.cpp b/zone/trading.cpp index 5d080cdba5..9d2f90ac0a 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -668,7 +668,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st } } else if(tradingWith->IsNPC()) { - NPCHandinEventLog(trade, tradingWith->CastToNPC()); QSPlayerLogHandin_Struct* qs_audit = nullptr; bool qs_log = false; From fc09ec7e6b6e6cfe5198459149f95cc0dd869e94 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 04:21:17 -0500 Subject: [PATCH 10/44] ReturnHandinItems --- zone/client.cpp | 4 ++++ zone/client.h | 1 + 2 files changed, 5 insertions(+) diff --git a/zone/client.cpp b/zone/client.cpp index 880b306847..333eed803b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13049,3 +13049,7 @@ bool Client::CheckHandin( return success; } + +void Client::ReturnHandinItems() { + // implement +} diff --git a/zone/client.h b/zone/client.h index ff79655fea..03c0cea7bd 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1738,6 +1738,7 @@ class Client : public Mob std::map required, std::vector items ); + bool ReturnHandinItems(); void ItemTimerCheck(); void TryItemTimer(int slot); From 7f61bbd20e5ff1cc5b91e4b99f486c9ac93a4d6f Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 04:27:58 -0500 Subject: [PATCH 11/44] Update client.h --- zone/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client.h b/zone/client.h index 03c0cea7bd..54d6fce8bb 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1738,7 +1738,7 @@ class Client : public Mob std::map required, std::vector items ); - bool ReturnHandinItems(); + void ReturnHandinItems(); void ItemTimerCheck(); void TryItemTimer(int slot); From adba63e04f82dbf1420f4669798c2df997d142c6 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Mon, 14 Oct 2024 05:31:12 -0400 Subject: [PATCH 12/44] Perl/Lua ReturnHandinItems --- zone/lua_client.cpp | 7 +++++++ zone/lua_client.h | 1 + zone/perl_client.cpp | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index d25681d73e..228645f1b8 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3511,6 +3511,12 @@ bool Lua_Client::LuaCheckHandin( return self->CheckHandin(n, handin_map, required_map, items); } +void Lua_Client::ReturnHandinItems() +{ + Lua_Safe_Call_Void(); + self->ReturnHandinItems(); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -3917,6 +3923,7 @@ luabind::scope lua_register_client() { .def("ResetItemCooldown", (void(Lua_Client::*)(uint32))&Lua_Client::ResetItemCooldown) .def("ResetLeadershipAA", (void(Lua_Client::*)(void))&Lua_Client::ResetLeadershipAA) .def("ResetTrade", (void(Lua_Client::*)(void))&Lua_Client::ResetTrade) + .def("ReturnHandinItems", (void(Lua_Client::*)(void))&Lua_Client::ReturnHandinItems) .def("RewardFaction", (void(Lua_Client::*)(int,int))&Lua_Client::RewardFaction) .def("Save", (void(Lua_Client::*)(int))&Lua_Client::Save) .def("Save", (void(Lua_Client::*)(void))&Lua_Client::Save) diff --git a/zone/lua_client.h b/zone/lua_client.h index 6b601a69c7..43456d0220 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -588,6 +588,7 @@ class Lua_Client : public Lua_Mob luabind::adl::object required_table, luabind::adl::object items_table ); + void ReturnHandinItems(); void DialogueWindow(std::string markdown); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index e145ba7cbd..74fe2dd307 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3276,6 +3276,11 @@ bool Perl_Client_CheckHandin( return self->CheckHandin(n, handin_map, required_map, items); } +void Perl_Client_ReturnHandinItems(Client* self) +{ + self->ReturnHandinItems(); +} + void perl_register_client() { perl::interpreter perl(PERL_GET_THX); @@ -3679,6 +3684,7 @@ void perl_register_client() package.add("ResetItemCooldown", &Perl_Client_ResetItemCooldown); package.add("ResetLeadershipAA", &Perl_Client_ResetLeadershipAA); package.add("ResetTrade", &Perl_Client_ResetTrade); + package.add("ReturnHandinItems", &Perl_Client_ReturnHandinItems); package.add("Save", &Perl_Client_Save); package.add("ScribeSpell", (void(*)(Client*, uint16, int))&Perl_Client_ScribeSpell); package.add("ScribeSpell", (void(*)(Client*, uint16, int, bool))&Perl_Client_ScribeSpell); From b82d815c7a74c2255320921b7a1d96709c86e5c5 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Mon, 14 Oct 2024 05:36:12 -0400 Subject: [PATCH 13/44] Update client.cpp --- zone/client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 333eed803b..b78f198d63 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12859,9 +12859,9 @@ bool Client::CheckHandin( }; std::vector handin_items; - PlayerEvent::HandinMoney handin_money; + PlayerEvent::HandinMoney handin_money{ }; std::vector return_items; - PlayerEvent::HandinMoney return_money; + PlayerEvent::HandinMoney return_money{ }; auto h = Handin{}; From ca25e7b85867e2aa249a6cd69916b40bbcbd3225 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Mon, 14 Oct 2024 05:36:56 -0400 Subject: [PATCH 14/44] Update player_event_discord_formatter.cpp --- common/events/player_event_discord_formatter.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/common/events/player_event_discord_formatter.cpp b/common/events/player_event_discord_formatter.cpp index 65bda760a0..d593cb5a54 100644 --- a/common/events/player_event_discord_formatter.cpp +++ b/common/events/player_event_discord_formatter.cpp @@ -739,6 +739,18 @@ std::string PlayerEventDiscordFormatter::FormatNPCHandinEvent( r.charges > 1 ? fmt::format(" Charges: {}", r.charges) : "", r.attuned ? " (Attuned)" : "" ); + + for (int i = 0; i < r.augment_ids.size(); i++) { + if (!Strings::EqualFold(r.augment_names[i], "None")) { + const uint8 slot_id = (i + 1); + handin_items_info += fmt::format( + "Augment {}: {} ({})\n", + slot_id, + r.augment_names[i], + r.augment_ids[i] + ); + } + } } } From a4d8347d900b7adb3399b9aa093e388cbc3aa0ab Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 05:07:50 -0500 Subject: [PATCH 15/44] Checkin --- zone/client.cpp | 257 ++++++++++++++++++++++------------------- zone/client.h | 26 ++++- zone/client_packet.cpp | 10 -- zone/trading.cpp | 23 +--- 4 files changed, 164 insertions(+), 152 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index b78f198d63..b96eaa7bd3 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12835,49 +12835,28 @@ void Client::ClientToNpcAggroProcess() } bool Client::CheckHandin( - NPC* n, + NPC *n, std::map handin, std::map required, - std::vector items + std::vector items ) { - struct HandinEntry { - std::string item_id; - uint32 count; - }; - - struct HandinMoney { - uint32 platinum; - uint32 gold; - uint32 silver; - uint32 copper; - }; - - struct Handin { - std::vector items; - HandinMoney money; - }; - - std::vector handin_items; - PlayerEvent::HandinMoney handin_money{ }; - std::vector return_items; - PlayerEvent::HandinMoney return_money{ }; - - auto h = Handin{}; + m_handin = Handin{}; // loop through handin - for (const auto& [key, value] : handin) { + for (const auto &[key, value]: handin) { LogTradingDetail("Handin key [{}] value [{}]", key, value); // items if (Strings::IsNumber(key)) { - const EQ::ItemData* item = database.GetItem(Strings::ToUnsignedInt(key)); + const EQ::ItemData *item = database.GetItem(Strings::ToUnsignedInt(key)); if (!item) { continue; } - h.items.emplace_back(HandinEntry{.item_id = key, .count = value}); - } else if (!key.empty()) { + m_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + } + else if (!key.empty()) { std::vector moneys = { "platinum", "gold", @@ -12888,35 +12867,35 @@ bool Client::CheckHandin( // money if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { if (key == "platinum") { - h.money.platinum = value; - handin_money.platinum = value; - } else if (key == "gold") { - h.money.gold = value; - handin_money.gold = value; - } else if (key == "silver") { - h.money.silver = value; - handin_money.silver = value; - } else if (key == "copper") { - h.money.copper = value; - handin_money.copper = value; + m_handin.money.platinum = value; + } + else if (key == "gold") { + m_handin.money.gold = value; + } + else if (key == "silver") { + m_handin.money.silver = value; + } + else if (key == "copper") { + m_handin.money.copper = value; } } } } - auto r = Handin{}; - for (const auto& [key, value] : required) { + m_required = Handin{}; + for (const auto &[key, value]: required) { LogTradingDetail("Required key [{}] value [{}]", key, value); // items if (Strings::IsNumber(key)) { - const EQ::ItemData* item = database.GetItem(Strings::ToUnsignedInt(key)); + const EQ::ItemData *item = database.GetItem(Strings::ToUnsignedInt(key)); if (!item) { continue; } - r.items.emplace_back(HandinEntry{.item_id = key, .count = value}); - } else if (!key.empty()) { + m_required.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + } + else if (!key.empty()) { std::vector moneys = { "platinum", "gold", @@ -12927,13 +12906,16 @@ bool Client::CheckHandin( // money if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { if (key == "platinum") { - r.money.platinum = value; - } else if (key == "gold") { - r.money.gold = value; - } else if (key == "silver") { - r.money.silver = value; - } else if (key == "copper") { - r.money.copper = value; + m_required.money.platinum = value; + } + else if (key == "gold") { + m_required.money.gold = value; + } + else if (key == "silver") { + m_required.money.silver = value; + } + else if (key == "copper") { + m_required.money.copper = value; } } } @@ -12941,10 +12923,10 @@ bool Client::CheckHandin( // compare hand-in to required, the item_id can be in any slot bool success = true; - if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { - for (const auto& h_item : h.items) { + if (m_handin.items.size() == m_required.items.size() && !m_handin.items.empty() && !m_required.items.empty()) { + for (const auto &h_item: m_handin.items) { bool found = false; - for (const auto& r_item : r.items) { + for (const auto &r_item: m_required.items) { if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { found = true; break; @@ -12956,44 +12938,86 @@ bool Client::CheckHandin( break; } } - } else { + } + else { success = false; } // compare hand-in money to required if (success) { - if (h.money.platinum != r.money.platinum || h.money.gold != r.money.gold || h.money.silver != r.money.silver || h.money.copper != r.money.copper) { + if (m_handin.money.platinum != m_required.money.platinum || + m_handin.money.gold != m_required.money.gold || + m_handin.money.silver != m_required.money.silver || + m_handin.money.copper != m_required.money.copper) { success = false; } } - for (auto i : items) { - if (i && i->GetItem()) { - handin_items.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = i->GetID(), - .item_name = i->GetItem()->Name, - .augment_ids = i->GetAugmentIDs(), - .augment_names = i->GetAugmentNames(), - .charges = static_cast(i->GetCharges()) - } - ); - } - } + m_handin_success = success; + m_handin_npc = n; + m_handin_items = items; + +// const bool handed_in_money = ( +// handin_money.platinum > 0 || +// handin_money.gold > 0 || +// handin_money.silver > 0 || +// handin_money.copper > 0 +// ); +// const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; +// +// if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { +// auto e = PlayerEvent::HandinEvent{ +// .npc_id = n->GetNPCTypeID(), +// .npc_name = n->GetCleanName(), +// .handin_items = handin_items, +// .handin_money = handin_money, +// .return_items = return_items, +// .return_money = return_money, +// .is_quest_handin = parse->HasQuestSub(n->GetNPCTypeID(), EVENT_TRADE) +// }; +// +// RecordPlayerEventLogWithClient(this, PlayerEvent::NPC_HANDIN, e); +// } + + return success; +} + +void Client::ReturnHandinItems() +{ + + // player event + std::vector handin_items; + PlayerEvent::HandinMoney handin_money{}; + std::vector return_items; + PlayerEvent::HandinMoney return_money{}; + +// for (auto i : items) { +// if (i && i->GetItem()) { +// handin_items.emplace_back( +// PlayerEvent::HandinEntry{ +// .item_id = i->GetID(), +// .item_name = i->GetItem()->Name, +// .augment_ids = i->GetAugmentIDs(), +// .augment_names = i->GetAugmentNames(), +// .charges = static_cast(i->GetCharges()) +// } +// ); +// } +// } bool handed_back = false; - if (!success) { - for (auto i : items) { + if (!m_handin_success) { + for (auto i: m_handin_items) { if (i && i->GetItem()) { - return_items.emplace_back( - PlayerEvent::HandinEntry{ - .item_id = i->GetID(), - .item_name = i->GetItem()->Name, - .augment_ids = i->GetAugmentIDs(), - .augment_names = i->GetAugmentNames(), - .charges = static_cast(i->GetCharges()) - } - ); +// return_items.emplace_back( +// PlayerEvent::HandinEntry{ +// .item_id = i->GetID(), +// .item_name = i->GetItem()->Name, +// .augment_ids = i->GetAugmentIDs(), +// .augment_names = i->GetAugmentNames(), +// .charges = static_cast(i->GetCharges()) +// } +// ); PushItemOnCursor(*i, true); LogTradingDetail("Handin failed, returning item [{}]", i->GetItem()->Name); @@ -13002,54 +13026,45 @@ bool Client::CheckHandin( } // check if any money was handed in - if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { - return_money.copper = h.money.copper; - return_money.silver = h.money.silver; - return_money.gold = h.money.gold; - return_money.platinum = h.money.platinum; - - AddMoneyToPP(h.money.copper, h.money.silver, h.money.gold, h.money.platinum, true); + if (m_handin.money.platinum > 0 || m_handin.money.gold > 0 || m_handin.money.silver > 0 || + m_handin.money.copper > 0) { +// return_money.copper = h.money.copper; +// return_money.silver = h.money.silver; +// return_money.gold = h.money.gold; +// return_money.platinum = h.money.platinum; + + AddMoneyToPP( + m_handin.money.copper, + m_handin.money.silver, + m_handin.money.gold, + m_handin.money.platinum, + true + ); handed_back = true; - LogTradingDetail("Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + LogTradingDetail( + "Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", + m_handin.money.platinum, + m_handin.money.gold, + m_handin.money.silver, + m_handin.money.copper + ); + + // player event + handin_money.copper = m_handin.money.copper; + handin_money.silver = m_handin.money.silver; + handin_money.gold = m_handin.money.gold; + handin_money.platinum = m_handin.money.platinum; } + + m_processed_handin_return = handed_back; } - if (handed_back) { - n->Say( + if (handed_back && m_handin_npc) { + m_handin_npc->Say( fmt::format( "I have no need for this {}, you can have it back.", GetCleanName() ).c_str() ); } - - m_processed_handin = true; - - const bool handed_in_money = ( - handin_money.platinum > 0 || - handin_money.gold > 0 || - handin_money.silver > 0 || - handin_money.copper > 0 - ); - const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; - - if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { - auto e = PlayerEvent::HandinEvent{ - .npc_id = n->GetNPCTypeID(), - .npc_name = n->GetCleanName(), - .handin_items = handin_items, - .handin_money = handin_money, - .return_items = return_items, - .return_money = return_money, - .is_quest_handin = parse->HasQuestSub(n->GetNPCTypeID(), EVENT_TRADE) - }; - - RecordPlayerEventLogWithClient(this, PlayerEvent::NPC_HANDIN, e); - } - - return success; -} - -void Client::ReturnHandinItems() { - // implement } diff --git a/zone/client.h b/zone/client.h index 54d6fce8bb..7d66c0dfe9 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1818,8 +1818,28 @@ class Client : public Mob uint32 GetEXPForLevel(uint16 check_level); - bool HasProcessedHandin() const; - void SetProcessedHandin(bool processed); + struct HandinEntry { + std::string item_id; + uint32 count; + }; + + struct HandinMoney { + uint32 platinum; + uint32 gold; + uint32 silver; + uint32 copper; + }; + + struct Handin { + std::vector items; + HandinMoney money; + }; + + Handin m_handin = {}; + Handin m_required = {}; + bool m_handin_success = false; + NPC* m_handin_npc = nullptr; + std::vector m_handin_items = {}; protected: friend class Mob; @@ -2244,7 +2264,7 @@ class Client : public Mob std::string m_mail_key_full; std::string m_mail_key; - bool m_processed_handin = false; + bool m_processed_handin_return = false; public: const std::string &GetMailKeyFull() const; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index ead892e42b..c880e310f8 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -17226,13 +17226,3 @@ void Client::Handle_OP_ShopRetrieveParcel(const EQApplicationPacket *app) auto parcel_in = (ParcelRetrieve_Struct *)app->pBuffer; DoParcelRetrieve(*parcel_in); } - -bool Client::HasProcessedHandin() const -{ - return m_processed_handin; -} - -void Client::SetProcessedHandin(bool processed) -{ - m_processed_handin = processed; -} diff --git a/zone/trading.cpp b/zone/trading.cpp index 9d2f90ac0a..5e813c9226 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -871,27 +871,14 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st // 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 CheckHandin() event, so we need to check if it's already been processed from EVENT_TRADE - if (!HasProcessedHandin()) { - std::vector remaining_item_list; - for (EQ::ItemInstance *inst: items) { - if (!inst || !inst->GetItem()) { - continue; - } - remaining_item_list.push_back(inst); - } - std::map handin = { - {"copper", trade->cp}, - {"silver", trade->sp}, - {"gold", trade->gp}, - {"platinum", trade->pp} - }; - CheckHandin(tradingWith->CastToNPC(), handin, {}, remaining_item_list); - LogTradingDetail("CheckHandin() called for NPC [{}]", tradingWith->GetNPCTypeID()); + // we can't double fire the ReturnHandinItems() event, so we need to check if it's already been processed from EVENT_TRADE + if (!m_processed_handin_return) { + ReturnHandinItems(); + LogTradingDetail("ReturnHandinItems() called for NPC [{}]", tradingWith->GetNPCTypeID()); } // reset the handin - SetProcessedHandin(false); + m_processed_handin_return = false; for (auto &inst: insts) { if (inst) { From 7fd0896b156d3d852311ecdf1e9610ad17558f63 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 14 Oct 2024 06:12:17 -0500 Subject: [PATCH 16/44] Fin --- zone/client.cpp | 283 ++++++++++++++++++++++++++++------------------- zone/client.h | 18 +-- zone/trading.cpp | 3 +- 3 files changed, 184 insertions(+), 120 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index b96eaa7bd3..b9fc7c2397 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12841,7 +12841,7 @@ bool Client::CheckHandin( std::vector items ) { - m_handin = Handin{}; + auto h = Handin{}; // loop through handin for (const auto &[key, value]: handin) { @@ -12854,7 +12854,7 @@ bool Client::CheckHandin( continue; } - m_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + h.items.emplace_back(HandinEntry{.item_id = key, .count = value}); } else if (!key.empty()) { std::vector moneys = { @@ -12867,22 +12867,22 @@ bool Client::CheckHandin( // money if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { if (key == "platinum") { - m_handin.money.platinum = value; + h.money.platinum = value; } else if (key == "gold") { - m_handin.money.gold = value; + h.money.gold = value; } else if (key == "silver") { - m_handin.money.silver = value; + h.money.silver = value; } else if (key == "copper") { - m_handin.money.copper = value; + h.money.copper = value; } } } } - m_required = Handin{}; + auto r = Handin{}; for (const auto &[key, value]: required) { LogTradingDetail("Required key [{}] value [{}]", key, value); @@ -12893,7 +12893,7 @@ bool Client::CheckHandin( continue; } - m_required.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + r.items.emplace_back(HandinEntry{.item_id = key, .count = value}); } else if (!key.empty()) { std::vector moneys = { @@ -12906,16 +12906,16 @@ bool Client::CheckHandin( // money if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { if (key == "platinum") { - m_required.money.platinum = value; + r.money.platinum = value; } else if (key == "gold") { - m_required.money.gold = value; + r.money.gold = value; } else if (key == "silver") { - m_required.money.silver = value; + r.money.silver = value; } else if (key == "copper") { - m_required.money.copper = value; + r.money.copper = value; } } } @@ -12923,10 +12923,10 @@ bool Client::CheckHandin( // compare hand-in to required, the item_id can be in any slot bool success = true; - if (m_handin.items.size() == m_required.items.size() && !m_handin.items.empty() && !m_required.items.empty()) { - for (const auto &h_item: m_handin.items) { + if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { + for (const auto &h_item: h.items) { bool found = false; - for (const auto &r_item: m_required.items) { + for (const auto &r_item: r.items) { if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { found = true; break; @@ -12945,126 +12945,187 @@ bool Client::CheckHandin( // compare hand-in money to required if (success) { - if (m_handin.money.platinum != m_required.money.platinum || - m_handin.money.gold != m_required.money.gold || - m_handin.money.silver != m_required.money.silver || - m_handin.money.copper != m_required.money.copper) { + if (h.money.platinum != r.money.platinum || + h.money.gold != r.money.gold || + h.money.silver != r.money.silver || + h.money.copper != r.money.copper) { success = false; } } - m_handin_success = success; - m_handin_npc = n; - m_handin_items = items; + // in-case we trigger CheckHandin multiple times, only set these once + if (!m_handin_started) { + m_handin_started = true; + m_handin_npc = n; + m_return_items = items; + m_handed_in_items = items; + m_return_money = h.money; + m_handed_in_money = h.money; + } -// const bool handed_in_money = ( -// handin_money.platinum > 0 || -// handin_money.gold > 0 || -// handin_money.silver > 0 || -// handin_money.copper > 0 -// ); -// const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; -// -// if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { -// auto e = PlayerEvent::HandinEvent{ -// .npc_id = n->GetNPCTypeID(), -// .npc_name = n->GetCleanName(), -// .handin_items = handin_items, -// .handin_money = handin_money, -// .return_items = return_items, -// .return_money = return_money, -// .is_quest_handin = parse->HasQuestSub(n->GetNPCTypeID(), EVENT_TRADE) -// }; -// -// RecordPlayerEventLogWithClient(this, PlayerEvent::NPC_HANDIN, e); -// } + if (success) { + // remove items in m_handin from m_return_items + for (const auto &h_item: h.items) { + for (const auto &r_item: r.items) { + if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { + m_return_items.erase( + std::remove_if( + m_return_items.begin(), + m_return_items.end(), + [&](const EQ::ItemInstance *i) { + bool removed = i->GetItem()->ID == Strings::ToUnsignedInt(h_item.item_id) + && i->GetCharges() == h_item.count; + + if (removed) { + LogTradingDetail("Handin success, removing item [{}] from m_return_items", i->GetItem()->Name); + } + + return removed; + } + ), + m_return_items.end() + ); + } + } + } + + // subtract m_handin money from m_return_money + if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { + LogTradingDetail("Handin success, removing money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + m_return_money.platinum -= h.money.platinum; + m_return_money.gold -= h.money.gold; + m_return_money.silver -= h.money.silver; + m_return_money.copper -= h.money.copper; + } + } return success; } void Client::ReturnHandinItems() { - // player event std::vector handin_items; PlayerEvent::HandinMoney handin_money{}; std::vector return_items; PlayerEvent::HandinMoney return_money{}; + for (auto i : m_handed_in_items) { + if (i && i->GetItem()) { + handin_items.emplace_back( + PlayerEvent::HandinEntry{ + .item_id = i->GetID(), + .item_name = i->GetItem()->Name, + .augment_ids = i->GetAugmentIDs(), + .augment_names = i->GetAugmentNames(), + .charges = static_cast(i->GetCharges()) + } + ); + } + } -// for (auto i : items) { -// if (i && i->GetItem()) { -// handin_items.emplace_back( -// PlayerEvent::HandinEntry{ -// .item_id = i->GetID(), -// .item_name = i->GetItem()->Name, -// .augment_ids = i->GetAugmentIDs(), -// .augment_names = i->GetAugmentNames(), -// .charges = static_cast(i->GetCharges()) -// } -// ); -// } -// } + // check if any money was handed in + if (m_handed_in_money.platinum > 0 || + m_handed_in_money.gold > 0 || + m_handed_in_money.silver > 0 || + m_handed_in_money.copper > 0 + ) { + handin_money.copper = m_handed_in_money.copper; + handin_money.silver = m_handed_in_money.silver; + handin_money.gold = m_handed_in_money.gold; + handin_money.platinum = m_handed_in_money.platinum; + } bool handed_back = false; - if (!m_handin_success) { - for (auto i: m_handin_items) { - if (i && i->GetItem()) { -// return_items.emplace_back( -// PlayerEvent::HandinEntry{ -// .item_id = i->GetID(), -// .item_name = i->GetItem()->Name, -// .augment_ids = i->GetAugmentIDs(), -// .augment_names = i->GetAugmentNames(), -// .charges = static_cast(i->GetCharges()) -// } -// ); - - PushItemOnCursor(*i, true); - LogTradingDetail("Handin failed, returning item [{}]", i->GetItem()->Name); - handed_back = true; - } - } - - // check if any money was handed in - if (m_handin.money.platinum > 0 || m_handin.money.gold > 0 || m_handin.money.silver > 0 || - m_handin.money.copper > 0) { -// return_money.copper = h.money.copper; -// return_money.silver = h.money.silver; -// return_money.gold = h.money.gold; -// return_money.platinum = h.money.platinum; - - AddMoneyToPP( - m_handin.money.copper, - m_handin.money.silver, - m_handin.money.gold, - m_handin.money.platinum, - true - ); - handed_back = true; - LogTradingDetail( - "Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", - m_handin.money.platinum, - m_handin.money.gold, - m_handin.money.silver, - m_handin.money.copper + for (auto i: m_return_items) { + if (i && i->GetItem()) { + return_items.emplace_back( + PlayerEvent::HandinEntry{ + .item_id = i->GetID(), + .item_name = i->GetItem()->Name, + .augment_ids = i->GetAugmentIDs(), + .augment_names = i->GetAugmentNames(), + .charges = static_cast(i->GetCharges()) + } ); - // player event - handin_money.copper = m_handin.money.copper; - handin_money.silver = m_handin.money.silver; - handin_money.gold = m_handin.money.gold; - handin_money.platinum = m_handin.money.platinum; + PushItemOnCursor(*i, true); + LogTradingDetail("Handin failed, returning item [{}]", i->GetItem()->Name); + handed_back = true; } - - m_processed_handin_return = handed_back; } - if (handed_back && m_handin_npc) { - m_handin_npc->Say( - fmt::format( - "I have no need for this {}, you can have it back.", - GetCleanName() - ).c_str() + // check if any money was handed in + if (m_return_money.platinum > 0 || + m_return_money.gold > 0 || + m_return_money.silver > 0 || + m_return_money.copper > 0 + ) { + AddMoneyToPP( + m_return_money.copper, + m_return_money.silver, + m_return_money.gold, + m_return_money.platinum, + true ); + handed_back = true; + LogTradingDetail( + "Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", + m_return_money.platinum, + m_return_money.gold, + m_return_money.silver, + m_return_money.copper + ); + + // player event + return_money.copper = m_return_money.copper; + return_money.silver = m_return_money.silver; + return_money.gold = m_return_money.gold; + return_money.platinum = m_return_money.platinum; } + + m_processed_handin_return = handed_back; + + if (handed_back) { + if (m_handin_npc) { + m_handin_npc->Say( + fmt::format( + "I have no need for this {}, you can have it back.", + GetCleanName() + ).c_str() + ); + } + } + + const bool handed_in_money = ( + handin_money.platinum > 0 || + handin_money.gold > 0 || + handin_money.silver > 0 || + handin_money.copper > 0 + ); + const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; + + if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { + auto e = PlayerEvent::HandinEvent{ + .npc_id = m_handin_npc->GetNPCTypeID(), + .npc_name = m_handin_npc->GetCleanName(), + .handin_items = handin_items, + .handin_money = handin_money, + .return_items = return_items, + .return_money = return_money, + .is_quest_handin = parse->HasQuestSub(m_handin_npc->GetNPCTypeID(), EVENT_TRADE) + }; + + RecordPlayerEventLogWithClient(this, PlayerEvent::NPC_HANDIN, e); + } +} + +void Client::ResetHandin() +{ + m_handin_npc = nullptr; + m_handed_in_items.clear(); + m_return_items.clear(); + m_return_money = {}; + m_processed_handin_return = false; + m_handin_started = false; + m_handed_in_money = {}; } diff --git a/zone/client.h b/zone/client.h index 7d66c0dfe9..d171c94080 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1739,6 +1739,7 @@ class Client : public Mob std::vector items ); void ReturnHandinItems(); + void ResetHandin(); void ItemTimerCheck(); void TryItemTimer(int slot); @@ -1818,6 +1819,7 @@ class Client : public Mob uint32 GetEXPForLevel(uint16 check_level); + // NPC Handin struct HandinEntry { std::string item_id; uint32 count; @@ -1835,11 +1837,15 @@ class Client : public Mob HandinMoney money; }; - Handin m_handin = {}; - Handin m_required = {}; - bool m_handin_success = false; - NPC* m_handin_npc = nullptr; - std::vector m_handin_items = {}; + bool m_handin_started = false; + NPC *m_handin_npc = nullptr; + bool m_processed_handin_return = false; + std::vector m_return_items = {}; + HandinMoney m_return_money = {}; + + // player event log usage only + std::vector m_handed_in_items = {}; + HandinMoney m_handed_in_money = {}; protected: friend class Mob; @@ -2264,8 +2270,6 @@ class Client : public Mob std::string m_mail_key_full; std::string m_mail_key; - bool m_processed_handin_return = false; - public: const std::string &GetMailKeyFull() const; const std::string &GetMailKey() const; diff --git a/zone/trading.cpp b/zone/trading.cpp index 5e813c9226..8be13d86db 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -877,8 +877,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st LogTradingDetail("ReturnHandinItems() called for NPC [{}]", tradingWith->GetNPCTypeID()); } - // reset the handin - m_processed_handin_return = false; + ResetHandin(); for (auto &inst: insts) { if (inst) { From b7d131ab3e3fe77ac34542e540f53a37e3bfb386 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 10:27:20 -0500 Subject: [PATCH 17/44] Fix handin tracking --- zone/client.cpp | 105 ++++++++++++++++++++++++++++++++---------------- zone/client.h | 1 + 2 files changed, 72 insertions(+), 34 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index b9fc7c2397..10325528de 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12842,44 +12842,46 @@ bool Client::CheckHandin( ) { auto h = Handin{}; + if (!m_handin_started) { + for (const auto &[key, value]: handin) { + LogTradingDetail("Handin key [{}] value [{}]", key, value); - // loop through handin - for (const auto &[key, value]: handin) { - LogTradingDetail("Handin key [{}] value [{}]", key, value); + // items + if (Strings::IsNumber(key)) { + const EQ::ItemData *item = database.GetItem(Strings::ToUnsignedInt(key)); + if (!item) { + continue; + } - // items - if (Strings::IsNumber(key)) { - const EQ::ItemData *item = database.GetItem(Strings::ToUnsignedInt(key)); - if (!item) { - continue; + h.items.emplace_back(HandinEntry{.item_id = key, .count = value}); } + else if (!key.empty()) { + std::vector moneys = { + "platinum", + "gold", + "silver", + "copper" + }; - h.items.emplace_back(HandinEntry{.item_id = key, .count = value}); - } - else if (!key.empty()) { - std::vector moneys = { - "platinum", - "gold", - "silver", - "copper" - }; - - // money - if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { - if (key == "platinum") { - h.money.platinum = value; - } - else if (key == "gold") { - h.money.gold = value; - } - else if (key == "silver") { - h.money.silver = value; - } - else if (key == "copper") { - h.money.copper = value; + // money + if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { + if (key == "platinum") { + h.money.platinum = value; + } + else if (key == "gold") { + h.money.gold = value; + } + else if (key == "silver") { + h.money.silver = value; + } + else if (key == "copper") { + h.money.copper = value; + } } } } + } else { + h = m_handin; } auto r = Handin{}; @@ -12961,10 +12963,11 @@ bool Client::CheckHandin( m_handed_in_items = items; m_return_money = h.money; m_handed_in_money = h.money; + m_handin = h; } if (success) { - // remove items in m_handin from m_return_items + // remove items in h from m_return_items for (const auto &h_item: h.items) { for (const auto &r_item: r.items) { if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { @@ -12973,11 +12976,22 @@ bool Client::CheckHandin( m_return_items.begin(), m_return_items.end(), [&](const EQ::ItemInstance *i) { + int charges = i->GetCharges() > 0 ? i->GetCharges() : 1; bool removed = i->GetItem()->ID == Strings::ToUnsignedInt(h_item.item_id) - && i->GetCharges() == h_item.count; + && charges == h_item.count; + + LogTradingDetail( + "Handin success (debug), removing item [{}] count [{}] from m_return_items", + i->GetItem()->Name, + i->GetCharges() + ); if (removed) { - LogTradingDetail("Handin success, removing item [{}] from m_return_items", i->GetItem()->Name); + LogTradingDetail( + "Handin success, removing item [{}] count [{}] from m_return_items", + i->GetItem()->Name, + i->GetCharges() + ); } return removed; @@ -12985,10 +12999,32 @@ bool Client::CheckHandin( ), m_return_items.end() ); + + m_handin.items.erase( + std::remove_if( + m_handin.items.begin(), + m_handin.items.end(), + [&](const HandinEntry &i) { + bool removed = i.item_id == h_item.item_id && i.count == h_item.count; + + if (removed) { + LogTradingDetail( + "Handin success, removing item [{}] count [{}] from m_handin.items", + i.item_id, + i.count + ); + } + + return removed; + } + ), + m_handin.items.end() + ); } } } + // subtract m_handin money from m_return_money if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { LogTradingDetail("Handin success, removing money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); @@ -13128,4 +13164,5 @@ void Client::ResetHandin() m_processed_handin_return = false; m_handin_started = false; m_handed_in_money = {}; + m_handin = {}; } diff --git a/zone/client.h b/zone/client.h index d171c94080..d3694a41b0 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1842,6 +1842,7 @@ class Client : public Mob bool m_processed_handin_return = false; std::vector m_return_items = {}; HandinMoney m_return_money = {}; + Handin m_handin = {}; // player event log usage only std::vector m_handed_in_items = {}; From 58fffaa41147ba75d706525e93f12ddea7cfdc1d Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 12:12:34 -0500 Subject: [PATCH 18/44] Further simplify hand-in logic --- zone/client.cpp | 313 +++++++++++++++++++++-------------------------- zone/client.h | 35 +++--- zone/trading.cpp | 2 +- 3 files changed, 158 insertions(+), 192 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 10325528de..818e90c22f 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12842,89 +12842,73 @@ bool Client::CheckHandin( ) { auto h = Handin{}; - if (!m_handin_started) { - for (const auto &[key, value]: handin) { - LogTradingDetail("Handin key [{}] value [{}]", key, value); + auto r = Handin{}; + + std::vector&, Handin&>> datasets = {}; + + // if we've already started the hand-in process, we don't want to re-process the hand-in data + // we continue to use the originally set hand-in bucket and decrement from it with each successive hand-in + if (m_handin_started) { + h = m_hand_in; + } else { + datasets.emplace_back(handin, h); + } + datasets.emplace_back(required, r); - // items + const std::string set_hand_in = "Hand-in"; + const std::string set_required = "Required"; + for (const auto &[data_map, current_handin]: datasets) { + std::string current_dataset = ¤t_handin == &h ? set_hand_in : set_required; + int index = 0; + for (const auto &[key, value]: data_map) { + LogTradingDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value); + + // Handle items if (Strings::IsNumber(key)) { - const EQ::ItemData *item = database.GetItem(Strings::ToUnsignedInt(key)); - if (!item) { + const EQ::ItemData *exists = database.GetItem(Strings::ToUnsignedInt(key)); + if (!exists) { + index++; + continue; + } + + // we only care about the item instance for the hand-in bucket, not the required bucket + auto item = items[index]; + if (!item && current_dataset == set_hand_in) { + LogTradingDetail( + "[{}] item [{}] instance, not found - this is a big problem", + current_dataset, + key + ); + index++; continue; } - h.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value, .item = item}); } - else if (!key.empty()) { - std::vector moneys = { - "platinum", - "gold", - "silver", - "copper" - }; + else if (!key.empty()) { // Handle money + std::vector moneys = {"platinum", "gold", "silver", "copper"}; - // money if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { if (key == "platinum") { - h.money.platinum = value; + current_handin.money.platinum = value; } else if (key == "gold") { - h.money.gold = value; + current_handin.money.gold = value; } else if (key == "silver") { - h.money.silver = value; + current_handin.money.silver = value; } else if (key == "copper") { - h.money.copper = value; + current_handin.money.copper = value; } } } - } - } else { - h = m_handin; - } - - auto r = Handin{}; - for (const auto &[key, value]: required) { - LogTradingDetail("Required key [{}] value [{}]", key, value); - - // items - if (Strings::IsNumber(key)) { - const EQ::ItemData *item = database.GetItem(Strings::ToUnsignedInt(key)); - if (!item) { - continue; - } - - r.items.emplace_back(HandinEntry{.item_id = key, .count = value}); - } - else if (!key.empty()) { - std::vector moneys = { - "platinum", - "gold", - "silver", - "copper" - }; - - // money - if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { - if (key == "platinum") { - r.money.platinum = value; - } - else if (key == "gold") { - r.money.gold = value; - } - else if (key == "silver") { - r.money.silver = value; - } - else if (key == "copper") { - r.money.copper = value; - } - } + index++; } } // compare hand-in to required, the item_id can be in any slot - bool success = true; + bool met_requirement = true; if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { for (const auto &h_item: h.items) { bool found = false; @@ -12936,80 +12920,58 @@ bool Client::CheckHandin( } if (!found) { - success = false; + met_requirement = false; break; } } } else { - success = false; + met_requirement = false; } // compare hand-in money to required - if (success) { + if (met_requirement) { if (h.money.platinum != r.money.platinum || h.money.gold != r.money.gold || h.money.silver != r.money.silver || h.money.copper != r.money.copper) { - success = false; + met_requirement = false; } } - // in-case we trigger CheckHandin multiple times, only set these once + // in-case we trigger CheckHand-in multiple times, only set these once if (!m_handin_started) { m_handin_started = true; - m_handin_npc = n; - m_return_items = items; - m_handed_in_items = items; - m_return_money = h.money; - m_handed_in_money = h.money; - m_handin = h; + m_hand_in = h; + // save original items for later + m_hand_in.original_items = m_hand_in.items; + m_hand_in.original_money = m_hand_in.money; + m_hand_in.npc = n; } - if (success) { - // remove items in h from m_return_items + if (met_requirement) { + // print current hand-in bucket + LogTradingDetail("---"); + LogTradingDetail("Hand-in success | Printing current hand-in bucket"); + LogTradingDetail("Items [{}] money p [{}] g [{}] s [{}] c [{}]", h.items.size(), h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + for (const auto &i: h.items) { + LogTradingDetail("Hand-in success, item [{}] ({}) count [{}]", i.item->GetItem()->Name, i.item_id, i.count); + } + + // remove items in h from m_handin.items for (const auto &h_item: h.items) { for (const auto &r_item: r.items) { if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { - m_return_items.erase( - std::remove_if( - m_return_items.begin(), - m_return_items.end(), - [&](const EQ::ItemInstance *i) { - int charges = i->GetCharges() > 0 ? i->GetCharges() : 1; - bool removed = i->GetItem()->ID == Strings::ToUnsignedInt(h_item.item_id) - && charges == h_item.count; - - LogTradingDetail( - "Handin success (debug), removing item [{}] count [{}] from m_return_items", - i->GetItem()->Name, - i->GetCharges() - ); - - if (removed) { - LogTradingDetail( - "Handin success, removing item [{}] count [{}] from m_return_items", - i->GetItem()->Name, - i->GetCharges() - ); - } - - return removed; - } - ), - m_return_items.end() - ); - - m_handin.items.erase( + m_hand_in.items.erase( std::remove_if( - m_handin.items.begin(), - m_handin.items.end(), + m_hand_in.items.begin(), + m_hand_in.items.end(), [&](const HandinEntry &i) { bool removed = i.item_id == h_item.item_id && i.count == h_item.count; if (removed) { LogTradingDetail( - "Handin success, removing item [{}] count [{}] from m_handin.items", + "Hand-in success, removing item [{}] count [{}] from m_handin.items", i.item_id, i.count ); @@ -13018,24 +12980,31 @@ bool Client::CheckHandin( return removed; } ), - m_handin.items.end() + m_hand_in.items.end() ); } } } + // print remainder hand-in bucket + LogTradingDetail("---"); + LogTradingDetail("Hand-in | Remainder of hand-in bucket"); + LogTradingDetail("Items [{}] money p [{}] g [{}] s [{}] c [{}]", m_hand_in.items.size(), m_hand_in.money.platinum, m_hand_in.money.gold, m_hand_in.money.silver, m_hand_in.money.copper); + for (const auto &i: m_hand_in.items) { + LogTradingDetail("Hand-in success, item [{}] ({}) count [{}]", i.item->GetItem()->Name, i.item_id, i.count); + } - // subtract m_handin money from m_return_money + // decrement successful hand-in money from current hand-in bucket if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { - LogTradingDetail("Handin success, removing money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); - m_return_money.platinum -= h.money.platinum; - m_return_money.gold -= h.money.gold; - m_return_money.silver -= h.money.silver; - m_return_money.copper -= h.money.copper; + LogTradingDetail("Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + m_hand_in.money.platinum -= h.money.platinum; + m_hand_in.money.gold -= h.money.gold; + m_hand_in.money.silver -= h.money.silver; + m_hand_in.money.copper -= h.money.copper; } } - return success; + return met_requirement; } void Client::ReturnHandinItems() @@ -13045,85 +13014,85 @@ void Client::ReturnHandinItems() PlayerEvent::HandinMoney handin_money{}; std::vector return_items; PlayerEvent::HandinMoney return_money{}; - for (auto i : m_handed_in_items) { - if (i && i->GetItem()) { + for (auto i : m_hand_in.original_items) { + if (i.item && i.item->GetItem()) { handin_items.emplace_back( PlayerEvent::HandinEntry{ - .item_id = i->GetID(), - .item_name = i->GetItem()->Name, - .augment_ids = i->GetAugmentIDs(), - .augment_names = i->GetAugmentNames(), - .charges = static_cast(i->GetCharges()) + .item_id = i.item->GetID(), + .item_name = i.item->GetItem()->Name, + .augment_ids = i.item->GetAugmentIDs(), + .augment_names = i.item->GetAugmentNames(), + .charges = static_cast(i.item->GetCharges()) } ); } } // check if any money was handed in - if (m_handed_in_money.platinum > 0 || - m_handed_in_money.gold > 0 || - m_handed_in_money.silver > 0 || - m_handed_in_money.copper > 0 + if (m_hand_in.original_money.platinum > 0 || + m_hand_in.original_money.gold > 0 || + m_hand_in.original_money.silver > 0 || + m_hand_in.original_money.copper > 0 ) { - handin_money.copper = m_handed_in_money.copper; - handin_money.silver = m_handed_in_money.silver; - handin_money.gold = m_handed_in_money.gold; - handin_money.platinum = m_handed_in_money.platinum; + handin_money.copper = m_hand_in.original_money.copper; + handin_money.silver = m_hand_in.original_money.silver; + handin_money.gold = m_hand_in.original_money.gold; + handin_money.platinum = m_hand_in.original_money.platinum; } - bool handed_back = false; - for (auto i: m_return_items) { - if (i && i->GetItem()) { + bool return_handin = false; + for (auto i: m_hand_in.items) { + if (i.item && i.item->GetItem()) { return_items.emplace_back( PlayerEvent::HandinEntry{ - .item_id = i->GetID(), - .item_name = i->GetItem()->Name, - .augment_ids = i->GetAugmentIDs(), - .augment_names = i->GetAugmentNames(), - .charges = static_cast(i->GetCharges()) + .item_id = i.item->GetID(), + .item_name = i.item->GetItem()->Name, + .augment_ids = i.item->GetAugmentIDs(), + .augment_names = i.item->GetAugmentNames(), + .charges = static_cast(i.item->GetCharges()) } ); - PushItemOnCursor(*i, true); - LogTradingDetail("Handin failed, returning item [{}]", i->GetItem()->Name); - handed_back = true; + PushItemOnCursor(*i.item, true); + LogTradingDetail("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); + return_handin = true; } } // check if any money was handed in - if (m_return_money.platinum > 0 || - m_return_money.gold > 0 || - m_return_money.silver > 0 || - m_return_money.copper > 0 + if (m_hand_in.money.platinum > 0 || + m_hand_in.money.gold > 0 || + m_hand_in.money.silver > 0 || + m_hand_in.money.copper > 0 ) { AddMoneyToPP( - m_return_money.copper, - m_return_money.silver, - m_return_money.gold, - m_return_money.platinum, + m_hand_in.money.copper, + m_hand_in.money.silver, + m_hand_in.money.gold, + m_hand_in.money.platinum, true ); - handed_back = true; + return_handin = true; LogTradingDetail( - "Handin failed, returning money p [{}] g [{}] s [{}] c [{}]", - m_return_money.platinum, - m_return_money.gold, - m_return_money.silver, - m_return_money.copper + "Hand-in failed, returning money p [{}] g [{}] s [{}] c [{}]", + m_hand_in.money.platinum, + m_hand_in.money.gold, + m_hand_in.money.silver, + m_hand_in.money.copper ); // player event - return_money.copper = m_return_money.copper; - return_money.silver = m_return_money.silver; - return_money.gold = m_return_money.gold; - return_money.platinum = m_return_money.platinum; + return_money.copper = m_hand_in.money.copper; + return_money.silver = m_hand_in.money.silver; + return_money.gold = m_hand_in.money.gold; + return_money.platinum = m_hand_in.money.platinum; } - m_processed_handin_return = handed_back; + m_has_processed_handin_return = return_handin; - if (handed_back) { - if (m_handin_npc) { - m_handin_npc->Say( + if (return_handin) { + if (m_hand_in.npc) { + m_hand_in.npc->Say( fmt::format( "I have no need for this {}, you can have it back.", GetCleanName() @@ -13142,13 +13111,13 @@ void Client::ReturnHandinItems() if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { auto e = PlayerEvent::HandinEvent{ - .npc_id = m_handin_npc->GetNPCTypeID(), - .npc_name = m_handin_npc->GetCleanName(), + .npc_id = m_hand_in.npc->GetNPCTypeID(), + .npc_name = m_hand_in.npc->GetCleanName(), .handin_items = handin_items, .handin_money = handin_money, .return_items = return_items, .return_money = return_money, - .is_quest_handin = parse->HasQuestSub(m_handin_npc->GetNPCTypeID(), EVENT_TRADE) + .is_quest_handin = parse->HasQuestSub(m_hand_in.npc->GetNPCTypeID(), EVENT_TRADE) }; RecordPlayerEventLogWithClient(this, PlayerEvent::NPC_HANDIN, e); @@ -13157,12 +13126,8 @@ void Client::ReturnHandinItems() void Client::ResetHandin() { - m_handin_npc = nullptr; - m_handed_in_items.clear(); - m_return_items.clear(); - m_return_money = {}; - m_processed_handin_return = false; - m_handin_started = false; - m_handed_in_money = {}; - m_handin = {}; + m_has_processed_handin_return = false; + m_handin_started = false; + m_hand_in.original_money = {}; + m_hand_in = {}; } diff --git a/zone/client.h b/zone/client.h index d3694a41b0..879f23b365 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1821,32 +1821,33 @@ class Client : public Mob // NPC Handin struct HandinEntry { - std::string item_id; - uint32 count; + std::string item_id = "0"; + uint32 count = 0; + const EQ::ItemInstance *item = nullptr; }; struct HandinMoney { - uint32 platinum; - uint32 gold; - uint32 silver; - uint32 copper; + uint32 platinum = 0; + uint32 gold = 0; + uint32 silver = 0; + uint32 copper = 0; }; struct Handin { - std::vector items; - HandinMoney money; + std::vector original_items = {}; // this is what the player originally handed in, never modified + std::vector items = {}; // items can be removed from this set as successful handins are made + HandinMoney original_money = {}; // this is what the player originally handed in, never modified + HandinMoney money = {}; // money can be removed from this set as successful handins are made + NPC *npc = nullptr; // the NPC the player is handing in to }; - bool m_handin_started = false; - NPC *m_handin_npc = nullptr; - bool m_processed_handin_return = false; - std::vector m_return_items = {}; - HandinMoney m_return_money = {}; - Handin m_handin = {}; + bool m_handin_started = false; + bool m_has_processed_handin_return = false; - // player event log usage only - std::vector m_handed_in_items = {}; - HandinMoney m_handed_in_money = {}; + // this is the working handin data from the player + // items can be decremented from this as each successful + // check is ran in scripts, the remainder is what is returned + Handin m_hand_in = {}; protected: friend class Mob; diff --git a/zone/trading.cpp b/zone/trading.cpp index 8be13d86db..5fc51aedad 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -872,7 +872,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st // 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 - if (!m_processed_handin_return) { + if (!m_has_processed_handin_return) { ReturnHandinItems(); LogTradingDetail("ReturnHandinItems() called for NPC [{}]", tradingWith->GetNPCTypeID()); } From 6baf9f8b4b631cb466b2968be58d2bdb44b6f654 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 14:10:19 -0500 Subject: [PATCH 19/44] Further cleanup and simplification --- common/item_data.cpp | 21 +++++++++++++++++ common/item_data.h | 1 + zone/client.cpp | 56 ++++++++++++++++++++++++-------------------- zone/npc.cpp | 5 +++- zone/trading.cpp | 23 ++++++++++++++++++ 5 files changed, 80 insertions(+), 26 deletions(-) diff --git a/common/item_data.cpp b/common/item_data.cpp index 6dcb1f5778..bcc311a0f8 100644 --- a/common/item_data.cpp +++ b/common/item_data.cpp @@ -220,6 +220,27 @@ bool EQ::ItemData::IsType1HWeapon() const return ((ItemType == item::ItemType1HBlunt) || (ItemType == item::ItemType1HSlash) || (ItemType == item::ItemType1HPiercing) || (ItemType == item::ItemTypeMartial)); } +bool EQ::ItemData::IsPetUsable() const +{ + if (ItemClass == item::ItemClassBag) { + return true; + } + + switch (ItemType) { + case item::ItemType1HBlunt: + case item::ItemType1HSlash: + case item::ItemType1HPiercing: + case item::ItemType2HBlunt: + case item::ItemType2HSlash: + case item::ItemTypeMartial: + case item::ItemTypeShield: + case item::ItemTypeArmor: + return true; + default: + return false; + } +} + bool EQ::ItemData::IsType2HWeapon() const { return ((ItemType == item::ItemType2HBlunt) || (ItemType == item::ItemType2HSlash) || (ItemType == item::ItemType2HPiercing)); diff --git a/common/item_data.h b/common/item_data.h index 449a867598..e4cad0e7a3 100644 --- a/common/item_data.h +++ b/common/item_data.h @@ -550,6 +550,7 @@ namespace EQ bool IsType1HWeapon() const; bool IsType2HWeapon() const; bool IsTypeShield() const; + bool IsPetUsable() const; bool IsQuestItem() const; static bool CheckLoreConflict(const ItemData* l_item, const ItemData* r_item); diff --git a/zone/client.cpp b/zone/client.cpp index 818e90c22f..980e6b47cf 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12855,11 +12855,10 @@ bool Client::CheckHandin( } datasets.emplace_back(required, r); - const std::string set_hand_in = "Hand-in"; + const std::string set_hand_in = "Hand-in"; const std::string set_required = "Required"; for (const auto &[data_map, current_handin]: datasets) { std::string current_dataset = ¤t_handin == &h ? set_hand_in : set_required; - int index = 0; for (const auto &[key, value]: data_map) { LogTradingDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value); @@ -12867,26 +12866,16 @@ bool Client::CheckHandin( if (Strings::IsNumber(key)) { const EQ::ItemData *exists = database.GetItem(Strings::ToUnsignedInt(key)); if (!exists) { - index++; continue; } - // we only care about the item instance for the hand-in bucket, not the required bucket - auto item = items[index]; - if (!item && current_dataset == set_hand_in) { - LogTradingDetail( - "[{}] item [{}] instance, not found - this is a big problem", - current_dataset, - key - ); - index++; - continue; + // only handle required here, for hand-in we'll pull from the item instances for accuracy + if (current_dataset == set_required) { + current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value}); } - - current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value, .item = item}); } else if (!key.empty()) { // Handle money - std::vector moneys = {"platinum", "gold", "silver", "copper"}; + std::vector moneys = {"platinum", "gold", "silver", "copper"}; if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { if (key == "platinum") { @@ -12903,10 +12892,20 @@ bool Client::CheckHandin( } } } - index++; } } + // pull hand-in items from the item instances + for (const auto &i: items) { + h.items.emplace_back( + HandinEntry{ + .item_id = std::to_string(i->GetItem()->ID), + .count = i->GetCharges(), + .item = i + } + ); + } + // compare hand-in to required, the item_id can be in any slot bool met_requirement = true; if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { @@ -12949,15 +12948,15 @@ bool Client::CheckHandin( m_hand_in.npc = n; } - if (met_requirement) { - // print current hand-in bucket - LogTradingDetail("---"); - LogTradingDetail("Hand-in success | Printing current hand-in bucket"); - LogTradingDetail("Items [{}] money p [{}] g [{}] s [{}] c [{}]", h.items.size(), h.money.platinum, h.money.gold, h.money.silver, h.money.copper); - for (const auto &i: h.items) { - LogTradingDetail("Hand-in success, item [{}] ({}) count [{}]", i.item->GetItem()->Name, i.item_id, i.count); - } + // print current hand-in bucket + LogTradingDetail("---"); + LogTradingDetail("Hand-in | Printing current hand-in bucket"); + LogTradingDetail("--- Items [{}] money p [{}] g [{}] s [{}] c [{}]", h.items.size(), h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + for (const auto &i: h.items) { + LogTradingDetail("--- Hand-in | item [{}] ({}) count [{}]", i.item->GetItem()->Name, i.item_id, i.count); + } + if (met_requirement) { // remove items in h from m_handin.items for (const auto &h_item: h.items) { for (const auto &r_item: r.items) { @@ -13004,6 +13003,13 @@ bool Client::CheckHandin( } } + LogTradingDetail( + "CheckHandin [{}] met_requirement [{}] npc [{}]", + GetCleanName(), + met_requirement, + n->GetCleanName() + ); + return met_requirement; } diff --git a/zone/npc.cpp b/zone/npc.cpp index e575dd4fec..eac775f5c8 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4244,7 +4244,10 @@ bool NPC::CanPetTakeItem(const EQ::ItemInstance *inst) } const bool can_take_nodrop = RuleB(Pets, CanTakeNoDrop) || inst->GetItem()->NoDrop != 0; - const bool can_pet_take_item = !inst->GetItem()->IsQuestItem() && can_take_nodrop && !inst->IsAttuned(); + const bool can_pet_take_item = !inst->GetItem()->IsQuestItem() + && can_take_nodrop + && inst->GetItem()->IsPetUsable() + && !inst->IsAttuned(); if (!can_pet_take_item) { return false; diff --git a/zone/trading.cpp b/zone/trading.cpp index 5fc51aedad..1543c359f3 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -873,6 +873,29 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st // 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 if (!m_has_processed_handin_return) { + if (!m_handin_started) { + LogTradingDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID()); + std::map handin; + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + + std::string item_id = fmt::format("{}", inst->GetItem()->ID); + handin[item_id] += inst->GetCharges(); + } + + std::vector list(items.begin(), items.end()); + for (EQ::ItemInstance *inst: items) { + if (!inst || !inst->GetItem()) { + continue; + } + item_list.emplace_back(inst); + } + + CheckHandin(tradingWith->CastToNPC(), handin, {}, list); + } + ReturnHandinItems(); LogTradingDetail("ReturnHandinItems() called for NPC [{}]", tradingWith->GetNPCTypeID()); } From b55bbd40316d3e849abbdc2b876c99bfab2c645a Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 14:17:41 -0500 Subject: [PATCH 20/44] Tweaks --- zone/client.cpp | 6 +++++- zone/trading.cpp | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 980e6b47cf..7cc5817539 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12897,10 +12897,14 @@ bool Client::CheckHandin( // pull hand-in items from the item instances for (const auto &i: items) { + if (!i) { + continue; + } + h.items.emplace_back( HandinEntry{ .item_id = std::to_string(i->GetItem()->ID), - .count = i->GetCharges(), + .count = static_cast(i->GetCharges()), .item = i } ); diff --git a/zone/trading.cpp b/zone/trading.cpp index 1543c359f3..1de3b9785b 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -875,7 +875,13 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (!m_has_processed_handin_return) { if (!m_handin_started) { LogTradingDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID()); - std::map handin; + 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; From 53a166e2f6efb00a0065eb05ce6888318e7ae72b Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 14:37:54 -0500 Subject: [PATCH 21/44] Tweaks: weapon charges should be at minimum 1 --- zone/client.cpp | 28 +++++++++++++++------------- zone/client.h | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 7cc5817539..f35c5b6a07 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12896,18 +12896,20 @@ bool Client::CheckHandin( } // pull hand-in items from the item instances - for (const auto &i: items) { - if (!i) { - continue; - } - - h.items.emplace_back( - HandinEntry{ - .item_id = std::to_string(i->GetItem()->ID), - .count = static_cast(i->GetCharges()), - .item = i + if (!m_handin_started) { + for (const auto &i: items) { + if (!i) { + continue; } - ); + + h.items.emplace_back( + HandinEntry{ + .item_id = std::to_string(i->GetItem()->ID), + .count = std::max(static_cast(i->GetCharges()), static_cast(1)), + .item = i + } + ); + } } // compare hand-in to required, the item_id can be in any slot @@ -13032,7 +13034,7 @@ void Client::ReturnHandinItems() .item_name = i.item->GetItem()->Name, .augment_ids = i.item->GetAugmentIDs(), .augment_names = i.item->GetAugmentNames(), - .charges = static_cast(i.item->GetCharges()) + .charges = std::max(static_cast(i.item->GetCharges()), static_cast(1)) } ); } @@ -13059,7 +13061,7 @@ void Client::ReturnHandinItems() .item_name = i.item->GetItem()->Name, .augment_ids = i.item->GetAugmentIDs(), .augment_names = i.item->GetAugmentNames(), - .charges = static_cast(i.item->GetCharges()) + .charges = std::max(static_cast(i.item->GetCharges()), static_cast(1)) } ); diff --git a/zone/client.h b/zone/client.h index 879f23b365..c62a385d0f 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1822,7 +1822,7 @@ class Client : public Mob // NPC Handin struct HandinEntry { std::string item_id = "0"; - uint32 count = 0; + uint16 count = 0; const EQ::ItemInstance *item = nullptr; }; From c48016a6758a8c203d298fbac587a5ced50b1bbf Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 14:54:21 -0500 Subject: [PATCH 22/44] Consistent types --- zone/client.cpp | 6 +++--- zone/client.h | 4 ++-- zone/lua_client.cpp | 4 ++-- zone/perl_client.cpp | 4 ++-- zone/trading.cpp | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index f35c5b6a07..a70120855d 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12836,15 +12836,15 @@ void Client::ClientToNpcAggroProcess() bool Client::CheckHandin( NPC *n, - std::map handin, - std::map required, + std::map handin, + std::map required, std::vector items ) { auto h = Handin{}; auto r = Handin{}; - std::vector&, Handin&>> datasets = {}; + std::vector&, Handin&>> datasets = {}; // if we've already started the hand-in process, we don't want to re-process the hand-in data // we continue to use the originally set hand-in bucket and decrement from it with each successive hand-in diff --git a/zone/client.h b/zone/client.h index c62a385d0f..ea751a3a5b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1734,8 +1734,8 @@ class Client : public Mob bool CheckHandin( NPC* n, - std::map handin, - std::map required, + std::map handin, + std::map required, std::vector items ); void ReturnHandinItems(); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 228645f1b8..a0fe3083ea 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3453,8 +3453,8 @@ bool Lua_Client::LuaCheckHandin( return false; } - std::map handin_map; - std::map required_map; + std::map handin_map; + std::map required_map; std::vector items; for (luabind::iterator i(handin_table), end; i != end; i++) { diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 74fe2dd307..5f655bc116 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3223,8 +3223,8 @@ bool Perl_Client_CheckHandin( perl::hash handin = handin_ref; perl::hash required = required_ref; - std::map handin_map; - std::map required_map; + std::map handin_map; + std::map required_map; std::vector items; for (auto e: handin) { diff --git a/zone/trading.cpp b/zone/trading.cpp index 1de3b9785b..3e7bb95c40 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -875,7 +875,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (!m_has_processed_handin_return) { if (!m_handin_started) { LogTradingDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID()); - std::map handin = { + std::map handin = { {"copper", trade->cp}, {"silver", trade->sp}, {"gold", trade->gp}, From f22b487e7087c7c98f8fcb29880b1644ba41c784 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 15:01:48 -0500 Subject: [PATCH 23/44] Simplify --- zone/trading.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/zone/trading.cpp b/zone/trading.cpp index 3e7bb95c40..d6895c6c3b 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -881,7 +881,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st {"gold", trade->gp}, {"platinum", trade->pp} }; - + std::vector list(items.begin(), items.end()); for (EQ::ItemInstance *inst: items) { if (!inst || !inst->GetItem()) { continue; @@ -889,13 +889,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st std::string item_id = fmt::format("{}", inst->GetItem()->ID); handin[item_id] += inst->GetCharges(); - } - - std::vector list(items.begin(), items.end()); - for (EQ::ItemInstance *inst: items) { - if (!inst || !inst->GetItem()) { - continue; - } item_list.emplace_back(inst); } From 682e8e8a6d7af4d8109dddea52e4a50576d86195 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 15 Oct 2024 15:26:06 -0500 Subject: [PATCH 24/44] Add CheckForCompatibleQuestPlugins --- zone/main.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/zone/main.cpp b/zone/main.cpp index a05af774a4..2e089394ec 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -122,6 +122,7 @@ void CatchSignal(int sig_num); extern void MapOpcodes(); +bool CheckForCompatibleQuestPlugins(); int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformZone); @@ -367,6 +368,11 @@ int main(int argc, char **argv) return 1; } + if (!CheckForCompatibleQuestPlugins()) { + LogError("Incompatible quest plugins detected, please update your plugins to the latest version"); + return 1; + } + // load these here for now until spells and items can be truly repointed to "content_db" database.SetSharedItemsCount(content_db.GetItemsCount()); database.SetSharedSpellsCount(content_db.GetSpellsCount()); @@ -705,3 +711,25 @@ void UpdateWindowTitle(char *iNewTitle) SetConsoleTitle(tmp); #endif } + +bool CheckForCompatibleQuestPlugins() +{ + std::vector files = { + "quests/plugins/check_handin.pl", + "lua_modules/items.lua", + }; + + bool found = true; + for (const auto &file : files) { + auto f = path.GetServerPath() + "/" + file; + if (File::Exists(f)) { + auto r = File::GetContents(std::filesystem::path{f}.string()); + if (!Strings::Contains(r.contents, "CheckHandin")) { + found = false; + break; + } + } + } + + return found; +} From 06fc61bb5a2aa6d7f41ab959e4656100101b4123 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Tue, 15 Oct 2024 18:48:37 -0400 Subject: [PATCH 25/44] Add multiquest_enabled flag to NPCs --- common/database/database_update_manifest.cpp | 11 +++++++++++ common/repositories/base/base_npc_types_repository.h | 12 ++++++++++++ common/version.h | 2 +- zone/npc.cpp | 1 + zone/npc.h | 6 +++--- zone/zonedb.cpp | 1 + zone/zonedump.h | 3 ++- 7 files changed, 31 insertions(+), 5 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 63f3ba35b3..6b9d099735 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5758,6 +5758,17 @@ ALTER TABLE `inventory_snapshots` ALTER TABLE `character_exp_modifiers` MODIFY COLUMN `aa_modifier` float NOT NULL DEFAULT 1.0 AFTER `instance_version`, MODIFY COLUMN `exp_modifier` float NOT NULL DEFAULT 1.0 AFTER `aa_modifier`; +)" + }, + ManifestEntry{ + .version = 9285, + .description = "2024_10_15_npc_types_multiquest_enabled.sql", + .check = "SHOW COLUMNS FROM `npc_types` LIKE 'multiquest_enabled'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `npc_types` +ADD COLUMN `multiquest_enabled` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `is_parcel_merchant`; )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/repositories/base/base_npc_types_repository.h b/common/repositories/base/base_npc_types_repository.h index 1eb12660bf..02632be481 100644 --- a/common/repositories/base/base_npc_types_repository.h +++ b/common/repositories/base/base_npc_types_repository.h @@ -148,6 +148,7 @@ class BaseNpcTypesRepository { int32_t faction_amount; uint8_t keeps_sold_items; uint8_t is_parcel_merchant; + uint8_t multiquest_enabled; }; static std::string PrimaryKey() @@ -287,6 +288,7 @@ class BaseNpcTypesRepository { "faction_amount", "keeps_sold_items", "is_parcel_merchant", + "multiquest_enabled", }; } @@ -422,6 +424,7 @@ class BaseNpcTypesRepository { "faction_amount", "keeps_sold_items", "is_parcel_merchant", + "multiquest_enabled", }; } @@ -591,6 +594,7 @@ class BaseNpcTypesRepository { e.faction_amount = 0; e.keeps_sold_items = 1; e.is_parcel_merchant = 0; + e.multiquest_enabled = 0; return e; } @@ -756,6 +760,7 @@ class BaseNpcTypesRepository { e.faction_amount = row[126] ? static_cast(atoi(row[126])) : 0; e.keeps_sold_items = row[127] ? static_cast(strtoul(row[127], nullptr, 10)) : 1; e.is_parcel_merchant = row[128] ? static_cast(strtoul(row[128], nullptr, 10)) : 0; + e.multiquest_enabled = row[129] ? static_cast(strtoul(row[129], nullptr, 10)) : 0; return e; } @@ -917,6 +922,7 @@ class BaseNpcTypesRepository { v.push_back(columns[126] + " = " + std::to_string(e.faction_amount)); v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items)); v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant)); + v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled)); auto results = db.QueryDatabase( fmt::format( @@ -1067,6 +1073,7 @@ class BaseNpcTypesRepository { v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); auto results = db.QueryDatabase( fmt::format( @@ -1225,6 +1232,7 @@ class BaseNpcTypesRepository { v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -1387,6 +1395,7 @@ class BaseNpcTypesRepository { e.faction_amount = row[126] ? static_cast(atoi(row[126])) : 0; e.keeps_sold_items = row[127] ? static_cast(strtoul(row[127], nullptr, 10)) : 1; e.is_parcel_merchant = row[128] ? static_cast(strtoul(row[128], nullptr, 10)) : 0; + e.multiquest_enabled = row[129] ? static_cast(strtoul(row[129], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1540,6 +1549,7 @@ class BaseNpcTypesRepository { e.faction_amount = row[126] ? static_cast(atoi(row[126])) : 0; e.keeps_sold_items = row[127] ? static_cast(strtoul(row[127], nullptr, 10)) : 1; e.is_parcel_merchant = row[128] ? static_cast(strtoul(row[128], nullptr, 10)) : 0; + e.multiquest_enabled = row[129] ? static_cast(strtoul(row[129], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -1743,6 +1753,7 @@ class BaseNpcTypesRepository { v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); auto results = db.QueryDatabase( fmt::format( @@ -1894,6 +1905,7 @@ class BaseNpcTypesRepository { v.push_back(std::to_string(e.faction_amount)); v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.is_parcel_merchant)); + v.push_back(std::to_string(e.multiquest_enabled)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/version.h b/common/version.h index 6e1c31abcf..4afe28eff7 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9284 +#define CURRENT_BINARY_DATABASE_VERSION 9285 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045 #endif diff --git a/zone/npc.cpp b/zone/npc.cpp index eac775f5c8..df32375104 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -226,6 +226,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi ATK = npc_type_data->ATK; heroic_strikethrough = npc_type_data->heroic_strikethrough; keeps_sold_items = npc_type_data->keeps_sold_items; + multiquest_enabled = npc_type_data->multiquest_enabled; // used for when switch back to charm default_ac = npc_type_data->AC; diff --git a/zone/npc.h b/zone/npc.h index 893aeaa3c0..33510772fa 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -561,8 +561,8 @@ class NPC : public Mob bool CanPetTakeItem(const EQ::ItemInstance *inst); - bool IsMultiQuest() { return m_is_multiquest_npc; } - void SetMultiQuest(bool b) { m_is_multiquest_npc = b; } + bool IsMultiQuest() { return multiquest_enabled; } + void SetMultiQuest(bool b) { multiquest_enabled = b; } protected: @@ -705,7 +705,7 @@ class NPC : public Mob bool raid_target; bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup - bool m_is_multiquest_npc; + bool multiquest_enabled; private: uint32 m_loottable_id; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 3f0ac20e8c..d8323160fa 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1905,6 +1905,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load t->heroic_strikethrough = n.heroic_strikethrough; t->faction_amount = n.faction_amount; t->keeps_sold_items = n.keeps_sold_items; + t->multiquest_enabled = n.multiquest_enabled != 0; // If NPC with duplicate NPC id already in table, // free item we attempted to add. diff --git a/zone/zonedump.h b/zone/zonedump.h index 52dbb5b3ed..cefa2574d3 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -155,7 +155,8 @@ struct NPCType int heroic_strikethrough; bool keeps_sold_items; bool is_parcel_merchant; - uint8 greed; + uint8 greed; + bool multiquest_enabled; }; #pragma pack() From 19affb6bcd40e4d313906f560dd8ca7541f5faa1 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Tue, 15 Oct 2024 19:58:57 -0400 Subject: [PATCH 26/44] Update main.cpp --- zone/main.cpp | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/zone/main.cpp b/zone/main.cpp index 2e089394ec..b4cbf289b4 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -714,22 +714,32 @@ void UpdateWindowTitle(char *iNewTitle) bool CheckForCompatibleQuestPlugins() { - std::vector files = { - "quests/plugins/check_handin.pl", - "lua_modules/items.lua", - }; - - bool found = true; - for (const auto &file : files) { - auto f = path.GetServerPath() + "/" + file; - if (File::Exists(f)) { - auto r = File::GetContents(std::filesystem::path{f}.string()); - if (!Strings::Contains(r.contents, "CheckHandin")) { - found = false; - break; + const std::vector& directories = { "lua_modules", "plugins" }; + + bool lua_found = true; + bool perl_found = true; + + for (const auto& directory : directories) { + for (const auto& file : fs::directory_iterator(path.GetServerPath() + "/" + directory)) { + if (file.is_regular_file()) { + auto f = path.GetServerPath() + "/" + file.path().string(); + if (File::Exists(f)) { + auto r = File::GetContents(std::filesystem::path{ f }.string()); + if (!Strings::Contains(r.contents, "CheckHandin")) { + if (Strings::EqualFold(directory, "lua_modules")) { + lua_found = false; + LogError("Failed to find CheckHandin in lua_modules"); + } else if (Strings::EqualFold(directory, "plugins")) { + perl_found = false; + LogError("Failed to find CheckHandin in plugins"); + } + + break; + } + } } } } - return found; + return lua_found && perl_found; } From c6c4aac0c9b966bd07e1b747cf2caabc4b7aa04c Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 20 Oct 2024 17:57:23 -0500 Subject: [PATCH 27/44] More code simplification --- common/ruletypes.h | 1 - zone/client.cpp | 108 ++++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index c1abbe836e..8933e6b409 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -656,7 +656,6 @@ RULE_BOOL(NPC, EnableNPCQuestJournal, false, "Setting whether the NPC Quest Jour RULE_INT(NPC, LastFightingDelayMovingMin, 10000, "Minimum time before mob goes home after all aggro loss (milliseconds)") 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, 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/client.cpp b/zone/client.cpp index a70120855d..e48e9db4e4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12864,34 +12864,18 @@ bool Client::CheckHandin( // Handle items if (Strings::IsNumber(key)) { - const EQ::ItemData *exists = database.GetItem(Strings::ToUnsignedInt(key)); - if (!exists) { - continue; - } - - // only handle required here, for hand-in we'll pull from the item instances for accuracy - if (current_dataset == set_required) { + if (const auto *exists = database.GetItem(Strings::ToUnsignedInt(key)); + exists && current_dataset == set_required) { current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value}); } + continue; } - else if (!key.empty()) { // Handle money - std::vector moneys = {"platinum", "gold", "silver", "copper"}; - if (std::find(moneys.begin(), moneys.end(), key) != moneys.end()) { - if (key == "platinum") { - current_handin.money.platinum = value; - } - else if (key == "gold") { - current_handin.money.gold = value; - } - else if (key == "silver") { - current_handin.money.silver = value; - } - else if (key == "copper") { - current_handin.money.copper = value; - } - } - } + // Handle money and any other key-value pairs + if (key == "platinum") { current_handin.money.platinum = value; } + else if (key == "gold") { current_handin.money.gold = value; } + else if (key == "silver") { current_handin.money.silver = value; } + else if (key == "copper") { current_handin.money.copper = value; } } } @@ -12948,7 +12932,7 @@ bool Client::CheckHandin( if (!m_handin_started) { m_handin_started = true; m_hand_in = h; - // save original items for later + // save original items for logging m_hand_in.original_items = m_hand_in.items; m_hand_in.original_money = m_hand_in.money; m_hand_in.npc = n; @@ -12963,45 +12947,61 @@ bool Client::CheckHandin( } if (met_requirement) { - // remove items in h from m_handin.items - for (const auto &h_item: h.items) { - for (const auto &r_item: r.items) { - if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { - m_hand_in.items.erase( - std::remove_if( - m_hand_in.items.begin(), - m_hand_in.items.end(), - [&](const HandinEntry &i) { - bool removed = i.item_id == h_item.item_id && i.count == h_item.count; - - if (removed) { - LogTradingDetail( - "Hand-in success, removing item [{}] count [{}] from m_handin.items", - i.item_id, - i.count - ); - } - - return removed; + for (const auto &h_item : h.items) { + auto it = std::find_if(r.items.begin(), r.items.end(), [&](const auto &r_item) { + return h_item.item_id == r_item.item_id && h_item.count == r_item.count; + }); + + if (it != r.items.end()) { + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](const HandinEntry &i) { + bool removed = i.item_id == h_item.item_id && i.count == h_item.count; + if (removed) { + LogTradingDetail( + "Hand-in success, removing item [{}] count [{}] from m_handin.items", + i.item_id, i.count + ); } - ), - m_hand_in.items.end() - ); - } + return removed; + } + ), + m_hand_in.items.end() + ); } } // print remainder hand-in bucket LogTradingDetail("---"); LogTradingDetail("Hand-in | Remainder of hand-in bucket"); - LogTradingDetail("Items [{}] money p [{}] g [{}] s [{}] c [{}]", m_hand_in.items.size(), m_hand_in.money.platinum, m_hand_in.money.gold, m_hand_in.money.silver, m_hand_in.money.copper); + LogTradingDetail( + "Items [{}] money p [{}] g [{}] s [{}] c [{}]", + m_hand_in.items.size(), + m_hand_in.money.platinum, + m_hand_in.money.gold, + m_hand_in.money.silver, + m_hand_in.money.copper + ); for (const auto &i: m_hand_in.items) { - LogTradingDetail("Hand-in success, item [{}] ({}) count [{}]", i.item->GetItem()->Name, i.item_id, i.count); + LogTradingDetail( + "Hand-in success, item [{}] ({}) count [{}]", + i.item->GetItem()->Name, + i.item_id, + i.count + ); } // decrement successful hand-in money from current hand-in bucket if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { - LogTradingDetail("Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + LogTradingDetail( + "Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); m_hand_in.money.platinum -= h.money.platinum; m_hand_in.money.gold -= h.money.gold; m_hand_in.money.silver -= h.money.silver; @@ -13026,7 +13026,7 @@ void Client::ReturnHandinItems() PlayerEvent::HandinMoney handin_money{}; std::vector return_items; PlayerEvent::HandinMoney return_money{}; - for (auto i : m_hand_in.original_items) { + for (const auto& i : m_hand_in.original_items) { if (i.item && i.item->GetItem()) { handin_items.emplace_back( PlayerEvent::HandinEntry{ @@ -13053,7 +13053,7 @@ void Client::ReturnHandinItems() } bool return_handin = false; - for (auto i: m_hand_in.items) { + for (const auto& i: m_hand_in.items) { if (i.item && i.item->GetItem()) { return_items.emplace_back( PlayerEvent::HandinEntry{ From 409efefe42343e059e87b4e76e2ec2d067f057af Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 20 Oct 2024 19:00:19 -0500 Subject: [PATCH 28/44] Create NpcHandin log category, improve logging messages --- common/eqemu_logsys.cpp | 2 + common/eqemu_logsys.h | 4 +- common/eqemu_logsys_log_aliases.h | 11 ++- zone/client.cpp | 107 +++++++++++++++++++++++------- zone/lua_client.cpp | 6 +- zone/perl_client.cpp | 6 +- zone/trading.cpp | 6 +- 7 files changed, 107 insertions(+), 35 deletions(-) diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index c4c14f82f5..f502d79518 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -102,6 +102,8 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults() log_settings[Logs::QuestErrors].log_to_console = static_cast(Logs::General); log_settings[Logs::EqTime].log_to_console = static_cast(Logs::General); log_settings[Logs::EqTime].log_to_gmsay = static_cast(Logs::General); + log_settings[Logs::NpcHandin].log_to_console = static_cast(Logs::General); + log_settings[Logs::NpcHandin].log_to_gmsay = static_cast(Logs::General); /** * RFC 5424 diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 8bf474f349..fb64b7d4e6 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -142,6 +142,7 @@ namespace Logs { EqTime, Corpses, XTargets, + NpcHandin, MaxCategoryID /* Don't Remove this */ }; @@ -242,7 +243,8 @@ namespace Logs { "Zoning", "EqTime", "Corpses", - "XTargets" + "XTargets", + "NpcHandin" }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index 10c9cd98a2..f34ddf4a71 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -361,7 +361,6 @@ OutF(LogSys, Logs::Detail, Logs::PacketServerClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) - #define LogLoginserver(message, ...) do {\ if (LogSys.IsLogEnabled(Logs::General, Logs::Loginserver))\ OutF(LogSys, Logs::General, Logs::Loginserver, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ @@ -844,6 +843,16 @@ OutF(LogSys, Logs::Detail, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogNpcHandin(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::NpcHandin))\ + OutF(LogSys, Logs::General, Logs::NpcHandin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogNpcHandinDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::NpcHandin))\ + OutF(LogSys, Logs::Detail, Logs::NpcHandin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ if (LogSys.IsLogEnabled(debug_level, log_category))\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ diff --git a/zone/client.cpp b/zone/client.cpp index e48e9db4e4..1eb6d5c9c1 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12860,7 +12860,7 @@ bool Client::CheckHandin( for (const auto &[data_map, current_handin]: datasets) { std::string current_dataset = ¤t_handin == &h ? set_hand_in : set_required; for (const auto &[key, value]: data_map) { - LogTradingDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value); + LogNpcHandinDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value); // Handle items if (Strings::IsNumber(key)) { @@ -12939,14 +12939,70 @@ bool Client::CheckHandin( } // print current hand-in bucket - LogTradingDetail("---"); - LogTradingDetail("Hand-in | Printing current hand-in bucket"); - LogTradingDetail("--- Items [{}] money p [{}] g [{}] s [{}] c [{}]", h.items.size(), h.money.platinum, h.money.gold, h.money.silver, h.money.copper); + LogNpcHandin( + " > Before processing hand-in | client [{}] met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + GetCleanName(), + met_requirement, + h.items.size(), + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + std::vector log_entries = {}; + int item_count = 1; for (const auto &i: h.items) { - LogTradingDetail("--- Hand-in | item [{}] ({}) count [{}]", i.item->GetItem()->Name, i.item_id, i.count); + log_entries.emplace_back( + fmt::format( + "item{} [{}] ({}) count [{}]", + item_count, + i.item->GetItem()->Name, + i.item_id, + i.count + ) + ); + item_count++; } + LogNpcHandin( + " >> Handed Items | Item(s) ({}) {} platinum [{}] gold [{}] silver [{}] copper [{}]", + h.items.size(), + Strings::Join(log_entries, ", "), + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + + log_entries.clear(); + item_count = 1; + for (const auto &i: r.items) { + auto item = database.GetItem(Strings::ToUnsignedInt(i.item_id)); + + log_entries.emplace_back( + fmt::format( + "item{} [{}] ({}) count [{}]", + item_count, + item ? item->Name : "Unknown", + i.item_id, + i.count + ) + ); + item_count++; + } + + LogNpcHandin( + " >> Required Items | Item(s) ({}) {} platinum [{}] gold [{}] silver [{}] copper [{}]", + r.items.size(), + Strings::Join(log_entries, ", "), + r.money.platinum, + r.money.gold, + r.money.silver, + r.money.copper + ); + if (met_requirement) { + log_entries = {}; for (const auto &h_item : h.items) { auto it = std::find_if(r.items.begin(), r.items.end(), [&](const auto &r_item) { return h_item.item_id == r_item.item_id && h_item.count == r_item.count; @@ -12960,9 +13016,13 @@ bool Client::CheckHandin( [&](const HandinEntry &i) { bool removed = i.item_id == h_item.item_id && i.count == h_item.count; if (removed) { - LogTradingDetail( - "Hand-in success, removing item [{}] count [{}] from m_handin.items", - i.item_id, i.count + log_entries.emplace_back( + fmt::format( + " >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", + i.item->GetItem()->Name, + i.item_id, + i.count + ) ); } return removed; @@ -12973,11 +13033,17 @@ bool Client::CheckHandin( } } - // print remainder hand-in bucket - LogTradingDetail("---"); - LogTradingDetail("Hand-in | Remainder of hand-in bucket"); - LogTradingDetail( - "Items [{}] money p [{}] g [{}] s [{}] c [{}]", + // log successful hand-in items + if (!log_entries.empty()) { + for (const auto& log : log_entries) { + LogNpcHandin("{}", log); + } + } + + LogNpcHandin( + " > End of hand-in | client [{}] met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + GetCleanName(), + met_requirement, m_hand_in.items.size(), m_hand_in.money.platinum, m_hand_in.money.gold, @@ -12985,7 +13051,7 @@ bool Client::CheckHandin( m_hand_in.money.copper ); for (const auto &i: m_hand_in.items) { - LogTradingDetail( + LogNpcHandin( "Hand-in success, item [{}] ({}) count [{}]", i.item->GetItem()->Name, i.item_id, @@ -12995,7 +13061,7 @@ bool Client::CheckHandin( // decrement successful hand-in money from current hand-in bucket if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { - LogTradingDetail( + LogNpcHandin( "Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", h.money.platinum, h.money.gold, @@ -13009,13 +13075,6 @@ bool Client::CheckHandin( } } - LogTradingDetail( - "CheckHandin [{}] met_requirement [{}] npc [{}]", - GetCleanName(), - met_requirement, - n->GetCleanName() - ); - return met_requirement; } @@ -13066,7 +13125,7 @@ void Client::ReturnHandinItems() ); PushItemOnCursor(*i.item, true); - LogTradingDetail("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); + LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); return_handin = true; } } @@ -13085,7 +13144,7 @@ void Client::ReturnHandinItems() true ); return_handin = true; - LogTradingDetail( + LogNpcHandin( "Hand-in failed, returning money p [{}] g [{}] s [{}] c [{}]", m_hand_in.money.platinum, m_hand_in.money.gold, diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index a0fe3083ea..79b2e02a3f 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3471,7 +3471,7 @@ bool Lua_Client::LuaCheckHandin( if (!key.empty()) { handin_map[key] = luabind::object_cast(handin_table[i.key()]); - LogTradingDetail("Handin key [{}] value [{}]", key, handin_map[key]); + LogNpcHandinDetail("Handin key [{}] value [{}]", key, handin_map[key]); } } @@ -3489,7 +3489,7 @@ bool Lua_Client::LuaCheckHandin( if (!key.empty()) { required_map[key] = luabind::object_cast(required_table[i.key()]); - LogTradingDetail("Required key [{}] value [{}]", key, required_map[key]); + LogNpcHandinDetail("Required key [{}] value [{}]", key, required_map[key]); } } @@ -3497,7 +3497,7 @@ bool Lua_Client::LuaCheckHandin( auto item = luabind::object_cast(items_table[i.key()]); if (item && item.GetItem()) { - LogTradingDetail( + LogNpcHandinDetail( "Item instance [{}] ({}) UUID ({}) added to handin list", item.GetName(), item.GetID(), diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 5f655bc116..c56e616a8a 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3236,7 +3236,7 @@ bool Perl_Client_CheckHandin( continue; } - LogTradingDetail("Handin key [{}] value [{}]", e.first, handin.at(e.first).c_str()); + LogNpcHandinDetail("Handin key [{}] value [{}]", e.first, handin.at(e.first).c_str()); const uint32 count = static_cast(handin.at(e.first)); handin_map[e.first] = count; @@ -3251,7 +3251,7 @@ bool Perl_Client_CheckHandin( continue; } - LogTradingDetail("Required key [{}] value [{}]", e.first, required.at(e.first).c_str()); + LogNpcHandinDetail("Required key [{}] value [{}]", e.first, required.at(e.first).c_str()); const uint32 count = static_cast(required.at(e.first)); required_map[e.first] = count; @@ -3265,7 +3265,7 @@ bool Perl_Client_CheckHandin( items.emplace_back(i); - LogTradingDetail( + LogNpcHandinDetail( "Item instance [{}] ({}) UUID ({}) added to handin list", i->GetItem()->Name, i->GetItem()->ID, diff --git a/zone/trading.cpp b/zone/trading.cpp index d6895c6c3b..4fed8d6f83 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -866,7 +866,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) { parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list); - LogTradingDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID()); + LogNpcHandinDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID()); } // this is a catch-all return for items that weren't consumed by the EVENT_TRADE subroutine @@ -874,7 +874,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st // we can't double fire the ReturnHandinItems() event, so we need to check if it's already been processed from EVENT_TRADE if (!m_has_processed_handin_return) { if (!m_handin_started) { - LogTradingDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID()); + LogNpcHandinDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID()); std::map handin = { {"copper", trade->cp}, {"silver", trade->sp}, @@ -896,7 +896,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st } ReturnHandinItems(); - LogTradingDetail("ReturnHandinItems() called for NPC [{}]", tradingWith->GetNPCTypeID()); + LogNpcHandin("ReturnHandinItems() called for NPC [{}]", tradingWith->GetNPCTypeID()); } ResetHandin(); From 201944eefb775877fcfd2dbe94bb1e82a8d4a363 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 20 Oct 2024 21:16:45 -0500 Subject: [PATCH 29/44] Implement tome hand in source side --- common/spdat.cpp | 43 ++++++++++++++++++++++++++++++++++++++++++- common/spdat.h | 2 ++ zone/client.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ zone/mob.cpp | 24 ++++++++++++++++++++++++ zone/mob.h | 2 ++ zone/npc.cpp | 41 ++++++++++++++++++++++++++++++++++++++++- zone/npc.h | 11 ++++++++--- zone/questmgr.cpp | 45 +-------------------------------------------- 8 files changed, 161 insertions(+), 49 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 3acac1c8b9..5fe9332664 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2367,7 +2367,7 @@ bool IsAegolismSpell(uint16 spell_id) { bool AegolismStackingIsSymbolSpell(uint16 spell_id) { - + /* This is hardcoded to be specific to the type of HP buffs that are removed if a mob has an Aegolism buff. */ @@ -2430,3 +2430,44 @@ bool AegolismStackingIsArmorClassSpell(uint16 spell_id) { return 0; } + +bool IsDisciplineTome(const EQ::ItemData* item) +{ + if (!item->IsClassCommon() || item->ItemType != EQ::item::ItemTypeSpell) { + return false; + } + + //Need a way to determine the difference between a spell and a tome + //so they cant turn in a spell and get it as a discipline + //this is kinda a hack: + + const std::string item_name = item->Name; + + if ( + !Strings::BeginsWith(item_name, "Tome of ") && + !Strings::BeginsWith(item_name, "Skill: ") + ) { + return false; + } + + //we know for sure none of the int casters get disciplines + uint32 class_bit = 0; + class_bit |= 1 << (Class::Wizard - 1); + class_bit |= 1 << (Class::Enchanter - 1); + class_bit |= 1 << (Class::Magician - 1); + class_bit |= 1 << (Class::Necromancer - 1); + if (item->Classes & class_bit) { + return false; + } + + const auto& spell_id = static_cast(item->Scroll.Effect); + if (!IsValidSpell(spell_id)) { + return false; + } + + if (!IsDiscipline(spell_id)) { + return false; + } + + return true; +} diff --git a/common/spdat.h b/common/spdat.h index d597c79faf..0b29d26887 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -20,6 +20,7 @@ #include "classes.h" #include "skills.h" +#include "item_data.h" #define SPELL_UNKNOWN 0xFFFF #define POISON_PROC 0xFFFE @@ -1628,5 +1629,6 @@ bool IsCastRestrictedSpell(uint16 spell_id); bool IsAegolismSpell(uint16 spell_id); bool AegolismStackingIsSymbolSpell(uint16 spell_id); bool AegolismStackingIsArmorClassSpell(uint16 spell_id); +bool IsDisciplineTome(const EQ::ItemData* item); #endif diff --git a/zone/client.cpp b/zone/client.cpp index 1eb6d5c9c1..13a447a544 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12928,6 +12928,10 @@ bool Client::CheckHandin( } } + if (n->IsMultiQuest()) { + // + } + // in-case we trigger CheckHand-in multiple times, only set these once if (!m_handin_started) { m_handin_started = true; @@ -12938,6 +12942,44 @@ bool Client::CheckHandin( m_hand_in.npc = n; } + // check if npc is guildmaster + if (n->IsGuildmaster()) { + for (const auto &h_item: m_hand_in.items) { + if (!h_item.item) { + continue; + } + + if (!IsDisciplineTome(h_item.item->GetItem())) { + continue; + } + + if (n->IsGuildmasterForClient(this)) { + TrainDiscipline(h_item.item->GetID()); + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](const HandinEntry &i) { + bool removed = i.item_id == h_item.item_id; + if (removed) { + LogNpcHandin( + "Hand-in success, removing discipline tome [{}] from hand-in bucket", + i.item_id + ); + } + return removed; + } + ), + m_hand_in.items.end() + ); + } else { + n->Say("You are not a member of my guild. I will not train you!"); + met_requirement = false; + break; + } + } + } + // print current hand-in bucket LogNpcHandin( " > Before processing hand-in | client [{}] met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", diff --git a/zone/mob.cpp b/zone/mob.cpp index a1d9b5d380..217affa50b 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8625,3 +8625,27 @@ std::unordered_map &Mob::GetCloseMobList(float distance) { return entity_list.GetCloseMobList(this, distance); } + +bool Mob::IsGuildmaster() const { + switch (GetClass()) { + case Class::WarriorGM: + case Class::ClericGM: + case Class::PaladinGM: + case Class::RangerGM: + case Class::ShadowKnightGM: + case Class::DruidGM: + case Class::MonkGM: + case Class::BardGM: + case Class::RogueGM: + case Class::ShamanGM: + case Class::NecromancerGM: + case Class::WizardGM: + case Class::MagicianGM: + case Class::EnchanterGM: + case Class::BeastlordGM: + case Class::BerserkerGM: + return true; + default: + return false; + } +} diff --git a/zone/mob.h b/zone/mob.h index 8914534cb7..5ac91ffb15 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1492,6 +1492,8 @@ class Mob : public Entity { std::unordered_map &GetCloseMobList(float distance = 0.0f); void CheckScanCloseMobsMovingTimer(); + bool IsGuildmaster() const; + protected: void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None); static uint16 GetProcID(uint16 spell_id, uint8 effect_index); diff --git a/zone/npc.cpp b/zone/npc.cpp index df32375104..da3ca0c60f 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -226,7 +226,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi ATK = npc_type_data->ATK; heroic_strikethrough = npc_type_data->heroic_strikethrough; keeps_sold_items = npc_type_data->keeps_sold_items; - multiquest_enabled = npc_type_data->multiquest_enabled; + m_multiquest_enabled = npc_type_data->multiquest_enabled; // used for when switch back to charm default_ac = npc_type_data->AC; @@ -4256,3 +4256,42 @@ bool NPC::CanPetTakeItem(const EQ::ItemInstance *inst) return true; } + +std::vector NPC::GetMultiQuestItems() const +{ + return m_multiquest_items; +} + +void NPC::SetMultiQuestItems(std::vector multiquest_items) +{ + m_multiquest_items = multiquest_items; +} + +bool NPC::IsGuildmasterForClient(Client *c) { + std::map guildmaster_map = { + { Class::Warrior, Class::WarriorGM }, + { Class::Cleric, Class::ClericGM }, + { Class::Paladin, Class::PaladinGM }, + { Class::Ranger, Class::RangerGM }, + { Class::ShadowKnight, Class::ShadowKnightGM }, + { Class::Druid, Class::DruidGM }, + { Class::Monk, Class::MonkGM }, + { Class::Bard, Class::BardGM }, + { Class::Rogue, Class::RogueGM }, + { Class::Shaman, Class::ShamanGM }, + { Class::Necromancer, Class::NecromancerGM }, + { Class::Wizard, Class::WizardGM }, + { Class::Magician, Class::MagicianGM }, + { Class::Enchanter, Class::EnchanterGM }, + { Class::Beastlord, Class::BeastlordGM }, + { Class::Berserker, Class::BerserkerGM }, + }; + + if (guildmaster_map.find(c->GetClass()) != guildmaster_map.end()) { + if (guildmaster_map[c->GetClass()] == GetClass()) { + return true; + } + } + + return false; +} diff --git a/zone/npc.h b/zone/npc.h index 33510772fa..d320da9dac 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -561,8 +561,11 @@ class NPC : public Mob bool CanPetTakeItem(const EQ::ItemInstance *inst); - bool IsMultiQuest() { return multiquest_enabled; } - void SetMultiQuest(bool b) { multiquest_enabled = b; } + bool IsMultiQuest() { return m_multiquest_enabled; } + void SetMultiQuest(bool b) { m_multiquest_enabled = b; } + std::vector GetMultiQuestItems() const; + void SetMultiQuestItems(std::vector multiquest_items); + bool IsGuildmasterForClient(Client *c); protected: @@ -705,7 +708,9 @@ class NPC : public Mob bool raid_target; bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup - bool multiquest_enabled; + bool m_multiquest_enabled; + + std::vector m_multiquest_items; private: uint32 m_loottable_id; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index cf77e503e0..ba70cfb206 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1262,50 +1262,7 @@ bool QuestManager::isdisctome(uint32 item_id) { return false; } - if (!item->IsClassCommon() || item->ItemType != EQ::item::ItemTypeSpell) { - return false; - } - - //Need a way to determine the difference between a spell and a tome - //so they cant turn in a spell and get it as a discipline - //this is kinda a hack: - - const std::string item_name = item->Name; - - if ( - !Strings::BeginsWith(item_name, "Tome of ") && - !Strings::BeginsWith(item_name, "Skill: ") - ) { - return false; - } - - //we know for sure none of the int casters get disciplines - uint32 class_bit = 0; - class_bit |= 1 << (Class::Wizard - 1); - class_bit |= 1 << (Class::Enchanter - 1); - class_bit |= 1 << (Class::Magician - 1); - class_bit |= 1 << (Class::Necromancer - 1); - if (item->Classes & class_bit) { - return false; - } - - const auto& spell_id = static_cast(item->Scroll.Effect); - if (!IsValidSpell(spell_id)) { - return false; - } - - //we know for sure none of the int casters get disciplines - const auto& spell = spells[spell_id]; - if( - spell.classes[Class::Wizard - 1] != 255 && - spell.classes[Class::Enchanter - 1] != 255 && - spell.classes[Class::Magician - 1] != 255 && - spell.classes[Class::Necromancer - 1] != 255 - ) { - return false; - } - - return true; + return IsDisciplineTome(item); } std::string QuestManager::getracename(uint16 race_id) { From ae9daaf0c8b2cfeb86458e87a6f07bddef01806d Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Sun, 20 Oct 2024 22:34:46 -0400 Subject: [PATCH 30/44] Remove casters from guildmaster_map --- zone/npc.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index da3ca0c60f..5f2bc2fe2f 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4269,22 +4269,18 @@ void NPC::SetMultiQuestItems(std::vector multiquest_it bool NPC::IsGuildmasterForClient(Client *c) { std::map guildmaster_map = { - { Class::Warrior, Class::WarriorGM }, - { Class::Cleric, Class::ClericGM }, - { Class::Paladin, Class::PaladinGM }, - { Class::Ranger, Class::RangerGM }, + { Class::Warrior, Class::WarriorGM }, + { Class::Cleric, Class::ClericGM }, + { Class::Paladin, Class::PaladinGM }, + { Class::Ranger, Class::RangerGM }, { Class::ShadowKnight, Class::ShadowKnightGM }, - { Class::Druid, Class::DruidGM }, - { Class::Monk, Class::MonkGM }, - { Class::Bard, Class::BardGM }, - { Class::Rogue, Class::RogueGM }, - { Class::Shaman, Class::ShamanGM }, - { Class::Necromancer, Class::NecromancerGM }, - { Class::Wizard, Class::WizardGM }, - { Class::Magician, Class::MagicianGM }, - { Class::Enchanter, Class::EnchanterGM }, - { Class::Beastlord, Class::BeastlordGM }, - { Class::Berserker, Class::BerserkerGM }, + { Class::Druid, Class::DruidGM }, + { Class::Monk, Class::MonkGM }, + { Class::Bard, Class::BardGM }, + { Class::Rogue, Class::RogueGM }, + { Class::Shaman, Class::ShamanGM }, + { Class::Beastlord, Class::BeastlordGM }, + { Class::Berserker, Class::BerserkerGM }, }; if (guildmaster_map.find(c->GetClass()) != guildmaster_map.end()) { From 8e8608cd6dc9d8325f429911b538c712cbf2c8bb Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 21 Oct 2024 16:17:34 -0500 Subject: [PATCH 31/44] Revert "Remove casters from guildmaster_map" This reverts commit ae9daaf0c8b2cfeb86458e87a6f07bddef01806d. --- zone/npc.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index 5f2bc2fe2f..da3ca0c60f 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4269,18 +4269,22 @@ void NPC::SetMultiQuestItems(std::vector multiquest_it bool NPC::IsGuildmasterForClient(Client *c) { std::map guildmaster_map = { - { Class::Warrior, Class::WarriorGM }, - { Class::Cleric, Class::ClericGM }, - { Class::Paladin, Class::PaladinGM }, - { Class::Ranger, Class::RangerGM }, + { Class::Warrior, Class::WarriorGM }, + { Class::Cleric, Class::ClericGM }, + { Class::Paladin, Class::PaladinGM }, + { Class::Ranger, Class::RangerGM }, { Class::ShadowKnight, Class::ShadowKnightGM }, - { Class::Druid, Class::DruidGM }, - { Class::Monk, Class::MonkGM }, - { Class::Bard, Class::BardGM }, - { Class::Rogue, Class::RogueGM }, - { Class::Shaman, Class::ShamanGM }, - { Class::Beastlord, Class::BeastlordGM }, - { Class::Berserker, Class::BerserkerGM }, + { Class::Druid, Class::DruidGM }, + { Class::Monk, Class::MonkGM }, + { Class::Bard, Class::BardGM }, + { Class::Rogue, Class::RogueGM }, + { Class::Shaman, Class::ShamanGM }, + { Class::Necromancer, Class::NecromancerGM }, + { Class::Wizard, Class::WizardGM }, + { Class::Magician, Class::MagicianGM }, + { Class::Enchanter, Class::EnchanterGM }, + { Class::Beastlord, Class::BeastlordGM }, + { Class::Berserker, Class::BerserkerGM }, }; if (guildmaster_map.find(c->GetClass()) != guildmaster_map.end()) { From 361cf5ec3c2a68e2af2ea44b17d560ae3505b062 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 21 Oct 2024 16:19:53 -0500 Subject: [PATCH 32/44] re-introduce check into IsDisciplineTome --- common/spdat.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/spdat.cpp b/common/spdat.cpp index 5fe9332664..59a22aa4da 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2469,5 +2469,15 @@ bool IsDisciplineTome(const EQ::ItemData* item) return false; } + const auto &spell = spells[spell_id]; + if ( + spell.classes[Class::Wizard - 1] != 255 && + spell.classes[Class::Enchanter - 1] != 255 && + spell.classes[Class::Magician - 1] != 255 && + spell.classes[Class::Necromancer - 1] != 255 + ) { + return false; + } + return true; } From 34163c5bb90c67346771e70bd2ea69e5ca51629d Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 22 Oct 2024 01:02:36 -0500 Subject: [PATCH 33/44] Tweaks --- zone/client.cpp | 2 +- zone/lua_npc.cpp | 12 ++++++------ zone/lua_npc.h | 4 ++-- zone/npc.h | 4 ++-- zone/perl_npc.cpp | 12 ++++++------ 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 13a447a544..21f8304e5c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12928,7 +12928,7 @@ bool Client::CheckHandin( } } - if (n->IsMultiQuest()) { + if (n->IsMultiQuestEnabled()) { // } diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index 40bc2a4606..47ffdf5d82 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -837,16 +837,16 @@ void Lua_NPC::DescribeSpecialAbilities(Lua_Client c) self->DescribeSpecialAbilities(c); } -bool Lua_NPC::IsMultiQuest() +bool Lua_NPC::IsMultiQuestEnabled() { Lua_Safe_Call_Bool(); - return self->IsMultiQuest(); + return self->IsMultiQuestEnabled(); } -void Lua_NPC::SetMultiQuest(bool b) +void Lua_NPC::MultiQuestEnable() { Lua_Safe_Call_Void(); - self->SetMultiQuest(b); + self->MultiQuestEnable(); } luabind::scope lua_register_npc() { @@ -942,7 +942,7 @@ luabind::scope lua_register_npc() { .def("IsAnimal", (bool(Lua_NPC::*)(void))&Lua_NPC::IsAnimal) .def("IsGuarding", (bool(Lua_NPC::*)(void))&Lua_NPC::IsGuarding) .def("IsLDoNLocked", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNLocked) - .def("IsMultiQuest", (bool(Lua_NPC::*)(void))&Lua_NPC::IsMultiQuest) + .def("IsMultiQuestEnabled", (bool(Lua_NPC::*)(void))&Lua_NPC::IsMultiQuestEnabled) .def("IsLDoNTrapped", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapped) .def("IsLDoNTrapDetected", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapDetected) .def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist) @@ -954,6 +954,7 @@ luabind::scope lua_register_npc() { .def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop) .def("ModifyNPCStat", (void(Lua_NPC::*)(std::string,std::string))&Lua_NPC::ModifyNPCStat) .def("MoveTo", (void(Lua_NPC::*)(float,float,float,float,bool))&Lua_NPC::MoveTo) + .def("MultiQuestEnable", &Lua_NPC::MultiQuestEnable) .def("NextGuardPosition", (void(Lua_NPC::*)(void))&Lua_NPC::NextGuardPosition) .def("PauseWandering", (void(Lua_NPC::*)(int))&Lua_NPC::PauseWandering) .def("PickPocket", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::PickPocket) @@ -982,7 +983,6 @@ luabind::scope lua_register_npc() { .def("SetGold", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetGold) .def("SetGrid", (void(Lua_NPC::*)(int))&Lua_NPC::SetGrid) .def("SetKeepsSoldItems", (void(Lua_NPC::*)(bool))&Lua_NPC::SetKeepsSoldItems) - .def("SetMultiQuest", (void(Lua_NPC::*)(bool))&Lua_NPC::SetMultiQuest) .def("SetLDoNLocked", (void(Lua_NPC::*)(bool))&Lua_NPC::SetLDoNLocked) .def("SetLDoNLockedSkill", (void(Lua_NPC::*)(uint16))&Lua_NPC::SetLDoNLockedSkill) .def("SetLDoNTrapped", (void(Lua_NPC::*)(bool))&Lua_NPC::SetLDoNTrapped) diff --git a/zone/lua_npc.h b/zone/lua_npc.h index 84af0a8a97..2ef457859b 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -186,8 +186,8 @@ class Lua_NPC : public Lua_Mob void SetNPCAggro(bool in_npc_aggro); uint32 GetNPCSpellsEffectsID(); void DescribeSpecialAbilities(Lua_Client c); - bool IsMultiQuest(); - void SetMultiQuest(bool b); + bool IsMultiQuestEnabled(); + void MultiQuestEnable(); }; #endif diff --git a/zone/npc.h b/zone/npc.h index d320da9dac..9505bd30ae 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -561,8 +561,8 @@ class NPC : public Mob bool CanPetTakeItem(const EQ::ItemInstance *inst); - bool IsMultiQuest() { return m_multiquest_enabled; } - void SetMultiQuest(bool b) { m_multiquest_enabled = b; } + bool IsMultiQuestEnabled() { return m_multiquest_enabled; } + void MultiQuestEnable() { m_multiquest_enabled = true; } std::vector GetMultiQuestItems() const; void SetMultiQuestItems(std::vector multiquest_items); bool IsGuildmasterForClient(Client *c); diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index 690ac4f177..0a480f5e8f 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -796,14 +796,14 @@ void Perl_NPC_DescribeSpecialAbilities(NPC* self, Client* c) self->DescribeSpecialAbilities(c); } -bool Perl_NPC_IsMultiQuest(NPC* self) +bool Perl_NPC_IsMultiQuestEnabled(NPC* self) { - return self->IsMultiQuest(); + return self->IsMultiQuestEnabled(); } -void Perl_NPC_SetMultiQuest(NPC* self, bool b) +void Perl_NPC_MultiQuestEnable(NPC* self) { - self->SetMultiQuest(b); + self->MultiQuestEnable(); } void perl_register_npc() @@ -903,7 +903,7 @@ void perl_register_npc() package.add("IsLDoNLocked", &Perl_NPC_IsLDoNLocked); package.add("IsLDoNTrapped", &Perl_NPC_IsLDoNTrapped); package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected); - package.add("IsMultiQuest", &Perl_NPC_IsMultiQuest); + package.add("IsMultiQuestEnabled", &Perl_NPC_IsMultiQuestEnabled); package.add("IsOnHatelist", &Perl_NPC_IsOnHatelist); package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget); package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn); @@ -946,7 +946,7 @@ void perl_register_npc() package.add("SetLDoNTrapDetected", &Perl_NPC_SetLDoNTrapDetected); package.add("SetLDoNTrapSpellID", &Perl_NPC_SetLDoNTrapSpellID); package.add("SetLDoNTrapType", &Perl_NPC_SetLDoNTrapType); - package.add("SetMultiQuest", &Perl_NPC_SetMultiQuest); + package.add("MultiQuestEnable", &Perl_NPC_MultiQuestEnable); package.add("SetNPCAggro", &Perl_NPC_SetNPCAggro); package.add("SetGold", &Perl_NPC_SetGold); package.add("SetGrid", &Perl_NPC_SetGrid); From 0a621bf060552a925457270932d2751e56f19bf2 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 22 Oct 2024 03:45:12 -0500 Subject: [PATCH 34/44] multi-quest working --- zone/client.cpp | 192 +++++++++++++++++++++++++++++------------------- zone/client.h | 7 +- zone/npc.cpp | 10 --- zone/npc.h | 6 +- 4 files changed, 121 insertions(+), 94 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 21f8304e5c..38c3b5c49d 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12844,6 +12844,13 @@ bool Client::CheckHandin( auto h = Handin{}; auto r = Handin{}; + std::string log_handin_prefix = fmt::format("[{}] -> [{}]", GetCleanName(), n->GetCleanName()); + + // if the npc is a multi-quest npc, we want to re-use our previously set hand-in bucket + if (!m_handin_started && m_hand_in.npc && m_hand_in.npc->IsMultiQuestEnabled()) { + h = m_hand_in; + } + std::vector&, Handin&>> datasets = {}; // if we've already started the hand-in process, we don't want to re-process the hand-in data @@ -12890,7 +12897,7 @@ bool Client::CheckHandin( HandinEntry{ .item_id = std::to_string(i->GetItem()->ID), .count = std::max(static_cast(i->GetCharges()), static_cast(1)), - .item = i + .item = i->Clone() } ); } @@ -12899,7 +12906,7 @@ bool Client::CheckHandin( // compare hand-in to required, the item_id can be in any slot bool met_requirement = true; if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { - for (const auto &h_item: h.items) { + for (auto &h_item: h.items) { bool found = false; for (const auto &r_item: r.items) { if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { @@ -12918,6 +12925,28 @@ bool Client::CheckHandin( met_requirement = false; } + // multi-quest + if (n && n->IsMultiQuestEnabled()) { + for (auto &h_item: h.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; + } + } + } + } + + for (auto &h_item: h.items) { + LogNpcHandinDetail( + "{} Hand-in item [{}] ({}) count [{}] is_multiquest_item [{}]", + log_handin_prefix, + h_item.item->GetItem()->Name, + h_item.item_id, + h_item.count, + h_item.is_multiquest_item + ); + } + // compare hand-in money to required if (met_requirement) { if (h.money.platinum != r.money.platinum || @@ -12928,10 +12957,6 @@ bool Client::CheckHandin( } } - if (n->IsMultiQuestEnabled()) { - // - } - // in-case we trigger CheckHand-in multiple times, only set these once if (!m_handin_started) { m_handin_started = true; @@ -12963,7 +12988,8 @@ bool Client::CheckHandin( bool removed = i.item_id == h_item.item_id; if (removed) { LogNpcHandin( - "Hand-in success, removing discipline tome [{}] from hand-in bucket", + "{} Hand-in success, removing discipline tome [{}] from hand-in bucket", + log_handin_prefix, i.item_id ); } @@ -12982,8 +13008,8 @@ bool Client::CheckHandin( // print current hand-in bucket LogNpcHandin( - " > Before processing hand-in | client [{}] met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", - GetCleanName(), + "{} > Before processing hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, met_requirement, h.items.size(), h.money.platinum, @@ -12991,60 +13017,60 @@ bool Client::CheckHandin( h.money.silver, h.money.copper ); - std::vector log_entries = {}; - int item_count = 1; - for (const auto &i: h.items) { - log_entries.emplace_back( - fmt::format( - "item{} [{}] ({}) count [{}]", - item_count, - i.item->GetItem()->Name, - i.item_id, - i.count - ) - ); - item_count++; - } LogNpcHandin( - " >> Handed Items | Item(s) ({}) {} platinum [{}] gold [{}] silver [{}] copper [{}]", + "{} >> Handed Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, h.items.size(), - Strings::Join(log_entries, ", "), h.money.platinum, h.money.gold, h.money.silver, h.money.copper ); - log_entries.clear(); - item_count = 1; - for (const auto &i: r.items) { - auto item = database.GetItem(Strings::ToUnsignedInt(i.item_id)); - - log_entries.emplace_back( - fmt::format( - "item{} [{}] ({}) count [{}]", - item_count, - item ? item->Name : "Unknown", - i.item_id, - i.count - ) + int item_count = 1; + for (const auto &i: h.items) { + LogNpcHandin( + "{} >>> item{} [{}] ({}) count [{}]", + log_handin_prefix, + item_count, + i.item->GetItem()->Name, + i.item_id, + i.count ); item_count++; } + + LogNpcHandin( - " >> Required Items | Item(s) ({}) {} platinum [{}] gold [{}] silver [{}] copper [{}]", + "{} >> Required Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, r.items.size(), - Strings::Join(log_entries, ", "), r.money.platinum, r.money.gold, r.money.silver, r.money.copper ); + item_count = 1; + for (const auto &i: r.items) { + auto item = database.GetItem(Strings::ToUnsignedInt(i.item_id)); + + LogNpcHandin( + "{} >>> item{} [{}] ({}) count [{}]", + log_handin_prefix, + item_count, + item ? item->Name : "Unknown", + i.item_id, + i.count + ); + + item_count++; + } + if (met_requirement) { - log_entries = {}; + std::vector log_entries = {}; for (const auto &h_item : h.items) { auto it = std::find_if(r.items.begin(), r.items.end(), [&](const auto &r_item) { return h_item.item_id == r_item.item_id && h_item.count == r_item.count; @@ -13060,7 +13086,8 @@ bool Client::CheckHandin( if (removed) { log_entries.emplace_back( fmt::format( - " >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", + "{} >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", + log_handin_prefix, i.item->GetItem()->Name, i.item_id, i.count @@ -13083,8 +13110,8 @@ bool Client::CheckHandin( } LogNpcHandin( - " > End of hand-in | client [{}] met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", - GetCleanName(), + "{} > End of hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, met_requirement, m_hand_in.items.size(), m_hand_in.money.platinum, @@ -13094,7 +13121,8 @@ bool Client::CheckHandin( ); for (const auto &i: m_hand_in.items) { LogNpcHandin( - "Hand-in success, item [{}] ({}) count [{}]", + "{} Hand-in success, item [{}] ({}) count [{}]", + log_handin_prefix, i.item->GetItem()->Name, i.item_id, i.count @@ -13104,7 +13132,8 @@ bool Client::CheckHandin( // decrement successful hand-in money from current hand-in bucket if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { LogNpcHandin( - "Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", + "{} Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", + log_handin_prefix, h.money.platinum, h.money.gold, h.money.silver, @@ -13153,24 +13182,36 @@ void Client::ReturnHandinItems() handin_money.platinum = m_hand_in.original_money.platinum; } - bool return_handin = false; - for (const auto& i: m_hand_in.items) { - if (i.item && i.item->GetItem()) { - 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)) - } - ); + bool returned_handin = false; + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](auto& i) { + if (i.item && i.item->GetItem() && !i.is_multiquest_item) { + 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)) + } + ); - PushItemOnCursor(*i.item, true); - LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); - return_handin = true; - } - } + PushItemOnCursor(*i.item, true); + LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); + + // Safely delete the item + safe_delete(i.item); + returned_handin = true; + return true; // Mark this item for removal + } + return false; + } + ), + m_hand_in.items.end() + ); // check if any money was handed in if (m_hand_in.money.platinum > 0 || @@ -13185,7 +13226,7 @@ void Client::ReturnHandinItems() m_hand_in.money.platinum, true ); - return_handin = true; + returned_handin = true; LogNpcHandin( "Hand-in failed, returning money p [{}] g [{}] s [{}] c [{}]", m_hand_in.money.platinum, @@ -13201,17 +13242,15 @@ void Client::ReturnHandinItems() return_money.platinum = m_hand_in.money.platinum; } - m_has_processed_handin_return = return_handin; + m_has_processed_handin_return = returned_handin; - if (return_handin) { - if (m_hand_in.npc) { - m_hand_in.npc->Say( - fmt::format( - "I have no need for this {}, you can have it back.", - GetCleanName() - ).c_str() - ); - } + if (returned_handin && m_hand_in.npc) { + m_hand_in.npc->Say( + fmt::format( + "I have no need for this {}, you can have it back.", + GetCleanName() + ).c_str() + ); } const bool handed_in_money = ( @@ -13241,6 +13280,7 @@ void Client::ResetHandin() { m_has_processed_handin_return = false; m_handin_started = false; - m_hand_in.original_money = {}; - m_hand_in = {}; + if (m_hand_in.npc && !m_hand_in.npc->IsMultiQuestEnabled()) { + m_hand_in = {}; + } } diff --git a/zone/client.h b/zone/client.h index ea751a3a5b..5ee18d6854 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1821,9 +1821,10 @@ class Client : public Mob // NPC Handin struct HandinEntry { - std::string item_id = "0"; - uint16 count = 0; - const EQ::ItemInstance *item = nullptr; + std::string item_id = "0"; + uint16 count = 0; + const EQ::ItemInstance *item = nullptr; + bool is_multiquest_item = false; }; struct HandinMoney { diff --git a/zone/npc.cpp b/zone/npc.cpp index da3ca0c60f..3c77a96478 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4257,16 +4257,6 @@ bool NPC::CanPetTakeItem(const EQ::ItemInstance *inst) return true; } -std::vector NPC::GetMultiQuestItems() const -{ - return m_multiquest_items; -} - -void NPC::SetMultiQuestItems(std::vector multiquest_items) -{ - m_multiquest_items = multiquest_items; -} - bool NPC::IsGuildmasterForClient(Client *c) { std::map guildmaster_map = { { Class::Warrior, Class::WarriorGM }, diff --git a/zone/npc.h b/zone/npc.h index 9505bd30ae..bc37fc39c2 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -563,8 +563,6 @@ class NPC : public Mob bool IsMultiQuestEnabled() { return m_multiquest_enabled; } void MultiQuestEnable() { m_multiquest_enabled = true; } - std::vector GetMultiQuestItems() const; - void SetMultiQuestItems(std::vector multiquest_items); bool IsGuildmasterForClient(Client *c); protected: @@ -708,9 +706,7 @@ class NPC : public Mob bool raid_target; bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup - bool m_multiquest_enabled; - - std::vector m_multiquest_items; + bool m_multiquest_enabled = false; private: uint32 m_loottable_id; From 78ed388eaf0daedfd96b0b874914d32faf8c2d09 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 22 Oct 2024 04:33:53 -0500 Subject: [PATCH 35/44] move multi-quest buckets and methods to be npc scoped --- zone/client.cpp | 451 ------------------------------------------- zone/client.h | 40 ---- zone/lua_client.cpp | 83 -------- zone/lua_client.h | 7 - zone/lua_npc.cpp | 85 ++++++++ zone/lua_npc.h | 8 + zone/npc.cpp | 450 ++++++++++++++++++++++++++++++++++++++++++ zone/npc.h | 43 ++++- zone/perl_client.cpp | 71 ------- zone/perl_npc.cpp | 71 +++++++ zone/trading.cpp | 14 +- 11 files changed, 664 insertions(+), 659 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 38c3b5c49d..8dd970c544 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12833,454 +12833,3 @@ void Client::ClientToNpcAggroProcess() LogAggro("Checking Reverse Aggro (client->npc) scanned_npcs ([{}])", npc_scan_count); } } - -bool Client::CheckHandin( - NPC *n, - std::map handin, - std::map required, - std::vector items -) -{ - auto h = Handin{}; - auto r = Handin{}; - - std::string log_handin_prefix = fmt::format("[{}] -> [{}]", GetCleanName(), n->GetCleanName()); - - // if the npc is a multi-quest npc, we want to re-use our previously set hand-in bucket - if (!m_handin_started && m_hand_in.npc && m_hand_in.npc->IsMultiQuestEnabled()) { - h = m_hand_in; - } - - std::vector&, Handin&>> datasets = {}; - - // if we've already started the hand-in process, we don't want to re-process the hand-in data - // we continue to use the originally set hand-in bucket and decrement from it with each successive hand-in - if (m_handin_started) { - h = m_hand_in; - } else { - datasets.emplace_back(handin, h); - } - datasets.emplace_back(required, r); - - const std::string set_hand_in = "Hand-in"; - const std::string set_required = "Required"; - for (const auto &[data_map, current_handin]: datasets) { - std::string current_dataset = ¤t_handin == &h ? set_hand_in : set_required; - for (const auto &[key, value]: data_map) { - LogNpcHandinDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value); - - // Handle items - if (Strings::IsNumber(key)) { - if (const auto *exists = database.GetItem(Strings::ToUnsignedInt(key)); - exists && current_dataset == set_required) { - current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value}); - } - continue; - } - - // Handle money and any other key-value pairs - if (key == "platinum") { current_handin.money.platinum = value; } - else if (key == "gold") { current_handin.money.gold = value; } - else if (key == "silver") { current_handin.money.silver = value; } - else if (key == "copper") { current_handin.money.copper = value; } - } - } - - // pull hand-in items from the item instances - if (!m_handin_started) { - for (const auto &i: items) { - if (!i) { - continue; - } - - h.items.emplace_back( - HandinEntry{ - .item_id = std::to_string(i->GetItem()->ID), - .count = std::max(static_cast(i->GetCharges()), static_cast(1)), - .item = i->Clone() - } - ); - } - } - - // compare hand-in to required, the item_id can be in any slot - bool met_requirement = true; - if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { - for (auto &h_item: h.items) { - bool found = false; - for (const auto &r_item: r.items) { - if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { - found = true; - break; - } - } - - if (!found) { - met_requirement = false; - break; - } - } - } - else { - met_requirement = false; - } - - // multi-quest - if (n && n->IsMultiQuestEnabled()) { - for (auto &h_item: h.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; - } - } - } - } - - for (auto &h_item: h.items) { - LogNpcHandinDetail( - "{} Hand-in item [{}] ({}) count [{}] is_multiquest_item [{}]", - log_handin_prefix, - h_item.item->GetItem()->Name, - h_item.item_id, - h_item.count, - h_item.is_multiquest_item - ); - } - - // compare hand-in money to required - if (met_requirement) { - if (h.money.platinum != r.money.platinum || - h.money.gold != r.money.gold || - h.money.silver != r.money.silver || - h.money.copper != r.money.copper) { - met_requirement = false; - } - } - - // in-case we trigger CheckHand-in multiple times, only set these once - if (!m_handin_started) { - m_handin_started = true; - m_hand_in = h; - // save original items for logging - m_hand_in.original_items = m_hand_in.items; - m_hand_in.original_money = m_hand_in.money; - m_hand_in.npc = n; - } - - // check if npc is guildmaster - if (n->IsGuildmaster()) { - for (const auto &h_item: m_hand_in.items) { - if (!h_item.item) { - continue; - } - - if (!IsDisciplineTome(h_item.item->GetItem())) { - continue; - } - - if (n->IsGuildmasterForClient(this)) { - TrainDiscipline(h_item.item->GetID()); - m_hand_in.items.erase( - std::remove_if( - m_hand_in.items.begin(), - m_hand_in.items.end(), - [&](const HandinEntry &i) { - bool removed = i.item_id == h_item.item_id; - if (removed) { - LogNpcHandin( - "{} Hand-in success, removing discipline tome [{}] from hand-in bucket", - log_handin_prefix, - i.item_id - ); - } - return removed; - } - ), - m_hand_in.items.end() - ); - } else { - n->Say("You are not a member of my guild. I will not train you!"); - met_requirement = false; - break; - } - } - } - - // print current hand-in bucket - LogNpcHandin( - "{} > Before processing hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", - log_handin_prefix, - met_requirement, - h.items.size(), - h.money.platinum, - h.money.gold, - h.money.silver, - h.money.copper - ); - - LogNpcHandin( - "{} >> Handed Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", - log_handin_prefix, - h.items.size(), - h.money.platinum, - h.money.gold, - h.money.silver, - h.money.copper - ); - - int item_count = 1; - for (const auto &i: h.items) { - LogNpcHandin( - "{} >>> item{} [{}] ({}) count [{}]", - log_handin_prefix, - item_count, - i.item->GetItem()->Name, - i.item_id, - i.count - ); - item_count++; - } - - - - LogNpcHandin( - "{} >> Required Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", - log_handin_prefix, - r.items.size(), - r.money.platinum, - r.money.gold, - r.money.silver, - r.money.copper - ); - - item_count = 1; - for (const auto &i: r.items) { - auto item = database.GetItem(Strings::ToUnsignedInt(i.item_id)); - - LogNpcHandin( - "{} >>> item{} [{}] ({}) count [{}]", - log_handin_prefix, - item_count, - item ? item->Name : "Unknown", - i.item_id, - i.count - ); - - item_count++; - } - - if (met_requirement) { - std::vector log_entries = {}; - for (const auto &h_item : h.items) { - auto it = std::find_if(r.items.begin(), r.items.end(), [&](const auto &r_item) { - return h_item.item_id == r_item.item_id && h_item.count == r_item.count; - }); - - if (it != r.items.end()) { - m_hand_in.items.erase( - std::remove_if( - m_hand_in.items.begin(), - m_hand_in.items.end(), - [&](const HandinEntry &i) { - bool removed = i.item_id == h_item.item_id && i.count == h_item.count; - if (removed) { - log_entries.emplace_back( - fmt::format( - "{} >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", - log_handin_prefix, - i.item->GetItem()->Name, - i.item_id, - i.count - ) - ); - } - return removed; - } - ), - m_hand_in.items.end() - ); - } - } - - // log successful hand-in items - if (!log_entries.empty()) { - for (const auto& log : log_entries) { - LogNpcHandin("{}", log); - } - } - - LogNpcHandin( - "{} > End of hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", - log_handin_prefix, - met_requirement, - m_hand_in.items.size(), - m_hand_in.money.platinum, - m_hand_in.money.gold, - m_hand_in.money.silver, - m_hand_in.money.copper - ); - for (const auto &i: m_hand_in.items) { - LogNpcHandin( - "{} Hand-in success, item [{}] ({}) count [{}]", - log_handin_prefix, - i.item->GetItem()->Name, - i.item_id, - i.count - ); - } - - // decrement successful hand-in money from current hand-in bucket - if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { - LogNpcHandin( - "{} Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", - log_handin_prefix, - h.money.platinum, - h.money.gold, - h.money.silver, - h.money.copper - ); - m_hand_in.money.platinum -= h.money.platinum; - m_hand_in.money.gold -= h.money.gold; - m_hand_in.money.silver -= h.money.silver; - m_hand_in.money.copper -= h.money.copper; - } - } - - return met_requirement; -} - -void Client::ReturnHandinItems() -{ - // player event - std::vector handin_items; - PlayerEvent::HandinMoney handin_money{}; - std::vector return_items; - PlayerEvent::HandinMoney return_money{}; - for (const auto& i : m_hand_in.original_items) { - if (i.item && i.item->GetItem()) { - handin_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)) - } - ); - } - } - - // check if any money was handed in - if (m_hand_in.original_money.platinum > 0 || - m_hand_in.original_money.gold > 0 || - m_hand_in.original_money.silver > 0 || - m_hand_in.original_money.copper > 0 - ) { - handin_money.copper = m_hand_in.original_money.copper; - handin_money.silver = m_hand_in.original_money.silver; - handin_money.gold = m_hand_in.original_money.gold; - handin_money.platinum = m_hand_in.original_money.platinum; - } - - bool returned_handin = false; - m_hand_in.items.erase( - std::remove_if( - m_hand_in.items.begin(), - m_hand_in.items.end(), - [&](auto& i) { - if (i.item && i.item->GetItem() && !i.is_multiquest_item) { - 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)) - } - ); - - PushItemOnCursor(*i.item, true); - LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); - - // Safely delete the item - safe_delete(i.item); - returned_handin = true; - return true; // Mark this item for removal - } - return false; - } - ), - m_hand_in.items.end() - ); - - // check if any money was handed in - if (m_hand_in.money.platinum > 0 || - m_hand_in.money.gold > 0 || - m_hand_in.money.silver > 0 || - m_hand_in.money.copper > 0 - ) { - AddMoneyToPP( - m_hand_in.money.copper, - m_hand_in.money.silver, - m_hand_in.money.gold, - m_hand_in.money.platinum, - true - ); - returned_handin = true; - LogNpcHandin( - "Hand-in failed, returning money p [{}] g [{}] s [{}] c [{}]", - m_hand_in.money.platinum, - m_hand_in.money.gold, - m_hand_in.money.silver, - m_hand_in.money.copper - ); - - // player event - return_money.copper = m_hand_in.money.copper; - return_money.silver = m_hand_in.money.silver; - return_money.gold = m_hand_in.money.gold; - return_money.platinum = m_hand_in.money.platinum; - } - - m_has_processed_handin_return = returned_handin; - - if (returned_handin && m_hand_in.npc) { - m_hand_in.npc->Say( - fmt::format( - "I have no need for this {}, you can have it back.", - GetCleanName() - ).c_str() - ); - } - - const bool handed_in_money = ( - handin_money.platinum > 0 || - handin_money.gold > 0 || - handin_money.silver > 0 || - handin_money.copper > 0 - ); - const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; - - if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { - auto e = PlayerEvent::HandinEvent{ - .npc_id = m_hand_in.npc->GetNPCTypeID(), - .npc_name = m_hand_in.npc->GetCleanName(), - .handin_items = handin_items, - .handin_money = handin_money, - .return_items = return_items, - .return_money = return_money, - .is_quest_handin = parse->HasQuestSub(m_hand_in.npc->GetNPCTypeID(), EVENT_TRADE) - }; - - RecordPlayerEventLogWithClient(this, PlayerEvent::NPC_HANDIN, e); - } -} - -void Client::ResetHandin() -{ - m_has_processed_handin_return = false; - m_handin_started = false; - if (m_hand_in.npc && !m_hand_in.npc->IsMultiQuestEnabled()) { - m_hand_in = {}; - } -} diff --git a/zone/client.h b/zone/client.h index 5ee18d6854..e087e0bbe7 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1732,15 +1732,6 @@ class Client : public Mob void DuplicateLoreMessage(uint32 ItemID); void GarbleMessage(char *, uint8); - bool CheckHandin( - NPC* n, - std::map handin, - std::map required, - std::vector items - ); - void ReturnHandinItems(); - void ResetHandin(); - void ItemTimerCheck(); void TryItemTimer(int slot); void SendItemScale(EQ::ItemInstance *inst); @@ -1819,37 +1810,6 @@ class Client : public Mob uint32 GetEXPForLevel(uint16 check_level); - // NPC Handin - struct HandinEntry { - std::string item_id = "0"; - uint16 count = 0; - const EQ::ItemInstance *item = nullptr; - bool is_multiquest_item = false; - }; - - struct HandinMoney { - uint32 platinum = 0; - uint32 gold = 0; - uint32 silver = 0; - uint32 copper = 0; - }; - - struct Handin { - std::vector original_items = {}; // this is what the player originally handed in, never modified - std::vector items = {}; // items can be removed from this set as successful handins are made - HandinMoney original_money = {}; // this is what the player originally handed in, never modified - HandinMoney money = {}; // money can be removed from this set as successful handins are made - NPC *npc = nullptr; // the NPC the player is handing in to - }; - - bool m_handin_started = false; - bool m_has_processed_handin_return = false; - - // this is the working handin data from the player - // items can be decremented from this as each successful - // check is ran in scripts, the remainder is what is returned - Handin m_hand_in = {}; - protected: friend class Mob; void CalcEdibleBonuses(StatBonuses* newbon); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 79b2e02a3f..f8d5ca64d0 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3436,87 +3436,6 @@ void Lua_Client::AreaTaunt(float range, int bonus_hate) entity_list.AETaunt(self, range, bonus_hate); } -bool Lua_Client::LuaCheckHandin( - Lua_NPC n, - luabind::adl::object handin_table, - luabind::adl::object required_table, - luabind::adl::object items_table -) -{ - Lua_Safe_Call_Bool(); - - if ( - luabind::type(handin_table) != LUA_TTABLE || - luabind::type(required_table) != LUA_TTABLE || - luabind::type(items_table) != LUA_TTABLE - ) { - return false; - } - - std::map handin_map; - std::map required_map; - std::vector items; - - 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]); - } - } - - for (luabind::iterator i(required_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("Required key type [{}] not supported", luabind::type(i.key())); - } - - if (!key.empty()) { - required_map[key] = luabind::object_cast(required_table[i.key()]); - LogNpcHandinDetail("Required key [{}] value [{}]", key, required_map[key]); - } - } - - for (luabind::iterator i(items_table), end; i != end; i++) { - auto item = luabind::object_cast(items_table[i.key()]); - - if (item && item.GetItem()) { - LogNpcHandinDetail( - "Item instance [{}] ({}) UUID ({}) added to handin list", - item.GetName(), - item.GetID(), - item.GetSerialNumber() - ); - - items.emplace_back(item); - } - } - - return self->CheckHandin(n, handin_map, required_map, items); -} - -void Lua_Client::ReturnHandinItems() -{ - Lua_Safe_Call_Void(); - self->ReturnHandinItems(); -} - luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -3586,7 +3505,6 @@ luabind::scope lua_register_client() { .def("CashReward", &Lua_Client::CashReward) .def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName) .def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID) - .def("CheckHandin", (bool(Lua_Client::*)(Lua_NPC,luabind::adl::object,luabind::adl::object,luabind::adl::object))&Lua_Client::LuaCheckHandin) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill) .def("CheckSpecializeIncrease", (void(Lua_Client::*)(int))&Lua_Client::CheckSpecializeIncrease) @@ -3923,7 +3841,6 @@ luabind::scope lua_register_client() { .def("ResetItemCooldown", (void(Lua_Client::*)(uint32))&Lua_Client::ResetItemCooldown) .def("ResetLeadershipAA", (void(Lua_Client::*)(void))&Lua_Client::ResetLeadershipAA) .def("ResetTrade", (void(Lua_Client::*)(void))&Lua_Client::ResetTrade) - .def("ReturnHandinItems", (void(Lua_Client::*)(void))&Lua_Client::ReturnHandinItems) .def("RewardFaction", (void(Lua_Client::*)(int,int))&Lua_Client::RewardFaction) .def("Save", (void(Lua_Client::*)(int))&Lua_Client::Save) .def("Save", (void(Lua_Client::*)(void))&Lua_Client::Save) diff --git a/zone/lua_client.h b/zone/lua_client.h index 43456d0220..faff8b1572 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -582,13 +582,6 @@ class Lua_Client : public Lua_Mob bool RemoveAAPoints(uint32 points); bool RemoveAlternateCurrencyValue(uint32 currency_id, uint32 amount); bool AreTasksCompleted(luabind::object task_ids); - bool LuaCheckHandin( - Lua_NPC n, - luabind::adl::object handin_table, - luabind::adl::object required_table, - luabind::adl::object items_table - ); - void ReturnHandinItems(); void DialogueWindow(std::string markdown); diff --git a/zone/lua_npc.cpp b/zone/lua_npc.cpp index 47ffdf5d82..7e3021378e 100644 --- a/zone/lua_npc.cpp +++ b/zone/lua_npc.cpp @@ -7,6 +7,8 @@ #include "npc.h" #include "lua_npc.h" #include "lua_client.h" +#include "lua_item.h" +#include "lua_iteminst.h" struct Lua_NPC_Loot_List { std::vector entries; @@ -849,6 +851,87 @@ void Lua_NPC::MultiQuestEnable() self->MultiQuestEnable(); } +bool Lua_NPC::LuaCheckHandin( + Lua_Client c, + luabind::adl::object handin_table, + luabind::adl::object required_table, + luabind::adl::object items_table +) +{ + Lua_Safe_Call_Bool(); + + if ( + luabind::type(handin_table) != LUA_TTABLE || + luabind::type(required_table) != LUA_TTABLE || + luabind::type(items_table) != LUA_TTABLE + ) { + return false; + } + + std::map handin_map; + std::map required_map; + std::vector items; + + 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]); + } + } + + for (luabind::iterator i(required_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("Required key type [{}] not supported", luabind::type(i.key())); + } + + if (!key.empty()) { + required_map[key] = luabind::object_cast(required_table[i.key()]); + LogNpcHandinDetail("Required key [{}] value [{}]", key, required_map[key]); + } + } + + for (luabind::iterator i(items_table), end; i != end; i++) { + auto item = luabind::object_cast(items_table[i.key()]); + + if (item && item.GetItem()) { + LogNpcHandinDetail( + "Item instance [{}] ({}) UUID ({}) added to handin list", + item.GetName(), + item.GetID(), + item.GetSerialNumber() + ); + + items.emplace_back(item); + } + } + + return self->CheckHandin(c, handin_map, required_map, items); +} + +void Lua_NPC::ReturnHandinItems(Lua_Client c) +{ + Lua_Safe_Call_Void(); + self->ReturnHandinItems(c); +} + luabind::scope lua_register_npc() { return luabind::class_("NPC") .def(luabind::constructor<>()) @@ -871,6 +954,7 @@ luabind::scope lua_register_npc() { .def("AssignWaypoints", (void(Lua_NPC::*)(int))&Lua_NPC::AssignWaypoints) .def("CalculateNewWaypoint", (void(Lua_NPC::*)(void))&Lua_NPC::CalculateNewWaypoint) .def("ChangeLastName", (void(Lua_NPC::*)(std::string))&Lua_NPC::ChangeLastName) + .def("CheckHandin", (bool(Lua_NPC::*)(Lua_Client,luabind::adl::object,luabind::adl::object,luabind::adl::object))&Lua_NPC::LuaCheckHandin) .def("CheckNPCFactionAlly", (int(Lua_NPC::*)(int))&Lua_NPC::CheckNPCFactionAlly) .def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLootItems) .def("ClearLastName", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLastName) @@ -967,6 +1051,7 @@ luabind::scope lua_register_npc() { .def("RemoveItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::RemoveItem) .def("RemoveItem", (void(Lua_NPC::*)(int,int,int))&Lua_NPC::RemoveItem) .def("ResumeWandering", (void(Lua_NPC::*)(void))&Lua_NPC::ResumeWandering) + .def("ReturnHandinItems", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::ReturnHandinItems) .def("SaveGuardSpot", (void(Lua_NPC::*)(void))&Lua_NPC::SaveGuardSpot) .def("SaveGuardSpot", (void(Lua_NPC::*)(bool))&Lua_NPC::SaveGuardSpot) .def("SaveGuardSpot", (void(Lua_NPC::*)(float,float,float,float))&Lua_NPC::SaveGuardSpot) diff --git a/zone/lua_npc.h b/zone/lua_npc.h index 2ef457859b..6f74e1ab6b 100644 --- a/zone/lua_npc.h +++ b/zone/lua_npc.h @@ -9,6 +9,7 @@ class Lua_Mob; class Lua_NPC; class Lua_Client; struct Lua_NPC_Loot_List; +class Lua_Inventory; namespace luabind { struct scope; @@ -188,6 +189,13 @@ class Lua_NPC : public Lua_Mob void DescribeSpecialAbilities(Lua_Client c); bool IsMultiQuestEnabled(); void MultiQuestEnable(); + bool LuaCheckHandin( + Lua_Client c, + luabind::adl::object handin_table, + luabind::adl::object required_table, + luabind::adl::object items_table + ); + void ReturnHandinItems(Lua_Client c); }; #endif diff --git a/zone/npc.cpp b/zone/npc.cpp index 3c77a96478..dbaf936116 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -49,6 +49,7 @@ #include "bot.h" #include "../common/skill_caps.h" +#include "../common/events/player_event_logs.h" #include #include @@ -4285,3 +4286,452 @@ bool NPC::IsGuildmasterForClient(Client *c) { return false; } + +bool NPC::CheckHandin( + Client *c, + std::map handin, + std::map required, + std::vector items +) +{ + auto h = Handin{}; + auto r = Handin{}; + + std::string log_handin_prefix = fmt::format("[{}] -> [{}]", c->GetCleanName(), GetCleanName()); + + // if the npc is a multi-quest npc, we want to re-use our previously set hand-in bucket + if (!m_handin_started && m_hand_in.npc && m_hand_in.npc->IsMultiQuestEnabled()) { + h = m_hand_in; + } + + std::vector&, Handin&>> datasets = {}; + + // if we've already started the hand-in process, we don't want to re-process the hand-in data + // we continue to use the originally set hand-in bucket and decrement from it with each successive hand-in + if (m_handin_started) { + h = m_hand_in; + } else { + datasets.emplace_back(handin, h); + } + datasets.emplace_back(required, r); + + const std::string set_hand_in = "Hand-in"; + const std::string set_required = "Required"; + for (const auto &[data_map, current_handin]: datasets) { + std::string current_dataset = ¤t_handin == &h ? set_hand_in : set_required; + for (const auto &[key, value]: data_map) { + LogNpcHandinDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value); + + // Handle items + if (Strings::IsNumber(key)) { + if (const auto *exists = database.GetItem(Strings::ToUnsignedInt(key)); + exists && current_dataset == set_required) { + current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value}); + } + continue; + } + + // Handle money and any other key-value pairs + if (key == "platinum") { current_handin.money.platinum = value; } + else if (key == "gold") { current_handin.money.gold = value; } + else if (key == "silver") { current_handin.money.silver = value; } + else if (key == "copper") { current_handin.money.copper = value; } + } + } + + // pull hand-in items from the item instances + if (!m_handin_started) { + for (const auto &i: items) { + if (!i) { + continue; + } + + h.items.emplace_back( + HandinEntry{ + .item_id = std::to_string(i->GetItem()->ID), + .count = std::max(static_cast(i->GetCharges()), static_cast(1)), + .item = i->Clone() + } + ); + } + } + + // compare hand-in to required, the item_id can be in any slot + bool met_requirement = true; + if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { + for (auto &h_item: h.items) { + bool found = false; + for (const auto &r_item: r.items) { + if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { + found = true; + break; + } + } + + if (!found) { + met_requirement = false; + break; + } + } + } + else { + met_requirement = false; + } + + // multi-quest + if (IsMultiQuestEnabled()) { + for (auto &h_item: h.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; + } + } + } + } + + for (auto &h_item: h.items) { + LogNpcHandinDetail( + "{} Hand-in item [{}] ({}) count [{}] is_multiquest_item [{}]", + log_handin_prefix, + h_item.item->GetItem()->Name, + h_item.item_id, + h_item.count, + h_item.is_multiquest_item + ); + } + + // compare hand-in money to required + if (met_requirement) { + if (h.money.platinum != r.money.platinum || + h.money.gold != r.money.gold || + h.money.silver != r.money.silver || + h.money.copper != r.money.copper) { + met_requirement = false; + } + } + + // in-case we trigger CheckHand-in multiple times, only set these once + if (!m_handin_started) { + m_handin_started = true; + m_hand_in = h; + // save original items for logging + m_hand_in.original_items = m_hand_in.items; + m_hand_in.original_money = m_hand_in.money; + m_hand_in.npc = this; + } + + // check if npc is guildmaster + if (IsGuildmaster()) { + for (const auto &h_item: m_hand_in.items) { + if (!h_item.item) { + continue; + } + + if (!IsDisciplineTome(h_item.item->GetItem())) { + continue; + } + + if (IsGuildmasterForClient(c)) { + c->TrainDiscipline(h_item.item->GetID()); + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](const HandinEntry &i) { + bool removed = i.item_id == h_item.item_id; + if (removed) { + LogNpcHandin( + "{} Hand-in success, removing discipline tome [{}] from hand-in bucket", + log_handin_prefix, + i.item_id + ); + } + return removed; + } + ), + m_hand_in.items.end() + ); + } else { + Say("You are not a member of my guild. I will not train you!"); + met_requirement = false; + break; + } + } + } + + // print current hand-in bucket + LogNpcHandin( + "{} > Before processing hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + met_requirement, + h.items.size(), + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + + LogNpcHandin( + "{} >> Handed Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + h.items.size(), + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + + int item_count = 1; + for (const auto &i: h.items) { + LogNpcHandin( + "{} >>> item{} [{}] ({}) count [{}]", + log_handin_prefix, + item_count, + i.item->GetItem()->Name, + i.item_id, + i.count + ); + item_count++; + } + + LogNpcHandin( + "{} >> Required Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + r.items.size(), + r.money.platinum, + r.money.gold, + r.money.silver, + r.money.copper + ); + + item_count = 1; + for (const auto &i: r.items) { + auto item = database.GetItem(Strings::ToUnsignedInt(i.item_id)); + + LogNpcHandin( + "{} >>> item{} [{}] ({}) count [{}]", + log_handin_prefix, + item_count, + item ? item->Name : "Unknown", + i.item_id, + i.count + ); + + item_count++; + } + + if (met_requirement) { + std::vector log_entries = {}; + for (const auto &h_item : h.items) { + auto it = std::find_if(r.items.begin(), r.items.end(), [&](const auto &r_item) { + return h_item.item_id == r_item.item_id && h_item.count == r_item.count; + }); + + if (it != r.items.end()) { + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](const HandinEntry &i) { + bool removed = i.item_id == h_item.item_id && i.count == h_item.count; + if (removed) { + log_entries.emplace_back( + fmt::format( + "{} >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", + log_handin_prefix, + i.item->GetItem()->Name, + i.item_id, + i.count + ) + ); + } + return removed; + } + ), + m_hand_in.items.end() + ); + } + } + + // log successful hand-in items + if (!log_entries.empty()) { + for (const auto& log : log_entries) { + LogNpcHandin("{}", log); + } + } + + LogNpcHandin( + "{} > End of hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + log_handin_prefix, + met_requirement, + m_hand_in.items.size(), + m_hand_in.money.platinum, + m_hand_in.money.gold, + m_hand_in.money.silver, + m_hand_in.money.copper + ); + for (const auto &i: m_hand_in.items) { + LogNpcHandin( + "{} Hand-in success, item [{}] ({}) count [{}]", + log_handin_prefix, + i.item->GetItem()->Name, + i.item_id, + i.count + ); + } + + // decrement successful hand-in money from current hand-in bucket + if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { + LogNpcHandin( + "{} Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", + log_handin_prefix, + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + m_hand_in.money.platinum -= h.money.platinum; + m_hand_in.money.gold -= h.money.gold; + m_hand_in.money.silver -= h.money.silver; + m_hand_in.money.copper -= h.money.copper; + } + } + + return met_requirement; +} + +void NPC::ReturnHandinItems(Client *c) +{ + // player event + std::vector handin_items; + PlayerEvent::HandinMoney handin_money{}; + std::vector return_items; + PlayerEvent::HandinMoney return_money{}; + for (const auto& i : m_hand_in.original_items) { + if (i.item && i.item->GetItem()) { + handin_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)) + } + ); + } + } + + // check if any money was handed in + if (m_hand_in.original_money.platinum > 0 || + m_hand_in.original_money.gold > 0 || + m_hand_in.original_money.silver > 0 || + m_hand_in.original_money.copper > 0 + ) { + handin_money.copper = m_hand_in.original_money.copper; + handin_money.silver = m_hand_in.original_money.silver; + handin_money.gold = m_hand_in.original_money.gold; + handin_money.platinum = m_hand_in.original_money.platinum; + } + + bool returned_handin = false; + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](auto& i) { + if (i.item && i.item->GetItem() && !i.is_multiquest_item) { + 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)) + } + ); + + c->PushItemOnCursor(*i.item, true); + LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name); + + // Safely delete the item + safe_delete(i.item); + returned_handin = true; + return true; // Mark this item for removal + } + return false; + } + ), + m_hand_in.items.end() + ); + + // check if any money was handed in + if (m_hand_in.money.platinum > 0 || + m_hand_in.money.gold > 0 || + m_hand_in.money.silver > 0 || + m_hand_in.money.copper > 0 + ) { + c->AddMoneyToPP( + m_hand_in.money.copper, + m_hand_in.money.silver, + m_hand_in.money.gold, + m_hand_in.money.platinum, + true + ); + returned_handin = true; + LogNpcHandin( + "Hand-in failed, returning money p [{}] g [{}] s [{}] c [{}]", + m_hand_in.money.platinum, + m_hand_in.money.gold, + m_hand_in.money.silver, + m_hand_in.money.copper + ); + + // player event + return_money.copper = m_hand_in.money.copper; + return_money.silver = m_hand_in.money.silver; + return_money.gold = m_hand_in.money.gold; + return_money.platinum = m_hand_in.money.platinum; + } + + m_has_processed_handin_return = returned_handin; + + if (returned_handin && m_hand_in.npc) { + m_hand_in.npc->Say( + fmt::format( + "I have no need for this {}, you can have it back.", + GetCleanName() + ).c_str() + ); + } + + const bool handed_in_money = ( + handin_money.platinum > 0 || + handin_money.gold > 0 || + handin_money.silver > 0 || + handin_money.copper > 0 + ); + const bool event_has_data_to_record = !handin_items.empty() || handed_in_money; + + if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { + auto e = PlayerEvent::HandinEvent{ + .npc_id = m_hand_in.npc->GetNPCTypeID(), + .npc_name = m_hand_in.npc->GetCleanName(), + .handin_items = handin_items, + .handin_money = handin_money, + .return_items = return_items, + .return_money = return_money, + .is_quest_handin = parse->HasQuestSub(m_hand_in.npc->GetNPCTypeID(), EVENT_TRADE) + }; + + RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); + } +} + +void NPC::ResetHandin() +{ + m_has_processed_handin_return = false; + m_handin_started = false; + if (m_hand_in.npc && !m_hand_in.npc->IsMultiQuestEnabled()) { + m_hand_in = {}; + } +} diff --git a/zone/npc.h b/zone/npc.h index bc37fc39c2..02d7fc5212 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -561,12 +561,50 @@ class NPC : public Mob bool CanPetTakeItem(const EQ::ItemInstance *inst); + // NPC Hand-in bool IsMultiQuestEnabled() { return m_multiquest_enabled; } void MultiQuestEnable() { m_multiquest_enabled = true; } bool IsGuildmasterForClient(Client *c); + bool CheckHandin( + Client *c, + std::map handin, + std::map required, + std::vector items + ); + void ReturnHandinItems(Client *c); + void ResetHandin(); + bool HasProcessedHandinReturn() { return m_has_processed_handin_return; } + bool HandinStarted() { return m_handin_started; } + + struct HandinEntry { + std::string item_id = "0"; + uint16 count = 0; + const EQ::ItemInstance *item = nullptr; + bool is_multiquest_item = false; + }; + + struct HandinMoney { + uint32 platinum = 0; + uint32 gold = 0; + uint32 silver = 0; + uint32 copper = 0; + }; + + struct Handin { + std::vector original_items = {}; // this is what the player originally handed in, never modified + std::vector items = {}; // items can be removed from this set as successful handins are made + HandinMoney original_money = {}; // this is what the player originally handed in, never modified + HandinMoney money = {}; // money can be removed from this set as successful handins are made + NPC *npc = nullptr; // the NPC the player is handing in to + }; protected: + // this is the working handin data from the player + // items can be decremented from this as each successful + // check is ran in scripts, the remainder is what is returned + Handin m_hand_in = {}; + void HandleRoambox(); const NPCType* NPCTypedata; @@ -706,7 +744,10 @@ class NPC : public Mob bool raid_target; bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup - bool m_multiquest_enabled = false; + // NPC Hand-in + bool m_multiquest_enabled = false; + bool m_handin_started = false; + bool m_has_processed_handin_return = false; private: uint32 m_loottable_id; diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index c56e616a8a..8bb39afda2 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3212,75 +3212,6 @@ Merc* Perl_Client_GetMerc(Client* self) return self->GetMerc(); } -bool Perl_Client_CheckHandin( - Client* self, - NPC* n, - perl::reference handin_ref, - perl::reference required_ref, - perl::array items_ref -) -{ - perl::hash handin = handin_ref; - perl::hash required = required_ref; - - std::map handin_map; - std::map required_map; - std::vector items; - - for (auto e: handin) { - if (!e.first) { - continue; - } - - if (Strings::EqualFold(e.first, "0")) { - continue; - } - - LogNpcHandinDetail("Handin key [{}] value [{}]", e.first, handin.at(e.first).c_str()); - - const uint32 count = static_cast(handin.at(e.first)); - handin_map[e.first] = count; - } - - for (auto e: required) { - if (!e.first) { - continue; - } - - if (Strings::EqualFold(e.first, "0")) { - continue; - } - - LogNpcHandinDetail("Required key [{}] value [{}]", e.first, required.at(e.first).c_str()); - - const uint32 count = static_cast(required.at(e.first)); - required_map[e.first] = count; - } - - for (auto e : items_ref) { - const EQ::ItemInstance* i = static_cast(e); - if (!i) { - continue; - } - - items.emplace_back(i); - - LogNpcHandinDetail( - "Item instance [{}] ({}) UUID ({}) added to handin list", - i->GetItem()->Name, - i->GetItem()->ID, - i->GetSerialNumber() - ); - } - - return self->CheckHandin(n, handin_map, required_map, items); -} - -void Perl_Client_ReturnHandinItems(Client* self) -{ - self->ReturnHandinItems(); -} - void perl_register_client() { perl::interpreter perl(PERL_GET_THX); @@ -3353,7 +3284,6 @@ void perl_register_client() package.add("CashReward", &Perl_Client_CashReward); package.add("ChangeLastName", &Perl_Client_ChangeLastName); package.add("CharacterID", &Perl_Client_CharacterID); - package.add("CheckHandin", &Perl_Client_CheckHandin); package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill); package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill); package.add("CheckSpecializeIncrease", &Perl_Client_CheckSpecializeIncrease); @@ -3684,7 +3614,6 @@ void perl_register_client() package.add("ResetItemCooldown", &Perl_Client_ResetItemCooldown); package.add("ResetLeadershipAA", &Perl_Client_ResetLeadershipAA); package.add("ResetTrade", &Perl_Client_ResetTrade); - package.add("ReturnHandinItems", &Perl_Client_ReturnHandinItems); package.add("Save", &Perl_Client_Save); package.add("ScribeSpell", (void(*)(Client*, uint16, int))&Perl_Client_ScribeSpell); package.add("ScribeSpell", (void(*)(Client*, uint16, int, bool))&Perl_Client_ScribeSpell); diff --git a/zone/perl_npc.cpp b/zone/perl_npc.cpp index 0a480f5e8f..0000b974be 100644 --- a/zone/perl_npc.cpp +++ b/zone/perl_npc.cpp @@ -806,6 +806,75 @@ void Perl_NPC_MultiQuestEnable(NPC* self) self->MultiQuestEnable(); } +bool Perl_NPC_CheckHandin( + NPC* self, + Client* c, + perl::reference handin_ref, + perl::reference required_ref, + perl::array items_ref +) +{ + perl::hash handin = handin_ref; + perl::hash required = required_ref; + + std::map handin_map; + std::map required_map; + std::vector items; + + for (auto e: handin) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } + + LogNpcHandinDetail("Handin key [{}] value [{}]", e.first, handin.at(e.first).c_str()); + + const uint32 count = static_cast(handin.at(e.first)); + handin_map[e.first] = count; + } + + for (auto e: required) { + if (!e.first) { + continue; + } + + if (Strings::EqualFold(e.first, "0")) { + continue; + } + + LogNpcHandinDetail("Required key [{}] value [{}]", e.first, required.at(e.first).c_str()); + + const uint32 count = static_cast(required.at(e.first)); + required_map[e.first] = count; + } + + for (auto e : items_ref) { + const EQ::ItemInstance* i = static_cast(e); + if (!i) { + continue; + } + + items.emplace_back(i); + + LogNpcHandinDetail( + "Item instance [{}] ({}) UUID ({}) added to handin list", + i->GetItem()->Name, + i->GetItem()->ID, + i->GetSerialNumber() + ); + } + + return self->CheckHandin(c, handin_map, required_map, items); +} + +void Perl_NPC_ReturnHandinItems(NPC *self, Client* c) +{ + self->ReturnHandinItems(c); +} + void perl_register_npc() { perl::interpreter perl(PERL_GET_THX); @@ -837,6 +906,7 @@ void perl_register_npc() package.add("CalculateNewWaypoint", &Perl_NPC_CalculateNewWaypoint); package.add("ChangeLastName", &Perl_NPC_ChangeLastName); package.add("CheckNPCFactionAlly", &Perl_NPC_CheckNPCFactionAlly); + package.add("CheckHandin", &Perl_NPC_CheckHandin); package.add("ClearItemList", &Perl_NPC_ClearLootItems); package.add("ClearLastName", &Perl_NPC_ClearLastName); package.add("CountItem", &Perl_NPC_CountItem); @@ -931,6 +1001,7 @@ void perl_register_npc() package.add("RemoveMeleeProc", &Perl_NPC_RemoveMeleeProc); package.add("RemoveRangedProc", &Perl_NPC_RemoveRangedProc); package.add("ResumeWandering", &Perl_NPC_ResumeWandering); + package.add("ReturnHandinItems", &Perl_NPC_ReturnHandinItems); package.add("SaveGuardSpot", (void(*)(NPC*))&Perl_NPC_SaveGuardSpot); package.add("SaveGuardSpot", (void(*)(NPC*, bool))&Perl_NPC_SaveGuardSpot); package.add("SaveGuardSpot", (void(*)(NPC*, float, float, float, float))&Perl_NPC_SaveGuardSpot); diff --git a/zone/trading.cpp b/zone/trading.cpp index 4fed8d6f83..c2c0d826db 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -869,11 +869,13 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st 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 - if (!m_has_processed_handin_return) { - if (!m_handin_started) { + if (!handin_npc->HasProcessedHandinReturn()) { + if (!handin_npc->HandinStarted()) { LogNpcHandinDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID()); std::map handin = { {"copper", trade->cp}, @@ -892,14 +894,14 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st item_list.emplace_back(inst); } - CheckHandin(tradingWith->CastToNPC(), handin, {}, list); + handin_npc->CheckHandin(this, handin, {}, list); } - ReturnHandinItems(); - LogNpcHandin("ReturnHandinItems() called for NPC [{}]", tradingWith->GetNPCTypeID()); + handin_npc->ReturnHandinItems(this); + LogNpcHandin("ReturnHandinItems() called for NPC [{}]", handin_npc->GetNPCTypeID()); } - ResetHandin(); + handin_npc->ResetHandin(); for (auto &inst: insts) { if (inst) { From 7eae2031d66ea947d6ef7a237484bcf180b75793 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 22 Oct 2024 04:37:09 -0500 Subject: [PATCH 36/44] Update npc.h --- zone/npc.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zone/npc.h b/zone/npc.h index 02d7fc5212..ca9b61a6b0 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -600,11 +600,6 @@ class NPC : public Mob protected: - // this is the working handin data from the player - // items can be decremented from this as each successful - // check is ran in scripts, the remainder is what is returned - Handin m_hand_in = {}; - void HandleRoambox(); const NPCType* NPCTypedata; @@ -749,6 +744,11 @@ class NPC : public Mob bool m_handin_started = false; bool m_has_processed_handin_return = false; + // this is the working handin data from the player + // items can be decremented from this as each successful + // check is ran in scripts, the remainder is what is returned + Handin m_hand_in = {}; + private: uint32 m_loottable_id; bool m_skip_global_loot; From 34c6d042613869ba46571b50024dfe910c47dd82 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 22 Oct 2024 05:41:59 -0500 Subject: [PATCH 37/44] Have multi-quest NPC's drop droppable items that were handed in --- zone/attack.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/zone/attack.cpp b/zone/attack.cpp index 6ffe44099e..c17f23eed3 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2513,6 +2513,17 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy return false; } + if (IsMultiQuestEnabled()) { + for (auto &i: m_hand_in.items) { + if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) { + auto lde = LootdropEntriesRepository::NewNpcEntity(); + lde.equip_item = 0; + lde.item_charges = i.item->GetCharges(); + AddLootDrop(i.item->GetItem(), lde, true); + } + } + } + if (killer_mob && killer_mob->IsOfClientBot() && IsValidSpell(spell) && damage > 0) { char val1[20] = { 0 }; From d4c7704bd72fd1fb33f48455c6eb8819ca3f9a8a Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 22 Oct 2024 05:49:08 -0500 Subject: [PATCH 38/44] cleanup --- zone/npc.cpp | 19 +++++++++++-------- zone/npc.h | 1 - 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index dbaf936116..8193106f75 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4300,7 +4300,7 @@ bool NPC::CheckHandin( std::string log_handin_prefix = fmt::format("[{}] -> [{}]", c->GetCleanName(), GetCleanName()); // if the npc is a multi-quest npc, we want to re-use our previously set hand-in bucket - if (!m_handin_started && m_hand_in.npc && m_hand_in.npc->IsMultiQuestEnabled()) { + if (!m_handin_started && IsMultiQuestEnabled()) { h = m_hand_in; } @@ -4417,7 +4417,6 @@ bool NPC::CheckHandin( // save original items for logging m_hand_in.original_items = m_hand_in.items; m_hand_in.original_money = m_hand_in.money; - m_hand_in.npc = this; } // check if npc is guildmaster @@ -4695,8 +4694,8 @@ void NPC::ReturnHandinItems(Client *c) m_has_processed_handin_return = returned_handin; - if (returned_handin && m_hand_in.npc) { - m_hand_in.npc->Say( + if (returned_handin) { + Say( fmt::format( "I have no need for this {}, you can have it back.", GetCleanName() @@ -4714,13 +4713,13 @@ void NPC::ReturnHandinItems(Client *c) if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) { auto e = PlayerEvent::HandinEvent{ - .npc_id = m_hand_in.npc->GetNPCTypeID(), - .npc_name = m_hand_in.npc->GetCleanName(), + .npc_id = GetNPCTypeID(), + .npc_name = GetCleanName(), .handin_items = handin_items, .handin_money = handin_money, .return_items = return_items, .return_money = return_money, - .is_quest_handin = parse->HasQuestSub(m_hand_in.npc->GetNPCTypeID(), EVENT_TRADE) + .is_quest_handin = parse->HasQuestSub(GetNPCTypeID(), EVENT_TRADE) }; RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); @@ -4731,7 +4730,11 @@ void NPC::ResetHandin() { m_has_processed_handin_return = false; m_handin_started = false; - if (m_hand_in.npc && !m_hand_in.npc->IsMultiQuestEnabled()) { + if (!IsMultiQuestEnabled()) { + for (auto &i: m_hand_in.items) { + safe_delete(i.item); + } + m_hand_in = {}; } } diff --git a/zone/npc.h b/zone/npc.h index ca9b61a6b0..76daf4c5ee 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -595,7 +595,6 @@ class NPC : public Mob std::vector items = {}; // items can be removed from this set as successful handins are made HandinMoney original_money = {}; // this is what the player originally handed in, never modified HandinMoney money = {}; // money can be removed from this set as successful handins are made - NPC *npc = nullptr; // the NPC the player is handing in to }; protected: From 0afa5f7099aece3c862a726ae17407aeb1a12775 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Wed, 23 Oct 2024 22:37:05 -0400 Subject: [PATCH 39/44] Update main.cpp --- zone/main.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/zone/main.cpp b/zone/main.cpp index b4cbf289b4..ff01f524a2 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -716,30 +716,38 @@ bool CheckForCompatibleQuestPlugins() { const std::vector& directories = { "lua_modules", "plugins" }; - bool lua_found = true; - bool perl_found = true; + bool lua_found = false; + bool perl_found = false; for (const auto& directory : directories) { for (const auto& file : fs::directory_iterator(path.GetServerPath() + "/" + directory)) { if (file.is_regular_file()) { - auto f = path.GetServerPath() + "/" + file.path().string(); + auto f = file.path().string(); if (File::Exists(f)) { auto r = File::GetContents(std::filesystem::path{ f }.string()); - if (!Strings::Contains(r.contents, "CheckHandin")) { + if (Strings::Contains(r.contents, "CheckHandin")) { if (Strings::EqualFold(directory, "lua_modules")) { - lua_found = false; - LogError("Failed to find CheckHandin in lua_modules"); + lua_found = true; } else if (Strings::EqualFold(directory, "plugins")) { - perl_found = false; - LogError("Failed to find CheckHandin in plugins"); + perl_found = true; } - break; + if (lua_found && perl_found) { + return true; + } } } } } } + if (!lua_found) { + LogError("Failed to find CheckHandin in lua_modules"); + } + + if (!perl_found) { + LogError("Failed to find CheckHandin in plugins"); + } + return lua_found && perl_found; } From 26d819992bae9860d4b265b1907e14dc89bb0e3f Mon Sep 17 00:00:00 2001 From: Akkadius Date: Thu, 24 Oct 2024 00:04:35 -0500 Subject: [PATCH 40/44] Fix client response --- zone/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index 8193106f75..e2d58c7116 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4698,7 +4698,7 @@ void NPC::ReturnHandinItems(Client *c) Say( fmt::format( "I have no need for this {}, you can have it back.", - GetCleanName() + c->GetCleanName() ).c_str() ); } From 8d9451ae620ad680954abf19ec670bd7a1de61c8 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Thu, 24 Oct 2024 01:49:55 -0500 Subject: [PATCH 41/44] logic simplification --- zone/npc.cpp | 94 ++++++++++++++++++++++++++++------------------------ zone/npc.h | 2 +- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index e2d58c7116..dcdc4402e5 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4350,7 +4350,8 @@ bool NPC::CheckHandin( HandinEntry{ .item_id = std::to_string(i->GetItem()->ID), .count = std::max(static_cast(i->GetCharges()), static_cast(1)), - .item = i->Clone() + .item = i->Clone(), + .is_multiquest_item = false } ); } @@ -4358,26 +4359,47 @@ bool NPC::CheckHandin( // compare hand-in to required, the item_id can be in any slot bool met_requirement = true; + + // money + bool money_met = h.money.platinum == r.money.platinum + && h.money.gold == r.money.gold + && h.money.silver == r.money.silver + && h.money.copper == r.money.copper; + + // items + bool items_met = true; if (h.items.size() == r.items.size() && !h.items.empty() && !r.items.empty()) { - for (auto &h_item: h.items) { - bool found = false; - for (const auto &r_item: r.items) { + for (const auto &r_item: r.items) { + bool found = false; + for (auto &h_item: h.items) { if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) { found = true; + LogNpcHandinDetail( + "{} >>>> Found required item [{}] ({}) count [{}]", + log_handin_prefix, + h_item.item->GetItem()->Name, + h_item.item_id, + h_item.count + ); break; } } if (!found) { - met_requirement = false; + items_met = false; break; } } } + else if (h.items.empty() && r.items.empty()) { + items_met = true; + } else { - met_requirement = false; + items_met = false; } + met_requirement = money_met && items_met; + // multi-quest if (IsMultiQuestEnabled()) { for (auto &h_item: h.items) { @@ -4400,16 +4422,6 @@ bool NPC::CheckHandin( ); } - // compare hand-in money to required - if (met_requirement) { - if (h.money.platinum != r.money.platinum || - h.money.gold != r.money.gold || - h.money.silver != r.money.silver || - h.money.copper != r.money.copper) { - met_requirement = false; - } - } - // in-case we trigger CheckHand-in multiple times, only set these once if (!m_handin_started) { m_handin_started = true; @@ -4522,34 +4534,30 @@ bool NPC::CheckHandin( if (met_requirement) { std::vector log_entries = {}; for (const auto &h_item : h.items) { - auto it = std::find_if(r.items.begin(), r.items.end(), [&](const auto &r_item) { - return h_item.item_id == r_item.item_id && h_item.count == r_item.count; - }); - - if (it != r.items.end()) { - m_hand_in.items.erase( - std::remove_if( - m_hand_in.items.begin(), - m_hand_in.items.end(), - [&](const HandinEntry &i) { - bool removed = i.item_id == h_item.item_id && i.count == h_item.count; - if (removed) { - log_entries.emplace_back( - fmt::format( - "{} >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", - log_handin_prefix, - i.item->GetItem()->Name, - i.item_id, - i.count - ) - ); - } - return removed; + m_hand_in.items.erase( + std::remove_if( + m_hand_in.items.begin(), + m_hand_in.items.end(), + [&](const HandinEntry &i) { + bool removed = i.item_id == h_item.item_id + && i.count == h_item.count + && i.item->GetSerialNumber() == h_item.item->GetSerialNumber(); + if (removed) { + log_entries.emplace_back( + fmt::format( + "{} >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]", + log_handin_prefix, + i.item->GetItem()->Name, + i.item_id, + i.count + ) + ); } - ), - m_hand_in.items.end() - ); - } + return removed; + } + ), + m_hand_in.items.end() + ); } // log successful hand-in items diff --git a/zone/npc.h b/zone/npc.h index 76daf4c5ee..6bed1bf84d 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -580,7 +580,7 @@ class NPC : public Mob std::string item_id = "0"; uint16 count = 0; const EQ::ItemInstance *item = nullptr; - bool is_multiquest_item = false; + bool is_multiquest_item = false; // state }; struct HandinMoney { From 2c4e69e8431be53fd8f75566cdc7787cbbc1d309 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Thu, 24 Oct 2024 02:43:27 -0500 Subject: [PATCH 42/44] tweaks --- zone/npc.cpp | 60 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index dcdc4402e5..cdeab758dc 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4358,7 +4358,7 @@ bool NPC::CheckHandin( } // compare hand-in to required, the item_id can be in any slot - bool met_requirement = true; + bool requirement_met = true; // money bool money_met = h.money.platinum == r.money.platinum @@ -4398,7 +4398,7 @@ bool NPC::CheckHandin( items_met = false; } - met_requirement = money_met && items_met; + requirement_met = money_met && items_met; // multi-quest if (IsMultiQuestEnabled()) { @@ -4464,7 +4464,7 @@ bool NPC::CheckHandin( ); } else { Say("You are not a member of my guild. I will not train you!"); - met_requirement = false; + requirement_met = false; break; } } @@ -4472,9 +4472,9 @@ bool NPC::CheckHandin( // print current hand-in bucket LogNpcHandin( - "{} > Before processing hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + "{} > Before processing hand-in | requirement_met [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", log_handin_prefix, - met_requirement, + requirement_met, h.items.size(), h.money.platinum, h.money.gold, @@ -4531,7 +4531,7 @@ bool NPC::CheckHandin( item_count++; } - if (met_requirement) { + if (requirement_met) { std::vector log_entries = {}; for (const auto &h_item : h.items) { m_hand_in.items.erase( @@ -4567,10 +4567,26 @@ bool NPC::CheckHandin( } } + // decrement successful hand-in money from current hand-in bucket + if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { + LogNpcHandin( + "{} Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", + log_handin_prefix, + h.money.platinum, + h.money.gold, + h.money.silver, + h.money.copper + ); + m_hand_in.money.platinum -= h.money.platinum; + m_hand_in.money.gold -= h.money.gold; + m_hand_in.money.silver -= h.money.silver; + m_hand_in.money.copper -= h.money.copper; + } + LogNpcHandin( - "{} > End of hand-in | met_requirement [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", + "{} > End of hand-in | requirement_met [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]", log_handin_prefix, - met_requirement, + requirement_met, m_hand_in.items.size(), m_hand_in.money.platinum, m_hand_in.money.gold, @@ -4586,25 +4602,9 @@ bool NPC::CheckHandin( i.count ); } - - // decrement successful hand-in money from current hand-in bucket - if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) { - LogNpcHandin( - "{} Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]", - log_handin_prefix, - h.money.platinum, - h.money.gold, - h.money.silver, - h.money.copper - ); - m_hand_in.money.platinum -= h.money.platinum; - m_hand_in.money.gold -= h.money.gold; - m_hand_in.money.silver -= h.money.silver; - m_hand_in.money.copper -= h.money.copper; - } } - return met_requirement; + return requirement_met; } void NPC::ReturnHandinItems(Client *c) @@ -4672,11 +4672,11 @@ void NPC::ReturnHandinItems(Client *c) ); // check if any money was handed in - if (m_hand_in.money.platinum > 0 || - m_hand_in.money.gold > 0 || - m_hand_in.money.silver > 0 || - m_hand_in.money.copper > 0 - ) { + bool money_handed = m_hand_in.money.platinum > 0 || + m_hand_in.money.gold > 0 || + m_hand_in.money.silver > 0 || + m_hand_in.money.copper > 0; + if (money_handed) { c->AddMoneyToPP( m_hand_in.money.copper, m_hand_in.money.silver, From 14b7901f51f15cbf65d9142cd6da3de071ab2c09 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Thu, 24 Oct 2024 03:15:43 -0500 Subject: [PATCH 43/44] tests --- zone/cli/npc_handins.cpp | 51 +++++++++++++++++++ zone/client.cpp | 104 +++++++++++++++++++++++++++++++++++++++ zone/client.h | 1 + zone/client_packet.cpp | 2 + zone/main.cpp | 5 +- zone/zone_cli.cpp | 7 +++ zone/zone_cli.h | 2 + 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 zone/cli/npc_handins.cpp diff --git a/zone/cli/npc_handins.cpp b/zone/cli/npc_handins.cpp new file mode 100644 index 0000000000..689fb23466 --- /dev/null +++ b/zone/cli/npc_handins.cpp @@ -0,0 +1,51 @@ +#include "../../common/http/httplib.h" +#include "../../common/eqemu_logsys.h" +#include "../../common/platform.h" +#include "../zone.h" +#include "../client.h" +#include "../../common/net/eqstream.h" + +extern Zone *zone; + +void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description) +{ + if (cmd[{"-h", "--help"}]) { + return; + } + + RegisterExecutablePlatform(EQEmuExePlatform::ExePlatformZoneSidecar); + + LogInfo("----------------------------------------"); + LogInfo("Booting test zone for NPC handins"); + LogInfo("----------------------------------------"); + + Zone::Bootup(ZoneID("qrg"), 0, false); + zone->StopShutdownTimer(); + + entity_list.Process(); + entity_list.MobProcess(); + + LogInfo("----------------------------------------"); + LogInfo("Done booting test zone"); + LogInfo("----------------------------------------"); + + auto c = Client(); + auto npc_type = content_db.LoadNPCTypesData(754008); + if (npc_type) { + auto npc = new NPC( + npc_type, + nullptr, + glm::vec4(0, 0, 0, 0), + GravityBehavior::Water + ); + + entity_list.AddNPC(npc); + + LogInfo("Spawned NPC [{}]", npc->GetCleanName()); + + LogInfo("Spawned client [{}]", c.GetCleanName()); + + + + } +} diff --git a/zone/client.cpp b/zone/client.cpp index 8dd970c544..a0e2ab0175 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -87,6 +87,110 @@ extern PetitionList petition_list; void UpdateWindowTitle(char* iNewTitle); +Client::Client() : Mob( + "No name", // in_name + "", // in_lastname + 0, // in_cur_hp + 0, // in_max_hp + Gender::Male, // in_gender + Race::Doug, // in_race + Class::None, // in_class + BodyType::Humanoid, // in_bodytype + Deity::Unknown, // in_deity + 0, // in_level + 0, // in_npctype_id + 0.0f, // in_size + 0.7f, // in_runspeed + glm::vec4(), // position + 0, // in_light + 0xFF, // in_texture + 0xFF, // in_helmtexture + 0, // in_ac + 0, // in_atk + 0, // in_str + 0, // in_sta + 0, // in_dex + 0, // in_agi + 0, // in_int + 0, // in_wis + 0, // in_cha + 0, // in_haircolor + 0, // in_beardcolor + 0, // in_eyecolor1 + 0, // in_eyecolor2 + 0, // in_hairstyle + 0, // in_luclinface + 0, // in_beard + 0, // in_drakkin_heritage + 0, // in_drakkin_tattoo + 0, // in_drakkin_details + EQ::TintProfile(), // in_armor_tint + 0xff, // in_aa_title + 0, // in_see_invis + 0, // in_see_invis_undead + 0, // in_see_hide + 0, // in_see_improved_hide + 0, // in_hp_regen + 0, // in_mana_regen + 0, // in_qglobal + 0, // in_maxlevel + 0, // in_scalerate + 0, // in_armtexture + 0, // in_bracertexture + 0, // in_handtexture + 0, // in_legtexture + 0, // in_feettexture + 0, // in_usemodel + false, // in_always_aggros_foes + 0, // in_heroic_strikethrough + false // in_keeps_sold_items +), + hpupdate_timer(2000), + camp_timer(29000), + process_timer(100), + consume_food_timer(CONSUMPTION_TIMER), + zoneinpacket_timer(1000), + linkdead_timer(RuleI(Zone, ClientLinkdeadMS)), + dead_timer(2000), + global_channel_timer(1000), + fishing_timer(8000), + endupkeep_timer(1000), + autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), + m_client_npc_aggro_scan_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)), + m_client_zone_wide_full_position_update_timer(5 * 60 * 1000), + tribute_timer(Tribute_duration), + proximity_timer(ClientProximity_interval), + TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), + charm_update_timer(6000), + rest_timer(1), + pick_lock_timer(1000), + charm_class_attacks_timer(3000), + charm_cast_timer(3500), + qglobal_purge_timer(30000), + TrackingTimer(2000), + RespawnFromHoverTimer(0), + merc_timer(RuleI(Mercs, UpkeepIntervalMS)), + ItemQuestTimer(500), + anon_toggle_timer(250), + afk_toggle_timer(250), + helm_toggle_timer(250), + aggro_meter_timer(AGGRO_METER_UPDATE_MS), + m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number + m_ZoneSummonLocation(-2.0f, -2.0f, -2.0f, -2.0f), + m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), + m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), + last_region_type(RegionTypeUnsupported), + m_dirtyautohaters(false), + m_position_update_timer(10000), + consent_throttle_timer(2000), + tmSitting(0), + parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)), + lazy_load_bank_check_timer(1000), + bandolier_throttle_timer(0) +{ + return this; +} + Client::Client(EQStreamInterface *ieqs) : Mob( "No name", // in_name "", // in_lastname diff --git a/zone/client.h b/zone/client.h index e087e0bbe7..17f1d61f3a 100644 --- a/zone/client.h +++ b/zone/client.h @@ -239,6 +239,7 @@ class Client : public Mob #include "client_packet.h" Client(EQStreamInterface * ieqs); + Client(); ~Client(); void ReconnectUCS(); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index c880e310f8..1f2b7c7c77 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -17226,3 +17226,5 @@ void Client::Handle_OP_ShopRetrieveParcel(const EQApplicationPacket *app) auto parcel_in = (ParcelRetrieve_Struct *)app->pBuffer; DoParcelRetrieve(*parcel_in); } + + diff --git a/zone/main.cpp b/zone/main.cpp index ff01f524a2..01fafea663 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -297,7 +297,7 @@ int main(int argc, char **argv) } // command handler - if (ZoneCLI::RanConsoleCommand(argc, argv) && !ZoneCLI::RanSidecarCommand(argc, argv)) { + if (ZoneCLI::RanConsoleCommand(argc, argv) && !(ZoneCLI::RanSidecarCommand(argc, argv) || ZoneCLI::RanTestCommand(argc, argv))) { LogSys.EnableConsoleLogging(); ZoneCLI::CommandHandler(argc, argv); } @@ -480,7 +480,8 @@ int main(int argc, char **argv) worldserver.SetScheduler(&event_scheduler); // sidecar command handler - if (ZoneCLI::RanConsoleCommand(argc, argv) && ZoneCLI::RanSidecarCommand(argc, argv)) { + if (ZoneCLI::RanConsoleCommand(argc, argv) + && (ZoneCLI::RanSidecarCommand(argc, argv) || ZoneCLI::RanTestCommand(argc, argv))) { ZoneCLI::CommandHandler(argc, argv); } diff --git a/zone/zone_cli.cpp b/zone/zone_cli.cpp index 5739aa6ca1..a1db754ca8 100644 --- a/zone/zone_cli.cpp +++ b/zone/zone_cli.cpp @@ -12,6 +12,11 @@ bool ZoneCLI::RanSidecarCommand(int argc, char **argv) return argc > 1 && (strstr(argv[1], "sidecar:") != nullptr); } +bool ZoneCLI::RanTestCommand(int argc, char **argv) +{ + return argc > 1 && (strstr(argv[1], "tests:") != nullptr); +} + void ZoneCLI::CommandHandler(int argc, char **argv) { if (argc == 1) { return; } @@ -25,8 +30,10 @@ void ZoneCLI::CommandHandler(int argc, char **argv) // Register commands function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp; + function_map["tests:npc-handins"] = &ZoneCLI::NpcHandins; EQEmuCommand::HandleMenu(function_map, cmd, argc, argv); } +#include "cli/npc_handins.cpp" #include "cli/sidecar_serve_http.cpp" diff --git a/zone/zone_cli.h b/zone/zone_cli.h index e9ed183672..cb73078aeb 100644 --- a/zone/zone_cli.h +++ b/zone/zone_cli.h @@ -7,8 +7,10 @@ class ZoneCLI { public: static void CommandHandler(int argc, char **argv); static void SidecarServeHttp(int argc, char **argv, argh::parser &cmd, std::string &description); + static void NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description); static bool RanConsoleCommand(int argc, char **argv); static bool RanSidecarCommand(int argc, char **argv); + static bool RanTestCommand(int argc, char **argv); }; From d155896b3ae9eaa8ffff634958606bc7a38c2e18 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Thu, 24 Oct 2024 04:57:54 -0500 Subject: [PATCH 44/44] Automated testing --- zone/cli/npc_handins.cpp | 400 ++++++++++++++++++++++++++++++++++++++- zone/client.cpp | 214 ++++++++++++++++++++- zone/client.h | 2 +- zone/inventory.cpp | 7 +- zone/npc.cpp | 6 +- zone/npc.h | 31 +-- 6 files changed, 635 insertions(+), 25 deletions(-) diff --git a/zone/cli/npc_handins.cpp b/zone/cli/npc_handins.cpp index 689fb23466..d4c10b84c8 100644 --- a/zone/cli/npc_handins.cpp +++ b/zone/cli/npc_handins.cpp @@ -29,8 +29,8 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string & LogInfo("Done booting test zone"); LogInfo("----------------------------------------"); - auto c = Client(); - auto npc_type = content_db.LoadNPCTypesData(754008); + Client *c = new Client(); + auto npc_type = content_db.LoadNPCTypesData(754008); if (npc_type) { auto npc = new NPC( npc_type, @@ -42,10 +42,404 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string & entity_list.AddNPC(npc); LogInfo("Spawned NPC [{}]", npc->GetCleanName()); + LogInfo("Spawned client [{}]", c->GetCleanName()); - LogInfo("Spawned client [{}]", c.GetCleanName()); + struct HandinEntry { + std::string item_id = "0"; + uint16 count = 0; + const EQ::ItemInstance *item = nullptr; + bool is_multiquest_item = false; // state + }; + struct HandinMoney { + uint32 platinum = 0; + uint32 gold = 0; + uint32 silver = 0; + uint32 copper = 0; + }; + struct Handin { + std::vector items = {}; // items can be removed from this set as successful handins are made + HandinMoney money = {}; // money can be removed from this set as successful handins are made + }; + struct TestCase { + std::string description = ""; + Handin hand_in; + Handin required; + Handin returned; + bool handin_check_result; + }; + + std::vector test_cases = { + TestCase{ + .description = "Test basic cloth-cap hand-in", + .hand_in = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test basic cloth-cap hand-in failure", + .hand_in = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .returned = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test basic cloth-cap hand-in failure from handing in too many", + .hand_in = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .returned = { + .items = { + HandinEntry{.item_id = "9997", .count = 1}, + HandinEntry{.item_id = "9997", .count = 1}, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in money", + .hand_in = { + .items = {}, + .money = {.platinum = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 1}, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in money, but not enough", + .hand_in = { + .items = {}, + .money = {.platinum = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 100}, + }, + .returned = {}, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in money, but not enough of any type", + .hand_in = { + .items = {}, + .money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 100, .gold = 100, .silver = 100, .copper = 100}, + }, + .returned = {}, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in money of all types", + .hand_in = { + .items = {}, + .money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1}, + }, + .required = { + .items = {}, + .money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1}, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in platinum with items with success", + .hand_in = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 1}, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 1}, + }, + .returned = {}, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in platinum with items with failure", + .hand_in = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 1}, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 100}, + }, + .returned = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test returning money and items", + .hand_in = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .required = { + .items = { + HandinEntry{.item_id = "1001", .count = 1}, + }, + .money = {.platinum = 100}, + }, + .returned = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test returning money", + .hand_in = { + .items = {}, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .required = { + .items = {}, + .money = {.platinum = 100}, + }, + .returned = { + .items = { + }, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in many items of the same required item", + .hand_in = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = {}, + }, + .returned = { + .items = { + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + HandinEntry{.item_id = "1007", .count = 1}, + }, + .money = { + .platinum = 1, + .gold = 666, + .silver = 234, + .copper = 444, + }, + }, + .handin_check_result = false, + }, + TestCase{ + .description = "Test handing in item of a stack", + .hand_in = { + .items = { + HandinEntry{.item_id = "13005", .count = 20}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "13005", .count = 20}, + }, + .money = {}, + }, + .returned = { + .items = {}, + .money = {}, + }, + .handin_check_result = true, + }, + TestCase{ + .description = "Test handing in item of a stack but not enough", + .hand_in = { + .items = { + HandinEntry{.item_id = "13005", .count = 10}, + }, + .money = {}, + }, + .required = { + .items = { + HandinEntry{.item_id = "13005", .count = 20}, + }, + .money = {}, + }, + .returned = { + .items = { + HandinEntry{.item_id = "13005", .count = 10}, + }, + .money = {}, + }, + .handin_check_result = false, + }, + }; + + std::map hand_ins; + std::map required; + std::vector items; + + // turn this on to see debugging output + LogSys.log_settings[Logs::NpcHandin].log_to_console = 0; + + for (auto &test_case: test_cases) { + hand_ins.clear(); + required.clear(); + items.clear(); + + for (auto &hand_in: test_case.hand_in.items) { + hand_ins[hand_in.item_id] = hand_in.count; + auto item_id = Strings::ToInt(hand_in.item_id); + EQ::ItemInstance *inst = database.CreateItem(item_id); + inst->SetCharges(hand_in.count); + items.push_back(inst); + } + + // money + if (test_case.hand_in.money.platinum > 0) { + hand_ins["platinum"] = test_case.hand_in.money.platinum; + } + if (test_case.hand_in.money.gold > 0) { + hand_ins["gold"] = test_case.hand_in.money.gold; + } + if (test_case.hand_in.money.silver > 0) { + hand_ins["silver"] = test_case.hand_in.money.silver; + } + if (test_case.hand_in.money.copper > 0) { + hand_ins["copper"] = test_case.hand_in.money.copper; + } + + for (auto &req: test_case.required.items) { + required[req.item_id] = req.count; + } + + // money + if (test_case.required.money.platinum > 0) { + required["platinum"] = test_case.required.money.platinum; + } + if (test_case.required.money.gold > 0) { + required["gold"] = test_case.required.money.gold; + } + if (test_case.required.money.silver > 0) { + required["silver"] = test_case.required.money.silver; + } + if (test_case.required.money.copper > 0) { + required["copper"] = test_case.required.money.copper; + } + + auto result = npc->CheckHandin(c, hand_ins, required, items); + if (result != test_case.handin_check_result) { + LogError("FAIL [{}]", test_case.description); + // print out the hand-ins + LogError("Hand-ins >"); + for (auto &hand_in: hand_ins) { + LogError(" > Item [{}] count [{}]", hand_in.first, hand_in.second); + } + LogError("Required >"); + for (auto &req: required) { + LogError(" > Item [{}] count [{}]", req.first, req.second); + } + LogError("Expected [{}] got [{}]", test_case.handin_check_result, result); + } + else { + LogInfo("PASS [{}]", test_case.description); + } + + auto returned = npc->ReturnHandinItems(c); + + // assert that returned items are expected + for (auto &item: test_case.returned.items) { + auto found = false; + for (auto &ret: returned.items) { + if (ret.item_id == item.item_id) { + found = true; + break; + } + } + if (!found) { + LogError("Returned item [{}] not expected", item.item_id); + } + } + + npc->ResetHandin(); + } } } diff --git a/zone/client.cpp b/zone/client.cpp index a0e2ab0175..7cf0963b33 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -87,6 +87,7 @@ extern PetitionList petition_list; void UpdateWindowTitle(char* iNewTitle); +// client constructor purely for testing / mocking Client::Client() : Mob( "No name", // in_name "", // in_lastname @@ -188,7 +189,210 @@ Client::Client() : Mob( lazy_load_bank_check_timer(1000), bandolier_throttle_timer(0) { - return this; + eqs = nullptr; + for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) { + SetFilter(client_filter, FilterShow); + } + + cheat_manager.SetClient(this); + mMovementManager->AddClient(this); + character_id = 0; + conn_state = NoPacketsReceived; + client_data_loaded = false; + berserk = false; + dead = false; + client_state = CLIENT_CONNECTING; + SetTrader(false); + Haste = 0; + SetCustomerID(0); + SetTraderID(0); + TrackingID = 0; + WID = 0; + account_id = 0; + admin = AccountStatus::Player; + lsaccountid = 0; + guild_id = GUILD_NONE; + guildrank = 0; + guild_tribute_opt_in = 0; + SetGuildListDirty(false); + GuildBanker = false; + memset(lskey, 0, sizeof(lskey)); + strcpy(account_name, ""); + tellsoff = false; + last_reported_mana = 0; + last_reported_endurance = 0; + last_reported_endurance_percent = 0; + last_reported_mana_percent = 0; + gm_hide_me = false; + AFK = false; + LFG = false; + LFGFromLevel = 0; + LFGToLevel = 0; + LFGMatchFilter = false; + LFGComments[0] = '\0'; + LFP = false; + gmspeed = 0; + gminvul = false; + playeraction = 0; + SetTarget(0); + auto_attack = false; + auto_fire = false; + runmode = false; + linkdead_timer.Disable(); + zonesummon_id = 0; + zonesummon_ignorerestrictions = 0; + bZoning = false; + m_lock_save_position = false; + zone_mode = ZoneUnsolicited; + casting_spell_id = 0; + npcflag = false; + npclevel = 0; + fishing_timer.Disable(); + dead_timer.Disable(); + camp_timer.Disable(); + autosave_timer.Disable(); + GetMercTimer()->Disable(); + instalog = false; + m_pp.autosplit = false; + // initialise haste variable + m_tradeskill_object = nullptr; + delaytimer = false; + PendingRezzXP = -1; + PendingRezzDBID = 0; + PendingRezzSpellID = 0; + numclients++; + // emuerror; + UpdateWindowTitle(nullptr); + horseId = 0; + tgb = false; + tribute_master_id = 0xFFFFFFFF; + tribute_timer.Disable(); + task_state = nullptr; + TotalSecondsPlayed = 0; + keyring.clear(); + bind_sight_target = nullptr; + p_raid_instance = nullptr; + mercid = 0; + mercSlot = 0; + InitializeMercInfo(); + SetMerc(0); + if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true, false); + dynamiczone_removal_timer.Disable(); + + //for good measure: + memset(&m_pp, 0, sizeof(m_pp)); + memset(&m_epp, 0, sizeof(m_epp)); + PendingTranslocate = false; + PendingSacrifice = false; + sacrifice_caster_id = 0; + controlling_boat_id = 0; + controlled_mob_id = 0; + qGlobals = nullptr; + + if (!RuleB(Character, PerCharacterQglobalMaxLevel) && !RuleB(Character, PerCharacterBucketMaxLevel)) { + SetClientMaxLevel(0); + } else if (RuleB(Character, PerCharacterQglobalMaxLevel)) { + SetClientMaxLevel(GetCharMaxLevelFromQGlobal()); + } else if (RuleB(Character, PerCharacterBucketMaxLevel)) { + SetClientMaxLevel(GetCharMaxLevelFromBucket()); + } + + KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS)); + GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS)); + AttemptedMessages = 0; + TotalKarma = 0; + m_ClientVersion = EQ::versions::ClientVersion::Unknown; + m_ClientVersionBit = 0; + AggroCount = 0; + ooc_regen = false; + AreaHPRegen = 1.0f; + AreaManaRegen = 1.0f; + AreaEndRegen = 1.0f; + XPRate = 100; + current_endurance = 0; + + CanUseReport = true; + aa_los_them_mob = nullptr; + los_status = false; + los_status_facing = false; + HideCorpseMode = HideCorpseNone; + PendingGuildInvitation = false; + + InitializeBuffSlots(); + + adventure_request_timer = nullptr; + adventure_create_timer = nullptr; + adventure_leave_timer = nullptr; + adventure_door_timer = nullptr; + adv_requested_data = nullptr; + adventure_stats_timer = nullptr; + adventure_leaderboard_timer = nullptr; + adv_data = nullptr; + adv_requested_theme = LDoNThemes::Unused; + adv_requested_id = 0; + adv_requested_member_count = 0; + + for(int i = 0; i < XTARGET_HARDCAP; ++i) + { + XTargets[i].Type = Auto; + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + XTargets[i].dirty = false; + } + MaxXTargets = 5; + XTargetAutoAddHaters = true; + m_autohatermgr.SetOwner(this, nullptr, nullptr); + m_activeautohatermgr = &m_autohatermgr; + + initial_respawn_selection = 0; + alternate_currency_loaded = false; + + interrogateinv_flag = false; + + trapid = 0; + + for (int i = 0; i < InnateSkillMax; ++i) + m_pp.InnateSkills[i] = InnateDisabled; + + temp_pvp = false; + + moving = false; + + environment_damage_modifier = 0; + invulnerable_environment_damage = false; + + // rate limiter + m_list_task_timers_rate_limit.Start(1000); + + // gm + SetDisplayMobInfoWindow(true); + SetDevToolsEnabled(true); + + bot_owner_options[booDeathMarquee] = false; + bot_owner_options[booStatsUpdate] = false; + bot_owner_options[booSpawnMessageSay] = false; + bot_owner_options[booSpawnMessageTell] = true; + bot_owner_options[booSpawnMessageClassSpecific] = true; + bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend); + bot_owner_options[booBuffCounter] = false; + bot_owner_options[booMonkWuMessage] = false; + + m_parcel_platinum = 0; + m_parcel_gold = 0; + m_parcel_silver = 0; + m_parcel_copper = 0; + m_parcel_count = 0; + m_parcel_enabled = true; + m_parcel_merchant_engaged = false; + m_parcels.clear(); + + m_buyer_id = 0; + + SetBotPulling(false); + SetBotPrecombat(false); + + AI_Init(); + } Client::Client(EQStreamInterface *ieqs) : Mob( @@ -603,9 +807,11 @@ Client::~Client() { zone->RemoveAuth(GetName(), lskey); //let the stream factory know were done with this stream - eqs->Close(); - eqs->ReleaseFromUse(); - safe_delete(eqs); + if (eqs) { + eqs->Close(); + eqs->ReleaseFromUse(); + safe_delete(eqs); + } UninitializeBuffSlots(); } diff --git a/zone/client.h b/zone/client.h index 17f1d61f3a..f4c75eb0fe 100644 --- a/zone/client.h +++ b/zone/client.h @@ -239,7 +239,7 @@ class Client : public Mob #include "client_packet.h" Client(EQStreamInterface * ieqs); - Client(); + Client(); // mocking ~Client(); void ReconnectUCS(); diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 30f4c31daf..def91cd878 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -3173,8 +3173,13 @@ uint32 Client::GetEquipmentColor(uint8 material_slot) const // Send an item packet (including all subitems of the item) void Client::SendItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPacketType packet_type) { - if (!inst) + if (!inst) { + return; + } + + if (!eqs) { return; + } if (packet_type != ItemPacketMerchant) { if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { diff --git a/zone/npc.cpp b/zone/npc.cpp index cdeab758dc..78bbb4c37a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -4607,7 +4607,7 @@ bool NPC::CheckHandin( return requirement_met; } -void NPC::ReturnHandinItems(Client *c) +NPC::Handin NPC::ReturnHandinItems(Client *c) { // player event std::vector handin_items; @@ -4628,6 +4628,8 @@ void NPC::ReturnHandinItems(Client *c) } } + auto returned = m_hand_in; + // check if any money was handed in if (m_hand_in.original_money.platinum > 0 || m_hand_in.original_money.gold > 0 || @@ -4732,6 +4734,8 @@ void NPC::ReturnHandinItems(Client *c) RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e); } + + return returned; } void NPC::ResetHandin() diff --git a/zone/npc.h b/zone/npc.h index 6bed1bf84d..3f6d501165 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -561,21 +561,6 @@ class NPC : public Mob bool CanPetTakeItem(const EQ::ItemInstance *inst); - // NPC Hand-in - bool IsMultiQuestEnabled() { return m_multiquest_enabled; } - void MultiQuestEnable() { m_multiquest_enabled = true; } - bool IsGuildmasterForClient(Client *c); - bool CheckHandin( - Client *c, - std::map handin, - std::map required, - std::vector items - ); - void ReturnHandinItems(Client *c); - void ResetHandin(); - bool HasProcessedHandinReturn() { return m_has_processed_handin_return; } - bool HandinStarted() { return m_handin_started; } - struct HandinEntry { std::string item_id = "0"; uint16 count = 0; @@ -597,6 +582,22 @@ class NPC : public Mob HandinMoney money = {}; // money can be removed from this set as successful handins are made }; + // NPC Hand-in + bool IsMultiQuestEnabled() { return m_multiquest_enabled; } + void MultiQuestEnable() { m_multiquest_enabled = true; } + bool IsGuildmasterForClient(Client *c); + bool CheckHandin( + Client *c, + std::map handin, + std::map required, + std::vector items + ); + Handin ReturnHandinItems(Client *c); + void ResetHandin(); + bool HasProcessedHandinReturn() { return m_has_processed_handin_return; } + bool HandinStarted() { return m_handin_started; } + + protected: void HandleRoambox();