Added component-based patch system (#5070)
Some checks failed
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled

This commit is contained in:
dannuic 2026-04-26 00:29:12 -06:00 committed by GitHub
parent 0ada77f340
commit 743fd45b17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 955 additions and 352 deletions

View File

@ -71,7 +71,7 @@ if(UNIX)
endif() endif()
endif() endif()
find_package(Boost REQUIRED COMPONENTS dynamic_bitset foreach tuple) find_package(Boost REQUIRED COMPONENTS dynamic_bitset foreach tuple CONFIG REQUIRED)
find_package(cereal CONFIG REQUIRED) find_package(cereal CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED) find_package(glm CONFIG REQUIRED)

View File

@ -86,6 +86,7 @@ set(common_sources
packet_dump_file.cpp packet_dump_file.cpp
packet_dump.cpp packet_dump.cpp
packet_functions.cpp packet_functions.cpp
patches/client_version.cpp
patches/patches.cpp patches/patches.cpp
patches/rof_limits.cpp patches/rof_limits.cpp
patches/rof.cpp patches/rof.cpp
@ -135,6 +136,7 @@ set(common_sources
util/directory.cpp util/directory.cpp
util/uuid.cpp util/uuid.cpp
zone_store.cpp zone_store.cpp
links.cpp
) )
set(repositories set(repositories
@ -654,6 +656,8 @@ set(common_headers
packet_dump_file.h packet_dump_file.h
packet_dump.h packet_dump.h
packet_functions.h packet_functions.h
patches/IMessage.h
patches/client_version.h
patches/patches.h patches/patches.h
patches/rof_limits.h patches/rof_limits.h
patches/rof_ops.h patches/rof_ops.h
@ -733,9 +737,8 @@ set(common_headers
util/memory_stream.h util/memory_stream.h
util/uuid.h util/uuid.h
version.h version.h
zone_store.h zone_store.h
links.h links.h
links.cpp
) )
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${common_sources}) source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${common_sources})

View File

@ -61,8 +61,8 @@ namespace EQ
maskAllClients = 0xFFFFFFFF maskAllClients = 0xFFFFFFFF
}; };
const ClientVersion LastClientVersion = ClientVersion::TOB; inline constexpr ClientVersion LastClientVersion = ClientVersion::TOB;
const size_t ClientVersionCount = (static_cast<size_t>(LastClientVersion) + 1); inline constexpr size_t ClientVersionCount = (static_cast<size_t>(LastClientVersion) + 1);
bool IsValidClientVersion(ClientVersion client_version); bool IsValidClientVersion(ClientVersion client_version);
ClientVersion ValidateClientVersion(ClientVersion client_version); ClientVersion ValidateClientVersion(ClientVersion client_version);

View File

@ -4,7 +4,28 @@
#include "links.h" #include "links.h"
std::string Links::FormatSpellLink(uint32_t SpellID, const std::string& SpellName) #include "spdat.h"
void Links::FormatItemLink(char* Buffer, size_t BufferSize, const EQ::ItemInstance* item)
{ {
return fmt::format("{}63^{}^0^'{}{}", ITEM_TAG_CHAR, SpellID, SpellName.c_str(), ITEM_TAG_CHAR); // TODO: Reverse 0x14064B220 to get definition of this function
}
void Links::FormatSpellLink(char* Buffer, size_t BufferSize, uint32_t SpellID,
const char* spellNameOverride)
{
snprintf(Buffer, BufferSize, "%c%d3^%d^0^'%s%c", ITEM_TAG_CHAR, ETAG_SPELL, SpellID,
spellNameOverride && spellNameOverride[0] ? spellNameOverride : GetSpellName(SpellID), ITEM_TAG_CHAR);
}
void Links::FormatDialogLink(char* Buffer, size_t BufferSize, std::string_view keyword, std::string_view text)
{
if (text.empty()) {
snprintf(Buffer, BufferSize, "%c%d%.*s%c", ITEM_TAG_CHAR, ETAG_DIALOG_RESPONSE,
static_cast<int>(keyword.length()), keyword.data(), ITEM_TAG_CHAR);
} else {
snprintf(Buffer, BufferSize, "%c%d%.*s:%.*s%c", ITEM_TAG_CHAR, ETAG_DIALOG_RESPONSE,
static_cast<int>(keyword.length()), keyword.data(),
static_cast<int>(text.length()), text.data(), ITEM_TAG_CHAR);
}
} }

View File

@ -3,9 +3,59 @@
// //
#pragma once #pragma once
#include "item_instance.h"
namespace EQ { class ItemInstance; }
namespace Links namespace Links
{ {
constexpr char ITEM_TAG_CHAR = '\x12'; // Max Link Size in bytes
std::string FormatSpellLink(uint32_t SpellID, const std::string& SpellName); constexpr size_t MAX_LINK_SIZE = 512;
// Universal link tag character
constexpr char ITEM_TAG_CHAR = '\x12';
// Enumeration of different types of item tags
enum ETagCodes
{
ETAG_ITEM = 0,
ETAG_PLAYER,
ETAG_SPAM,
ETAG_ACHIEVEMENT,
ETAG_DIALOG_RESPONSE,
ETAG_COMMAND,
ETAG_SPELL,
ETAG_FACTION,
ETAG_COMMAND2,
ETAG_UNKNOWN9,
ETAG_COUNT,
ETAG_FIRST = ETAG_ITEM,
ETAG_LAST = ETAG_UNKNOWN9,
ETAG_INVALID = -1,
};
//----------------------------------------------------------------------------
// Link Formatting -- Pulled from MQ code
// Create an achievement link for the given achievement.
// TODO: implement this when achievements are added, leave the signature here for reference. Code in eqlib's ItemLinks.cpp
// void FormatAchievementLink(char* Buffer, size_t BufferSize, const Achievement* achievement,
// std::string_view playerName);
// Create an item link from the given item.
void FormatItemLink(char* Buffer, size_t BufferSize, const EQ::ItemInstance* item);
// Create a spell link for the given spell, with optional spell name override. Spells on items often have
// spell name overrides that changes the display name of the spell.
void FormatSpellLink(char* Buffer, size_t BufferSize, uint32_t SpellID,
const char* spellNameOverride = nullptr);
// Format text into a clickable dialog link. The keyword is the text that will be displayed in the chat window,
// and the text is the text that will be sent to the server when the link is clicked. If no text is provided,
// then the keyword will be used as the text.
void FormatDialogLink(char* Buffer, size_t BufferSize, std::string_view keyword,
std::string_view text = {});
} }

51
common/patches/IMessage.h Normal file
View File

@ -0,0 +1,51 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#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<typename... Args>
concept AllConstChar = (std::is_convertible_v<Args, const char*> && ...);
class IMessage
{
public:
IMessage() = default;
virtual ~IMessage() = default;
// these two are the basic string message packets
virtual std::unique_ptr<EQApplicationPacket> Simple(uint32_t color, uint32_t id) const = 0;
virtual std::unique_ptr<EQApplicationPacket> Formatted(uint32_t color, uint32_t id,
const std::array<const char*, 9>& args) const = 0;
// These aren't technically messages, but they use the same format and are similar enough to include here
virtual std::unique_ptr<EQApplicationPacket> InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const = 0;
virtual std::unique_ptr<EQApplicationPacket> InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id,
const char* name, const char* spell_link) const = 0;
};
} // namespace Message

View File

@ -0,0 +1,86 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include "client_version.h"
#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"
#include <array>
using Version = EQ::versions::ClientVersion;
struct ClientComponents
{
explicit ClientComponents(Version version) : version(version)
{
switch (version) {
case Version::TOB:
messageComponent = std::make_unique<Message::TOB>();
break;
case Version::RoF2:
messageComponent = std::make_unique<Message::RoF2>();
break;
case Version::RoF:
messageComponent = std::make_unique<Message::RoF>();
break;
case Version::UF:
messageComponent = std::make_unique<Message::UF>();
break;
case Version::SoD:
messageComponent = std::make_unique<Message::SoD>();
break;
case Version::SoF:
messageComponent = std::make_unique<Message::SoF>();
break;
case Version::Titanium:
messageComponent = std::make_unique<Message::Titanium>();
break;
default:
break;
}
}
const Version version;
std::unique_ptr<Message::IMessage> messageComponent;
};
// this array must be in the same order as the Version enum because it converts Version to index directly
static const std::array<ClientComponents, EQ::versions::ClientVersionCount> s_patches = {
{
ClientComponents(Version::Unknown), // empty
ClientComponents(Version::Client62), // empty
ClientComponents(Version::Titanium),
ClientComponents(Version::SoF),
ClientComponents(Version::SoD),
ClientComponents(Version::UF),
ClientComponents(Version::RoF),
ClientComponents(Version::RoF2),
ClientComponents(Version::TOB),
}
};
const std::unique_ptr<Message::IMessage>& GetMessageComponent(Version version)
{
return s_patches.at(static_cast<uint32_t>(version)).messageComponent;
}

View File

@ -0,0 +1,13 @@
//
// Created by dannu on 4/21/2026.
//
#pragma once
#include "common/emu_versions.h"
#include <memory>
namespace Message { class IMessage; }
// store all static functions for the different patches here, this can return nullptr for unsupported patches
const std::unique_ptr<Message::IMessage>& GetMessageComponent(EQ::versions::ClientVersion version);

View File

@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "uf.h"
#include "common/struct_strategy.h" #include "common/struct_strategy.h"
class EQStreamIdentifier; class EQStreamIdentifier;
@ -48,3 +49,14 @@ namespace RoF
}; };
} /*RoF*/ } /*RoF*/
namespace Message {
class RoF : public UF
{
public:
RoF() = default;
~RoF() override = default;
};
} // namespace Message

View File

@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "rof.h"
#include "common/struct_strategy.h" #include "common/struct_strategy.h"
class EQStreamIdentifier; class EQStreamIdentifier;
@ -48,3 +49,14 @@ namespace RoF2
}; };
}; /*RoF2*/ }; /*RoF2*/
namespace Message {
class RoF2 : public RoF
{
public:
RoF2() = default;
~RoF2() override = default;
};
} // namespace Message

View File

@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "sof.h"
#include "common/struct_strategy.h" #include "common/struct_strategy.h"
class EQStreamIdentifier; class EQStreamIdentifier;
@ -48,3 +49,14 @@ namespace SoD
}; };
} /*SoD*/ } /*SoD*/
namespace Message {
class SoD : public SoF
{
public:
SoD() = default;
~SoD() override = default;
};
} // namespace Message

View File

@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "titanium.h"
#include "common/struct_strategy.h" #include "common/struct_strategy.h"
class EQStreamIdentifier; class EQStreamIdentifier;
@ -48,3 +49,14 @@ namespace SoF
}; };
} /*SoF*/ } /*SoF*/
namespace Message {
class SoF : public Titanium
{
public:
SoF() = default;
~SoF() override = default;
};
} // namespace Message

View File

@ -32,6 +32,7 @@
#include "common/raid.h" #include "common/raid.h"
#include "common/rulesys.h" #include "common/rulesys.h"
#include "common/strings.h" #include "common/strings.h"
#include "zone/string_ids.h"
#include <sstream> #include <sstream>
@ -3919,3 +3920,98 @@ namespace Titanium
return index; // as long as we guard against bad slots server side, we should be fine return index; // as long as we guard against bad slots server side, we should be fine
} }
} /*Titanium*/ } /*Titanium*/
namespace Message {
std::unique_ptr<EQApplicationPacket> Titanium::Simple(uint32_t color, uint32_t id) const
{
uint32_t string_id = ResolveID(id);
if (string_id > 0) {
auto outapp = std::make_unique<EQApplicationPacket>(OP_SimpleMessage, sizeof(SimpleMessage_Struct));
auto* sms = reinterpret_cast<SimpleMessage_Struct*>(outapp->pBuffer);
sms->string_id = string_id;
sms->color = color;
sms->unknown8 = 0;
return outapp;
}
return nullptr;
}
std::unique_ptr<EQApplicationPacket> Titanium::Formatted(
uint32_t color, uint32_t id, const std::array<const char*, 9>& args) const
{
uint32_t string_id = ResolveID(id);
if (string_id > 0) {
std::array<const char*, 9> 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 std::make_unique<EQApplicationPacket>(OP_FormattedMessage, std::move(buf));
}
return nullptr;
}
std::unique_ptr<EQApplicationPacket> Titanium::InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const
{
auto outapp = std::make_unique<EQApplicationPacket>(OP_InterruptCast, sizeof(InterruptCast_Struct));
auto ic = reinterpret_cast<InterruptCast_Struct*>(outapp->pBuffer);
ic->messageid = ResolveID(message);
ic->spawnid = spawn_id;
outapp->priority = 5;
return outapp;
}
std::unique_ptr<EQApplicationPacket> Titanium::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id,
const char* name,
const char* spell_link) const
{
auto outapp = std::make_unique<EQApplicationPacket>(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(name) + 1);
auto ic = reinterpret_cast<InterruptCast_Struct*>(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<const char*, 9>& 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

View File

@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "IMessage.h"
#include "common/struct_strategy.h" #include "common/struct_strategy.h"
class EQStreamIdentifier; class EQStreamIdentifier;
@ -48,3 +49,30 @@ namespace Titanium
}; };
} /*Titanium*/ } /*Titanium*/
// out-going message packets
namespace Message {
class Titanium : public IMessage
{
public:
Titanium() = default;
~Titanium() override = default;
std::unique_ptr<EQApplicationPacket> Simple(uint32_t color, uint32_t id) const override;
std::unique_ptr<EQApplicationPacket> Formatted(uint32_t color, uint32_t id,
const std::array<const char*, 9>& args) const override;
std::unique_ptr<EQApplicationPacket> InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const override;
std::unique_ptr<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<const char*, 9>& args) const;
};
} // namespace Message

View File

@ -27,6 +27,7 @@
#include "common/packet_dump.h" #include "common/packet_dump.h"
#include "world/sof_char_create_data.h" #include "world/sof_char_create_data.h"
#include "zone/string_ids.h"
namespace TOB namespace TOB
{ {
@ -37,8 +38,8 @@ namespace TOB
void SerializeItem(SerializeBuffer &buffer, const EQ::ItemInstance* inst, int16 slot_id, uint8 depth, ItemPacketType packet_type); void SerializeItem(SerializeBuffer &buffer, const EQ::ItemInstance* inst, int16 slot_id, uint8 depth, ItemPacketType packet_type);
// message link converters // message link converters
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);
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);
// SpawnAppearance // SpawnAppearance
static inline uint32 ServerToTOBSpawnAppearanceType(uint32 server_type); static inline uint32 ServerToTOBSpawnAppearanceType(uint32 server_type);
@ -388,8 +389,7 @@ namespace TOB
VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sender); VARSTRUCT_ENCODE_STRING(OutBuffer, emu->sender);
VARSTRUCT_ENCODE_STRING(OutBuffer, emu->targetname); VARSTRUCT_ENCODE_STRING(OutBuffer, emu->targetname);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown VARSTRUCT_ENCODE_TYPE(uint64, OutBuffer, 0); // Unknown
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->language); VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->language);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->chan_num); VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->chan_num);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown
@ -397,11 +397,13 @@ namespace TOB
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->skill_in_language); VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->skill_in_language);
VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str());
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0); // Unknown
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0);// Unknown
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0);// Unknown
VARSTRUCT_ENCODE_TYPE(uint16, OutBuffer, 0); // Unknown
VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0); // Unknown VARSTRUCT_ENCODE_STRING(OutBuffer, "");
VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, 0); // Unknown
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0);// Unknown
delete[] __emu_buffer; delete[] __emu_buffer;
dest->FastQueuePacket(&in, ack_req); dest->FastQueuePacket(&in, ack_req);
@ -651,43 +653,6 @@ namespace TOB
FINISH_ENCODE(); FINISH_ENCODE();
} }
ENCODE(OP_FormattedMessage)
{
EQApplicationPacket* in = *p;
*p = nullptr;
FormattedMessage_Struct* emu = (FormattedMessage_Struct*)in->pBuffer;
char* old_message_ptr = (char*)in->pBuffer;
old_message_ptr += sizeof(FormattedMessage_Struct);
std::string old_message_array[9];
for (int i = 0; i < 9; ++i) {
if (*old_message_ptr == 0) { break; }
old_message_array[i] = old_message_ptr;
old_message_ptr += old_message_array[i].length() + 1;
}
SerializeBuffer buffer;
buffer.WriteUInt32(0); // This is a string written like the message arrays
buffer.WriteUInt8(emu->unknown0);
buffer.WriteUInt32(emu->string_id);
buffer.WriteUInt32(emu->type);
for (int i = 0; i < 9; ++i) {
std::string new_message;
ServerToTOBConvertLinks(new_message, old_message_array[i]);
buffer.WriteLengthString(new_message);
}
auto outapp = new EQApplicationPacket(OP_FormattedMessage, buffer.size());
outapp->WriteData(buffer.buffer(), buffer.size());
dest->FastQueuePacket(&outapp, ack_req);
delete in;
}
ENCODE(OP_GMTraining) { ENCODE(OP_GMTraining) {
ENCODE_LENGTH_EXACT(GMTrainee_Struct); ENCODE_LENGTH_EXACT(GMTrainee_Struct);
SETUP_DIRECT_ENCODE(GMTrainee_Struct, structs::GMTrainee_Struct); SETUP_DIRECT_ENCODE(GMTrainee_Struct, structs::GMTrainee_Struct);
@ -3676,10 +3641,13 @@ namespace TOB
uint32 Skill = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); uint32 Skill = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
// this has a size limit of 11k in the client
std::string old_message = InBuffer; std::string old_message = InBuffer;
std::string new_message; std::string new_message;
TOBToServerConvertLinks(new_message, old_message); TOBToServerConvertLinks(new_message, old_message);
// there are 15 bytes after this, part of which is an unk string, check the ENCODE for the layout
__packet->size = sizeof(ChannelMessage_Struct) + new_message.length() + 1; __packet->size = sizeof(ChannelMessage_Struct) + new_message.length() + 1;
__packet->pBuffer = new unsigned char[__packet->size]; __packet->pBuffer = new unsigned char[__packet->size];
ChannelMessage_Struct* emu = (ChannelMessage_Struct*)__packet->pBuffer; ChannelMessage_Struct* emu = (ChannelMessage_Struct*)__packet->pBuffer;
@ -4780,60 +4748,59 @@ namespace TOB
buffer.WriteInt32(0); //unsupported atm 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) { if (message_in.find('\x12') == std::string::npos) {
message_out = message_in; message_out = message_in;
return; return;
} }
auto segments = Strings::Split(message_in, '\x12'); std::vector<std::string> segments = Strings::Split(message_in, '\x12');
for (size_t segment_iter = 0; segment_iter < segments.size(); ++segment_iter) { for (size_t segment_iter = 0; segment_iter < segments.size(); ++segment_iter) {
if (segment_iter & 1) { if (segment_iter & 1) {
auto etag = std::stoi(segments[segment_iter].substr(0, 1)); auto etag = std::stoi(segments[segment_iter].substr(0, 1));
switch (etag) { switch (etag) {
case 0: case 0: {
{
size_t index = 1; 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; index += 5;
auto aug1 = segments[segment_iter].substr(index, 5); std::string aug1 = segments[segment_iter].substr(index, 5);
index += 5; index += 5;
auto aug2 = segments[segment_iter].substr(index, 5); std::string aug2 = segments[segment_iter].substr(index, 5);
index += 5; index += 5;
auto aug3 = segments[segment_iter].substr(index, 5); std::string aug3 = segments[segment_iter].substr(index, 5);
index += 5; index += 5;
auto aug4 = segments[segment_iter].substr(index, 5); std::string aug4 = segments[segment_iter].substr(index, 5);
index += 5; index += 5;
auto aug5 = segments[segment_iter].substr(index, 5); std::string aug5 = segments[segment_iter].substr(index, 5);
index += 5; index += 5;
auto aug6 = segments[segment_iter].substr(index, 5); std::string aug6 = segments[segment_iter].substr(index, 5);
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; index += 1;
auto evolutionGroup = segments[segment_iter].substr(index, 4); std::string evolutionGroup = segments[segment_iter].substr(index, 4);
index += 4; index += 4;
auto evolutionLevel = segments[segment_iter].substr(index, 2); std::string evolutionLevel = segments[segment_iter].substr(index, 2);
index += 2; index += 2;
auto ornamentationIconID = segments[segment_iter].substr(index, 5); std::string ornamentationIconID = segments[segment_iter].substr(index, 5);
index += 5; index += 5;
auto itemHash = segments[segment_iter].substr(index, 8); std::string itemHash = segments[segment_iter].substr(index, 8);
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('\x12');
message_out.push_back('0'); //etag item message_out.push_back('0'); //etag item
message_out.append(item_id); message_out.append(item_id);
@ -4867,14 +4834,13 @@ namespace TOB
message_out.push_back('\x12'); message_out.push_back('\x12');
break; break;
} }
} } else {
else {
message_out.append(segments[segment_iter]); 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; message_out = message_in;
} }
@ -5598,3 +5564,135 @@ namespace TOB
} }
} /*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<const char*, 9>& 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;
}
}
std::unique_ptr<EQApplicationPacket> TOB::Formatted(uint32_t color, uint32_t id,
const std::array<const char*, 9>& args) const
{
uint32_t string_id = ResolveID(id);
if (string_id > 0) {
std::array<const char*, 9> 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 std::make_unique<EQApplicationPacket>(OP_FormattedMessage, std::move(buffer));
}
return nullptr;
}
std::unique_ptr<EQApplicationPacket> TOB::InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const
{
auto outapp = std::make_unique<EQApplicationPacket>(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(spell_link) + 1);
auto ic = reinterpret_cast<InterruptCast_Struct*>(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;
}
std::unique_ptr<EQApplicationPacket> TOB::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id,
const char* name,
const char* spell_link) const
{
auto outapp = std::make_unique<EQApplicationPacket>(OP_InterruptCast,
sizeof(InterruptCast_Struct) + strlen(name) + strlen(spell_link) + 2);
auto ic = reinterpret_cast<InterruptCast_Struct*>(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

View File

@ -1,6 +1,6 @@
#ifndef COMMON_LAURION_H #pragma once
#define COMMON_LAURION_H
#include "rof2.h"
#include "../struct_strategy.h" #include "../struct_strategy.h"
class EQStreamIdentifier; class EQStreamIdentifier;
@ -34,4 +34,25 @@ namespace TOB
}; /*TOB*/ }; /*TOB*/
#endif /*COMMON_LAURION_H*/ namespace Message {
class TOB : public RoF2
{
public:
TOB() {}
~TOB() override {}
std::unique_ptr<EQApplicationPacket> Formatted(uint32_t color, uint32_t id,
const std::array<const char*, 9>& args) const override;
std::unique_ptr<EQApplicationPacket> InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const override;
std::unique_ptr<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<const char*, 9>& args) const override;
};
} // namespace Message

View File

@ -24,7 +24,6 @@ E(OP_DeleteSpawn)
E(OP_DisciplineUpdate) E(OP_DisciplineUpdate)
E(OP_ExpansionInfo) E(OP_ExpansionInfo)
E(OP_ExpUpdate) E(OP_ExpUpdate)
E(OP_FormattedMessage)
E(OP_GMTraining) E(OP_GMTraining)
E(OP_GMTrainSkillConfirm) E(OP_GMTrainSkillConfirm)
E(OP_GroundSpawn) E(OP_GroundSpawn)

View File

@ -741,8 +741,8 @@ namespace TOB {
/*132*/ float y; /*132*/ float y;
/*136*/ float x; /*136*/ float x;
/*140*/ float z; /*140*/ float z;
/*144*/ uint8 level; /*144*/ uint8 type;
/*145*/ uint8 type; /*145*/ uint8 level;
/*146*/ uint8 charges; //no idea if these are right; eqlib doesn't seem to know either /*146*/ uint8 charges; //no idea if these are right; eqlib doesn't seem to know either
/*147*/ uint8 activatable; /*147*/ uint8 activatable;
/*148*/ uint32 unknown1; //might be some timer, not sure though /*148*/ uint32 unknown1; //might be some timer, not sure though
@ -754,7 +754,7 @@ namespace TOB {
/*004*/ int32 unknown004; /*004*/ int32 unknown004;
/*008*/ EQAffect_Struct affect; /*008*/ EQAffect_Struct affect;
/*160*/ uint32 slot_id; /*160*/ uint32 slot_id;
/*164*/ uint32 buff_fade; /*164*/ uint32 buff_fade; // 1: remove, 2: modify, 3: add new
/*168*/ /*168*/
}; };

View File

@ -17,6 +17,7 @@
*/ */
#pragma once #pragma once
#include "sod.h"
#include "common/struct_strategy.h" #include "common/struct_strategy.h"
class EQStreamIdentifier; class EQStreamIdentifier;
@ -48,3 +49,14 @@ namespace UF
}; };
}; /*UF*/ }; /*UF*/
namespace Message {
class UF : public SoD
{
public:
UF() = default;
~UF() override = default;
};
} // namespace Message

View File

@ -81,7 +81,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th
| `OP_CashReward` | 🟡 Unverified | | | | `OP_CashReward` | 🟡 Unverified | | |
| `OP_CastSpell` | 🟢 Verified | | | | `OP_CastSpell` | 🟢 Verified | | |
| `OP_ChangeSize` | 🟢 Verified | | | | `OP_ChangeSize` | 🟢 Verified | | |
| `OP_ChannelMessage` | 🟡 Unverified | | | | `OP_ChannelMessage` | 🟢 Verified | | |
| `OP_ChangePetName` | 🔴 Not-Set | | | | `OP_ChangePetName` | 🔴 Not-Set | | |
| `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | | | `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | |
| `OP_CharacterCreateRequest` | 🟢 Verified | | | | `OP_CharacterCreateRequest` | 🟢 Verified | | |

View File

@ -28,6 +28,7 @@ set(zone_sources
client_mods.cpp client_mods.cpp
client_packet.cpp client_packet.cpp
client_process.cpp client_process.cpp
client_version.cpp
combat_record.cpp combat_record.cpp
corpse.cpp corpse.cpp
dialogue_window.cpp dialogue_window.cpp
@ -130,6 +131,7 @@ set(zone_headers
cheat_manager.h cheat_manager.h
client.h client.h
client_packet.h client_packet.h
client_version.h
combat_record.h combat_record.h
command.h command.h
common.h common.h
@ -673,6 +675,8 @@ set_property(TARGET gm_commands_zone PROPERTY FOLDER libraries)
add_executable(zone ${zone_sources} ${zone_headers}) add_executable(zone ${zone_sources} ${zone_headers})
target_include_directories(zone PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
install(TARGETS zone RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS zone RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
if(EQEMU_BUILD_PCH) if(EQEMU_BUILD_PCH)

View File

@ -5922,13 +5922,13 @@ bool Bot::CastSpell(
if (DivineAura()) { if (DivineAura()) {
LogSpellsDetail("Spell casting canceled: cannot cast while Divine Aura is in effect"); LogSpellsDetail("Spell casting canceled: cannot cast while Divine Aura is in effect");
InterruptSpell(SPELL_FIZZLE, 0x121, false); InterruptSpell(SPELL_FIZZLE, Chat::SpellFailure, false);
return false; return false;
} }
if (slot < EQ::spells::CastingSlot::MaxGems && !CheckFizzle(spell_id)) { if (slot < EQ::spells::CastingSlot::MaxGems && !CheckFizzle(spell_id)) {
int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE;
InterruptSpell(fizzle_msg, 0x121, spell_id); InterruptSpell(fizzle_msg, Chat::SpellFailure, spell_id);
uint32 use_mana = ((spells[spell_id].mana) / 4); uint32 use_mana = ((spells[spell_id].mana) / 4);
LogSpellsDetail("Spell casting canceled: fizzled. [{}] mana has been consumed", use_mana); LogSpellsDetail("Spell casting canceled: fizzled. [{}] mana has been consumed", use_mana);

View File

@ -46,6 +46,7 @@
#include "common/zone_store.h" #include "common/zone_store.h"
#include "zone/bot_command.h" #include "zone/bot_command.h"
#include "zone/cheat_manager.h" #include "zone/cheat_manager.h"
#include "zone/client_version.h"
#include "zone/command.h" #include "zone/command.h"
#include "zone/dialogue_window.h" #include "zone/dialogue_window.h"
#include "zone/dynamic_zone.h" #include "zone/dynamic_zone.h"
@ -1792,7 +1793,7 @@ void Client::Message(uint32 type, const char* message, ...) {
} }
void Client::FilteredMessage(Mob *sender, uint32 type, eqFilterType filter, const char* message, ...) { void Client::FilteredMessage(Mob *sender, uint32 type, eqFilterType filter, const char* message, ...) {
if (!FilteredMessageCheck(sender, filter)) if (!ShouldGetPacket(sender, filter))
return; return;
va_list argptr; va_list argptr;
@ -3808,18 +3809,12 @@ void Client::MessageString(uint32 type, uint32 string_id, uint32 distance)
return; return;
if (GetFilter(FilterSpellCrits) == FilterHide && type == Chat::SpellCrit) if (GetFilter(FilterSpellCrits) == FilterHide && type == Chat::SpellCrit)
return; return;
auto outapp = new EQApplicationPacket(OP_SimpleMessage, 12);
SimpleMessage_Struct* sms = (SimpleMessage_Struct*)outapp->pBuffer;
sms->color=type;
sms->string_id=string_id;
sms->unknown8=0; if (distance > 0)
Message::CloseMessageString(this, false, static_cast<float>(distance))(
if(distance>0) type, string_id);
entity_list.QueueCloseClients(this,outapp,false,distance);
else else
QueuePacket(outapp); Message::MessageString(this, type, string_id);
safe_delete(outapp);
} }
// //
@ -3829,9 +3824,9 @@ void Client::MessageString(uint32 type, uint32 string_id, uint32 distance)
// This hack sucks but it's gonna work for now. // This hack sucks but it's gonna work for now.
// //
void Client::MessageString(uint32 type, uint32 string_id, const char* message1, void Client::MessageString(uint32 type, uint32 string_id, const char* message1,
const char* message2,const char* message3,const char* message4, const char* message2, const char* message3, const char* message4,
const char* message5,const char* message6,const char* message7, const char* message5, const char* message6, const char* message7,
const char* message8,const char* message9, uint32 distance) const char* message8, const char* message9, uint32 distance)
{ {
if (GetFilter(FilterSpellDamage) == FilterHide && type == Chat::NonMelee) if (GetFilter(FilterSpellDamage) == FilterHide && type == Chat::NonMelee)
return; return;
@ -3847,34 +3842,12 @@ void Client::MessageString(uint32 type, uint32 string_id, const char* message1,
if (type == Chat::Emote) if (type == Chat::Emote)
type = 4; type = 4;
if (!message1) {
MessageString(type, string_id); // use the simple message instead
return;
}
const char *message_arg[] = {
message1, message2, message3, message4, message5,
message6, message7, message8, message9
};
SerializeBuffer buf(20);
buf.WriteInt32(0); // unknown
buf.WriteInt32(string_id);
buf.WriteInt32(type);
for (auto &m : message_arg) {
if (m == nullptr)
break;
buf.WriteString(m);
}
buf.WriteInt8(0); // prevent oob in packet translation, maybe clean that up sometime
auto outapp = std::make_unique<EQApplicationPacket>(OP_FormattedMessage, std::move(buf));
if (distance > 0) if (distance > 0)
entity_list.QueueCloseClients(this, outapp.get(), false, distance); Message::CloseMessageString(this, false, static_cast<float>(distance))(type, string_id, message1,
message2, message3, message4, message5, message6, message7, message8, message9);
else else
QueuePacket(outapp.get()); Message::MessageString(this, type, string_id, message1, message2, message3, message4, message5,
message6, message7, message8, message9);
} }
void Client::MessageString(const CZClientMessageString_Struct* msg) void Client::MessageString(const CZClientMessageString_Struct* msg)
@ -3898,60 +3871,49 @@ void Client::MessageString(const CZClientMessageString_Struct* msg)
} }
} }
// helper function, returns true if we should see the message // helper function, returns true if the client should get the packet based on the filter and sender
bool Client::FilteredMessageCheck(Mob *sender, eqFilterType filter) bool Client::ShouldGetPacket(Mob *sender, eqFilterType filter)
{ {
eqFilterMode mode = GetFilter(filter); eqFilterMode mode = GetFilter(filter);
// easy ones first
if (mode == FilterShow) {
return true;
} else if (mode == FilterHide) {
return false;
}
if (sender != this && mode == FilterShowSelfOnly) { // easy ones first
if (mode == FilterShow)
return true;
if (mode == FilterHide)
return false; return false;
} else if (sender) {
if (mode == FilterShowGroupOnly) { if (sender != this && mode == FilterShowSelfOnly)
auto g = GetGroup(); return false;
auto r = GetRaid();
if (g) { if (sender != nullptr && mode == FilterShowGroupOnly) {
if (g->IsGroupMember(sender)) { if (sender == this)
return true; return true;
}
} else if (r && sender->IsClient()) { Group* g = GetGroup();
auto rgid1 = r->GetGroup(this); if (g && g->IsGroupMember(sender))
auto rgid2 = r->GetGroup(sender->CastToClient()); return true;
if (rgid1 != RAID_GROUPLESS && rgid1 == rgid2) {
return true; Raid* r = GetRaid();
} if (r && sender->IsClient()) {
} else { uint32 rgid1 = r->GetGroup(this);
return false; uint32 rgid2 = r->GetGroup(sender->CastToClient());
} if (rgid1 != RAID_GROUPLESS && rgid1 == rgid2)
return true;
} else {
return false;
} }
} }
// we passed our checks // fallback case (send by default)
return true; return true;
} }
void Client::FilteredMessageString(Mob *sender, uint32 type, void Client::FilteredMessageString(Mob *sender, uint32 type,
eqFilterType filter, uint32 string_id) eqFilterType filter, uint32 string_id)
{ {
if (!FilteredMessageCheck(sender, filter)) if (ShouldGetPacket(sender, filter))
return; MessageString(type, string_id);
auto outapp = new EQApplicationPacket(OP_SimpleMessage, 12);
SimpleMessage_Struct *sms = (SimpleMessage_Struct *)outapp->pBuffer;
sms->color = type;
sms->string_id = string_id;
sms->unknown8 = 0;
QueuePacket(outapp);
safe_delete(outapp);
return;
} }
void Client::FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id, void Client::FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id,
@ -3959,37 +3921,16 @@ void Client::FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter
const char *message4, const char *message5, const char *message6, const char *message4, const char *message5, const char *message6,
const char *message7, const char *message8, const char *message9) const char *message7, const char *message8, const char *message9)
{ {
if (!FilteredMessageCheck(sender, filter))
return;
if (type == Chat::Emote)
type = 4;
if (!message1) { if (!message1) {
FilteredMessageString(sender, type, filter, string_id); // use the simple message instead FilteredMessageString(sender, type, filter, string_id); // use the simple message instead
return; } else if (ShouldGetPacket(sender, filter)) {
if (type == Chat::Emote)
type = 4;
MessageString(
type, string_id, message1, message2, message3, message4,
message5, message6, message7, message8, message9);
} }
const char *message_arg[] = {
message1, message2, message3, message4, message5,
message6, message7, message8, message9
};
SerializeBuffer buf(20);
buf.WriteInt32(0); // unknown
buf.WriteInt32(string_id);
buf.WriteInt32(type);
for (auto &m : message_arg) {
if (m == nullptr)
break;
buf.WriteString(m);
}
buf.WriteInt8(0); // prevent oob in packet translation, maybe clean that up sometime
auto outapp = std::make_unique<EQApplicationPacket>(OP_FormattedMessage, std::move(buf));
QueuePacket(outapp.get());
} }
void Client::Tell_StringID(uint32 string_id, const char *who, const char *message) void Client::Tell_StringID(uint32 string_id, const char *who, const char *message)

View File

@ -343,10 +343,10 @@ public:
void DyeArmor(EQ::TintProfile* dye); void DyeArmor(EQ::TintProfile* dye);
void DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue, uint8 use_tint = 0x00); void DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue, uint8 use_tint = 0x00);
uint8 SlotConvert(uint8 slot,bool bracer=false); uint8 SlotConvert(uint8 slot,bool bracer=false);
void MessageString(uint32 type, uint32 string_id, uint32 distance = 0); void MessageString(uint32 type, uint32 string_id, uint32 distance = 0) override;
void MessageString(uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0); void MessageString(uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0) override;
void MessageString(const CZClientMessageString_Struct* msg); void MessageString(const CZClientMessageString_Struct* msg);
bool FilteredMessageCheck(Mob *sender, eqFilterType filter); bool ShouldGetPacket(Mob *sender, eqFilterType filter);
void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id); void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id);
void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter,
uint32 string_id, const char *message1, const char *message2 = nullptr, uint32 string_id, const char *message1, const char *message2 = nullptr,
@ -1550,7 +1550,8 @@ public:
inline const EQ::versions::ClientVersion ClientVersion() const { return m_ClientVersion; } inline const EQ::versions::ClientVersion ClientVersion() const { return m_ClientVersion; }
inline const uint32 ClientVersionBit() const { return m_ClientVersionBit; } inline const uint32 ClientVersionBit() const { return m_ClientVersionBit; }
inline void SetClientVersion(EQ::versions::ClientVersion client_version) { m_ClientVersion = client_version; } void SetClientVersion(EQ::versions::ClientVersion client_version);
EQ::versions::ClientVersion GetClientVersion() const;
/** Adventure Stuff **/ /** Adventure Stuff **/
void SendAdventureError(const char *error); void SendAdventureError(const char *error);

View File

@ -8912,31 +8912,20 @@ void Client::Handle_OP_Hide(const EQApplicationPacket *app)
tmHidden = Timer::GetCurrentTime(); tmHidden = Timer::GetCurrentTime();
} }
if (GetClass() == Class::Rogue) { if (GetClass() == Class::Rogue) {
auto outapp = new EQApplicationPacket(OP_SimpleMessage, sizeof(SimpleMessage_Struct)); uint32 string_id = HIDE_FAIL;
SimpleMessage_Struct *msg = (SimpleMessage_Struct *)outapp->pBuffer; Mob* evadetar = GetTarget();
msg->color = 0x010E;
Mob *evadetar = GetTarget(); if (!auto_attack && evadetar && evadetar->CheckAggro(this) && evadetar->IsNPC()) {
if (!auto_attack && (evadetar && evadetar->CheckAggro(this)
&& evadetar->IsNPC())) {
if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) { if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) {
msg->string_id = EVADE_SUCCESS; string_id = EVADE_SUCCESS;
RogueEvade(evadetar); RogueEvade(evadetar);
} } else
else { string_id = EVADE_FAIL;
msg->string_id = EVADE_FAIL; } else if (hidden)
} string_id = HIDE_SUCCESS;
}
else { MessageString(Chat::Skills, string_id);
if (hidden) {
msg->string_id = HIDE_SUCCESS;
}
else {
msg->string_id = HIDE_FAIL;
}
}
FastQueuePacket(&outapp);
} }
return;
} }
void Client::Handle_OP_HideCorpse(const EQApplicationPacket *app) void Client::Handle_OP_HideCorpse(const EQApplicationPacket *app)
@ -10325,10 +10314,10 @@ void Client::Handle_OP_ManaChange(const EQApplicationPacket *app)
if (app->size == 0) { if (app->size == 0) {
// i think thats the sign to stop the songs // i think thats the sign to stop the songs
if (IsBardSong(casting_spell_id) || HasActiveSong()) { if (IsBardSong(casting_spell_id) || HasActiveSong()) {
InterruptSpell(SONG_ENDS, 0x121); //Live doesn't send song end message anymore (~Kayen 1/26/22) InterruptSpell(SONG_ENDS, Chat::SpellFailure); //Live doesn't send song end message anymore (~Kayen 1/26/22)
} }
else { else {
InterruptSpell(INTERRUPT_SPELL, 0x121); InterruptSpell(INTERRUPT_SPELL, Chat::SpellFailure);
} }
return; return;
} }

View File

@ -229,11 +229,11 @@ bool Client::Process() {
} }
if (song_target == nullptr) { if (song_target == nullptr) {
InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); InterruptSpell(SONG_ENDS_ABRUPTLY, Chat::SpellFailure, bardsong);
} }
else { else {
if (!ApplyBardPulse(bardsong, song_target, bardsong_slot)) { if (!ApplyBardPulse(bardsong, song_target, bardsong_slot)) {
InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); InterruptSpell(SONG_ENDS_ABRUPTLY, Chat::SpellFailure, bardsong);
} }
} }
} }

29
zone/client_version.cpp Normal file
View File

@ -0,0 +1,29 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include "client_version.h"
using Version = EQ::versions::ClientVersion;
void Client::SetClientVersion(Version client_version)
{
m_ClientVersion = client_version;
m_ClientVersionBit = EQ::versions::ConvertClientVersionToClientVersionBit(client_version);
}
Version Client::GetClientVersion() const { return m_ClientVersion; }

150
zone/client_version.h Normal file
View File

@ -0,0 +1,150 @@
//
// 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<uint16, Client*>;
template<typename Obj> using ComponentGetter = std::function<Obj*(const Client*)>;
template <typename Fun, typename Obj, typename... Args>
requires std::is_member_function_pointer_v<Fun>
static void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args)
{
if (obj != nullptr) {
std::unique_ptr<EQApplicationPacket> app = std::invoke(fun, obj, std::forward<Args>(args)...);
if (app)
c->QueuePacket(app.get());
}
}
// packet generator queue functions
static auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = true)
{
return [=]<typename Fun, typename Obj, typename... Args>(Fun fun, const ComponentGetter<Obj>& component, Args&&... args)
requires std::is_member_function_pointer_v<Fun>
{
std::array<std::unique_ptr<EQApplicationPacket>, EQ::versions::ClientVersionCount> build_packets;
std::unordered_map<uint16, Client*> client_list = entity_list.GetClientList();
for (auto [_, ent] : client_list) {
if (!ignore_sender || ent != sender) {
auto& packet = build_packets.at(static_cast<uint32_t>(ent->ClientVersion()));
if (!packet)
if (auto comp = component(ent); comp != nullptr)
packet = std::invoke(fun, comp, std::forward<Args>(args)...);
if (packet)
ent->QueuePacket(packet.get(), ackreq, Client::CLIENT_CONNECTED);
}
}
};
}
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<float>(zone->GetClientUpdateRange());
return [=]<typename Fun, typename Obj, typename... Args>(Fun fun, const ComponentGetter<Obj>& component, Args&&... args)
requires std::is_member_function_pointer_v<Fun>
{
if (sender == nullptr) {
QueueClients(sender, ignore_sender, is_ack_required)(fun, component, std::forward<Args>(args)...);
} else {
float distance_squared = distance * distance;
std::array<std::unique_ptr<EQApplicationPacket>, EQ::versions::ClientVersionCount> 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.at(static_cast<uint32_t>(client->ClientVersion()));
if (!packet)
if (auto comp = component(client); comp != nullptr)
packet = std::invoke(fun, comp, std::forward<Args>(args)...);
if (packet)
client->QueuePacket(packet.get(), is_ack_required, Client::CLIENT_CONNECTED);
}
}
}
}
};
}
} // namespace ClientPatch
// Helpers for the Message interface to send message packets
namespace Message {
// this can return nullptr when the component doesn't exist for the version
static std::function GetComponent = [](const Client* c) -> IMessage* {
return GetMessageComponent(c->GetClientVersion()).get();
};
// Helper functions to wrap the packet construction in sends
template <AllConstChar... Args>
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<const char*, 9> 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 [=]<AllConstChar... Args>(uint32_t type, uint32_t id, Args&&... args)
requires (sizeof...(Args) <= 9)
{
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<const char*, 9> 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

View File

@ -82,6 +82,7 @@
#include "common/strings.h" #include "common/strings.h"
#include "zone/bot.h" #include "zone/bot.h"
#include "zone/client.h" #include "zone/client.h"
#include "zone/client_version.h"
#include "zone/fastmath.h" #include "zone/fastmath.h"
#include "zone/lua_parser.h" #include "zone/lua_parser.h"
#include "zone/mob_movement_manager.h" #include "zone/mob_movement_manager.h"
@ -95,7 +96,6 @@
#include <cassert> #include <cassert>
#include "common/links.h" #include "common/links.h"
#include "common/packet_dump.h"
extern Zone *zone; extern Zone *zone;
extern volatile bool is_zone_loaded; extern volatile bool is_zone_loaded;
@ -335,35 +335,21 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
Mob::SetMana(GetMana() - use_mana); // We send StopCasting which will update mana Mob::SetMana(GetMana() - use_mana); // We send StopCasting which will update mana
StopCasting(); StopCasting();
// TODO: can handle spell name overrides here char spell_link[Links::MAX_LINK_SIZE];
std::string spell_name(GetSpellName(spell_id)); Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spell_id);
std::string spell_link = Links::FormatSpellLink(spell_id, spell_name);
// pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches if (IsClient())
MessageString(Chat::SpellFailure, fizzle_msg, spell_link.c_str()); Message::MessageString(CastToClient(), Chat::SpellFailure, fizzle_msg, spell_link);
/** /**
* Song Failure message * Song Failure message
* pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches
*/ */
entity_list.FilteredMessageCloseString( Message::CloseMessageString(this, true, RuleI(Range, SpellMessages),
this, nullptr, true, IsClient() ? FilterPCSpells : FilterNPCSpells)(
true, Chat::SpellFailure, fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER, GetName(), spell_link);
RuleI(Range, SpellMessages),
Chat::SpellFailure,
(IsClient() ? FilterPCSpells : FilterNPCSpells),
(fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER),
0,
/*
MessageFormat: A missed note brings %1's song to a close! (TOB: A missed note brings %1's %2 to a close!)
MessageFormat: %1's spell fizzles! (TOB: %1's %2 spell fizzles!)
*/
GetName(),
spell_link.c_str()
);
TryTriggerOnCastRequirement(); TryTriggerOnCastRequirement();
return(false); return false;
} }
SaveSpellLoc(); SaveSpellLoc();
@ -1254,7 +1240,6 @@ void Mob::InterruptSpell(uint16 spellid)
// color not used right now // color not used right now
void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid)
{ {
EQApplicationPacket *outapp = nullptr;
uint16 message_other; uint16 message_other;
bool bard_song_mode = false; //has the bard song gone to auto repeat mode bool bard_song_mode = false; //has the bard song gone to auto repeat mode
@ -1312,24 +1297,13 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid)
if(!message) if(!message)
message = IsBardSong(spellid) ? SONG_ENDS_ABRUPTLY : INTERRUPT_SPELL; message = IsBardSong(spellid) ? SONG_ENDS_ABRUPTLY : INTERRUPT_SPELL;
// TODO: can handle spell name overrides here
std::string spellname(GetSpellName(spellid));
std::string spelllink = Links::FormatSpellLink(spellid, spellname);
// clients need some packets // clients need some packets
if (IsClient() && message != SONG_ENDS) if (IsClient() && message != SONG_ENDS)
{ {
// the interrupt message // the interrupt message
outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + spelllink.size() + 1); char spell_link[Links::MAX_LINK_SIZE];
InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer; Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spellid);
ic->messageid = message; Message::InterruptSpell(CastToClient(), message, GetID(), spell_link);
ic->spawnid = GetID();
// pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches
fmt::format_to_n(ic->message, spelllink.size(), "{}", spelllink);
outapp->priority = 5;
CastToClient()->QueuePacket(outapp);
safe_delete(outapp);
SendSpellBarEnable(spellid); SendSpellBarEnable(spellid);
} }
@ -1355,15 +1329,9 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid)
} }
// this is the actual message, it works the same as a formatted message // this is the actual message, it works the same as a formatted message
outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(GetCleanName()) + spelllink.size() + 2); char spell_link[Links::MAX_LINK_SIZE];
InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer; Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spellid);
ic->messageid = message_other; Message::InterruptSpellOther(this, message_other, GetID(), GetCleanName(), spell_link);
ic->spawnid = GetID();
// pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches
fmt::format_to_n(ic->message, sizeof(GetCleanName()) + spelllink.size() + 1, "{}\x00{}", GetCleanName(), spelllink);
entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, SongMessages), 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells);
safe_delete(outapp);
} }
// this is like interrupt, just it doesn't spam interrupt packets to everyone // this is like interrupt, just it doesn't spam interrupt packets to everyone
@ -7299,16 +7267,11 @@ void Mob::DoBardCastingFromItemClick(bool is_casting_bard_song, uint32 cast_time
Known bug: When a bard uses an augment with a clicky that has a cast time, the cast won't display. This issue only affects bards. Known bug: When a bard uses an augment with a clicky that has a cast time, the cast won't display. This issue only affects bards.
*/ */
if (is_casting_bard_song) { if (is_casting_bard_song) {
//For spells with cast times. Cancel song cast, stop pusling and start item cast. //For spells with cast times. Cancel song cast, stop pulsing and start item cast.
if (cast_time != 0) { if (cast_time != 0) {
EQApplicationPacket *outapp = nullptr; char spell_link[Links::MAX_LINK_SIZE];
outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct)); Links::FormatSpellLink(spell_link, Links::MAX_LINK_SIZE, spell_id);
InterruptCast_Struct* ic = (InterruptCast_Struct*)outapp->pBuffer; Message::InterruptSpell(CastToClient(), SONG_ENDS, GetID(), spell_link);
ic->messageid = SONG_ENDS;
ic->spawnid = GetID();
outapp->priority = 5;
CastToClient()->QueuePacket(outapp);
safe_delete(outapp);
ZeroCastingVars(); ZeroCastingVars();
ZeroBardPulseVars(); ZeroBardPulseVars();

View File

@ -41,8 +41,8 @@
#define DOORS_INSUFFICIENT_SKILL 132 //You are not sufficiently skilled to pick this lock. #define DOORS_INSUFFICIENT_SKILL 132 //You are not sufficiently skilled to pick this lock.
#define DOORS_GM 133 //You opened the locked door with your magic GM key. #define DOORS_GM 133 //You opened the locked door with your magic GM key.
#define ITEMS_INSUFFICIENT_LEVEL 136 //You are not sufficient level to use this item. #define ITEMS_INSUFFICIENT_LEVEL 136 //You are not sufficient level to use this item.
#define GAIN_XP 138 //You gain experience!! #define GAIN_XP 138 //You gain experience!! // TODO: TOB added experience value - You gain experience!%1
#define GAIN_GROUPXP 139 //You gain party experience!! #define GAIN_GROUPXP 139 //You gain party experience!! // TODO: TOB added experience value - You gain party experience!%1
#define BOW_DOUBLE_DAMAGE 143 //Your bow shot did double dmg. #define BOW_DOUBLE_DAMAGE 143 //Your bow shot did double dmg.
#define YOU_ARE_BEING_BANDAGED 147 //Someone is bandaging you. #define YOU_ARE_BEING_BANDAGED 147 //Someone is bandaging you.
#define FORAGE_GRUBS 150 //You have scrounged up some fishing grubs. #define FORAGE_GRUBS 150 //You have scrounged up some fishing grubs.
@ -69,7 +69,7 @@
#define SPELL_FIZZLE 173 //Your spell fizzles! #define SPELL_FIZZLE 173 //Your spell fizzles!
#define MUST_EQUIP_ITEM 179 //You cannot use this item unless it is equipped. #define MUST_EQUIP_ITEM 179 //You cannot use this item unless it is equipped.
#define MISS_NOTE 180 //You miss a note, bringing your song to a close! #define MISS_NOTE 180 //You miss a note, bringing your song to a close!
#define CANNOT_USE_ITEM 181 //Your race, class, or deity cannot use this item. #define CANNOT_USE_ITEM 181 //Your race, class, or deity cannot use this item. // TODO: TOB moved this: 6611 Your class, deity, and/or race may not equip %1.
#define ITEM_OUT_OF_CHARGES 182 //Item is out of charges. #define ITEM_OUT_OF_CHARGES 182 //Item is out of charges.
#define ALREADY_ON_A_MOUNT 189 //You are already on a mount. #define ALREADY_ON_A_MOUNT 189 //You are already on a mount.
#define TARGET_NO_MANA 191 //Your target has no mana to affect #define TARGET_NO_MANA 191 //Your target has no mana to affect
@ -106,7 +106,7 @@
#define NO_PET 255 //You do not have a pet. #define NO_PET 255 //You do not have a pet.
#define GATE_FAIL 260 //Your gate is too unstable, and collapses. #define GATE_FAIL 260 //Your gate is too unstable, and collapses.
#define CORPSE_CANT_SENSE 262 //You cannot sense any corpses for this PC in this zone. #define CORPSE_CANT_SENSE 262 //You cannot sense any corpses for this PC in this zone.
#define SPELL_NO_HOLD 263 //Your spell did not take hold. #define SPELL_NO_HOLD 263 //Your spell did not take hold. // TODO: This one is complex. There are 4 replacement strings, all taking arguments 9164, 9209, 9210, 9211
#define CANNOT_CHARM 267 //This NPC cannot be charmed. #define CANNOT_CHARM 267 //This NPC cannot be charmed.
#define SPELL_NO_EFFECT 268 //Your target looks unaffected. #define SPELL_NO_EFFECT 268 //Your target looks unaffected.
#define NO_INSTRUMENT_SKILL 269 //Stick to singing until you learn to play this instrument. #define NO_INSTRUMENT_SKILL 269 //Stick to singing until you learn to play this instrument.
@ -123,9 +123,9 @@
#define DISARMED_TRAP 305 //You have disarmed the trap. #define DISARMED_TRAP 305 //You have disarmed the trap.
#define LDON_SENSE_TRAP1 306 //You do not Sense any traps. #define LDON_SENSE_TRAP1 306 //You do not Sense any traps.
#define TRADESKILL_NOCOMBINE 334 //You cannot combine these items in this container type! #define TRADESKILL_NOCOMBINE 334 //You cannot combine these items in this container type!
#define TRADESKILL_FAILED 336 //You lacked the skills to fashion the items together. #define TRADESKILL_FAILED 336 //You lacked the skills to fashion the items together. // TODO: TOB - 336 You lacked the skills to fashion %1.
#define TRADESKILL_TRIVIAL 338 //You can no longer advance your skill from making this item. #define TRADESKILL_TRIVIAL 338 //You can no longer advance your skill from making this item.
#define TRADESKILL_SUCCEED 339 //You have fashioned the items together to create something new! #define TRADESKILL_SUCCEED 339 //You have fashioned the items together to create something new! // TODO: TOB - 339 You have %2fashioned the items together to create something new: %1.
#define EVADE_SUCCESS 343 //You have momentarily ducked away from the main combat. #define EVADE_SUCCESS 343 //You have momentarily ducked away from the main combat.
#define EVADE_FAIL 344 //Your attempts at ducking clear of combat fail. #define EVADE_FAIL 344 //Your attempts at ducking clear of combat fail.
#define HIDE_FAIL 345 //You failed to hide yourself. #define HIDE_FAIL 345 //You failed to hide yourself.
@ -136,11 +136,11 @@
#define MEND_SUCCESS 350 //You mend your wounds and heal some damage. #define MEND_SUCCESS 350 //You mend your wounds and heal some damage.
#define MEND_WORSEN 351 //You have worsened your wounds! #define MEND_WORSEN 351 //You have worsened your wounds!
#define MEND_FAIL 352 //You have failed to mend your wounds. #define MEND_FAIL 352 //You have failed to mend your wounds.
#define LDON_SENSE_TRAP2 367 //You have not detected any traps. #define LDON_SENSE_TRAP2 367 //You have not detected any traps. // TODO: TOB - unk
#define TRAP_TOO_FAR 368 //You are too far away from that trap to affect it. #define TRAP_TOO_FAR 368 //You are too far away from that trap to affect it. // TODO: TOB - unk
#define FAIL_DISARM_DETECTED_TRAP 370 //You fail to disarm the detected trap. #define FAIL_DISARM_DETECTED_TRAP 370 //You fail to disarm the detected trap. // TODO: TOB - unk
#define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one. #define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one.
#define PICK_LORE 379 //You cannot pick up a lore item you already possess. #define PICK_LORE 379 //You cannot pick up a lore item you already possess. // TODO: TOB - 379 You cannot pick up %1 because it is a lore item you already possess.
#define POISON_TOO_HIGH 382 //This poison is too high level for you to apply. #define POISON_TOO_HIGH 382 //This poison is too high level for you to apply.
#define TAUNT_TOO_FAR 386 //You are too far away from your target to taunt. #define TAUNT_TOO_FAR 386 //You are too far away from your target to taunt.
#define CORPSE_TOO_FAR 389 //The corpse is too far away to summon. #define CORPSE_TOO_FAR 389 //The corpse is too far away to summon.
@ -154,17 +154,17 @@
#define SONG_NEEDS_WIND 406 //You need to play a wind instrument for this song #define SONG_NEEDS_WIND 406 //You need to play a wind instrument for this song
#define SONG_NEEDS_STRINGS 407 //You need to play a stringed instrument for this song #define SONG_NEEDS_STRINGS 407 //You need to play a stringed instrument for this song
#define SONG_NEEDS_BRASS 408 //You need to play a brass instrument for this song #define SONG_NEEDS_BRASS 408 //You need to play a brass instrument for this song
#define AA_GAIN_ABILITY 410 //You have gained the ability "%T1" at a cost of %2 ability %T3. #define AA_GAIN_ABILITY 410 //You have gained the ability "%T1" at a cost of %2 ability %T3. // TODO: TOB - You have gained the ability "%B1(1)" at a cost of %2 ability %T3.
#define AA_IMPROVE 411 //You have improved %T1 %2 at a cost of %3 ability %T4. #define AA_IMPROVE 411 //You have improved %T1 %2 at a cost of %3 ability %T4. // TODO: TOB - You have improved %B1(1) %2 at a cost of %3 ability %T4.
#define TAUNT_SUCCESS 412 //You taunt %1 to ignore others and attack you! #define TAUNT_SUCCESS 412 //You taunt %1 to ignore others and attack you! // TODO: TOB - unk
#define AA_REUSE_MSG 413 //You can use the ability %B1(1) again in %2 hour(s) %3 minute(s) %4 seconds. #define AA_REUSE_MSG 413 //You can use the ability %B1(1) again in %2 hour(s) %3 minute(s) %4 seconds.
#define AA_REUSE_MSG2 414 //You can use the ability %B1(1) again in %2 minute(s) %3 seconds. #define AA_REUSE_MSG2 414 //You can use the ability %B1(1) again in %2 minute(s) %3 seconds.
#define YOU_HEALED 419 //%1 has healed you for %2 points of damage. #define YOU_HEALED 419 //%1 has healed you for %2 points of damage. // TODO: TOB - 12998 %1 healed you for %2 hit points by %3.
#define BEGINS_TO_GLOW 422 //Your %1 begins to glow. #define BEGINS_TO_GLOW 422 //Your %1 begins to glow.
#define ALREADY_INVIS 423 //%1 tries to cast an invisibility spell on you, but you are already invisible. #define ALREADY_INVIS 423 //%1 tries to cast an invisibility spell on you, but you are already invisible.
#define YOU_ARE_PROTECTED 424 //%1 tries to cast a spell on you, but you are protected. #define YOU_ARE_PROTECTED 424 //%1 tries to cast a spell on you, but you are protected.
#define TARGET_RESISTED 425 //Your target resisted the %1 spell. #define TARGET_RESISTED 425 //Your target resisted the %1 spell. // TODO: TOB - 425 %1 resisted your %2!
#define YOU_RESIST 426 //You resist the %1 spell! #define YOU_RESIST 426 //You resist the %1 spell! // TODO: TOB - 426 You resist %1!
#define YOU_CRIT_HEAL 427 //You perform an exceptional heal! (%1) #define YOU_CRIT_HEAL 427 //You perform an exceptional heal! (%1)
#define YOU_CRIT_BLAST 428 //You deliver a critical blast! (%1) #define YOU_CRIT_BLAST 428 //You deliver a critical blast! (%1)
#define SUMMONING_CORPSE 429 //Summoning your corpse. #define SUMMONING_CORPSE 429 //Summoning your corpse.
@ -176,15 +176,15 @@
#define PET_TAUNTING 438 //Taunting attacker, Master. #define PET_TAUNTING 438 //Taunting attacker, Master.
#define INTERRUPT_SPELL 439 //Your spell is interrupted. #define INTERRUPT_SPELL 439 //Your spell is interrupted.
#define LOSE_LEVEL 442 //You LOST a level! You are now level %1! #define LOSE_LEVEL 442 //You LOST a level! You are now level %1!
#define GAIN_ABILITY_POINT 446 //You have gained an ability point! You now have %1 ability point%2. #define GAIN_ABILITY_POINT 446 //You have gained an ability point! You now have %1 ability point%2. // TODO: TOB - 446 You have gained an ability point! You now have %1 ability points. (Actual system moved to 8019-8021 and can handle multiples)
#define GAIN_LEVEL 447 //You have gained a level! Welcome to level %1! #define GAIN_LEVEL 447 //You have gained a level! Welcome to level %1!
#define LANG_SKILL_IMPROVED 449 //Your language skills have improved. #define LANG_SKILL_IMPROVED 449 //Your language skills have improved.
#define OTHER_LOOTED_MESSAGE 466 //--%1 has looted a %2-- #define OTHER_LOOTED_MESSAGE 466 //--%1 has looted a %2-- // TODO: TOB - 466 --%1 has looted %2 %3 from %4.--
#define LOOTED_MESSAGE 467 //--You have looted a %1-- #define LOOTED_MESSAGE 467 //--You have looted a %1-- // TODO: TOB - 467 --You have looted %1 %2 from %3.--
#define FACTION_WORST 469 //Your faction standing with %1 could not possibly get any worse. #define FACTION_WORST 469 //Your faction standing with %1 could not possibly get any worse. // TODO: TOB - 469 Your faction standing with %B1(45) could not possibly get any worse.
#define FACTION_WORSE 470 //Your faction standing with %1 got worse. #define FACTION_WORSE 470 //Your faction standing with %1 got worse. // TODO: TOB - 14261 Your faction standing with %B1(45) has been adjusted by %2.
#define FACTION_BEST 471 //Your faction standing with %1 could not possibly get any better. #define FACTION_BEST 471 //Your faction standing with %1 could not possibly get any better. // TODO: TOB - 471 Your faction standing with %B1(45) could not possibly get any better.
#define FACTION_BETTER 472 //Your faction standing with %1 got better. #define FACTION_BETTER 472 //Your faction standing with %1 got better. // TODO: TOB - 14261 Your faction standing with %B1(45) has been adjusted by %2.
#define PET_REPORT_HP 488 //I have %1 percent of my hit points left. #define PET_REPORT_HP 488 //I have %1 percent of my hit points left.
#define PET_NO_TAUNT 489 //No longer taunting attackers, Master. #define PET_NO_TAUNT 489 //No longer taunting attackers, Master.
#define PET_DO_TAUNT 490 //Taunting attackers as normal, Master. #define PET_DO_TAUNT 490 //Taunting attackers as normal, Master.
@ -241,7 +241,7 @@
#define NPC_RAMPAGE 1044 //%1 goes on a RAMPAGE! #define NPC_RAMPAGE 1044 //%1 goes on a RAMPAGE!
#define NPC_FLURRY 1045 //%1 executes a FLURRY of attacks on %2! #define NPC_FLURRY 1045 //%1 executes a FLURRY of attacks on %2!
#define DISCIPLINE_FEARLESS 1076 //%1 becomes fearless. #define DISCIPLINE_FEARLESS 1076 //%1 becomes fearless.
#define DUEL_FINISHED 1088 //dont know text #define DUEL_FINISHED 1088 //%1 has defeated %2 in a duel to the death!
#define EATING_MESSAGE 1091 //Chomp, chomp, chomp... %1 takes a bite from a %2. #define EATING_MESSAGE 1091 //Chomp, chomp, chomp... %1 takes a bite from a %2.
#define DRINKING_MESSAGE 1093 //Glug, glug, glug... %1 takes a drink from a %2. #define DRINKING_MESSAGE 1093 //Glug, glug, glug... %1 takes a drink from a %2.
#define SUCCESSFUL_TAUNT 1095 //I'll teach you to interfere with me %3. #define SUCCESSFUL_TAUNT 1095 //I'll teach you to interfere with me %3.
@ -285,8 +285,8 @@
#define MERCHANT_CLOSED_TWO 1200 //Can't you see I'm doing something here? #define MERCHANT_CLOSED_TWO 1200 //Can't you see I'm doing something here?
#define MERCHANT_CLOSED_THREE 1201 //I am not open for business right now. #define MERCHANT_CLOSED_THREE 1201 //I am not open for business right now.
#define AA_POINTS 1215 //points #define AA_POINTS 1215 //points
#define SPELL_FIZZLE_OTHER 1218 //%1's spell fizzles! #define SPELL_FIZZLE_OTHER 1218 //%1's spell fizzles! // TODO: TOB - 1218 %1's %2 spell fizzles!
#define MISSED_NOTE_OTHER 1219 //A missed note brings %1's song to a close! #define MISSED_NOTE_OTHER 1219 //A missed note brings %1's song to a close! // TODO: TOB - 1219 A missed note brings %1's %2 to a close!
#define SPELL_LEVEL_REQ 1226 //This spell only works on people who are level %1 and under. #define SPELL_LEVEL_REQ 1226 //This spell only works on people who are level %1 and under.
#define CORPSE_DECAY_NOW 1227 //This corpse is waiting to expire. #define CORPSE_DECAY_NOW 1227 //This corpse is waiting to expire.
#define CORPSE_ITEM_LOST 1228 //Your items will no longer stay with you when you respawn on death. You will now need to return to your corpse for your items. #define CORPSE_ITEM_LOST 1228 //Your items will no longer stay with you when you respawn on death. You will now need to return to your corpse for your items.
@ -327,27 +327,27 @@
#define SENSE_CORPSE_DIRECTION 1563 //You sense a corpse in this direction. #define SENSE_CORPSE_DIRECTION 1563 //You sense a corpse in this direction.
#define DUPE_LORE_MERCHANT 1573 //%1 tells you, 'You already have the lore item, %2, on your person, on your shroud, in the bank, in a real estate, or as an augment in another item. You cannot have more than one of a particular lore item at a time.' #define DUPE_LORE_MERCHANT 1573 //%1 tells you, 'You already have the lore item, %2, on your person, on your shroud, in the bank, in a real estate, or as an augment in another item. You cannot have more than one of a particular lore item at a time.'
#define QUEUED_TELL 2458 //[queued] #define QUEUED_TELL 2458 //[queued]
#define QUEUE_TELL_FULL 2459 //[zoing and queue is full] #define QUEUE_TELL_FULL 2459 //[zoning and queue is full]
#define TRADER_BUSY_TWO 3192 //Sorry, that action cannot be performed while trading. #define TRADER_BUSY_TWO 3192 //Sorry, that action cannot be performed while trading.
#define SUSPEND_MINION_UNSUSPEND 3267 //%1 tells you, 'I live again...' #define SUSPEND_MINION_UNSUSPEND 3267 //%1 tells you, 'I live again...'
#define SUSPEND_MINION_SUSPEND 3268 //%1 tells you, 'By your command, master.' #define SUSPEND_MINION_SUSPEND 3268 //%1 tells you, 'By your command, master.'
#define ONLY_SUMMONED_PETS 3269 //3269 This effect only works with summoned pets. #define ONLY_SUMMONED_PETS 3269 //This effect only works with summoned pets.
#define SUSPEND_MINION_FIGHTING 3270 //Your pet must be at peace, first. #define SUSPEND_MINION_FIGHTING 3270 //Your pet must be at peace, first.
#define SHIELD_TARGET_NPC 3278 //You must first target a living Player Character. #define SHIELD_TARGET_NPC 3278 //You must first target a living Player Character.
#define ALREADY_SHIELDED 3279 //Either you or your target is already being shielded. #define ALREADY_SHIELDED 3279 //Either you or your target is already being shielded.
#define ALREADY_SHIELDING 3280 //Either you or your target is already shielding another. #define ALREADY_SHIELDING 3280 //Either you or your target is already shielding another.
#define START_SHIELDING 3281 //%1 begins to use %2 as a living shield! #define START_SHIELDING 3281 //%1 begins to use %2 as a living shield!
#define END_SHIELDING 3282 //%1 ceases protecting %2. #define END_SHIELDING 3282 //%1 ceases protecting %2.
#define OVER_AA_CAP 3374 //Warning: You are currently over the earned Advancement point limit of %1. Please spend some of your stored AA points. The NEXT time you zone all of your AA points over %2 will be deleted. You MUST spend the extra points now. #define OVER_AA_CAP 3374 //Warning: You are currently over the earned Advancement point limit of %1. Please spend some of your stored AA points. The NEXT time you zone all of your AA points over %2 will be deleted. You MUST spend the extra points now. // TODO: TOB - 3374 You are currently over the earned Advancement point limit of %1. You must spend some of your ability points before you can earn more.
#define TRADESKILL_MISSING_ITEM 3455 //You are missing a %1. #define TRADESKILL_MISSING_ITEM 3455 //You are missing a %1.
#define TRADESKILL_MISSING_COMPONENTS 3456 //Sorry, but you don't have everything you need for this recipe in your general inventory. #define TRADESKILL_MISSING_COMPONENTS 3456 //Sorry, but you don't have everything you need for this recipe in your general inventory.
#define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1!
#define TASK_UPDATED 3471 //Your task '%1' has been updated. #define TASK_UPDATED 3471 //Your task '%1' has been updated.
#define YOU_ASSIGNED_TASK 3472 //You have been assigned the task '%1'. #define YOU_ASSIGNED_TASK 3472 //You have been assigned the task '%1'.
#define DZ_YOU_BELONG 3500 //You cannot create this expedition since you already belong to another. #define DZ_YOU_BELONG 3500 //You cannot create this expedition since you already belong to another.
#define DZ_REPLAY_YOU 3501 //You cannot create this expedition for another %1d:%2h:%3m since you have recently played here. #define DZ_REPLAY_YOU 3501 //You cannot create this expedition for another %1d:%2h:%3m since you have recently played here. // TODO: TOB - 3501 You cannot create this expedition for another %1d:%2h:%3m:%4s since you have recently played here.
#define DZ_PLAYER_COUNT 3503 //You do not meet the player count requirement. You have %1 players. You must have at least %2 and no more than %3. #define DZ_PLAYER_COUNT 3503 //You do not meet the player count requirement. You have %1 players. You must have at least %2 and no more than %3. // TODO: TOB - 3503 You do not meet the player count requirement. You have %1 players. You must have at least %2.
#define DZ_REPLAY_OTHER 3504 //%1 cannot be added to this expedition for another %2D:%3H:%4M since they have recently played in this area. #define DZ_REPLAY_OTHER 3504 //%1 cannot be added to this expedition for another %2D:%3H:%4M since they have recently played in this area. // TODO: TOB - 3504 %1 cannot be added to this expedition for another %2d:%3h:%4m:%5s since they have recently played in this area.
#define DZ_AVAILABLE 3507 //%1 is now available to you. #define DZ_AVAILABLE 3507 //%1 is now available to you.
#define DZADD_INVITE 3508 //Sending an invitation to: %1. #define DZADD_INVITE 3508 //Sending an invitation to: %1.
#define DZ_PREVENT_ENTERING 3510 //A strange magical presence prevents you from entering. It's too dangerous to enter at the moment. #define DZ_PREVENT_ENTERING 3510 //A strange magical presence prevents you from entering. It's too dangerous to enter at the moment.
@ -358,7 +358,7 @@
#define DZ_REMOVED 3516 //%1 has been removed from %2. #define DZ_REMOVED 3516 //%1 has been removed from %2.
#define DZSWAP_INVITE 3517 //Sending an invitation to: %1. They must accept in order to swap party members. #define DZSWAP_INVITE 3517 //Sending an invitation to: %1. They must accept in order to swap party members.
#define DZ_LEADER_OFFLINE 3518 //%1 is not currently online. You can only transfer leadership to an online member of the expedition you are in. #define DZ_LEADER_OFFLINE 3518 //%1 is not currently online. You can only transfer leadership to an online member of the expedition you are in.
#define DZ_TIMER 3519 //You have %1d:%2h:%3m remaining until you may enter %4. #define DZ_TIMER 3519 //You have %1d:%2h:%3m remaining until you may enter %4. // TODO: TOB - 3519 You have %1d:%2h:%3m:%4s remaining until you may enter %5.
#define DZ_LEADER_NAME 3520 //%1 has been made the leader for this expedition. #define DZ_LEADER_NAME 3520 //%1 has been made the leader for this expedition.
#define DZ_LEADER_YOU 3521 //You have been made the leader of this expedition. #define DZ_LEADER_YOU 3521 //You have been made the leader of this expedition.
#define DZ_INVITE_ACCEPTED 3522 //%1 has accepted your offer to join your expedition. #define DZ_INVITE_ACCEPTED 3522 //%1 has accepted your offer to join your expedition.
@ -371,8 +371,8 @@
#define DZ_MINUTES_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. #define DZ_MINUTES_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end.
#define DZ_LEADER 3552 //Expedition Leader: %1 #define DZ_LEADER 3552 //Expedition Leader: %1
#define DZ_MEMBERS 3553 //Expedition Members: %1 #define DZ_MEMBERS 3553 //Expedition Members: %1
#define DZ_EVENT_TIMER 3561 //%1 cannot be added to this expedition since they have recently experienced %2. They must wait another %3D:%4H:%5M until they can experience it again. They may be added to the expedition later, once %2 has been completed. #define DZ_EVENT_TIMER 3561 //%1 cannot be added to this expedition since they have recently experienced %2. They must wait another %3D:%4H:%5M until they can experience it again. They may be added to the expedition later, once %2 has been completed. // TODO: TOB - 3561 %1 cannot be added to this expedition since they have recently experienced %2. They must wait another %3d:%4h:%5m:%6s until they can experience it again. They may be added to the expedition later, once %2 has been completed.
#define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1. #define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1. // TODO: TOB - 8337 & 8338 (has a reason now)
#define DZ_UNABLE_RETRIEVE_LEADER 3583 //Unable to retrieve dynamic zone leader to check permissions. #define DZ_UNABLE_RETRIEVE_LEADER 3583 //Unable to retrieve dynamic zone leader to check permissions.
#define DZADD_NOT_ALLOWING 3585 //The expedition is not allowing players to be added. #define DZADD_NOT_ALLOWING 3585 //The expedition is not allowing players to be added.
#define DZADD_NOT_ONLINE 3586 //%1 is not currently online. A player needs to be online to be added to a Dynamic Zone #define DZADD_NOT_ONLINE 3586 //%1 is not currently online. A player needs to be online to be added to a Dynamic Zone
@ -380,8 +380,8 @@
#define DZADD_ALREADY_PART 3588 //You can not add %1 since they are already part of this zone. #define DZADD_ALREADY_PART 3588 //You can not add %1 since they are already part of this zone.
#define DZADD_LEAVE_ZONE 3589 //You can not add %1 since they first need to leave the zone before being allowed back in. #define DZADD_LEAVE_ZONE 3589 //You can not add %1 since they first need to leave the zone before being allowed back in.
#define DZADD_ALREADY_OTHER 3590 //%1 can not be added to this dynamic zone since they are already assigned to another dynamic zone. #define DZADD_ALREADY_OTHER 3590 //%1 can not be added to this dynamic zone since they are already assigned to another dynamic zone.
#define DZADD_REPLAY_TIMER 3591 //%1 can not be added to this dynamic zone for another %2D:%3H:%4M since they have recently played this zone. #define DZADD_REPLAY_TIMER 3591 //%1 can not be added to this dynamic zone for another %2D:%3H:%4M since they have recently played this zone. // TODO: TOB - 3591 %1 can not be added to this dynamic zone for another %2d:%3h:%4m:%5s since they have recently played this zone.
#define DZADD_EVENT_TIMER 3592 //%1 can not be added to this dynamic zone since they have recently experienced %2. They must wait for another %3D:%4H:%5M, or until event %2 has occurred. #define DZADD_EVENT_TIMER 3592 //%1 can not be added to this dynamic zone since they have recently experienced %2. They must wait for another %3D:%4H:%5M, or until event %2 has occurred. // TODO: TOB - 3592 %1 can not be added to this dynamic zone since they have recently experienced %2. They must wait for another %3d:%4h:%5m:%6s, or until event %2 has occurred.
#define DZADD_PENDING 3593 //%1 currently has an outstanding invitation to join this Dynamic Zone. #define DZADD_PENDING 3593 //%1 currently has an outstanding invitation to join this Dynamic Zone.
#define DZADD_PENDING_OTHER 3594 //%1 currently has an outstanding invitation to join another Dynamic Zone. Players may only have one invitation outstanding. #define DZADD_PENDING_OTHER 3594 //%1 currently has an outstanding invitation to join another Dynamic Zone. Players may only have one invitation outstanding.
#define DZSWAP_CANNOT_REMOVE 3595 //%1 can not be removed from this dynamic zone since they are not assigned to it. #define DZSWAP_CANNOT_REMOVE 3595 //%1 can not be removed from this dynamic zone since they are not assigned to it.
@ -405,9 +405,9 @@
#define PETITION_NO_DELETE 5053 //You do not have a petition in the queue. #define PETITION_NO_DELETE 5053 //You do not have a petition in the queue.
#define PETITION_DELETED 5054 //Your petition was successfully deleted. #define PETITION_DELETED 5054 //Your petition was successfully deleted.
#define ALREADY_IN_RAID 5060 //%1 is already in a raid. #define ALREADY_IN_RAID 5060 //%1 is already in a raid.
#define ALREADY_IN_YOUR_RAID 5077 //%1 is already in your raid. #define ALREADY_IN_YOUR_RAID 5077 //%1 is already in your raid. // TODO: TOB - 5077 That person is already in your raid.
#define NOT_IN_YOUR_RAID 5082 //%1 is not in your raid. #define NOT_IN_YOUR_RAID 5082 //%1 is not in your raid.
#define GAIN_RAIDEXP 5085 //You gained raid experience! #define GAIN_RAIDEXP 5085 //You gained raid experience! // TODO: TOB - 5085 You gained raid experience!%1
#define ALREADY_IN_GRP_RAID 5088 //% 1 rejects your invite because they are in a raid and you are not in theirs, or they are a raid group leader #define ALREADY_IN_GRP_RAID 5088 //% 1 rejects your invite because they are in a raid and you are not in theirs, or they are a raid group leader
#define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there. #define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there.
#define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure. #define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure.
@ -423,8 +423,8 @@
#define MELEE_SILENCE 5806 //You *CANNOT* use this melee ability, you are suffering from amnesia! #define MELEE_SILENCE 5806 //You *CANNOT* use this melee ability, you are suffering from amnesia!
#define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds. #define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds.
#define DISCIPLINE_REUSE_MSG2 5808 //You can use the ability %1 again in %2 minute(s) %3 seconds. #define DISCIPLINE_REUSE_MSG2 5808 //You can use the ability %1 again in %2 minute(s) %3 seconds.
#define FAILED_TAUNT 5811 //You have failed to taunt your target. #define FAILED_TAUNT 5811 //You have failed to taunt your target. // TODO: TOB - 5811 You have failed to taunt %1.
#define PHYSICAL_RESIST_FAIL 5817 //Your target avoided your %1 ability. #define PHYSICAL_RESIST_FAIL 5817 //Your target avoided your %1 ability. // TODO: TOB - 5817 %1 avoided your %2!
#define AA_NO_TARGET 5825 //You must first select a target for this ability! #define AA_NO_TARGET 5825 //You must first select a target for this ability!
#define YOU_RECEIVE 5941 //You receive %1. #define YOU_RECEIVE 5941 //You receive %1.
#define NO_TASK_OFFERS 6009 //Sorry %3, I don't have anything for someone with your abilities. #define NO_TASK_OFFERS 6009 //Sorry %3, I don't have anything for someone with your abilities.
@ -447,7 +447,7 @@
#define TRANSFORM_COMPLETE 6327 //You have successfully transformed your %1. #define TRANSFORM_COMPLETE 6327 //You have successfully transformed your %1.
#define DETRANSFORM_FAILED 6341 //%1 has no transformation that can be removed. #define DETRANSFORM_FAILED 6341 //%1 has no transformation that can be removed.
#define GUILD_PERMISSION_FAILED 6418 //You do not have permission to change access options. #define GUILD_PERMISSION_FAILED 6418 //You do not have permission to change access options.
#define PARCEL_DELIVERY_ARRIVED 6465 //You have received a new parcel delivery! #define PARCEL_DELIVERY_ARRIVED 6465 //You have received a new parcel delivery! // TODO: TOB - 6465 You have received a new parcel delivery %1!
#define PARCEL_DELIVERY 6466 //%1 tells you, 'I will deliver the %2 to %3 as soon as possible!' #define PARCEL_DELIVERY 6466 //%1 tells you, 'I will deliver the %2 to %3 as soon as possible!'
#define PARCEL_UNKNOWN_NAME 6467 //%1 tells you, 'Unfortunately, I don't know anyone by the name of %2. Here is your %3 back.'' #define PARCEL_UNKNOWN_NAME 6467 //%1 tells you, 'Unfortunately, I don't know anyone by the name of %2. Here is your %3 back.''
#define PARCEL_DELIVERED 6471 //%1 hands you the %2 that was sent from %3. #define PARCEL_DELIVERED 6471 //%1 hands you the %2 that was sent from %3.
@ -474,7 +474,7 @@
#define LDON_CERTAIN_TRAP 7557 //You are certain that %1 is trapped. #define LDON_CERTAIN_TRAP 7557 //You are certain that %1 is trapped.
#define LDON_CERTAIN_NOT_TRAP 7558 //You are certain that %1 is not trapped. #define LDON_CERTAIN_NOT_TRAP 7558 //You are certain that %1 is not trapped.
#define LDON_CANT_DETERMINE_TRAP 7559 //You are unable to determine if %1 is trapped. #define LDON_CANT_DETERMINE_TRAP 7559 //You are unable to determine if %1 is trapped.
#define LDON_PICKLOCK_SUCCESS 7560 //You have successfully picked %1! #define LDON_PICKLOCK_SUCCESS 7560 //You have successfully picked %1! // TODO: TOB - 7560 You have successfully picked %1%2!
#define LDON_PICKLOCK_FAILURE 7561 //You have failed to pick %1. #define LDON_PICKLOCK_FAILURE 7561 //You have failed to pick %1.
#define LDON_STILL_LOCKED 7562 //You cannot open %1, it is locked. #define LDON_STILL_LOCKED 7562 //You cannot open %1, it is locked.
#define LDON_BASH_CHEST 7563 //%1 try to %2 %3, but do no damage. #define LDON_BASH_CHEST 7563 //%1 try to %2 %3, but do no damage.
@ -497,7 +497,7 @@
#define NOT_DELEGATED_MARKER 8794 //You have not been delegated Raid Mark #define NOT_DELEGATED_MARKER 8794 //You have not been delegated Raid Mark
#define GAIN_GROUP_LEADERSHIP_EXP 8788 // #define GAIN_GROUP_LEADERSHIP_EXP 8788 //
#define GAIN_RAID_LEADERSHIP_EXP 8789 // #define GAIN_RAID_LEADERSHIP_EXP 8789 //
#define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining) #define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining) // TODO: TOB - 8799 --You sense %1%2 on %3 has %4.--
#define RAID_NO_LONGER_MARKED 8801 //%1 is no longer marked #define RAID_NO_LONGER_MARKED 8801 //%1 is no longer marked
#define YOU_HAVE_BEEN_GIVEN 8994 //You have been given: %1 #define YOU_HAVE_BEEN_GIVEN 8994 //You have been given: %1
#define NO_MORE_TRAPS 9002 //You have already placed your maximum number of traps. #define NO_MORE_TRAPS 9002 //You have already placed your maximum number of traps.
@ -508,13 +508,13 @@
#define SPELL_OPPOSITE_EFFECT 9032 //Your spell may have had the opposite effect of what you desired. #define SPELL_OPPOSITE_EFFECT 9032 //Your spell may have had the opposite effect of what you desired.
#define HAS_BEEN_AWAKENED 9037 //%1 has been awakened by %2. #define HAS_BEEN_AWAKENED 9037 //%1 has been awakened by %2.
#define YOU_HEAL 9068 //You have healed %1 for %2 points of damage. #define YOU_HEAL 9068 //You have healed %1 for %2 points of damage.
#define YOUR_HIT_DOT 9072 //%1 has taken %2 damage from your %3. #define YOUR_HIT_DOT 9072 //%1 has taken %2 damage from your %3. // TODO: TOB - 9072 %1 has taken %2 damage from your %3.%4
#define HIT_NON_MELEE 9073 //%1 hit %2 for %3 points of non-melee damage. #define HIT_NON_MELEE 9073 //%1 hit %2 for %3 points of non-melee damage. // TODO: TOB - 9073 %1 hit %2 for %3 points of %4 damage by %5.%6
#define GLOWS_BLUE 9074 //Your %1 glows blue. #define GLOWS_BLUE 9074 //Your %1 glows blue.
#define GLOWS_RED 9075 //Your %1 glows red. #define GLOWS_RED 9075 //Your %1 glows red.
#define SHAKE_OFF_STUN 9077 //You shake off the stun effect! #define SHAKE_OFF_STUN 9077 //You shake off the stun effect!
#define STRIKETHROUGH_STRING 9078 //You strike through your opponent's defenses! #define STRIKETHROUGH_STRING 9078 //You strike through your opponent's defenses!
#define SPELL_REFLECT 9082 //%1's spell has been reflected by %2. #define SPELL_REFLECT 9082 //%1's spell has been reflected by %2. // TODO: TOB - 9082 %1's %2 spell has been reflected by %3.
#define NO_MORE_AURAS 9160 //You do not have sufficient focus to maintain that ability. #define NO_MORE_AURAS 9160 //You do not have sufficient focus to maintain that ability.
#define NEW_SPELLS_AVAIL 9149 //You have new spells available to you. Check the merchants near your guild master. #define NEW_SPELLS_AVAIL 9149 //You have new spells available to you. Check the merchants near your guild master.
#define FD_CAST_ON_NO_BREAK 9174 //The strength of your will allows you to resume feigning death. #define FD_CAST_ON_NO_BREAK 9174 //The strength of your will allows you to resume feigning death.
@ -527,17 +527,17 @@
#define NO_CAST_OUT_OF_COMBAT 9191 //You can not cast this spell while out of combat. #define NO_CAST_OUT_OF_COMBAT 9191 //You can not cast this spell while out of combat.
#define NO_ABILITY_IN_COMBAT 9192 //You can not use this ability while in combat. #define NO_ABILITY_IN_COMBAT 9192 //You can not use this ability while in combat.
#define NO_ABILITY_OUT_OF_COMBAT 9194 //You can not use this ability while out of combat. #define NO_ABILITY_OUT_OF_COMBAT 9194 //You can not use this ability while out of combat.
#define GAIN_GROUPXP_BONUS 9298 //You gain party experience (with a bonus)! #define GAIN_GROUPXP_BONUS 9298 //You gain party experience (with a bonus)! // TODO: TOB - 9298 You gain party experience (with a bonus)!%1
#define GAIN_GROUPXP_PENALTY 9301 //You gain party experience (with a penalty)! #define GAIN_GROUPXP_PENALTY 9301 //You gain party experience (with a penalty)! // TODO: TOB - 9301 You gain party experience (with a penalty)!%1
#define GAIN_RAIDXP_BONUS 9302 //You gained raid experience (with a bonus)! #define GAIN_RAIDXP_BONUS 9302 //You gained raid experience (with a bonus)! // TODO: TOB - 9302 You gained raid experience (with a bonus)!%1
#define GAIN_RAIDXP_PENALTY 9303 //You gained raid experience (with a penalty)! #define GAIN_RAIDXP_PENALTY 9303 //You gained raid experience (with a penalty)! // TODO: TOB - 9303 You gained raid experience (with a penalty)!%1
#define LESSER_SPELL_VERSION 11004 //%1 is a lesser version of %2 and cannot be scribed #define LESSER_SPELL_VERSION 11004 //%1 is a lesser version of %2 and cannot be scribed
#define AE_RAMPAGE 11015 //%1 goes on a WILD RAMPAGE! #define AE_RAMPAGE 11015 //%1 goes on a WILD RAMPAGE!
#define GROUP_IS_FULL 12000 //You cannot join that group, it is full. #define GROUP_IS_FULL 12000 //You cannot join that group, it is full.
#define FACE_ACCEPTED 12028 //Facial features accepted. #define FACE_ACCEPTED 12028 //Facial features accepted.
#define TRACKING_BEGIN 12040 //You begin tracking %1. #define TRACKING_BEGIN 12040 //You begin tracking %1.
#define SPELL_LEVEL_TO_LOW 12048 //You will have to achieve level %1 before you can scribe the %2. #define SPELL_LEVEL_TO_LOW 12048 //You will have to achieve level %1 before you can scribe the %2.
#define YOU_RECEIVE_AS_SPLIT 12071 //You receive %1 as your split. #define YOU_RECEIVE_AS_SPLIT 12071 //You receive %1 as your split. // TODO: TOB - 12072 You receive %1 from the corpse%2.
#define ATTACKFAILED 12158 //%1 try to %2 %3, but %4! #define ATTACKFAILED 12158 //%1 try to %2 %3, but %4!
#define HIT_STRING 12183 //hit #define HIT_STRING 12183 //hit
#define CRUSH_STRING 12191 //crush #define CRUSH_STRING 12191 //crush
@ -554,7 +554,7 @@
#define TARGET_PLAYER_FOR_GUILD_STATUS 12260 #define TARGET_PLAYER_FOR_GUILD_STATUS 12260
#define TARGET_ALREADY_IN_GROUP 12265 //% 1 is already in another group. #define TARGET_ALREADY_IN_GROUP 12265 //% 1 is already in another group.
#define GROUP_INVITEE_NOT_FOUND 12268 //You must target a player or use /invite <name> to invite someone to your group. #define GROUP_INVITEE_NOT_FOUND 12268 //You must target a player or use /invite <name> to invite someone to your group.
#define GROUP_INVITEE_SELF 12270 //12270 You cannot invite yourself. #define GROUP_INVITEE_SELF 12270 //You cannot invite yourself.
#define ALREADY_IN_PARTY 12272 //That person is already in your party. #define ALREADY_IN_PARTY 12272 //That person is already in your party.
#define TALKING_TO_SELF 12323 //Talking to yourself again? #define TALKING_TO_SELF 12323 //Talking to yourself again?
#define SPLIT_NO_GROUP 12328 //You are not in a group! Keep it all. #define SPLIT_NO_GROUP 12328 //You are not in a group! Keep it all.
@ -593,7 +593,7 @@
#define RANGED_TOO_CLOSE 12698 //Your target is too close to use a ranged weapon! #define RANGED_TOO_CLOSE 12698 //Your target is too close to use a ranged weapon!
#define BACKSTAB_WEAPON 12874 //You need a piercing weapon as your primary weapon in order to backstab #define BACKSTAB_WEAPON 12874 //You need a piercing weapon as your primary weapon in order to backstab
#define DISARMED 12889 //You have been disarmed! #define DISARMED 12889 //You have been disarmed!
#define DISARM_SUCCESS 12890 //You disarmed %1! #define DISARM_SUCCESS 12890 //You disarmed %1! // TODO: TOB - 12890 You %2disarmed %1!
#define DISARM_FAILED 12891 //Your attempt to disarm failed. #define DISARM_FAILED 12891 //Your attempt to disarm failed.
#define MORE_SKILLED_THAN_I 12931 //%1 tells you, 'You are more skilled than I! What could I possibly teach you?' #define MORE_SKILLED_THAN_I 12931 //%1 tells you, 'You are more skilled than I! What could I possibly teach you?'
#define SURNAME_EXISTS 12939 //You already have a surname. Operation failed. #define SURNAME_EXISTS 12939 //You already have a surname. Operation failed.
@ -602,7 +602,7 @@
#define REPORT_ONCE 12945 //You may only submit a report once per time that you zone. Thank you. #define REPORT_ONCE 12945 //You may only submit a report once per time that you zone. Thank you.
#define NOW_INVISIBLE 12950 //%1 is now Invisible. #define NOW_INVISIBLE 12950 //%1 is now Invisible.
#define NOW_VISIBLE 12951 //%1 is now Visible. #define NOW_VISIBLE 12951 //%1 is now Visible.
#define YOU_TAKE_DOT 12954 //You have taken %1 damage from %2 by %3 #define YOU_TAKE_DOT 12954 //You have taken %1 damage from %2 by %3 // TODO: TOB - 12954 You have taken %1 damage from %2 by %3.%4
#define GUILD_NOT_MEMBER2 12966 //You are not in a guild. #define GUILD_NOT_MEMBER2 12966 //You are not in a guild.
#define HOT_HEAL_SELF 12976 //You have been healed for %1 hit points by your %2. #define HOT_HEAL_SELF 12976 //You have been healed for %1 hit points by your %2.
#define HOT_HEAL_OTHER 12997 //You have healed %1 for %2 hit points with your %3. #define HOT_HEAL_OTHER 12997 //You have healed %1 for %2 hit points with your %3.
@ -612,7 +612,7 @@
#define TOGGLE_ON 13172 //Asking server to turn ON your incoming tells. #define TOGGLE_ON 13172 //Asking server to turn ON your incoming tells.
#define TOGGLE_OFF 13173 //Asking server to turn OFF all incoming tells for you. #define TOGGLE_OFF 13173 //Asking server to turn OFF all incoming tells for you.
#define DUEL_INPROGRESS 13251 //You have already accepted a duel with someone else cowardly dog. #define DUEL_INPROGRESS 13251 //You have already accepted a duel with someone else cowardly dog.
#define OTHER_HIT_DOT 13327 //%1 has taken %2 damage from %3 by %4. #define OTHER_HIT_DOT 13327 //%1 has taken %2 damage from %3 by %4. // TODO: TOB - 13327 %1 has taken %2 damage from %3 by %4.%5
#define GAIN_XP_BONUS 14541 //You gain experience (with a bonus)! #define GAIN_XP_BONUS 14541 //You gain experience (with a bonus)! // TODO: TOB - 14541 You gain experience (with a bonus)!%1
#define GAIN_XP_PENALTY 14542 //You gain experience (with a penalty)! #define GAIN_XP_PENALTY 14542 //You gain experience (with a penalty)! // TODO: TOB - 14542 You gain experience (with a penalty)!%1
#define GENERIC_MISS 15041 //%1 missed %2 #define GENERIC_MISS 15041 //%1 missed %2