/* EQEmu: EQEmulator Copyright (C) 2001-2026 EQEmu Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "common/emu_versions.h" #include "common/patches/client_version.h" #include "common/patches/IBuff.h" #include "common/patches/IMessage.h" #include "zone/client.h" #include "zone/mob.h" #include // store all _generic_ static functions for the different patches here namespace ClientPatch { using ClientList = std::unordered_map; template using ComponentGetter = Obj*(*)(const Client*); using SendPredicate = std::function; using MutatePacket = std::function&, Client*)>; template Component* GetClientComponent(const Client* client) { return GetComponent(client->GetClientVersion()).get(); } template requires std::is_member_function_pointer_v void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) { if (obj != nullptr) { std::unique_ptr app = std::invoke(fun, obj, std::forward(args)...); if (app) c->QueuePacket(app.get()); } } // packet generator queue functions inline auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = true) { return [=](Fun fun, ComponentGetter component, Args&&... args) requires std::is_member_function_pointer_v { std::array, EQ::versions::ClientVersionCount> build_packets; for (auto ent : entity_list.GetClientList() | std::views::values) { if (!ignore_sender || ent != sender) { auto& packet = build_packets.at(static_cast(ent->ClientVersion())); if (!packet) if (auto comp = component(ent); comp != nullptr) packet = std::invoke(fun, comp, std::forward(args)...); if (packet) ent->QueuePacket(packet.get(), ackreq, Client::CLIENT_CONNECTED); } } }; } inline auto QueueCloseClients( Mob* sender, bool ignore_sender = false, float distance = 200, Mob* skipped_mob = nullptr, bool is_ack_required = true, eqFilterType filter = FilterNone) { if (distance <= 0) distance = static_cast(zone->GetClientUpdateRange()); return [=](Fun fun, ComponentGetter component, Args&&... args) requires std::is_member_function_pointer_v { if (sender == nullptr) { QueueClients(sender, ignore_sender, is_ack_required)(fun, component, std::forward(args)...); } else { float distance_squared = distance * distance; std::array, EQ::versions::ClientVersionCount> build_packets; for (auto mob : sender->GetCloseMobList(distance) | std::views::values) { if (mob && mob->IsClient()) { Client* client = mob->CastToClient(); if ((!ignore_sender || client != sender) && client != skipped_mob && client->Connected() && client->ShouldGetPacket(sender, filter) && DistanceSquared(client->GetPosition(), sender->GetPosition()) < distance_squared) { auto& packet = build_packets.at(static_cast(client->ClientVersion())); if (!packet) if (auto comp = component(client); comp != nullptr) packet = std::invoke(fun, comp, std::forward(args)...); if (packet) client->QueuePacket(packet.get(), is_ack_required, Client::CLIENT_CONNECTED); } } } } }; } template void FastQueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) requires std::is_member_function_pointer_v { if (obj != nullptr) { std::unique_ptr app = std::invoke(fun, obj, std::forward(args)...); if (app) { // FastQueuePacket specifically takes lifetime management of packet, so release here EQApplicationPacket* packet = app.release(); c->FastQueuePacket(&packet); } } } inline auto QueueClientsByTarget(Mob* sender, bool ackreq, const SendPredicate& should_send, const MutatePacket& mutate) { return [=](Fun fun, ComponentGetter component, Args&&... args) requires std::is_member_function_pointer_v { if (sender != nullptr) { std::array, EQ::versions::ClientVersionCount> build_packets; for (auto c : entity_list.GetClientList() | std::views::values) { Mob* Target = c->GetTarget(); if (Target == sender && should_send(c)) { auto& packet = build_packets.at(static_cast(c->ClientVersion())); if (!packet) if (auto comp = component(c); comp != nullptr) packet = std::invoke(fun, comp, std::forward(args)...); mutate(packet, c); if (packet) c->QueuePacket(packet.get(), ackreq, Client::CLIENT_CONNECTED); } } } }; } // Helper functions to wrap the packet construction in sends template requires (sizeof...(Args) <= 9) void SendMessageString(Client* c, uint32_t type, uint32_t id, Args&&... args) { if constexpr (sizeof...(Args) == 0) { QueuePacket(c, &IMessage::Simple, GetClientComponent(c), type, id); } else { IMessage::FormattedArgs a = {args...}; QueuePacket(c, &IMessage::Formatted, GetClientComponent(c), type, id, a); } } inline auto BroadcastMessageStringInRadius( Mob* sender, bool ignore_sender = false, float distance = 200.f, Mob* skipped_mob = nullptr, bool is_ack_required = true, eqFilterType filter = FilterNone) { return [=](uint32_t type, uint32_t id, Args&&... args) requires (sizeof...(Args) <= 9) { auto queue_close_clients = QueueCloseClients(sender, ignore_sender, distance, skipped_mob, is_ack_required, filter); if constexpr (sizeof...(Args) == 0) { return queue_close_clients(&IMessage::Simple, GetClientComponent, type, id); } else { IMessage::FormattedArgs a = {args...}; return queue_close_clients(&IMessage::Formatted, GetClientComponent, type, id, a); } }; } void InterruptSpell(Client* c, uint32_t message, uint32_t spawn_id, const char* spell_link); void InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, const char* spell_link); void SendFullBuffRefresh(Mob* sender, bool remove = false, bool ackreq = true); void SendSingleBuffChange(Mob* sender, const Buffs_Struct& buff, int slot, bool remove = false, bool ackreq = true); } // namespace ClientPatch