From f97cc7cdec1ad7f88606067b2501d2eaf5774405 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 14 Jun 2020 19:21:46 -0400 Subject: [PATCH] Add expedition event loot api Add SetLootEventByNPCTypeID and SetLootEventBySpawnID quest apis These associate events with npcs or entities inside the dz to prevent them from being looted by characters that didn't receive the event lockout from the current expedition. This fixes an exploit that allowed a player that already had a lockout from another expedition being added to loot after the event is complete --- zone/corpse.cpp | 15 ++++++ zone/expedition.cpp | 93 ++++++++++++++++++++++++++++++++++-- zone/expedition.h | 10 +++- zone/expedition_database.cpp | 18 ------- zone/expedition_database.h | 1 - zone/lua_expedition.cpp | 24 ++++++++++ zone/lua_expedition.h | 4 ++ zone/questmgr.cpp | 4 +- zone/zone.cpp | 2 +- 9 files changed, 143 insertions(+), 28 deletions(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index e0e1d1992..9bdedc492 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -38,6 +38,7 @@ Child of the Mob class. #include "corpse.h" #include "entity.h" +#include "expedition.h" #include "groups.h" #include "mob.h" #include "raids.h" @@ -1275,6 +1276,20 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) return; } + if (zone && zone->GetInstanceID() != 0) + { + // expeditions may prevent looting based on client's lockouts + auto expedition = Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); + if (expedition && !expedition->CanClientLootCorpse(client, GetNPCTypeID(), GetID())) + { + client->MessageString(Chat::Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); + client->QueuePacket(app); + SendEndLootErrorPacket(client); + ResetLooter(); + delete inst; + return; + } + } // do we want this to have a fail option too? parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 3ad0ed113..3ed57a099 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -363,13 +363,17 @@ Expedition* Expedition::FindCachedExpeditionByID(uint32_t expedition_id) return nullptr; } -Expedition* Expedition::FindExpeditionByInstanceID(uint32_t instance_id) +Expedition* Expedition::FindCachedExpeditionByInstanceID(uint32_t instance_id) { - if (instance_id) + if (instance_id && zone) { - // ask database since it may have expired - auto expedition_id = ExpeditionDatabase::GetExpeditionIDFromInstanceID(instance_id); - return Expedition::FindCachedExpeditionByID(expedition_id); + for (const auto& cached_expedition : zone->expedition_cache) + { + if (cached_expedition.second->GetInstanceID() == instance_id) + { + return cached_expedition.second.get(); + } + } } return nullptr; } @@ -1891,3 +1895,82 @@ void Expedition::SetDzZoneInLocation(float x, float y, float z, float heading, b SendWorldDzLocationUpdate(ServerOP_ExpeditionDzZoneIn, location); } } + +bool Expedition::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id) +{ + if (client && m_dynamiczone.IsCurrentZoneDzInstance()) + { + // entity id takes priority, falls back to checking by npc type if not set + std::string event_name = GetLootEventBySpawnID(spawn_id); + if (event_name.empty()) + { + event_name = GetLootEventByNPCTypeID(npc_type_id); + } + + if (!event_name.empty()) + { + auto client_lockout = client->GetExpeditionLockout(GetName(), event_name); + if (!client_lockout || client_lockout->GetExpeditionUUID() != GetUUID()) + { + // client lockout not received in this expedition, prevent looting + LogExpeditions( + "Character [{}] denied looting npc [{}] spawn [{}] for lockout event [{}]", + client->CharacterID(), npc_type_id, spawn_id, event_name + ); + return false; + } + } + } + + return true; +} + +void Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name) +{ + if (npc_type_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + LogExpeditions("Setting loot event [{}] for npc type id [{}]", event_name, npc_type_id); + m_npc_loot_events[npc_type_id] = event_name; + } +} + +void Expedition::SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name) +{ + if (spawn_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + LogExpeditions("Setting loot event [{}] for entity id [{}]", event_name, spawn_id); + m_spawn_loot_events[spawn_id] = event_name; + } +} + +std::string Expedition::GetLootEventByNPCTypeID(uint32_t npc_type_id) +{ + std::string event_name; + + if (npc_type_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + auto it = m_npc_loot_events.find(npc_type_id); + if (it != m_npc_loot_events.end()) + { + event_name = it->second; + } + } + + return event_name; +} + +std::string Expedition::GetLootEventBySpawnID(uint32_t spawn_id) +{ + std::string event_name; + + if (spawn_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + auto it = m_spawn_loot_events.find(spawn_id); + if (it != m_spawn_loot_events.end()) + { + event_name = it->second; + } + } + + return event_name; +} diff --git a/zone/expedition.h b/zone/expedition.h index cbd8fe896..53cc32306 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -76,7 +76,7 @@ public: static Expedition* FindCachedExpeditionByCharacterID(uint32_t character_id); static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); - static Expedition* FindExpeditionByInstanceID(uint32_t instance_id); + static Expedition* FindCachedExpeditionByInstanceID(uint32_t instance_id); static void RemoveCharacterLockouts(std::string character_name, std::string expedition_name = {}, std::string event_name = {}); static void HandleWorldMessage(ServerPacket* pack); @@ -110,6 +110,12 @@ public: void RemoveLockout(const std::string& event_name); void SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db = false); + bool CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id); + std::string GetLootEventByNPCTypeID(uint32_t npc_id); + std::string GetLootEventBySpawnID(uint32_t spawn_id); + void SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name); + void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); + void SendClientExpeditionInfo(Client* client); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); @@ -184,6 +190,8 @@ private: ExpeditionMember m_leader; std::vector m_members; std::unordered_map m_lockouts; + std::unordered_map m_npc_loot_events; // only valid inside dz zone + std::unordered_map m_spawn_loot_events; // only valid inside dz zone }; #endif diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index d69e36263..9f77d537c 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -429,24 +429,6 @@ uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_i return expedition_id; } -uint32_t ExpeditionDatabase::GetExpeditionIDFromInstanceID(uint32_t instance_id) -{ - LogExpeditionsDetail("Getting expedition id for instance [{}]", instance_id); - - uint32_t expedition_id = 0; - auto query = fmt::format( - "SELECT id FROM expedition_details WHERE instance_id = {};", instance_id - ); - - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount() > 0) - { - auto row = results.begin(); - expedition_id = std::strtoul(row[0], nullptr, 10); - } - return expedition_id; -} - ExpeditionMember ExpeditionDatabase::GetExpeditionLeader(uint32_t expedition_id) { LogExpeditionsDetail("Getting expedition leader for expedition [{}]", expedition_id); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index a3c283351..184fdb1d7 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -59,7 +59,6 @@ namespace ExpeditionDatabase void DeletePendingLockouts(uint32_t character_id); void DeleteAllMembersPendingLockouts(const std::vector& members); uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); - uint32_t GetExpeditionIDFromInstanceID(uint32_t instance_id); ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); void InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index c07379410..c819b4092 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -66,6 +66,16 @@ luabind::object Lua_Expedition::GetLockouts(lua_State* L) { return lua_table; } +std::string Lua_Expedition::GetLootEventByNPCTypeID(uint32_t npc_type_id) { + Lua_Safe_Call_String(); + return self->GetLootEventByNPCTypeID(npc_type_id); +} + +std::string Lua_Expedition::GetLootEventBySpawnID(uint32_t spawn_id) { + Lua_Safe_Call_String(); + return self->GetLootEventBySpawnID(spawn_id); +} + uint32_t Lua_Expedition::GetMemberCount() { Lua_Safe_Call_Int(); return self->GetMemberCount(); @@ -140,6 +150,16 @@ void Lua_Expedition::SetLocked(bool lock_expedition) { self->SetLocked(lock_expedition, true); } +void Lua_Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, std::string event_name) { + Lua_Safe_Call_Void(); + self->SetLootEventByNPCTypeID(npc_type_id, event_name); +} + +void Lua_Expedition::SetLootEventBySpawnID(uint32_t spawn_id, std::string event_name) { + Lua_Safe_Call_Void(); + self->SetLootEventBySpawnID(spawn_id, event_name); +} + void Lua_Expedition::SetReplayLockoutOnMemberJoin(bool enable) { Lua_Safe_Call_Void(); self->SetReplayLockoutOnMemberJoin(enable, true); @@ -171,6 +191,8 @@ luabind::scope lua_register_expedition() { .def("GetInstanceID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetInstanceID) .def("GetLeaderName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetLeaderName) .def("GetLockouts", &Lua_Expedition::GetLockouts) + .def("GetLootEventByNPCTypeID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventByNPCTypeID) + .def("GetLootEventBySpawnID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventBySpawnID) .def("GetMemberCount", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetMemberCount) .def("GetMembers", &Lua_Expedition::GetMembers) .def("GetName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetName) @@ -184,6 +206,8 @@ luabind::scope lua_register_expedition() { .def("SetCompass", (void(Lua_Expedition::*)(uint32_t, float, float, float))&Lua_Expedition::SetCompass) .def("SetCompass", (void(Lua_Expedition::*)(std::string, float, float, float))&Lua_Expedition::SetCompass) .def("SetLocked", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetLocked) + .def("SetLootEventByNPCTypeID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventByNPCTypeID) + .def("SetLootEventBySpawnID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventBySpawnID) .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) .def("SetSafeReturn", (void(Lua_Expedition::*)(uint32_t, float, float, float, float))&Lua_Expedition::SetSafeReturn) .def("SetSafeReturn", (void(Lua_Expedition::*)(std::string, float, float, float, float))&Lua_Expedition::SetSafeReturn) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 09d09be97..09881bf0f 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -58,6 +58,8 @@ public: int GetInstanceID(); std::string GetLeaderName(); luabind::object GetLockouts(lua_State* L); + std::string GetLootEventByNPCTypeID(uint32_t npc_type_id); + std::string GetLootEventBySpawnID(uint32_t spawn_id); uint32_t GetMemberCount(); luabind::object GetMembers(lua_State* L); std::string GetName(); @@ -71,6 +73,8 @@ public: void SetCompass(uint32_t zone_id, float x, float y, float z); void SetCompass(std::string zone_name, float x, float y, float z); void SetLocked(bool lock_expedition); + void SetLootEventByNPCTypeID(uint32_t npc_type_id, std::string event_name); + void SetLootEventBySpawnID(uint32_t spawn_id, std::string event_name); void SetReplayLockoutOnMemberJoin(bool enable); void SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading); void SetSafeReturn(std::string zone_name, float x, float y, float z, float heading); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index a255d110f..0529bced0 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -4336,14 +4336,14 @@ Expedition* QuestManager::GetExpeditionByCharID(uint32 char_id) Expedition* QuestManager::GetExpeditionByInstanceID(uint32 instance_id) { - return Expedition::FindExpeditionByInstanceID(instance_id); + return Expedition::FindCachedExpeditionByInstanceID(instance_id); } Expedition* QuestManager::GetExpeditionForCurrentInstance() { if (zone && zone->GetInstanceID() != 0) { - return Expedition::FindExpeditionByInstanceID(zone->GetInstanceID()); + return Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); } return nullptr; } diff --git a/zone/zone.cpp b/zone/zone.cpp index da2555785..0630dff50 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1492,7 +1492,7 @@ bool Zone::Process() { if(Instance_Timer->Check()) { // if this is a dynamic zone instance notify system associated with it - Expedition* expedition = Expedition::FindExpeditionByInstanceID(GetInstanceID()); + Expedition* expedition = Expedition::FindCachedExpeditionByInstanceID(GetInstanceID()); if (expedition) { expedition->RemoveAllMembers(false); // entity list will teleport clients out immediately