Integrate client-based say links for TOB+ clients (#5105)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled

This commit is contained in:
dannuic
2026-07-08 00:22:25 -06:00
committed by GitHub
parent 974dbcd6ff
commit c9c8747ac9
18 changed files with 378 additions and 202 deletions
+67 -28
View File
@@ -17,18 +17,20 @@
*/
#include "say_link.h"
#include "common/features.h"
#include "common/emu_constants.h"
#include "common/strings.h"
#include "common/item_instance.h"
#include "common/item_data.h"
#include "zone/zonedb.h"
#include "common/shareddb.h"
#include <algorithm>
// static database pointer (must be SharedDatabase since this module is in common)
static SharedDatabase* s_database = nullptr;
// static bucket global
std::vector<SaylinkRepository::Saylink> g_cached_saylinks = {};
bool EQ::saylink::DegenerateLinkBody(SayLinkBody_Struct &say_link_body_struct, const std::string &say_link_body)
bool EQ::saylink::DeserializeLinkBody(SayLinkBody_Struct &say_link_body_struct, const std::string &say_link_body)
{
memset(&say_link_body_struct, 0, sizeof(say_link_body_struct));
if (say_link_body.length() != EQ::constants::SAY_LINK_BODY_SIZE) {
@@ -52,8 +54,9 @@ bool EQ::saylink::DegenerateLinkBody(SayLinkBody_Struct &say_link_body_struct, c
return true;
}
bool EQ::saylink::GenerateLinkBody(std::string &say_link_body, const SayLinkBody_Struct &say_link_body_struct)
bool EQ::saylink::SerializeLinkBody(std::string &say_link_body, const SayLinkBody_Struct &say_link_body_struct)
{
// This is specifically the server struct for link bodies
say_link_body = StringFormat(
"%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X",
(0x0F & say_link_body_struct.action_id),
@@ -85,10 +88,16 @@ EQ::SayLinkEngine::SayLinkEngine()
const std::string &EQ::SayLinkEngine::GenerateLink()
{
// At this point, we can assume that we are generated an item link. Any client before TOB can only do item links
// so even dialog links will end up here. Other types of links aren't supported by the server yet, but they
// would require TOB+ without some other hacks like dialog links use now
m_Link.clear();
m_LinkBody.clear();
m_LinkText.clear();
// These two functions translate multiple server-internal objects into the saylink-internal struct for rendering
// to the packet. Detect the client version after this
generate_body();
generate_text();
@@ -162,8 +171,8 @@ void EQ::SayLinkEngine::generate_body()
// TODO: add hash call
break;
case saylink::SayLinkLootItem:
if (m_LootData == nullptr) { break; }
item_data = database.GetItem(m_LootData->item_id);
if (m_LootData == nullptr || !s_database) { break; }
item_data = s_database->GetItem(m_LootData->item_id);
if (item_data == nullptr) { break; }
m_LinkBodyStruct.item_id = item_data->ID;
m_LinkBodyStruct.augment_1 = m_LootData->aug_1;
@@ -276,8 +285,8 @@ void EQ::SayLinkEngine::generate_text()
m_LinkText = m_ItemData->Name;
return;
case saylink::SayLinkLootItem:
if (m_LootData == nullptr) { break; }
item_data = database.GetItem(m_LootData->item_id);
if (m_LootData == nullptr || !s_database) { break; }
item_data = s_database->GetItem(m_LootData->item_id);
if (item_data == nullptr) { break; }
m_LinkText = item_data->Name;
return;
@@ -373,11 +382,39 @@ std::string EQ::SayLinkEngine::InjectSaylinksIfNotExist(const char *message)
return new_message;
}
std::string EQ::SayLinkEngine::GetSaylinkPhrase(uint32_t saylink_id)
{
auto cached_link = std::find_if(g_cached_saylinks.begin(), g_cached_saylinks.end(),
[&saylink_id](const SaylinkRepository::Saylink& saylink) {
return saylink.id == saylink_id;
});
if (cached_link != g_cached_saylinks.end()) {
return cached_link->phrase;
}
if (s_database != nullptr) {
auto saylink = SaylinkRepository::FindOne(*s_database, saylink_id);
return saylink.phrase; // this will just be empty if it's not found, same as the default return case
}
return {};
}
void EQ::SayLinkEngine::SetDatabase(SharedDatabase* db)
{
s_database = db;
}
void EQ::SayLinkEngine::LoadCachedSaylinks()
{
auto saylinks = SaylinkRepository::GetWhere(database, "phrase not REGEXP '[A-Z]' and phrase not REGEXP '[0-9]'");
LogSaylink("Loaded [{}] saylinks into cache", saylinks.size());
g_cached_saylinks = saylinks;
if (s_database != nullptr) {
auto saylinks = SaylinkRepository::GetWhere(*s_database, "phrase not REGEXP '[A-Z]' and phrase not REGEXP '[0-9]'");
LogSaylink("Loaded [{}] saylinks into cache", saylinks.size());
g_cached_saylinks = saylinks;
} else {
LogSaylink("Failed to load saylinks into cache: no database");
}
}
SaylinkRepository::Saylink EQ::SayLinkEngine::GetOrSaveSaylink(std::string saylink_text)
@@ -391,26 +428,28 @@ SaylinkRepository::Saylink EQ::SayLinkEngine::GetOrSaveSaylink(std::string sayli
}
}
auto saylinks = SaylinkRepository::GetWhere(
database,
fmt::format("phrase = '{}'", Strings::Escape(saylink_text))
);
if (s_database != nullptr) {
auto saylinks = SaylinkRepository::GetWhere(
*s_database,
fmt::format("phrase = '{}'", Strings::Escape(saylink_text))
);
// return if found from the database
if (!saylinks.empty()) {
g_cached_saylinks.emplace_back(saylinks[0]);
return saylinks[0];
}
// return if found from the database
if (!saylinks.empty()) {
g_cached_saylinks.emplace_back(saylinks[0]);
return saylinks[0];
}
// if not found in database - save
auto new_saylink = SaylinkRepository::NewEntity();
new_saylink.phrase = saylink_text;
// if not found in database - save
auto new_saylink = SaylinkRepository::NewEntity();
new_saylink.phrase = saylink_text;
// persist to database
auto link = SaylinkRepository::InsertOne(database, new_saylink);
if (link.id > 0) {
g_cached_saylinks.emplace_back(link);
return link;
// persist to database
auto link = SaylinkRepository::InsertOne(*s_database, new_saylink);
if (link.id > 0) {
g_cached_saylinks.emplace_back(link);
return link;
}
}
return {};