eqemu-server/common/say_link.cpp
Knightly 7ab909ee47 Standardize Licensing
- License was intended to be GPLv3 per earlier commit of GPLv3 LICENSE FILE
- This is confirmed by the inclusion of libraries that are incompatible with GPLv2
- This is also confirmed by KLS and the agreement of KLS's predecessors
- Added GPLv3 license headers to the compilable source files
- Removed Folly licensing in strings.h since the string functions do not match the Folly functions and are standard functions - this must have been left over from previous implementations
- Removed individual contributor license headers since the project has been under the "developer" mantle for many years
- Removed comments on files that were previously automatically generated since they've been manually modified multiple times and there are no automatic scripts referencing them (removed in 2023)
2026-04-01 17:09:57 -07:00

428 lines
14 KiB
C++

/* 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 "say_link.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 <algorithm>
// 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)
{
memset(&say_link_body_struct, 0, sizeof(say_link_body_struct));
if (say_link_body.length() != EQ::constants::SAY_LINK_BODY_SIZE) {
return false;
}
say_link_body_struct.action_id = (uint8) strtol(say_link_body.substr(0, 1).c_str(), nullptr, 16);
say_link_body_struct.item_id = (uint32) strtol(say_link_body.substr(1, 5).c_str(), nullptr, 16);
say_link_body_struct.augment_1 = (uint32) strtol(say_link_body.substr(6, 5).c_str(), nullptr, 16);
say_link_body_struct.augment_2 = (uint32) strtol(say_link_body.substr(11, 5).c_str(), nullptr, 16);
say_link_body_struct.augment_3 = (uint32) strtol(say_link_body.substr(16, 5).c_str(), nullptr, 16);
say_link_body_struct.augment_4 = (uint32) strtol(say_link_body.substr(21, 5).c_str(), nullptr, 16);
say_link_body_struct.augment_5 = (uint32) strtol(say_link_body.substr(26, 5).c_str(), nullptr, 16);
say_link_body_struct.augment_6 = (uint32) strtol(say_link_body.substr(31, 5).c_str(), nullptr, 16);
say_link_body_struct.is_evolving = (uint8) strtol(say_link_body.substr(36, 1).c_str(), nullptr, 16);
say_link_body_struct.evolve_group = (uint32) strtol(say_link_body.substr(37, 4).c_str(), nullptr, 16);
say_link_body_struct.evolve_level = (uint8) strtol(say_link_body.substr(41, 2).c_str(), nullptr, 16);
say_link_body_struct.ornament_icon = (uint32) strtol(say_link_body.substr(43, 5).c_str(), nullptr, 16);
say_link_body_struct.hash = (uint32) strtol(say_link_body.substr(48, 8).c_str(), nullptr, 16);
return true;
}
bool EQ::saylink::GenerateLinkBody(std::string &say_link_body, const SayLinkBody_Struct &say_link_body_struct)
{
say_link_body = StringFormat(
"%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X",
(0x0F & say_link_body_struct.action_id),
(0x000FFFFF & say_link_body_struct.item_id),
(0x000FFFFF & say_link_body_struct.augment_1),
(0x000FFFFF & say_link_body_struct.augment_2),
(0x000FFFFF & say_link_body_struct.augment_3),
(0x000FFFFF & say_link_body_struct.augment_4),
(0x000FFFFF & say_link_body_struct.augment_5),
(0x000FFFFF & say_link_body_struct.augment_6),
(0x0F & say_link_body_struct.is_evolving),
(0x0000FFFF & say_link_body_struct.evolve_group),
(0xFF & say_link_body_struct.evolve_level),
(0x000FFFFF & say_link_body_struct.ornament_icon),
(0xFFFFFFFF & say_link_body_struct.hash)
);
if (say_link_body.length() != EQ::constants::SAY_LINK_BODY_SIZE) {
return false;
}
return true;
}
EQ::SayLinkEngine::SayLinkEngine()
{
Reset();
}
const std::string &EQ::SayLinkEngine::GenerateLink()
{
m_Link.clear();
m_LinkBody.clear();
m_LinkText.clear();
generate_body();
generate_text();
if ((m_LinkBody.length() == EQ::constants::SAY_LINK_BODY_SIZE) && (m_LinkText.length() > 0)) {
m_Link.push_back(0x12);
m_Link.append(m_LinkBody);
m_Link.append(m_LinkText);
m_Link.push_back(0x12);
}
if ((m_Link.length() == 0) || (m_Link.length() > (EQ::constants::SAY_LINK_MAXIMUM_SIZE))) {
m_Error = true;
m_Link = "<LINKER ERROR>";
LogError("SayLinkEngine::GenerateLink() failed to generate a useable say link");
LogError(">> LinkType: {}, Lengths: [link: {}({}), body: {}({}), text: {}({})]",
m_LinkType,
m_Link.length(),
EQ::constants::SAY_LINK_MAXIMUM_SIZE,
m_LinkBody.length(),
EQ::constants::SAY_LINK_BODY_SIZE,
m_LinkText.length(),
EQ::constants::SAY_LINK_TEXT_SIZE
);
LogError(">> LinkBody: {}", m_LinkBody.c_str());
LogError(">> LinkText: {}", m_LinkText.c_str());
}
return m_Link;
}
void EQ::SayLinkEngine::Reset()
{
m_LinkType = saylink::SayLinkBlank;
m_ItemData = nullptr;
m_LootData = nullptr;
m_ItemInst = nullptr;
memset(&m_LinkBodyStruct, 0, sizeof(SayLinkBody_Struct));
memset(&m_LinkProxyStruct, 0, sizeof(SayLinkProxy_Struct));
m_TaskUse = false;
m_Link.clear();
m_LinkBody.clear();
m_LinkText.clear();
m_Error = false;
}
void EQ::SayLinkEngine::generate_body()
{
/*
Current server mask: EQClientRoF2
RoF2: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X" (56)
RoF: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X" (55)
SoF: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X" (50)
6.2: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%08X" (45)
*/
memset(&m_LinkBodyStruct, 0, sizeof(SayLinkBody_Struct));
const EQ::ItemData *item_data = nullptr;
switch (m_LinkType) {
case saylink::SayLinkBlank:
break;
case saylink::SayLinkItemData:
if (m_ItemData == nullptr) { break; }
m_LinkBodyStruct.item_id = m_ItemData->ID;
m_LinkBodyStruct.evolve_group = m_ItemData->LoreGroup; // this probably won't work for all items
//m_LinkBodyStruct.evolve_level = m_ItemData->EvolvingLevel;
// TODO: add hash call
break;
case saylink::SayLinkLootItem:
if (m_LootData == nullptr) { break; }
item_data = 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;
m_LinkBodyStruct.augment_2 = m_LootData->aug_2;
m_LinkBodyStruct.augment_3 = m_LootData->aug_3;
m_LinkBodyStruct.augment_4 = m_LootData->aug_4;
m_LinkBodyStruct.augment_5 = m_LootData->aug_5;
m_LinkBodyStruct.augment_6 = m_LootData->aug_6;
m_LinkBodyStruct.evolve_group = item_data->LoreGroup; // see note above
//m_LinkBodyStruct.evolve_level = item_data->EvolvingLevel;
// TODO: add hash call
break;
case saylink::SayLinkItemInst:
if (m_ItemInst == nullptr) { break; }
if (m_ItemInst->GetItem() == nullptr) { break; }
m_LinkBodyStruct.item_id = m_ItemInst->GetItem()->ID;
m_LinkBodyStruct.augment_1 = m_ItemInst->GetAugmentItemID(0);
m_LinkBodyStruct.augment_2 = m_ItemInst->GetAugmentItemID(1);
m_LinkBodyStruct.augment_3 = m_ItemInst->GetAugmentItemID(2);
m_LinkBodyStruct.augment_4 = m_ItemInst->GetAugmentItemID(3);
m_LinkBodyStruct.augment_5 = m_ItemInst->GetAugmentItemID(4);
m_LinkBodyStruct.augment_6 = m_ItemInst->GetAugmentItemID(5);
m_LinkBodyStruct.is_evolving = (m_ItemInst->IsEvolving() ? 1 : 0);
m_LinkBodyStruct.evolve_group = m_ItemInst->GetItem()->LoreGroup; // see note above
m_LinkBodyStruct.evolve_level = m_ItemInst->GetEvolveLvl();
m_LinkBodyStruct.ornament_icon = m_ItemInst->GetOrnamentationIcon();
// TODO: add hash call
break;
default:
break;
}
if (m_LinkProxyStruct.action_id) {
m_LinkBodyStruct.action_id = m_LinkProxyStruct.action_id;
}
if (m_LinkProxyStruct.item_id) {
m_LinkBodyStruct.item_id = m_LinkProxyStruct.item_id;
}
if (m_LinkProxyStruct.augment_1) {
m_LinkBodyStruct.augment_1 = m_LinkProxyStruct.augment_1;
}
if (m_LinkProxyStruct.augment_2) {
m_LinkBodyStruct.augment_2 = m_LinkProxyStruct.augment_2;
}
if (m_LinkProxyStruct.augment_3) {
m_LinkBodyStruct.augment_3 = m_LinkProxyStruct.augment_3;
}
if (m_LinkProxyStruct.augment_4) {
m_LinkBodyStruct.augment_4 = m_LinkProxyStruct.augment_4;
}
if (m_LinkProxyStruct.augment_5) {
m_LinkBodyStruct.augment_5 = m_LinkProxyStruct.augment_5;
}
if (m_LinkProxyStruct.augment_6) {
m_LinkBodyStruct.augment_6 = m_LinkProxyStruct.augment_6;
}
if (m_LinkProxyStruct.is_evolving) {
m_LinkBodyStruct.is_evolving = m_LinkProxyStruct.is_evolving;
}
if (m_LinkProxyStruct.evolve_group) {
m_LinkBodyStruct.evolve_group = m_LinkProxyStruct.evolve_group;
}
if (m_LinkProxyStruct.evolve_level) {
m_LinkBodyStruct.evolve_level = m_LinkProxyStruct.evolve_level;
}
if (m_LinkProxyStruct.ornament_icon) {
m_LinkBodyStruct.ornament_icon = m_LinkProxyStruct.ornament_icon;
}
if (m_LinkProxyStruct.hash) {
m_LinkBodyStruct.hash = m_LinkProxyStruct.hash;
}
if (m_TaskUse) {
m_LinkBodyStruct.hash = 0x14505DC2;
}
m_LinkBody = StringFormat(
"%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X",
(0x0F & m_LinkBodyStruct.action_id),
(0x000FFFFF & m_LinkBodyStruct.item_id),
(0x000FFFFF & m_LinkBodyStruct.augment_1),
(0x000FFFFF & m_LinkBodyStruct.augment_2),
(0x000FFFFF & m_LinkBodyStruct.augment_3),
(0x000FFFFF & m_LinkBodyStruct.augment_4),
(0x000FFFFF & m_LinkBodyStruct.augment_5),
(0x000FFFFF & m_LinkBodyStruct.augment_6),
(0x0F & m_LinkBodyStruct.is_evolving),
(0x0000FFFF & m_LinkBodyStruct.evolve_group),
(0xFF & m_LinkBodyStruct.evolve_level),
(0x000FFFFF & m_LinkBodyStruct.ornament_icon),
(0xFFFFFFFF & m_LinkBodyStruct.hash)
);
}
void EQ::SayLinkEngine::generate_text()
{
if (m_LinkProxyStruct.text != nullptr) {
m_LinkText = m_LinkProxyStruct.text;
return;
}
const EQ::ItemData *item_data = nullptr;
switch (m_LinkType) {
case saylink::SayLinkBlank:
break;
case saylink::SayLinkItemData:
if (m_ItemData == nullptr) { break; }
m_LinkText = m_ItemData->Name;
return;
case saylink::SayLinkLootItem:
if (m_LootData == nullptr) { break; }
item_data = database.GetItem(m_LootData->item_id);
if (item_data == nullptr) { break; }
m_LinkText = item_data->Name;
return;
case saylink::SayLinkItemInst:
if (m_ItemInst == nullptr) { break; }
if (m_ItemInst->GetItem() == nullptr) { break; }
m_LinkText = m_ItemInst->GetItem()->Name;
return;
default:
break;
}
m_LinkText = "null";
}
std::string EQ::SayLinkEngine::GenerateQuestSaylink(const std::string& saylink_text, bool silent, const std::string& link_name)
{
uint32 saylink_id = 0;
SaylinkRepository::Saylink saylink = GetOrSaveSaylink(saylink_text);
if (saylink.id > 0) {
saylink_id = saylink.id;
}
/**
* Generate the actual link
*/
EQ::SayLinkEngine linker;
linker.SetProxyItemID(SAYLINK_ITEM_ID);
if (silent) {
linker.SetProxyAugment2ID(saylink_id);
}
else {
linker.SetProxyAugment1ID(saylink_id);
}
linker.SetProxyText(link_name.c_str());
return linker.GenerateLink();
}
std::string EQ::SayLinkEngine::InjectSaylinksIfNotExist(const char *message)
{
LogSaylinkDetail("message [{}]", message);
std::string new_message;
new_message.reserve(strlen(message));
bool in_bracket_state = false;
bool in_link_state = false;
const char* ch = message;
const char* startpos = message;
for (; *ch != '\0'; ++ch)
{
// saylinks not added if spaces touch brackets
bool abort_space = in_bracket_state && *ch == ' ' && (*(ch-1) == '[' || *(ch+1) == ']');
if (in_bracket_state && (*ch == '[' || *ch == '\x12' || abort_space))
{
// abort due to nested bracket (which starts another) or existing saylink
new_message.append(startpos, ch - startpos);
in_bracket_state = false;
}
else if (in_bracket_state && *ch == ']')
{
if (ch != startpos)
{
std::string str(startpos, ch - startpos);
new_message += Saylink::Create(str);
}
in_bracket_state = false;
}
if (!in_bracket_state)
{
new_message.push_back(*ch);
}
if (*ch == '[' && !in_link_state)
{
startpos = ch + 1;
in_bracket_state = true;
}
else if (*ch == '\x12')
{
in_link_state = !in_link_state;
}
}
LogSaylinkDetail("new_message [{}]", new_message);
return new_message;
}
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;
}
SaylinkRepository::Saylink EQ::SayLinkEngine::GetOrSaveSaylink(std::string saylink_text)
{
// return cached saylink if exist
if (!g_cached_saylinks.empty()) {
for (auto &s: g_cached_saylinks) {
if (s.phrase == saylink_text) {
return s;
}
}
}
auto saylinks = SaylinkRepository::GetWhere(
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];
}
// 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;
}
return {};
}
std::string Saylink::Create(const std::string& saylink_text, bool silent, const std::string& link_name)
{
return EQ::SayLinkEngine::GenerateQuestSaylink(saylink_text, silent, (link_name.empty() ? saylink_text : link_name));
}
std::string Saylink::Silent(const std::string& saylink_text, const std::string& link_name)
{
return EQ::SayLinkEngine::GenerateQuestSaylink(saylink_text, true, (link_name.empty() ? saylink_text : link_name));
}