From 1d47611b24a7649b88e0325ebf3b3e86709e76ed Mon Sep 17 00:00:00 2001 From: dannuic Date: Thu, 23 Apr 2026 18:33:07 -0600 Subject: [PATCH] Reorganized into more logical units --- common/CMakeLists.txt | 3 + common/patches/IMessage.h | 49 ++++ .../patches}/client_version.cpp | 29 +-- common/patches/client_version.h | 13 + common/patches/rof.h | 12 + common/patches/rof2.h | 12 + common/patches/sod.h | 12 + common/patches/sof.h | 12 + common/patches/titanium.cpp | 95 +++++++ common/patches/titanium.h | 25 ++ common/patches/tob.cpp | 198 +++++++++++--- common/patches/tob.h | 24 +- common/patches/uf.h | 12 + zone/CMakeLists.txt | 4 +- zone/client.cpp | 11 +- zone/client.h | 3 - .../message/rof.h => client_version.cpp} | 18 +- zone/client_version.h | 154 +++++++++++ zone/patch/CMakeLists.txt | 14 - zone/patch/client_version.h | 103 -------- zone/patch/components/CMakeLists.txt | 1 - zone/patch/components/message/CMakeLists.txt | 21 -- zone/patch/components/message/IMessage.h | 100 ------- zone/patch/components/message/rof2.cpp | 22 -- zone/patch/components/message/rof2.h | 29 --- zone/patch/components/message/sod.h | 29 --- zone/patch/components/message/sof.h | 29 --- zone/patch/components/message/titanium.cpp | 117 --------- zone/patch/components/message/titanium.h | 40 --- zone/patch/components/message/tob.cpp | 245 ------------------ zone/patch/components/message/tob.h | 38 --- zone/patch/components/message/uf.h | 29 --- zone/spells.cpp | 13 +- 33 files changed, 613 insertions(+), 903 deletions(-) create mode 100644 common/patches/IMessage.h rename {zone/patch => common/patches}/client_version.cpp (72%) create mode 100644 common/patches/client_version.h rename zone/{patch/components/message/rof.h => client_version.cpp} (68%) create mode 100644 zone/client_version.h delete mode 100644 zone/patch/CMakeLists.txt delete mode 100644 zone/patch/client_version.h delete mode 100644 zone/patch/components/CMakeLists.txt delete mode 100644 zone/patch/components/message/CMakeLists.txt delete mode 100644 zone/patch/components/message/IMessage.h delete mode 100644 zone/patch/components/message/rof2.cpp delete mode 100644 zone/patch/components/message/rof2.h delete mode 100644 zone/patch/components/message/sod.h delete mode 100644 zone/patch/components/message/sof.h delete mode 100644 zone/patch/components/message/titanium.cpp delete mode 100644 zone/patch/components/message/titanium.h delete mode 100644 zone/patch/components/message/tob.cpp delete mode 100644 zone/patch/components/message/tob.h delete mode 100644 zone/patch/components/message/uf.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 5a74d520a..63a303e38 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -86,6 +86,7 @@ set(common_sources packet_dump_file.cpp packet_dump.cpp packet_functions.cpp + patches/client_version.cpp patches/patches.cpp patches/rof_limits.cpp patches/rof.cpp @@ -655,6 +656,8 @@ set(common_headers packet_dump_file.h packet_dump.h packet_functions.h + patches/IMessage.h + patches/client_version.h patches/patches.h patches/rof_limits.h patches/rof_ops.h diff --git a/common/patches/IMessage.h b/common/patches/IMessage.h new file mode 100644 index 000000000..57ab52286 --- /dev/null +++ b/common/patches/IMessage.h @@ -0,0 +1,49 @@ +/* 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 "client_version.h" + +// Migration path: replace string_ids.h usage with ID enum values one call site at a time. + +class Client; +class Mob; +class EQApplicationPacket; + +namespace Message { + +template +concept AllConstChar = (std::is_convertible_v && ...); + +class IMessage +{ +public: + IMessage() = default; + virtual ~IMessage() = default; + + // these two are the basic string message packets + [[nodiscard]] virtual EQApplicationPacket* Simple(uint32_t color, uint32_t id) const = 0; + [[nodiscard]] virtual EQApplicationPacket* Formatted(uint32_t color, uint32_t id, const std::array& args) const = 0; + + // These aren't technically messages, but they use the same format and are similar enough to include here + virtual EQApplicationPacket* InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const = 0; + virtual EQApplicationPacket* InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, + const char* name, const char* spell_link) const = 0; +}; + +} // namespace Message diff --git a/zone/patch/client_version.cpp b/common/patches/client_version.cpp similarity index 72% rename from zone/patch/client_version.cpp rename to common/patches/client_version.cpp index 2318dfb6e..20a506899 100644 --- a/zone/patch/client_version.cpp +++ b/common/patches/client_version.cpp @@ -18,19 +18,15 @@ #include "client_version.h" -#include "zone/patch/components/message/titanium.h" -#include "zone/patch/components/message/sof.h" -#include "zone/patch/components/message/sod.h" -#include "zone/patch/components/message/uf.h" -#include "zone/patch/components/message/rof.h" -#include "zone/patch/components/message/rof2.h" -#include "zone/patch/components/message/tob.h" - -#include "client.h" -#include "websocketpp/http/parser.hpp" +#include "common/patches/titanium.h" +#include "common/patches/sof.h" +#include "common/patches/sod.h" +#include "common/patches/uf.h" +#include "common/patches/rof.h" +#include "common/patches/rof2.h" +#include "common/patches/tob.h" using Version = EQ::versions::ClientVersion; -using namespace ZoneClient; struct ClientComponents { @@ -82,16 +78,7 @@ static const ClientComponents& GetComponents(Version version) return patches.at(version); } -const std::shared_ptr& ClientPatch::GetMessageComponent(Version version) +const std::shared_ptr& GetMessageComponent(Version version) { return GetComponents(version).messageComponent; } - -void Client::SetClientVersion(Version client_version) -{ - m_ClientVersion = client_version; - m_ClientVersionBit = EQ::versions::ConvertClientVersionToClientVersionBit(client_version); - m_messageComponent = GetComponents(client_version).messageComponent; -} - -Version Client::GetClientVersion() const { return m_ClientVersion; } diff --git a/common/patches/client_version.h b/common/patches/client_version.h new file mode 100644 index 000000000..5c1ede856 --- /dev/null +++ b/common/patches/client_version.h @@ -0,0 +1,13 @@ +// +// Created by dannu on 4/21/2026. +// + +#pragma once + +#include "common/emu_versions.h" +#include + +namespace Message { class IMessage; } + +// store all static functions for the different patches here +const std::shared_ptr& GetMessageComponent(EQ::versions::ClientVersion version); diff --git a/common/patches/rof.h b/common/patches/rof.h index 229d371a6..0d08c6e68 100644 --- a/common/patches/rof.h +++ b/common/patches/rof.h @@ -17,6 +17,7 @@ */ #pragma once +#include "uf.h" #include "common/struct_strategy.h" class EQStreamIdentifier; @@ -48,3 +49,14 @@ namespace RoF }; } /*RoF*/ + +namespace Message { + +class RoF : public UF +{ +public: + RoF() = default; + ~RoF() override = default; +}; + +} // namespace Message diff --git a/common/patches/rof2.h b/common/patches/rof2.h index fffc1be23..3613fa354 100644 --- a/common/patches/rof2.h +++ b/common/patches/rof2.h @@ -17,6 +17,7 @@ */ #pragma once +#include "rof.h" #include "common/struct_strategy.h" class EQStreamIdentifier; @@ -48,3 +49,14 @@ namespace RoF2 }; }; /*RoF2*/ + +namespace Message { + +class RoF2 : public RoF +{ +public: + RoF2() = default; + ~RoF2() override = default; +}; + +} // namespace Message diff --git a/common/patches/sod.h b/common/patches/sod.h index 6820e6e72..203d52f19 100644 --- a/common/patches/sod.h +++ b/common/patches/sod.h @@ -17,6 +17,7 @@ */ #pragma once +#include "sof.h" #include "common/struct_strategy.h" class EQStreamIdentifier; @@ -48,3 +49,14 @@ namespace SoD }; } /*SoD*/ + +namespace Message { + +class SoD : public SoF +{ +public: + SoD() = default; + ~SoD() override = default; +}; + +} // namespace Message diff --git a/common/patches/sof.h b/common/patches/sof.h index bef47bb4d..3c0206d17 100644 --- a/common/patches/sof.h +++ b/common/patches/sof.h @@ -17,6 +17,7 @@ */ #pragma once +#include "titanium.h" #include "common/struct_strategy.h" class EQStreamIdentifier; @@ -48,3 +49,14 @@ namespace SoF }; } /*SoF*/ + +namespace Message { + +class SoF : public Titanium +{ +public: + SoF() = default; + ~SoF() override = default; +}; + +} // namespace Message diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 395094306..49ead949e 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -32,6 +32,7 @@ #include "common/raid.h" #include "common/rulesys.h" #include "common/strings.h" +#include "zone/string_ids.h" #include @@ -3919,3 +3920,97 @@ namespace Titanium return index; // as long as we guard against bad slots server side, we should be fine } } /*Titanium*/ + +namespace Message { + +EQApplicationPacket* Titanium::Simple(uint32_t color, uint32_t id) const +{ + uint32_t string_id = ResolveID(id); + if (string_id > 0) { + auto outapp = new EQApplicationPacket(OP_SimpleMessage, sizeof(SimpleMessage_Struct)); + auto* sms = reinterpret_cast(outapp->pBuffer); + sms->string_id = string_id; + sms->color = color; + sms->unknown8 = 0; + + return outapp; + } + + return nullptr; +} + +EQApplicationPacket* Titanium::Formatted( + uint32_t color, uint32_t id, const std::array& args) const +{ + uint32_t string_id = ResolveID(id); + if (string_id > 0) { + std::array resolved_args = args; + ResolveArguments(id, resolved_args); + if (!resolved_args[0]) + return Simple(color, id); + + SerializeBuffer buf(20); + buf.WriteUInt32(0); + buf.WriteUInt32(string_id); + buf.WriteUInt32(color); + + for (const auto* a : resolved_args) { + if (a != nullptr) + buf.WriteString(a); + } + + buf.WriteUInt8(0); + + return new EQApplicationPacket(OP_FormattedMessage, std::move(buf)); + } + + return nullptr; +} + +EQApplicationPacket* Titanium::InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const +{ + auto outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct)); + auto ic = reinterpret_cast(outapp->pBuffer); + ic->messageid = ResolveID(message); + ic->spawnid = spawn_id; + outapp->priority = 5; + + return outapp; +} + +EQApplicationPacket* Titanium::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, + const char* spell_link) const +{ + auto outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(name) + 1); + auto ic = reinterpret_cast(outapp->pBuffer); + ic->messageid = ResolveID(message); + ic->spawnid = spawn_id; + fmt::format_to_n(ic->message, strlen(name) + 1, "{}\0", name); + return outapp; +} + +// A value of 0 means that the string isn't mapped in this client, valid string ids start at 1 +uint32_t Titanium::ResolveID(uint32_t id) const +{ + // passthrough — string IDs are defined at the base client level; + // override in patches where IDs need remapping + return id; +} + +void Titanium::ResolveArguments(uint32_t id, std::array& args) const +{ + switch (id) { + case SPELL_FIZZLE: + case MISS_NOTE: + args[0] = nullptr; // drop spell link + break; + case SPELL_FIZZLE_OTHER: + case MISSED_NOTE_OTHER: + args[1] = nullptr; // drop spell link + break; + default: + break; + } +} + +} // namespace Message diff --git a/common/patches/titanium.h b/common/patches/titanium.h index 431e8346d..1d96475a2 100644 --- a/common/patches/titanium.h +++ b/common/patches/titanium.h @@ -17,6 +17,7 @@ */ #pragma once +#include "IMessage.h" #include "common/struct_strategy.h" class EQStreamIdentifier; @@ -48,3 +49,27 @@ namespace Titanium }; } /*Titanium*/ + +// out-going message packets +namespace Message { + +class Titanium : public IMessage +{ +public: + Titanium() = default; + ~Titanium() override = default; + + [[nodiscard]] EQApplicationPacket* Simple(uint32_t color, uint32_t id) const override; + [[nodiscard]] EQApplicationPacket* Formatted(uint32_t color, uint32_t id, const std::array& args) const override; + + EQApplicationPacket* InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const override; + EQApplicationPacket* InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, + const char* spell_link) const override; + +protected: + [[nodiscard]] virtual uint32_t ResolveID(uint32_t id) const; + virtual void ResolveArguments(uint32_t id, std::array& args) const; +}; + +} // namespace Message + diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index f53794951..39e5c5efc 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -27,6 +27,7 @@ #include "common/packet_dump.h" #include "world/sof_char_create_data.h" +#include "zone/string_ids.h" namespace TOB { @@ -37,8 +38,8 @@ namespace TOB void SerializeItem(SerializeBuffer &buffer, const EQ::ItemInstance* inst, int16 slot_id, uint8 depth, ItemPacketType packet_type); // message link converters - static inline void ServerToTOBConvertLinks(std::string& message_out, const std::string& message_in); - static inline void TOBToServerConvertLinks(std::string& message_out, const std::string& message_in); + static void ServerToTOBConvertLinks(std::string& message_out, const std::string& message_in); + static void TOBToServerConvertLinks(std::string& message_out, const std::string& message_in); // SpawnAppearance static inline uint32 ServerToTOBSpawnAppearanceType(uint32 server_type); @@ -4743,60 +4744,59 @@ namespace TOB buffer.WriteInt32(0); //unsupported atm } - static inline void ServerToTOBConvertLinks(std::string& message_out, const std::string& message_in) + static void ServerToTOBConvertLinks(std::string& message_out, const std::string& message_in) { if (message_in.find('\x12') == std::string::npos) { message_out = message_in; return; } - auto segments = Strings::Split(message_in, '\x12'); + std::vector segments = Strings::Split(message_in, '\x12'); for (size_t segment_iter = 0; segment_iter < segments.size(); ++segment_iter) { if (segment_iter & 1) { auto etag = std::stoi(segments[segment_iter].substr(0, 1)); switch (etag) { - case 0: - { + case 0: { size_t index = 1; - auto item_id = segments[segment_iter].substr(index, 5); + std::string item_id = segments[segment_iter].substr(index, 5); index += 5; - - auto aug1 = segments[segment_iter].substr(index, 5); + + std::string aug1 = segments[segment_iter].substr(index, 5); index += 5; - - auto aug2 = segments[segment_iter].substr(index, 5); + + std::string aug2 = segments[segment_iter].substr(index, 5); index += 5; - - auto aug3 = segments[segment_iter].substr(index, 5); + + std::string aug3 = segments[segment_iter].substr(index, 5); index += 5; - - auto aug4 = segments[segment_iter].substr(index, 5); + + std::string aug4 = segments[segment_iter].substr(index, 5); index += 5; - - auto aug5 = segments[segment_iter].substr(index, 5); + + std::string aug5 = segments[segment_iter].substr(index, 5); index += 5; - - auto aug6 = segments[segment_iter].substr(index, 5); + + std::string aug6 = segments[segment_iter].substr(index, 5); index += 5; - - auto is_evolving = segments[segment_iter].substr(index, 1); + + std::string is_evolving = segments[segment_iter].substr(index, 1); index += 1; - - auto evolutionGroup = segments[segment_iter].substr(index, 4); + + std::string evolutionGroup = segments[segment_iter].substr(index, 4); index += 4; - - auto evolutionLevel = segments[segment_iter].substr(index, 2); + + std::string evolutionLevel = segments[segment_iter].substr(index, 2); index += 2; - - auto ornamentationIconID = segments[segment_iter].substr(index, 5); + + std::string ornamentationIconID = segments[segment_iter].substr(index, 5); index += 5; - - auto itemHash = segments[segment_iter].substr(index, 8); + + std::string itemHash = segments[segment_iter].substr(index, 8); index += 8; - - auto text = segments[segment_iter].substr(index); - + + std::string text = segments[segment_iter].substr(index); + message_out.push_back('\x12'); message_out.push_back('0'); //etag item message_out.append(item_id); @@ -4830,14 +4830,13 @@ namespace TOB message_out.push_back('\x12'); break; } - } - else { + } else { message_out.append(segments[segment_iter]); } } } - static inline void TOBToServerConvertLinks(std::string& message_out, const std::string& message_in) { + static void TOBToServerConvertLinks(std::string& message_out, const std::string& message_in) { message_out = message_in; } @@ -5561,3 +5560,132 @@ namespace TOB } } /*TOB*/ +namespace Message { + +struct TOBStringIDs +{ + static constexpr uint32_t DisarmedTrap = 1458; // You successfully disarmed the trap +}; + +uint32_t TOB::ResolveID(uint32_t id) const +{ + switch (id) { + case YOU_FLURRY: + case BOW_DOUBLE_DAMAGE: + case NO_INSTRUMENT_SKILL: + case DISCIPLINE_CONLOST: + case TGB_ON: + case TGB_OFF: + case DISCIPLINE_RDY: + case SONG_NEEDS_DRUM: + case SONG_NEEDS_WIND: + case SONG_NEEDS_STRINGS: + case SONG_NEEDS_BRASS: + case YOU_CRIT_HEAL: + case YOU_CRIT_BLAST: + case SPELL_WORN_OFF: + case PET_TAUNTING: + case DISC_LEVEL_ERROR: + case MALE_SLAYUNDEAD: + case FEMALE_SLAYUNDEAD: + case FINISHING_BLOW: + case ASSASSINATES: + case CRIPPLING_BLOW: + case CRITICAL_HIT: + case DEADLY_STRIKE: + case OTHER_CRIT_HEAL: + case OTHER_CRIT_BLAST: + case NPC_RAMPAGE: + case NPC_FLURRY: + case DISCIPLINE_FEARLESS: + case CORPSE_ITEM_LOST: + case FATAL_BOW_SHOT: + case CURRENT_SPELL_EFFECTS: + case NOT_DELEGATED_MARKER: + case STRIKETHROUGH_STRING: + case AE_RAMPAGE: + case DISC_LEVEL_USE_ERROR: + case SPLIT_FAIL: + // removed from the client + return 0; + case DISARMED_TRAP: + return TOBStringIDs::DisarmedTrap; + default: + return RoF2::ResolveID(id); + } +} + +void TOB::ResolveArguments(uint32_t id, std::array& args) const +{ + switch (id) { + case SPELL_FIZZLE: + case MISS_NOTE: + case SPELL_FIZZLE_OTHER: + case MISSED_NOTE_OTHER: + // take all arguments (spell link) + break; + default: + RoF2::ResolveArguments(id, args); + break; + } +} + +EQApplicationPacket* TOB::Formatted(uint32_t color, uint32_t id, const std::array& args) const +{ + uint32_t string_id = ResolveID(id); + if (string_id > 0) { + std::array resolved_args = args; + ResolveArguments(id, resolved_args); + if (!resolved_args[0]) + return Simple(color, id); + + SerializeBuffer buffer(49); + // 49 is the minimum size needed for this packet since each arg writes at least 4 bytes + buffer.WriteUInt32(0); + // This is a string written like the message arrays, but it seems to be discarded by the client + buffer.WriteUInt8(0); // 0 is a zone packet, 1 is a world packet -- these are always sent from zone from here + buffer.WriteUInt32(string_id); + buffer.WriteUInt32(color); + + for (auto a : resolved_args) { + if (a != nullptr) { + std::string new_message; + ::TOB::ServerToTOBConvertLinks(new_message, a); + buffer.WriteLengthString(new_message); + } else + buffer.WriteUInt32(0); + } + + return new EQApplicationPacket(OP_FormattedMessage, std::move(buffer)); + } + + return nullptr; +} + +EQApplicationPacket* TOB::InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const +{ + auto outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(spell_link) + 1); + auto ic = reinterpret_cast(outapp->pBuffer); + ic->messageid = ResolveID(message); + ic->spawnid = spawn_id; + fmt::format_to_n(ic->message, strlen(spell_link) + 1, "{}\0", spell_link); + outapp->priority = 5; + + return outapp; +} + +EQApplicationPacket* TOB::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, + const char* spell_link) const +{ + auto outapp = new EQApplicationPacket(OP_InterruptCast, + sizeof(InterruptCast_Struct) + strlen(name) + strlen(spell_link) + 2); + auto ic = reinterpret_cast(outapp->pBuffer); + ic->messageid = ResolveID(message); + ic->spawnid = spawn_id; + fmt::format_to_n(ic->message, strlen(name) + strlen(spell_link) + 2, "{}\0{}\0", name, spell_link); + + return outapp; +} + +} // namespace Message + diff --git a/common/patches/tob.h b/common/patches/tob.h index 52aac01cb..c3cc9e2c9 100644 --- a/common/patches/tob.h +++ b/common/patches/tob.h @@ -1,6 +1,6 @@ -#ifndef COMMON_LAURION_H -#define COMMON_LAURION_H +#pragma once +#include "rof2.h" #include "../struct_strategy.h" class EQStreamIdentifier; @@ -34,4 +34,22 @@ namespace TOB }; /*TOB*/ -#endif /*COMMON_LAURION_H*/ +namespace Message { + +class TOB : public RoF2 +{ +public: + TOB() {} + ~TOB() override {} + + [[nodiscard]] EQApplicationPacket* Formatted(uint32_t color, uint32_t id, const std::array& args) const override; + + EQApplicationPacket* InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const override; + EQApplicationPacket* InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, const char* spell_link) const override; + +protected: + [[nodiscard]] uint32_t ResolveID(uint32_t id) const override; + void ResolveArguments(uint32_t id, std::array& args) const override; +}; + +} // namespace Message diff --git a/common/patches/uf.h b/common/patches/uf.h index e1172951b..05c27875b 100644 --- a/common/patches/uf.h +++ b/common/patches/uf.h @@ -17,6 +17,7 @@ */ #pragma once +#include "sod.h" #include "common/struct_strategy.h" class EQStreamIdentifier; @@ -48,3 +49,14 @@ namespace UF }; }; /*UF*/ + +namespace Message { + +class UF : public SoD +{ +public: + UF() = default; + ~UF() override = default; +}; + +} // namespace Message diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index a03be5051..0a9d97178 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -28,6 +28,7 @@ set(zone_sources client_mods.cpp client_packet.cpp client_process.cpp + client_version.cpp combat_record.cpp corpse.cpp dialogue_window.cpp @@ -130,6 +131,7 @@ set(zone_headers cheat_manager.h client.h client_packet.h + client_version.h combat_record.h command.h common.h @@ -675,8 +677,6 @@ add_executable(zone ${zone_sources} ${zone_headers}) target_include_directories(zone PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -add_subdirectory(patch) - install(TARGETS zone RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) if(EQEMU_BUILD_PCH) diff --git a/zone/client.cpp b/zone/client.cpp index 66a4f7523..dd9f0e6b2 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -44,10 +44,9 @@ #include "common/spdat.h" #include "common/strings.h" #include "common/zone_store.h" -#include "patch/client_version.h" -#include "patch/components/message/IMessage.h" #include "zone/bot_command.h" #include "zone/cheat_manager.h" +#include "zone/client_version.h" #include "zone/command.h" #include "zone/dialogue_window.h" #include "zone/dynamic_zone.h" @@ -3812,10 +3811,10 @@ void Client::MessageString(uint32 type, uint32 string_id, uint32 distance) return; if (distance > 0) - ZoneClient::Message::CloseMessageString(this, false, static_cast(distance))( + Message::CloseMessageString(this, false, static_cast(distance))( type, string_id); else - ZoneClient::Message::MessageString(this, type, string_id); + Message::MessageString(this, type, string_id); } // @@ -3844,10 +3843,10 @@ void Client::MessageString(uint32 type, uint32 string_id, const char* message1, type = 4; if (distance > 0) - ZoneClient::Message::CloseMessageString(this, false, static_cast(distance))(type, string_id, message1, + Message::CloseMessageString(this, false, static_cast(distance))(type, string_id, message1, message2, message3, message4, message5, message6, message7, message8, message9); else - ZoneClient::Message::MessageString(this, type, string_id, message1, message2, message3, message4, message5, + Message::MessageString(this, type, string_id, message1, message2, message3, message4, message5, message6, message7, message8, message9); } diff --git a/zone/client.h b/zone/client.h index 2b416840b..0fe3dd597 100644 --- a/zone/client.h +++ b/zone/client.h @@ -19,7 +19,6 @@ class Client; class EQApplicationPacket; -namespace ZoneClient::Message { class IMessage; } class DynamicZone; class DzLockout; class ExpeditionRequest; @@ -1553,7 +1552,6 @@ public: inline const uint32 ClientVersionBit() const { return m_ClientVersionBit; } void SetClientVersion(EQ::versions::ClientVersion client_version); EQ::versions::ClientVersion GetClientVersion() const; - const std::shared_ptr& GetMessageComponent() const { return m_messageComponent; } /** Adventure Stuff **/ void SendAdventureError(const char *error); @@ -2283,7 +2281,6 @@ private: EQ::versions::ClientVersion m_ClientVersion; uint32 m_ClientVersionBit; - std::shared_ptr m_messageComponent; int XPRate; diff --git a/zone/patch/components/message/rof.h b/zone/client_version.cpp similarity index 68% rename from zone/patch/components/message/rof.h rename to zone/client_version.cpp index 487df6586..1efc15835 100644 --- a/zone/patch/components/message/rof.h +++ b/zone/client_version.cpp @@ -15,15 +15,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#pragma once -#include "zone/patch/components/message/uf.h" +#include "client_version.h" -namespace ZoneClient::Message { -class RoF : public UF +using Version = EQ::versions::ClientVersion; + +void Client::SetClientVersion(Version client_version) { -public: - RoF() {} - ~RoF() override {} -}; -} // namespace Zone::Message + m_ClientVersion = client_version; + m_ClientVersionBit = EQ::versions::ConvertClientVersionToClientVersionBit(client_version); +} + +Version Client::GetClientVersion() const { return m_ClientVersion; } diff --git a/zone/client_version.h b/zone/client_version.h new file mode 100644 index 000000000..595f9bad9 --- /dev/null +++ b/zone/client_version.h @@ -0,0 +1,154 @@ +// +// Created by dannu on 4/21/2026. +// + +#pragma once + + +#include "common/emu_versions.h" +#include "common/patches/client_version.h" +#include "common/patches/IMessage.h" + +#include "zone/client.h" +#include "zone/mob.h" + +// store all _generic_ static functions for the different patches here +namespace ClientPatch { + +using ClientList = std::unordered_map; + +template +static void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) +{ + static_assert(std::is_member_function_pointer_v); + EQApplicationPacket* app = std::invoke(fun, obj, std::forward(args)...); + if (app != nullptr) { + c->QueuePacket(app); + delete app; + } +} + +// packet generator queue functions +static auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = true) +{ + return [=](Fun fun, + std::function component_getter, Args&&... args) { + static_assert(std::is_member_function_pointer_v && "Function is required to be a member function"); + + std::unordered_map build_packets; + std::unordered_map client_list = entity_list.GetClientList(); + + for (auto [_, ent] : client_list) { + if (!ignore_sender || ent != sender) { + auto [packet, _] = build_packets.try_emplace( + ent->ClientVersion(), + std::invoke(fun, component_getter(ent), std::forward(args)...)); + + if (packet->second != nullptr) + ent->QueuePacket(packet->second, ackreq, Client::CLIENT_CONNECTED); + } + } + + for (auto [_, packet] : build_packets) + if (packet != nullptr) + delete packet; + }; +} + +static 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, + std::function component_getter, Args&&... args) { + static_assert(std::is_member_function_pointer_v, "Function is required to be a member function"); + + if (sender == nullptr) { + QueueClients(sender, ignore_sender, is_ack_required)(fun, component_getter, std::forward(args)...); + } else { + float distance_squared = distance * distance; + std::unordered_map build_packets; + + for (auto& [_, mob] : sender->GetCloseMobList(distance)) { + if (mob && mob->IsClient()) { + Client* client = mob->CastToClient(); + if ((!ignore_sender || client != sender) + && client != skipped_mob + && DistanceSquared(client->GetPosition(), sender->GetPosition()) < distance_squared + && client->Connected() + && client->ShouldGetPacket(sender, filter)) { + auto [packet, _] = build_packets.try_emplace( + client->ClientVersion(), + std::invoke(fun, component_getter(client), std::forward(args)...)); + + if (packet->second != nullptr) + client->QueuePacket(packet->second, is_ack_required, Client::CLIENT_CONNECTED); + } + } + } + + for (auto [_, packet] : build_packets) + if (packet != nullptr) + delete packet;; + } + }; +} + +} // namespace ClientPatch + +// Helpers for the Message interface to send message packets +namespace Message { +static std::function GetComponent = [](const Client* c) -> IMessage* { + return GetMessageComponent(c->GetClientVersion()).get(); +}; + +// Helper functions to wrap the packet construction in sends +template + requires (sizeof...(Args) <= 9) +void MessageString(Client* c, uint32_t type, uint32_t id, Args&&... args) +{ + if constexpr (sizeof...(Args) == 0) { + ClientPatch::QueuePacket(c, &IMessage::Simple, GetComponent(c), type, id); + } else { + std::array a = {args...}; + ClientPatch::QueuePacket(c, &IMessage::Formatted, GetComponent(c), type, id, a); + } +} + +static auto CloseMessageString( + 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) { + static_assert(sizeof...(Args) <= 9, "Too many arguments"); + + auto queue_close_clients = ClientPatch::QueueCloseClients(sender, ignore_sender, distance, skipped_mob, + is_ack_required, filter); + + if constexpr (sizeof...(Args) == 0) { + return queue_close_clients(&IMessage::Simple, GetComponent, type, id); + } else { + std::array a = {args...}; + return queue_close_clients(&IMessage::Formatted, GetComponent, type, id, a); + } + }; +} + +inline void InterruptSpell(Client* c, uint32_t message, uint32_t spawn_id, const char* spell_link) +{ + ClientPatch::QueuePacket(c, &IMessage::InterruptSpell, GetComponent(c), message, spawn_id, spell_link); +} + +inline void InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, + const char* spell_link) +{ + ClientPatch::QueueCloseClients(sender, true, RuleI(Range, SongMessages), nullptr, true, + sender->IsClient() ? FilterPCSpells : FilterNPCSpells)( + &IMessage::InterruptSpellOther, GetComponent, sender, message, spawn_id, name, spell_link); +} + +} // namespace Message diff --git a/zone/patch/CMakeLists.txt b/zone/patch/CMakeLists.txt deleted file mode 100644 index 81abcf1eb..000000000 --- a/zone/patch/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -set(patch_sources - client_version.cpp -) - -set(patch_headers - client_version.h -) - -source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${patch_sources}) -source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Header Files" FILES ${patch_headers}) - -target_sources(zone PRIVATE ${patch_sources} ${patch_headers}) - -add_subdirectory(components) \ No newline at end of file diff --git a/zone/patch/client_version.h b/zone/patch/client_version.h deleted file mode 100644 index 5733b7ac7..000000000 --- a/zone/patch/client_version.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// Created by dannu on 4/21/2026. -// - -#pragma once - -#include - -#include "common/emu_versions.h" - -#include "zone/client.h" -#include "zone/mob.h" - -namespace ZoneClient { - -namespace Message { class IMessage; } - -// store all static functions for the different patches here -class ClientPatch { -public: - using ClientList = std::unordered_map; - static const std::shared_ptr& GetMessageComponent(EQ::versions::ClientVersion version); - - template - static void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) - { - static_assert(std::is_member_function_pointer_v); - EQApplicationPacket* app = std::invoke(fun, obj, std::forward(args)...); - if (app != nullptr) { - c->QueuePacket(app); - delete app; - } - } - - // packet generator queue functions - static auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = true) - { - return [=](Fun fun, std::function component_getter, Args&&... args) { - static_assert(std::is_member_function_pointer_v && "Function is required to be a member function"); - - std::unordered_map build_packets; - std::unordered_map client_list = entity_list.GetClientList(); - - for (auto [_, ent] : client_list) { - if (!ignore_sender || ent != sender) { - auto [packet, _] = build_packets.try_emplace( - ent->ClientVersion(), - std::invoke(fun, component_getter(ent), std::forward(args)...)); - - if (packet->second != nullptr) - ent->QueuePacket(packet->second, ackreq, Client::CLIENT_CONNECTED); - } - } - - for (auto [_, packet] : build_packets) - if (packet != nullptr) - delete packet; - }; - } - - static 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, std::function component_getter, Args&&... args) { - static_assert(std::is_member_function_pointer_v && "Function is required to be a member function"); - - if (sender == nullptr) { - QueueClients(sender, ignore_sender, is_ack_required)(fun, component_getter, std::forward(args)...); - } else { - float distance_squared = distance * distance; - std::unordered_map build_packets; - - for (auto& [_, mob] : sender->GetCloseMobList(distance)) { - if (mob && mob->IsClient()) { - Client* client = mob->CastToClient(); - if ((!ignore_sender || client != sender) - && client != skipped_mob - && DistanceSquared(client->GetPosition(), sender->GetPosition()) < distance_squared - && client->Connected() - && client->ShouldGetPacket(sender, filter)) { - auto [packet, _] = build_packets.try_emplace( - client->ClientVersion(), - std::invoke(fun, component_getter(client), std::forward(args)...)); - - if (packet->second != nullptr) - client->QueuePacket(packet->second, is_ack_required, Client::CLIENT_CONNECTED); - } - } - } - - for (auto [_, packet] : build_packets) - if (packet != nullptr) - delete packet;; - } - }; - } -}; - -} diff --git a/zone/patch/components/CMakeLists.txt b/zone/patch/components/CMakeLists.txt deleted file mode 100644 index de9dd636c..000000000 --- a/zone/patch/components/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(message) \ No newline at end of file diff --git a/zone/patch/components/message/CMakeLists.txt b/zone/patch/components/message/CMakeLists.txt deleted file mode 100644 index 4130fe840..000000000 --- a/zone/patch/components/message/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -set(message_component_sources - titanium.cpp - rof2.cpp - tob.cpp -) - -set(message_component_headers - IMessage.h - titanium.h - sof.h - sod.h - uf.h - rof.h - rof2.h - tob.h -) - -source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${message_component_sources}) -source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Header Files" FILES ${message_component_headers}) - -target_sources(zone PRIVATE ${message_component_sources} ${message_component_headers}) \ No newline at end of file diff --git a/zone/patch/components/message/IMessage.h b/zone/patch/components/message/IMessage.h deleted file mode 100644 index 7b48b2604..000000000 --- a/zone/patch/components/message/IMessage.h +++ /dev/null @@ -1,100 +0,0 @@ -/* 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 "patch/client_version.h" - -#include - -// Migration path: replace string_ids.h usage with ID enum values one call site at a time. - -class Client; -class Mob; -class EQApplicationPacket; - -namespace ZoneClient::Message { - -template -concept AllConstChar = (std::is_convertible_v && ...); - -class IMessage -{ -public: - IMessage() = default; - virtual ~IMessage() = default; - - // these two are the basic string message packets - [[nodiscard]] virtual EQApplicationPacket* Simple(uint32_t color, uint32_t id) const = 0; - [[nodiscard]] virtual EQApplicationPacket* Formatted(uint32_t color, uint32_t id, const std::array& args) const = 0; - - // These aren't technically messages, but they use the same format and are similar enough to include here - virtual EQApplicationPacket* InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const = 0; - virtual EQApplicationPacket* InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, - const char* name, const char* spell_link) const = 0; -}; - -static std::function GetComponent = [](const Client* c) -> IMessage* { - return ClientPatch::GetMessageComponent(c->GetClientVersion()).get(); -}; - -// Helper functions to wrap the packet construction in sends -template - requires (sizeof...(Args) <= 9) -void MessageString(Client* c, uint32_t type, uint32_t id, Args&&... args) -{ - if constexpr (sizeof...(Args) == 0) { - ClientPatch::QueuePacket(c, &IMessage::Simple, c->GetMessageComponent().get(), type, id); - } else { - std::array a = {args...}; - ClientPatch::QueuePacket(c, &IMessage::Formatted, c->GetMessageComponent().get(), type, id, a); - } -} - -static auto CloseMessageString( - 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) { - static_assert(sizeof...(Args) <= 9, "Too many arguments"); - - auto queue_close_clients = ClientPatch::QueueCloseClients(sender, ignore_sender, distance, skipped_mob, - is_ack_required, filter); - - if constexpr (sizeof...(Args) == 0) { - return queue_close_clients(&IMessage::Simple, GetComponent, type, id); - } else { - std::array a = {args...}; - return queue_close_clients(&IMessage::Formatted, GetComponent, type, id, a); - } - }; -} - -inline void InterruptSpell(Client* c, uint32_t message, uint32_t spawn_id, const char* spell_link) -{ - ClientPatch::QueuePacket(c, &IMessage::InterruptSpell, c->GetMessageComponent().get(), message, spawn_id, spell_link); -} - -inline void InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, const char* spell_link) -{ - ClientPatch::QueueCloseClients(sender, true, RuleI(Range, SongMessages), nullptr, true, - sender->IsClient() ? FilterPCSpells : FilterNPCSpells)( - &IMessage::InterruptSpellOther, GetComponent, sender, message, spawn_id, name, spell_link); -} - -} // namespace ZoneClient::Message diff --git a/zone/patch/components/message/rof2.cpp b/zone/patch/components/message/rof2.cpp deleted file mode 100644 index 7b2120e31..000000000 --- a/zone/patch/components/message/rof2.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* 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 . -*/ -#include "zone/patch/components/message/rof2.h" - -namespace ZoneClient::Message { - -} // namespace Zone::Message diff --git a/zone/patch/components/message/rof2.h b/zone/patch/components/message/rof2.h deleted file mode 100644 index 1318c007a..000000000 --- a/zone/patch/components/message/rof2.h +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 "zone/patch/components/message/rof.h" - -namespace ZoneClient::Message { -class RoF2 : public RoF -{ -public: - RoF2() {} - ~RoF2() override {} -}; -} // namespace Zone::Message diff --git a/zone/patch/components/message/sod.h b/zone/patch/components/message/sod.h deleted file mode 100644 index 16b14b10b..000000000 --- a/zone/patch/components/message/sod.h +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 "zone/patch/components/message/sof.h" - -namespace ZoneClient::Message { -class SoD : public SoF -{ -public: - SoD() {} - ~SoD() override {} -}; -} // namespace Zone::Message diff --git a/zone/patch/components/message/sof.h b/zone/patch/components/message/sof.h deleted file mode 100644 index 3b8c1d1ad..000000000 --- a/zone/patch/components/message/sof.h +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 "zone/patch/components/message/titanium.h" - -namespace ZoneClient::Message { -class SoF : public Titanium -{ -public: - SoF() {} - ~SoF() override {} -}; -} // namespace Zone::Message diff --git a/zone/patch/components/message/titanium.cpp b/zone/patch/components/message/titanium.cpp deleted file mode 100644 index 4efc3b2c1..000000000 --- a/zone/patch/components/message/titanium.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* 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 . -*/ -#include "zone/patch/components/message/titanium.h" - -#include "client.h" - -#include "common/eq_packet.h" -#include "common/eq_packet_structs.h" -#include "common/serialize_buffer.h" - -namespace ZoneClient::Message { - -EQApplicationPacket* Titanium::Simple(uint32_t color, uint32_t id) const -{ - uint32_t string_id = ResolveID(id); - if (string_id > 0) { - auto outapp = new EQApplicationPacket(OP_SimpleMessage, sizeof(SimpleMessage_Struct)); - auto* sms = reinterpret_cast(outapp->pBuffer); - sms->string_id = string_id; - sms->color = color; - sms->unknown8 = 0; - - return outapp; - } - - return nullptr; -} - -EQApplicationPacket* Titanium::Formatted( - uint32_t color, uint32_t id, const std::array& args) const -{ - uint32_t string_id = ResolveID(id); - if (string_id > 0) { - std::array resolved_args = args; - ResolveArguments(id, resolved_args); - if (!resolved_args[0]) - return Simple(color, id); - - SerializeBuffer buf(20); - buf.WriteUInt32(0); - buf.WriteUInt32(string_id); - buf.WriteUInt32(color); - - for (const auto* a : resolved_args) { - if (a != nullptr) - buf.WriteString(a); - } - - buf.WriteUInt8(0); - - return new EQApplicationPacket(OP_FormattedMessage, std::move(buf)); - } - - return nullptr; -} - -EQApplicationPacket* Titanium::InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const -{ - auto outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct)); - auto ic = reinterpret_cast(outapp->pBuffer); - ic->messageid = ResolveID(message); - ic->spawnid = spawn_id; - outapp->priority = 5; - - return outapp; -} - -EQApplicationPacket* Titanium::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, - const char* spell_link) const -{ - auto outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(name) + 1); - auto ic = reinterpret_cast(outapp->pBuffer); - ic->messageid = ResolveID(message); - ic->spawnid = spawn_id; - fmt::format_to_n(ic->message, strlen(name) + 1, "{}\0", name); - return outapp; -} - -// A value of 0 means that the string isn't mapped in this client, valid string ids start at 1 -uint32_t Titanium::ResolveID(uint32_t id) const -{ - // passthrough — string IDs are defined at the base client level; - // override in patches where IDs need remapping - return id; -} - -void Titanium::ResolveArguments(uint32_t id, std::array& args) const -{ - switch (id) { - case SPELL_FIZZLE: - case MISS_NOTE: - args[0] = nullptr; // drop spell link - break; - case SPELL_FIZZLE_OTHER: - case MISSED_NOTE_OTHER: - args[1] = nullptr; // drop spell link - break; - default: - break; - } -} -} // namespace ZoneClient::Message diff --git a/zone/patch/components/message/titanium.h b/zone/patch/components/message/titanium.h deleted file mode 100644 index ebf7dfb7b..000000000 --- a/zone/patch/components/message/titanium.h +++ /dev/null @@ -1,40 +0,0 @@ -/* 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 "zone/patch/components/message/IMessage.h" - -namespace ZoneClient::Message { -class Titanium : public IMessage -{ -public: - Titanium() = default; - ~Titanium() override = default; - - [[nodiscard]] EQApplicationPacket* Simple(uint32_t color, uint32_t id) const override; - [[nodiscard]] EQApplicationPacket* Formatted(uint32_t color, uint32_t id, const std::array& args) const override; - - EQApplicationPacket* InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const override; - EQApplicationPacket* InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, - const char* spell_link) const override; - -protected: - [[nodiscard]] virtual uint32_t ResolveID(uint32_t id) const; - virtual void ResolveArguments(uint32_t id, std::array& args) const; -}; -} // namespace ZoneClient::Message diff --git a/zone/patch/components/message/tob.cpp b/zone/patch/components/message/tob.cpp deleted file mode 100644 index 0d8c888d5..000000000 --- a/zone/patch/components/message/tob.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/* 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 . -*/ -#include "zone/patch/components/message/tob.h" - -#include "common/links.h" - -namespace ZoneClient::Message { -struct TOBStringIDs -{ - static constexpr uint32_t DisarmedTrap = 1458; // You successfully disarmed the trap -}; - -uint32_t TOB::ResolveID(uint32_t id) const -{ - switch (id) { - case YOU_FLURRY: - case BOW_DOUBLE_DAMAGE: - case NO_INSTRUMENT_SKILL: - case DISCIPLINE_CONLOST: - case TGB_ON: - case TGB_OFF: - case DISCIPLINE_RDY: - case SONG_NEEDS_DRUM: - case SONG_NEEDS_WIND: - case SONG_NEEDS_STRINGS: - case SONG_NEEDS_BRASS: - case YOU_CRIT_HEAL: - case YOU_CRIT_BLAST: - case SPELL_WORN_OFF: - case PET_TAUNTING: - case DISC_LEVEL_ERROR: - case MALE_SLAYUNDEAD: - case FEMALE_SLAYUNDEAD: - case FINISHING_BLOW: - case ASSASSINATES: - case CRIPPLING_BLOW: - case CRITICAL_HIT: - case DEADLY_STRIKE: - case OTHER_CRIT_HEAL: - case OTHER_CRIT_BLAST: - case NPC_RAMPAGE: - case NPC_FLURRY: - case DISCIPLINE_FEARLESS: - case CORPSE_ITEM_LOST: - case FATAL_BOW_SHOT: - case CURRENT_SPELL_EFFECTS: - case NOT_DELEGATED_MARKER: - case STRIKETHROUGH_STRING: - case AE_RAMPAGE: - case DISC_LEVEL_USE_ERROR: - case SPLIT_FAIL: - // removed from the client - return 0; - case DISARMED_TRAP: - return TOBStringIDs::DisarmedTrap; - default: - return RoF2::ResolveID(id); - } -} - -void TOB::ResolveArguments(uint32_t id, std::array& args) const -{ - switch (id) { - case SPELL_FIZZLE: - case MISS_NOTE: - case SPELL_FIZZLE_OTHER: - case MISSED_NOTE_OTHER: - // take all arguments (spell link) - break; - default: - RoF2::ResolveArguments(id, args); - break; - } -} - -// TOB is the first patch to fully support links in the client. This helper function is therefore internal to TOB -// because any future patches would default to the TOB message strings -static void ServerToTOBConvertLinks(std::string& message_out, const std::string& message_in) -{ - if (message_in.find('\x12') == std::string::npos) { - message_out = message_in; - return; - } - - std::vector segments = Strings::Split(message_in, '\x12'); - for (size_t segment_iter = 0; segment_iter < segments.size(); ++segment_iter) { - if (segment_iter & 1) { - auto etag = std::stoi(segments[segment_iter].substr(0, 1)); - - switch (etag) { - case 0: { - size_t index = 1; - std::string item_id = segments[segment_iter].substr(index, 5); - index += 5; - - std::string aug1 = segments[segment_iter].substr(index, 5); - index += 5; - - std::string aug2 = segments[segment_iter].substr(index, 5); - index += 5; - - std::string aug3 = segments[segment_iter].substr(index, 5); - index += 5; - - std::string aug4 = segments[segment_iter].substr(index, 5); - index += 5; - - std::string aug5 = segments[segment_iter].substr(index, 5); - index += 5; - - std::string aug6 = segments[segment_iter].substr(index, 5); - index += 5; - - std::string is_evolving = segments[segment_iter].substr(index, 1); - index += 1; - - std::string evolutionGroup = segments[segment_iter].substr(index, 4); - index += 4; - - std::string evolutionLevel = segments[segment_iter].substr(index, 2); - index += 2; - - std::string ornamentationIconID = segments[segment_iter].substr(index, 5); - index += 5; - - std::string itemHash = segments[segment_iter].substr(index, 8); - index += 8; - - std::string text = segments[segment_iter].substr(index); - - message_out.push_back('\x12'); - message_out.push_back('0'); //etag item - message_out.append(item_id); - message_out.append(aug1); - message_out.append("00000"); - message_out.append(aug2); - message_out.append("00000"); - message_out.append(aug3); - message_out.append("00000"); - message_out.append(aug4); - message_out.append("00000"); - message_out.append(aug5); - message_out.append("00000"); - message_out.append(aug6); - message_out.append("00000"); - message_out.append(is_evolving); - message_out.append(evolutionGroup); - message_out.append(evolutionLevel); - message_out.append(ornamentationIconID); - message_out.append("00000"); - message_out.append(itemHash); - message_out.append(text); - message_out.push_back('\x12'); - - break; - } - default: - //unsupported etag right now; just pass it as is - message_out.push_back('\x12'); - message_out.append(segments[segment_iter]); - message_out.push_back('\x12'); - break; - } - } else { - message_out.append(segments[segment_iter]); - } - } -} - -EQApplicationPacket* TOB::Formatted(uint32_t color, uint32_t id, const std::array& args) const - // const char* a1, const char* a2, const char* a3, - // const char* a4, const char* a5, const char* a6, - // const char* a7, const char* a8, const char* a9) const -{ - uint32_t string_id = ResolveID(id); - if (string_id > 0) { - std::array resolved_args = args; - ResolveArguments(id, resolved_args); - if (!resolved_args[0]) - return Simple(color, id); - - SerializeBuffer buffer(49); - // 49 is the minimum size needed for this packet since each arg writes at least 4 bytes - buffer.WriteUInt32(0); - // This is a string written like the message arrays, but it seems to be discarded by the client - buffer.WriteUInt8(0); // 0 is a zone packet, 1 is a world packet -- these are always sent from zone from here - buffer.WriteUInt32(string_id); - buffer.WriteUInt32(color); - - for (auto a : resolved_args) { - if (a != nullptr) { - std::string new_message; - ServerToTOBConvertLinks(new_message, a); - buffer.WriteLengthString(new_message); - } else - buffer.WriteUInt32(0); - } - - return new EQApplicationPacket(OP_FormattedMessage, std::move(buffer)); - } - - return nullptr; -} - -EQApplicationPacket* TOB::InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const -{ - auto outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(spell_link) + 1); - auto ic = reinterpret_cast(outapp->pBuffer); - ic->messageid = ResolveID(message); - ic->spawnid = spawn_id; - fmt::format_to_n(ic->message, strlen(spell_link) + 1, "{}\0", spell_link); - outapp->priority = 5; - - return outapp; -} - -EQApplicationPacket* TOB::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, - const char* spell_link) const -{ - auto outapp = new EQApplicationPacket(OP_InterruptCast, - sizeof(InterruptCast_Struct) + strlen(name) + strlen(spell_link) + 2); - auto ic = reinterpret_cast(outapp->pBuffer); - ic->messageid = ResolveID(message); - ic->spawnid = spawn_id; - fmt::format_to_n(ic->message, strlen(name) + strlen(spell_link) + 2, "{}\0{}\0", name, spell_link); - - return outapp; -} - -} // namespace ZoneClient::Message diff --git a/zone/patch/components/message/tob.h b/zone/patch/components/message/tob.h deleted file mode 100644 index a6084535a..000000000 --- a/zone/patch/components/message/tob.h +++ /dev/null @@ -1,38 +0,0 @@ -/* 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 "zone/patch/components/message/rof2.h" - -namespace ZoneClient::Message { -class TOB : public RoF2 -{ -public: - TOB() {} - ~TOB() override {} - - [[nodiscard]] EQApplicationPacket* Formatted(uint32_t color, uint32_t id, const std::array& args) const override; - - EQApplicationPacket* InterruptSpell(uint32_t message, uint32_t spawn_id, const char* spell_link) const override; - EQApplicationPacket* InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, const char* spell_link) const override; - -protected: - [[nodiscard]] uint32_t ResolveID(uint32_t id) const override; - void ResolveArguments(uint32_t id, std::array& args) const override; -}; -} // namespace ZoneClient::Message diff --git a/zone/patch/components/message/uf.h b/zone/patch/components/message/uf.h deleted file mode 100644 index f19677ae0..000000000 --- a/zone/patch/components/message/uf.h +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 "patch/components/message/sod.h" - -namespace ZoneClient::Message { -class UF : public SoD -{ -public: - UF() {} - ~UF() override {} -}; -} // namespace Zone::Message diff --git a/zone/spells.cpp b/zone/spells.cpp index 4bff2cf81..888b86406 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -80,9 +80,9 @@ #include "common/rulesys.h" #include "common/spdat.h" #include "common/strings.h" -#include "patch/client_version.h" #include "zone/bot.h" #include "zone/client.h" +#include "zone/client_version.h" #include "zone/fastmath.h" #include "zone/lua_parser.h" #include "zone/mob_movement_manager.h" @@ -96,7 +96,6 @@ #include #include "common/links.h" -#include "patch/components/message/IMessage.h" extern Zone *zone; extern volatile bool is_zone_loaded; @@ -340,12 +339,12 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spell_id); if (IsClient()) - ZoneClient::Message::MessageString(CastToClient(), Chat::SpellFailure, fizzle_msg, spell_link); + Message::MessageString(CastToClient(), Chat::SpellFailure, fizzle_msg, spell_link); /** * Song Failure message */ - ZoneClient::Message::CloseMessageString(this, true, RuleI(Range, SpellMessages), + Message::CloseMessageString(this, true, RuleI(Range, SpellMessages), nullptr, true, IsClient() ? FilterPCSpells : FilterNPCSpells)( Chat::SpellFailure, fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER, GetName(), spell_link); @@ -1304,7 +1303,7 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) // the interrupt message char spell_link[Links::MAX_LINK_SIZE]; Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spellid); - ZoneClient::Message::InterruptSpell(CastToClient(), message, GetID(), spell_link); + Message::InterruptSpell(CastToClient(), message, GetID(), spell_link); SendSpellBarEnable(spellid); } @@ -1332,7 +1331,7 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) // this is the actual message, it works the same as a formatted message char spell_link[Links::MAX_LINK_SIZE]; Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spellid); - ZoneClient::Message::InterruptSpellOther(this, message_other, GetID(), GetCleanName(), spell_link); + Message::InterruptSpellOther(this, message_other, GetID(), GetCleanName(), spell_link); } // this is like interrupt, just it doesn't spam interrupt packets to everyone @@ -7272,7 +7271,7 @@ void Mob::DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time if (cast_time != 0) { char spell_link[Links::MAX_LINK_SIZE]; Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spell_id); - ZoneClient::Message::InterruptSpell(CastToClient(), SONG_ENDS, GetID(), spell_link); + Message::InterruptSpell(CastToClient(), SONG_ENDS, GetID(), spell_link); ZeroCastingVars(); ZeroBardPulseVars();