mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 12:41:30 +00:00
1271 lines
42 KiB
C++
1271 lines
42 KiB
C++
#include "player_event_logs.h"
|
|
#include <cereal/archives/json.hpp>
|
|
|
|
#include "../platform.h"
|
|
#include "../rulesys.h"
|
|
#include "player_event_discord_formatter.h"
|
|
#include "../repositories/player_event_loot_items_repository.h"
|
|
#include "../repositories/player_event_merchant_sell_repository.h"
|
|
#include "../repositories/player_event_merchant_purchase_repository.h"
|
|
#include "../repositories/player_event_npc_handin_repository.h"
|
|
#include "../repositories/player_event_npc_handin_entries_repository.h"
|
|
|
|
const uint32 PROCESS_RETENTION_TRUNCATION_TIMER_INTERVAL = 60 * 60 * 1000; // 1 hour
|
|
|
|
// general initialization routine
|
|
void PlayerEventLogs::Init()
|
|
{
|
|
m_process_batch_events_timer.SetTimer(RuleI(Logging, BatchPlayerEventProcessIntervalSeconds) * 1000);
|
|
m_process_retention_truncation_timer.SetTimer(PROCESS_RETENTION_TRUNCATION_TIMER_INTERVAL);
|
|
m_database_ping_timer.SetTimer(10 * 1000); // 10 seconds
|
|
|
|
ValidateDatabaseConnection();
|
|
|
|
// initialize settings array
|
|
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
|
m_settings[i].id = i;
|
|
m_settings[i].event_name = PlayerEvent::EventName[i];
|
|
m_settings[i].event_enabled = 1;
|
|
m_settings[i].retention_days = 0;
|
|
m_settings[i].discord_webhook_id = 0;
|
|
m_settings[i].etl_enabled = false;
|
|
}
|
|
|
|
SetSettingsDefaults();
|
|
|
|
// initialize settings from database
|
|
auto s = PlayerEventLogSettingsRepository::All(*m_database);
|
|
std::vector<int> db{};
|
|
db.reserve(s.size());
|
|
for (auto &e: s) {
|
|
if (e.id >= PlayerEvent::MAX) {
|
|
continue;
|
|
}
|
|
m_settings[e.id] = e;
|
|
db.emplace_back(e.id);
|
|
}
|
|
|
|
std::vector<PlayerEventLogSettingsRepository::PlayerEventLogSettings> settings_to_insert{};
|
|
|
|
// insert entries that don't exist in database
|
|
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
|
bool is_in_database = std::find(db.begin(), db.end(), i) != db.end();
|
|
bool is_deprecated = Strings::Contains(PlayerEvent::EventName[i], "Deprecated");
|
|
bool is_implemented = !Strings::Contains(PlayerEvent::EventName[i], "Unimplemented");
|
|
|
|
// remove when deprecated
|
|
if (is_deprecated && is_in_database) {
|
|
LogInfo("[Deprecated] Removing PlayerEvent [{}] ({})", PlayerEvent::EventName[i], i);
|
|
PlayerEventLogSettingsRepository::DeleteWhere(*m_database, fmt::format("id = {}", i));
|
|
}
|
|
// remove when unimplemented if present
|
|
if (!is_implemented && is_in_database) {
|
|
LogInfo("[Unimplemented] Removing PlayerEvent [{}] ({})", PlayerEvent::EventName[i], i);
|
|
PlayerEventLogSettingsRepository::DeleteWhere(*m_database, fmt::format("id = {}", i));
|
|
}
|
|
|
|
bool is_missing_in_database = std::find(db.begin(), db.end(), i) == db.end();
|
|
if (is_missing_in_database && is_implemented && !is_deprecated) {
|
|
LogInfo("[New] PlayerEvent [{}] ({})", PlayerEvent::EventName[i], i);
|
|
|
|
auto c = PlayerEventLogSettingsRepository::NewEntity();
|
|
c.id = i;
|
|
c.event_name = PlayerEvent::EventName[i];
|
|
c.event_enabled = m_settings[i].event_enabled;
|
|
c.retention_days = m_settings[i].retention_days;
|
|
c.etl_enabled = false;
|
|
settings_to_insert.emplace_back(c);
|
|
}
|
|
}
|
|
|
|
if (!settings_to_insert.empty()) {
|
|
PlayerEventLogSettingsRepository::ReplaceMany(*m_database, settings_to_insert);
|
|
}
|
|
|
|
bool processing_in_world = !RuleB(Logging, PlayerEventsQSProcess) && IsWorld();
|
|
bool processing_in_qs = RuleB(Logging, PlayerEventsQSProcess) && IsQueryServ();
|
|
|
|
// on initial boot process truncation
|
|
if (processing_in_world || processing_in_qs) {
|
|
LoadEtlIds();
|
|
ProcessRetentionTruncation();
|
|
}
|
|
}
|
|
|
|
// set the database object, during initialization
|
|
PlayerEventLogs *PlayerEventLogs::SetDatabase(Database *db)
|
|
{
|
|
m_database = db;
|
|
|
|
return this;
|
|
}
|
|
|
|
// validates whether the connection is valid or not, used in initialization
|
|
bool PlayerEventLogs::ValidateDatabaseConnection()
|
|
{
|
|
if (!m_database) {
|
|
LogError("No database connection");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// determines if the passed in event is enabled or not
|
|
// this is used to gate logic or events from firing off
|
|
// this is used prior to building the events, we don't want to
|
|
// build the events, send them through the stack in a function call
|
|
// only to discard them immediately afterwards, very wasteful on resources
|
|
// the quest api currently does this
|
|
bool PlayerEventLogs::IsEventEnabled(PlayerEvent::EventType event)
|
|
{
|
|
return m_settings[event].event_enabled ? m_settings[event].event_enabled : false;
|
|
}
|
|
|
|
// this processes any current player events on the queue
|
|
void PlayerEventLogs::ProcessBatchQueue()
|
|
{
|
|
m_batch_queue_lock.lock();
|
|
if (m_record_batch_queue.empty()) {
|
|
m_batch_queue_lock.unlock();
|
|
return;
|
|
}
|
|
|
|
static std::map<uint32, uint32> counter{};
|
|
counter.clear();
|
|
for (auto const &e: m_record_batch_queue) {
|
|
counter[e.event_type_id]++;
|
|
}
|
|
|
|
BenchTimer benchmark;
|
|
|
|
EtlQueues etl_queues{};
|
|
for (const auto &[type, count]: counter) {
|
|
if (count > 0) {
|
|
switch (type) {
|
|
case PlayerEvent::TRADE:
|
|
etl_queues.trade.reserve(count);
|
|
break;
|
|
case PlayerEvent::SPEECH:
|
|
etl_queues.speech.reserve(count);
|
|
break;
|
|
case PlayerEvent::LOOT_ITEM:
|
|
etl_queues.loot_items.reserve(count);
|
|
break;
|
|
case PlayerEvent::KILLED_NPC:
|
|
etl_queues.killed_npc.reserve(count);
|
|
break;
|
|
case PlayerEvent::NPC_HANDIN:
|
|
etl_queues.npc_handin.reserve(count);
|
|
break;
|
|
case PlayerEvent::AA_PURCHASE:
|
|
etl_queues.aa_purchase.reserve(count);
|
|
break;
|
|
case PlayerEvent::MERCHANT_SELL:
|
|
etl_queues.merchant_sell.reserve(count);
|
|
break;
|
|
case PlayerEvent::KILLED_RAID_NPC:
|
|
etl_queues.killed_raid_npc.reserve(count);
|
|
break;
|
|
case PlayerEvent::KILLED_NAMED_NPC:
|
|
etl_queues.killed_named_npc.reserve(count);
|
|
break;
|
|
case PlayerEvent::MERCHANT_PURCHASE:
|
|
etl_queues.merchant_purchase.reserve(count);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to deserialize event data
|
|
auto Deserialize = [](const std::string &data, auto &out) {
|
|
if (!Strings::IsValidJson(data)) {
|
|
return;
|
|
}
|
|
|
|
// cpp exceptions are terrible, don't ever use them
|
|
try {
|
|
std::stringstream ss(data);
|
|
cereal::JSONInputArchive ar(ss);
|
|
out.serialize(ar);
|
|
}
|
|
catch (const std::exception &e) {}
|
|
};
|
|
|
|
// Helper to assign ETL table ID
|
|
|
|
auto AssignEtlId = [&](
|
|
PlayerEventLogsRepository::PlayerEventLogs& r,
|
|
PlayerEvent::EventType type
|
|
)
|
|
{
|
|
if (m_etl_settings.contains(type)) {
|
|
r.etl_table_id = m_etl_settings.at(type).next_id++;
|
|
}
|
|
};
|
|
|
|
// Define event processors
|
|
std::unordered_map<PlayerEvent::EventType, std::function<void(PlayerEventLogsRepository::PlayerEventLogs &)>> event_processors = {
|
|
{
|
|
PlayerEvent::EventType::LOOT_ITEM, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::LootItemEvent in{};
|
|
PlayerEventLootItemsRepository::PlayerEventLootItems out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.charges = in.charges;
|
|
out.corpse_name = in.corpse_name;
|
|
out.item_id = in.item_id;
|
|
out.item_name = in.item_name;
|
|
out.augment_1_id = in.augment_1_id;
|
|
out.augment_2_id = in.augment_2_id;
|
|
out.augment_3_id = in.augment_3_id;
|
|
out.augment_4_id = in.augment_4_id;
|
|
out.augment_5_id = in.augment_5_id;
|
|
out.augment_6_id = in.augment_6_id;
|
|
out.npc_id = in.npc_id;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::LOOT_ITEM);
|
|
etl_queues.loot_items.push_back(out);
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::EventType::MERCHANT_SELL, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::MerchantSellEvent in{};
|
|
PlayerEventMerchantSellRepository::PlayerEventMerchantSell out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.npc_id = in.npc_id;
|
|
out.merchant_name = in.merchant_name;
|
|
out.merchant_type = in.merchant_type;
|
|
out.item_id = in.item_id;
|
|
out.item_name = in.item_name;
|
|
out.charges = in.charges;
|
|
out.cost = in.cost;
|
|
out.alternate_currency_id = in.alternate_currency_id;
|
|
out.player_money_balance = in.player_money_balance;
|
|
out.player_currency_balance = in.player_currency_balance;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::MERCHANT_SELL);
|
|
etl_queues.merchant_sell.push_back(out);
|
|
}},
|
|
{
|
|
PlayerEvent::EventType::MERCHANT_PURCHASE, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::MerchantPurchaseEvent in{};
|
|
PlayerEventMerchantPurchaseRepository::PlayerEventMerchantPurchase out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.npc_id = in.npc_id;
|
|
out.merchant_name = in.merchant_name;
|
|
out.merchant_type = in.merchant_type;
|
|
out.item_id = in.item_id;
|
|
out.item_name = in.item_name;
|
|
out.charges = in.charges;
|
|
out.cost = in.cost;
|
|
out.alternate_currency_id = in.alternate_currency_id;
|
|
out.player_money_balance = in.player_money_balance;
|
|
out.player_currency_balance = in.player_currency_balance;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::MERCHANT_PURCHASE);
|
|
etl_queues.merchant_purchase.push_back(out);
|
|
}},
|
|
{
|
|
PlayerEvent::EventType::NPC_HANDIN, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::HandinEvent in{};
|
|
PlayerEventNpcHandinRepository::PlayerEventNpcHandin out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.npc_id = in.npc_id;
|
|
out.npc_name = in.npc_name;
|
|
out.handin_copper = in.handin_money.copper;
|
|
out.handin_silver = in.handin_money.silver;
|
|
out.handin_gold = in.handin_money.gold;
|
|
out.handin_platinum = in.handin_money.platinum;
|
|
out.return_copper = in.return_money.copper;
|
|
out.return_silver = in.return_money.silver;
|
|
out.return_gold = in.return_money.gold;
|
|
out.return_platinum = in.return_money.platinum;
|
|
out.is_quest_handin = in.is_quest_handin;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::NPC_HANDIN);
|
|
etl_queues.npc_handin.push_back(out);
|
|
|
|
for (const auto &i: in.handin_items) {
|
|
PlayerEventNpcHandinEntriesRepository::PlayerEventNpcHandinEntries entry{};
|
|
entry.player_event_npc_handin_id = r.etl_table_id;
|
|
entry.item_id = i.item_id;
|
|
entry.charges = i.charges;
|
|
entry.type = 1;
|
|
etl_queues.npc_handin_entries.push_back(entry);
|
|
}
|
|
for (const auto &i: in.return_items) {
|
|
PlayerEventNpcHandinEntriesRepository::PlayerEventNpcHandinEntries entry{};
|
|
entry.player_event_npc_handin_id = r.etl_table_id;
|
|
entry.item_id = i.item_id;
|
|
entry.charges = i.charges;
|
|
entry.type = 2;
|
|
etl_queues.npc_handin_entries.push_back(entry);
|
|
}
|
|
}},
|
|
{
|
|
PlayerEvent::EventType::TRADE, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::TradeEvent in{};
|
|
PlayerEventTradeRepository::PlayerEventTrade out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.char1_id = in.character_1_id;
|
|
out.char2_id = in.character_2_id;
|
|
out.char1_copper = in.character_1_give_money.copper;
|
|
out.char1_silver = in.character_1_give_money.silver;
|
|
out.char1_gold = in.character_1_give_money.gold;
|
|
out.char1_platinum = in.character_1_give_money.platinum;
|
|
out.char2_copper = in.character_2_give_money.copper;
|
|
out.char2_silver = in.character_2_give_money.silver;
|
|
out.char2_gold = in.character_2_give_money.gold;
|
|
out.char2_platinum = in.character_2_give_money.platinum;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::TRADE);
|
|
etl_queues.trade.push_back(out);
|
|
|
|
for (const auto &i: in.character_1_give_items) {
|
|
PlayerEventTradeEntriesRepository::PlayerEventTradeEntries entry{};
|
|
entry.player_event_trade_id = r.etl_table_id;
|
|
entry.char_id = in.character_1_id;
|
|
entry.item_id = i.item_id;
|
|
entry.charges = i.charges;
|
|
entry.slot = i.slot;
|
|
etl_queues.trade_entries.push_back(entry);
|
|
}
|
|
for (const auto &i: in.character_2_give_items) {
|
|
PlayerEventTradeEntriesRepository::PlayerEventTradeEntries entry{};
|
|
entry.player_event_trade_id = r.etl_table_id;
|
|
entry.char_id = in.character_2_id;
|
|
entry.item_id = i.item_id;
|
|
entry.charges = i.charges;
|
|
entry.slot = i.slot;
|
|
etl_queues.trade_entries.push_back(entry);
|
|
}
|
|
}},
|
|
{
|
|
PlayerEvent::EventType::SPEECH, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::PlayerSpeech in{};
|
|
PlayerEventSpeechRepository::PlayerEventSpeech out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.from_char_id = in.from;
|
|
out.to_char_id = in.to;
|
|
out.type = in.type;
|
|
out.min_status = in.min_status;
|
|
out.message = in.message;
|
|
out.guild_id = in.guild_id;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::SPEECH);
|
|
etl_queues.speech.push_back(out);
|
|
}},
|
|
{
|
|
PlayerEvent::EventType::KILLED_NPC, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::KilledNPCEvent in{};
|
|
PlayerEventKilledNpcRepository::PlayerEventKilledNpc out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.npc_id = in.npc_id;
|
|
out.npc_name = in.npc_name;
|
|
out.combat_time_seconds = in.combat_time_seconds;
|
|
out.total_damage_per_second_taken = in.total_damage_per_second_taken;
|
|
out.total_heal_per_second_taken = in.total_heal_per_second_taken;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::KILLED_NPC);
|
|
etl_queues.killed_npc.push_back(out);
|
|
}},
|
|
{
|
|
PlayerEvent::EventType::AA_PURCHASE, [&](PlayerEventLogsRepository::PlayerEventLogs &r) {
|
|
PlayerEvent::AAPurchasedEvent in{};
|
|
PlayerEventAaPurchaseRepository::PlayerEventAaPurchase out{};
|
|
Deserialize(r.event_data, in);
|
|
|
|
out.aa_ability_id = in.aa_id;
|
|
out.cost = in.aa_cost;
|
|
out.previous_id = in.aa_previous_id;
|
|
out.next_id = in.aa_next_id;
|
|
out.created_at = r.created_at;
|
|
|
|
AssignEtlId(r, PlayerEvent::EventType::AA_PURCHASE);
|
|
etl_queues.aa_purchase.push_back(out);
|
|
}},
|
|
};
|
|
|
|
// Process the batch queue
|
|
for (auto &r: m_record_batch_queue) {
|
|
if (m_settings[r.event_type_id].etl_enabled) {
|
|
auto it = event_processors.find(static_cast<PlayerEvent::EventType>(r.event_type_id));
|
|
if (it != event_processors.end()) {
|
|
it->second(r); // Call the appropriate lambda
|
|
}
|
|
else {
|
|
LogPlayerEventsDetail("Non-Implemented ETL routing [{}]", r.event_type_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to flush and clear queues
|
|
auto flush_queue = [&](auto insert_many, auto &queue) {
|
|
if (!queue.empty()) {
|
|
insert_many(*m_database, queue);
|
|
queue.clear();
|
|
}
|
|
};
|
|
|
|
// flush many
|
|
PlayerEventLogsRepository::InsertMany(*m_database, m_record_batch_queue);
|
|
|
|
// flush etl queues
|
|
flush_queue(PlayerEventLootItemsRepository::InsertMany, etl_queues.loot_items);
|
|
flush_queue(PlayerEventMerchantSellRepository::InsertMany, etl_queues.merchant_sell);
|
|
flush_queue(PlayerEventMerchantPurchaseRepository::InsertMany, etl_queues.merchant_purchase);
|
|
flush_queue(PlayerEventNpcHandinRepository::InsertMany, etl_queues.npc_handin);
|
|
flush_queue(PlayerEventNpcHandinEntriesRepository::InsertMany, etl_queues.npc_handin_entries);
|
|
flush_queue(PlayerEventTradeRepository::InsertMany, etl_queues.trade);
|
|
flush_queue(PlayerEventTradeEntriesRepository::InsertMany, etl_queues.trade_entries);
|
|
flush_queue(PlayerEventSpeechRepository::InsertMany, etl_queues.speech);
|
|
flush_queue(PlayerEventKilledNpcRepository::InsertMany, etl_queues.killed_npc);
|
|
flush_queue(PlayerEventKilledNamedNpcRepository::InsertMany, etl_queues.killed_named_npc);
|
|
flush_queue(PlayerEventKilledRaidNpcRepository::InsertMany, etl_queues.killed_raid_npc);
|
|
flush_queue(PlayerEventAaPurchaseRepository::InsertMany, etl_queues.aa_purchase);
|
|
|
|
LogPlayerEvents(
|
|
"Processing batch player event log queue of [{}] took [{}]",
|
|
m_record_batch_queue.size(),
|
|
benchmark.elapsed()
|
|
);
|
|
|
|
// empty
|
|
m_record_batch_queue.clear();
|
|
m_batch_queue_lock.unlock();
|
|
}
|
|
|
|
// adds a player event to the queue
|
|
void PlayerEventLogs::AddToQueue(PlayerEventLogsRepository::PlayerEventLogs &log)
|
|
{
|
|
m_batch_queue_lock.lock();
|
|
m_record_batch_queue.emplace_back(log);
|
|
m_batch_queue_lock.unlock();
|
|
}
|
|
|
|
// fills common event data in the SendEvent function
|
|
void PlayerEventLogs::FillPlayerEvent(
|
|
const PlayerEvent::PlayerEvent &p,
|
|
PlayerEventLogsRepository::PlayerEventLogs &n
|
|
)
|
|
{
|
|
n.account_id = p.account_id;
|
|
n.character_id = p.character_id;
|
|
n.zone_id = p.zone_id;
|
|
n.instance_id = p.instance_id;
|
|
n.x = p.x;
|
|
n.y = p.y;
|
|
n.z = p.z;
|
|
n.heading = p.heading;
|
|
}
|
|
|
|
// builds the dynamic packet used to ship the player event over the wire
|
|
// supports serializing the struct so it can be rebuilt on the other end
|
|
std::unique_ptr<ServerPacket>
|
|
PlayerEventLogs::BuildPlayerEventPacket(const PlayerEvent::PlayerEventContainer &e)
|
|
{
|
|
EQ::Net::DynamicPacket dyn_pack;
|
|
dyn_pack.PutSerialize(0, e);
|
|
auto pack_size = sizeof(ServerSendPlayerEvent_Struct) + dyn_pack.Length();
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_PlayerEvent, static_cast<uint32_t>(pack_size));
|
|
auto buf = reinterpret_cast<ServerSendPlayerEvent_Struct *>(pack->pBuffer);
|
|
buf->cereal_size = static_cast<uint32_t>(dyn_pack.Length());
|
|
memcpy(buf->cereal_data, dyn_pack.Data(), dyn_pack.Length());
|
|
|
|
return pack;
|
|
}
|
|
|
|
const PlayerEventLogSettingsRepository::PlayerEventLogSettings *PlayerEventLogs::GetSettings() const
|
|
{
|
|
return m_settings;
|
|
}
|
|
|
|
bool PlayerEventLogs::IsEventDiscordEnabled(int32_t event_type_id)
|
|
{
|
|
// out of bounds check
|
|
if (event_type_id >= PlayerEvent::EventType::MAX) {
|
|
return false;
|
|
}
|
|
|
|
// make sure webhook id is set
|
|
if (m_settings[event_type_id].discord_webhook_id == 0) {
|
|
return false;
|
|
}
|
|
|
|
// ensure there is a matching webhook to begin with
|
|
if (!EQEmuLogSys::Instance()->GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url.empty()) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string PlayerEventLogs::GetDiscordWebhookUrlFromEventType(int32_t event_type_id)
|
|
{
|
|
// out of bounds check
|
|
if (event_type_id >= PlayerEvent::EventType::MAX) {
|
|
return "";
|
|
}
|
|
|
|
// make sure webhook id is set
|
|
if (m_settings[event_type_id].discord_webhook_id == 0) {
|
|
return "";
|
|
}
|
|
|
|
// ensure there is a matching webhook to begin with
|
|
if (!EQEmuLogSys::Instance()->GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url.empty()) {
|
|
return EQEmuLogSys::Instance()->GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
void PlayerEventLogs::LoadPlayerEventSettingsFromQS(
|
|
const std::vector<PlayerEventLogSettingsRepository::PlayerEventLogSettings> &settings
|
|
)
|
|
{
|
|
for (const auto &e : settings) {
|
|
if (e.id >= PlayerEvent::MAX || e.id < 0) {
|
|
continue;
|
|
}
|
|
m_settings[e.id] = e;
|
|
}
|
|
|
|
LogInfo("Applied [{}] player event log settings from QS", settings.size());
|
|
}
|
|
|
|
// GM_COMMAND | [x] Implemented Formatter
|
|
// ZONING | [x] Implemented Formatter
|
|
// AA_GAIN | [x] Implemented Formatter
|
|
// AA_PURCHASE | [x] Implemented Formatter
|
|
// FORAGE_SUCCESS | [x] Implemented Formatter
|
|
// FORAGE_FAILURE | [x] Implemented Formatter
|
|
// FISH_SUCCESS | [x] Implemented Formatter
|
|
// FISH_FAILURE | [x] Implemented Formatter
|
|
// ITEM_DESTROY | [x] Implemented Formatter
|
|
// WENT_ONLINE | [x] Implemented Formatter
|
|
// WENT_OFFLINE | [x] Implemented Formatter
|
|
// LEVEL_GAIN | [x] Implemented Formatter
|
|
// LEVEL_LOSS | [x] Implemented Formatter
|
|
// LOOT_ITEM | [x] Implemented Formatter
|
|
// MERCHANT_PURCHASE | [x] Implemented Formatter
|
|
// MERCHANT_SELL | [x] Implemented Formatter
|
|
// GROUP_JOIN | [] Implemented Formatter
|
|
// GROUP_LEAVE | [] Implemented Formatter
|
|
// RAID_JOIN | [] Implemented Formatter
|
|
// RAID_LEAVE | [] Implemented Formatter
|
|
// GROUNDSPAWN_PICKUP | [x] Implemented Formatter
|
|
// NPC_HANDIN | [x] Implemented Formatter
|
|
// SKILL_UP | [x] Implemented Formatter
|
|
// TASK_ACCEPT | [x] Implemented Formatter
|
|
// TASK_UPDATE | [x] Implemented Formatter
|
|
// TASK_COMPLETE | [x] Implemented Formatter
|
|
// TRADE | [] Implemented Formatter
|
|
// GIVE_ITEM | [] Implemented Formatter
|
|
// SAY | [x] Implemented Formatter
|
|
// REZ_ACCEPTED | [x] Implemented Formatter
|
|
// DEATH | [x] Implemented Formatter
|
|
// COMBINE_FAILURE | [x] Implemented Formatter
|
|
// COMBINE_SUCCESS | [x] Implemented Formatter
|
|
// DROPPED_ITEM | [x] Implemented Formatter
|
|
// SPLIT_MONEY | [x] Implemented Formatter
|
|
// DZ_JOIN | [] Implemented Formatter
|
|
// DZ_LEAVE | [] Implemented Formatter
|
|
// TRADER_PURCHASE | [x] Implemented Formatter
|
|
// TRADER_SELL | [x] Implemented Formatter
|
|
// BANDOLIER_CREATE | [] Implemented Formatter
|
|
// BANDOLIER_SWAP | [] Implemented Formatter
|
|
// DISCOVER_ITEM | [X] Implemented Formatter
|
|
|
|
std::string PlayerEventLogs::GetDiscordPayloadFromEvent(const PlayerEvent::PlayerEventContainer &e)
|
|
{
|
|
std::string payload;
|
|
switch (e.player_event_log.event_type_id) {
|
|
case PlayerEvent::AA_GAIN: {
|
|
PlayerEvent::AAGainedEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatAAGainedEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::AA_PURCHASE: {
|
|
PlayerEvent::AAPurchasedEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatAAPurchasedEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::COMBINE_FAILURE:
|
|
case PlayerEvent::COMBINE_SUCCESS: {
|
|
PlayerEvent::CombineEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatCombineEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::DEATH: {
|
|
PlayerEvent::DeathEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatDeathEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::DISCOVER_ITEM: {
|
|
PlayerEvent::DiscoverItemEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatDiscoverItemEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::DROPPED_ITEM: {
|
|
PlayerEvent::DroppedItemEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatDroppedItemEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::FISH_FAILURE:
|
|
case PlayerEvent::FORAGE_FAILURE:
|
|
case PlayerEvent::WENT_ONLINE:
|
|
case PlayerEvent::WENT_OFFLINE: {
|
|
payload = PlayerEventDiscordFormatter::FormatWithNodata(e);
|
|
break;
|
|
}
|
|
case PlayerEvent::FISH_SUCCESS: {
|
|
PlayerEvent::FishSuccessEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatFishSuccessEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::FORAGE_SUCCESS: {
|
|
PlayerEvent::ForageSuccessEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatForageSuccessEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::ITEM_DESTROY: {
|
|
PlayerEvent::DestroyItemEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatDestroyItemEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::LEVEL_GAIN: {
|
|
PlayerEvent::LevelGainedEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatLevelGainedEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::LEVEL_LOSS: {
|
|
PlayerEvent::LevelLostEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatLevelLostEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::LOOT_ITEM: {
|
|
PlayerEvent::LootItemEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatLootItemEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::GROUNDSPAWN_PICKUP: {
|
|
PlayerEvent::GroundSpawnPickupEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatGroundSpawnPickupEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::NPC_HANDIN: {
|
|
PlayerEvent::HandinEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatNPCHandinEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::SAY: {
|
|
PlayerEvent::SayEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatEventSay(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::GM_COMMAND: {
|
|
PlayerEvent::GMCommandEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatGMCommand(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::SKILL_UP: {
|
|
PlayerEvent::SkillUpEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatSkillUpEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::SPLIT_MONEY: {
|
|
PlayerEvent::SplitMoneyEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatSplitMoneyEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::TASK_ACCEPT: {
|
|
PlayerEvent::TaskAcceptEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatTaskAcceptEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::TASK_COMPLETE: {
|
|
PlayerEvent::TaskCompleteEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatTaskCompleteEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::TASK_UPDATE: {
|
|
PlayerEvent::TaskUpdateEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatTaskUpdateEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::TRADE: {
|
|
PlayerEvent::TradeEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatTradeEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::TRADER_PURCHASE: {
|
|
PlayerEvent::TraderPurchaseEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatTraderPurchaseEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::TRADER_SELL: {
|
|
PlayerEvent::TraderSellEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatTraderSellEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::REZ_ACCEPTED: {
|
|
PlayerEvent::ResurrectAcceptEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
payload = PlayerEventDiscordFormatter::FormatResurrectAcceptEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::MERCHANT_PURCHASE: {
|
|
PlayerEvent::MerchantPurchaseEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
|
|
payload = PlayerEventDiscordFormatter::FormatMerchantPurchaseEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::MERCHANT_SELL: {
|
|
PlayerEvent::MerchantSellEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
|
|
payload = PlayerEventDiscordFormatter::FormatMerchantSellEvent(e, n);
|
|
break;
|
|
}
|
|
case PlayerEvent::ZONING: {
|
|
PlayerEvent::ZoningEvent n{};
|
|
std::stringstream ss;
|
|
{
|
|
ss << e.player_event_log.event_data;
|
|
cereal::JSONInputArchive ar(ss);
|
|
n.serialize(ar);
|
|
}
|
|
|
|
payload = PlayerEventDiscordFormatter::FormatZoningEvent(e, n);
|
|
break;
|
|
}
|
|
default: {
|
|
LogPlayerEventsDetail(
|
|
"Player event [{}] ({}) Discord formatter not implemented",
|
|
e.player_event_log.event_type_name,
|
|
e.player_event_log.event_type_id
|
|
);
|
|
}
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
// general process function, used in world or QS depending on rule Logging:PlayerEventsQSProcess
|
|
void PlayerEventLogs::Process()
|
|
{
|
|
if (m_database_ping_timer.Check()) {
|
|
m_database->ping();
|
|
}
|
|
|
|
if (m_process_batch_events_timer.Check() ||
|
|
m_record_batch_queue.size() >= RuleI(Logging, BatchPlayerEventProcessChunkSize)) {
|
|
ProcessBatchQueue();
|
|
}
|
|
|
|
if (m_process_retention_truncation_timer.Check()) {
|
|
ProcessRetentionTruncation();
|
|
}
|
|
}
|
|
|
|
void PlayerEventLogs::ProcessRetentionTruncation()
|
|
{
|
|
LogPlayerEventsDetail("Running truncation");
|
|
|
|
// Map of repository-specific deletion functions
|
|
std::unordered_map<PlayerEvent::EventType, std::function<uint32(const std::string &)>> repository_deleters = {
|
|
{
|
|
PlayerEvent::LOOT_ITEM, [&](const std::string &condition) {
|
|
return PlayerEventLootItemsRepository::DeleteWhere(*m_database, condition);
|
|
}},
|
|
{
|
|
PlayerEvent::MERCHANT_SELL, [&](const std::string &condition) {
|
|
return PlayerEventMerchantSellRepository::DeleteWhere(*m_database, condition);
|
|
}},
|
|
{
|
|
PlayerEvent::MERCHANT_PURCHASE, [&](const std::string &condition) {
|
|
return PlayerEventMerchantPurchaseRepository::DeleteWhere(*m_database, condition);
|
|
}},
|
|
{
|
|
PlayerEvent::NPC_HANDIN, [&](const std::string &condition) {
|
|
uint32 deleted_count = PlayerEventNpcHandinRepository::DeleteWhere(*m_database, condition);
|
|
deleted_count += PlayerEventNpcHandinEntriesRepository::DeleteWhere(*m_database, condition);
|
|
return deleted_count;
|
|
}},
|
|
{
|
|
PlayerEvent::TRADE, [&](const std::string &condition) {
|
|
uint32 deleted_count = PlayerEventTradeRepository::DeleteWhere(*m_database, condition);
|
|
deleted_count += PlayerEventTradeEntriesRepository::DeleteWhere(*m_database, condition);
|
|
return deleted_count;
|
|
}},
|
|
{
|
|
PlayerEvent::SPEECH, [&](const std::string &condition) {
|
|
return PlayerEventSpeechRepository::DeleteWhere(*m_database, condition);
|
|
}},
|
|
{
|
|
PlayerEvent::KILLED_NPC, [&](const std::string &condition) {
|
|
return PlayerEventKilledNpcRepository::DeleteWhere(*m_database, condition);
|
|
}},
|
|
{
|
|
PlayerEvent::KILLED_NAMED_NPC, [&](const std::string &condition) {
|
|
return PlayerEventKilledNamedNpcRepository::DeleteWhere(*m_database, condition);
|
|
}},
|
|
{
|
|
PlayerEvent::KILLED_RAID_NPC, [&](const std::string &condition) {
|
|
return PlayerEventKilledRaidNpcRepository::DeleteWhere(*m_database, condition);
|
|
}},
|
|
{
|
|
PlayerEvent::AA_PURCHASE, [&](const std::string &condition) {
|
|
return PlayerEventAaPurchaseRepository::DeleteWhere(*m_database, condition);
|
|
}}
|
|
};
|
|
|
|
// Group event types by retention interval
|
|
std::unordered_map<int, std::vector<int>> retention_groups;
|
|
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
|
if (m_settings[i].retention_days > 0) {
|
|
retention_groups[m_settings[i].retention_days].push_back(i);
|
|
}
|
|
}
|
|
|
|
for (const auto &[retention_days, event_types]: retention_groups) {
|
|
std::string condition = fmt::format(
|
|
"created_at < (NOW() - INTERVAL {} DAY)",
|
|
retention_days
|
|
);
|
|
|
|
// Handle ETL deletions for each event type in the group
|
|
uint32 total_deleted_count = 0;
|
|
for (int event_type_id: event_types) {
|
|
if (m_settings[event_type_id].etl_enabled) {
|
|
auto it = repository_deleters.find(static_cast<PlayerEvent::EventType>(m_settings[event_type_id].id));
|
|
if (it != repository_deleters.end()) {
|
|
total_deleted_count += it->second(condition);
|
|
}
|
|
else {
|
|
LogError("Non-Implemented ETL Event Type [{}]", static_cast<uint32>(m_settings[event_type_id].id));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (total_deleted_count > 0) {
|
|
LogInfo(
|
|
"Truncated [{}] ETL events older than [{}] days",
|
|
total_deleted_count,
|
|
retention_days
|
|
);
|
|
}
|
|
|
|
// Batch deletion for player_event_logs
|
|
std::string event_type_ids = fmt::format(
|
|
"({})",
|
|
fmt::join(event_types, ", ")
|
|
);
|
|
|
|
uint32 deleted_count = PlayerEventLogsRepository::DeleteWhere(
|
|
*m_database,
|
|
fmt::format(
|
|
"event_type_id IN {} AND {}",
|
|
event_type_ids,
|
|
condition
|
|
)
|
|
);
|
|
|
|
if (deleted_count > 0) {
|
|
LogInfo(
|
|
"Truncated [{}] events of types [{}] older than [{}] days",
|
|
deleted_count,
|
|
event_type_ids,
|
|
retention_days
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlayerEventLogs::ReloadSettings()
|
|
{
|
|
for (auto &e: PlayerEventLogSettingsRepository::All(*m_database)) {
|
|
if (e.id >= PlayerEvent::MAX || e.id < 0) {
|
|
continue;
|
|
}
|
|
|
|
m_settings[e.id] = e;
|
|
}
|
|
}
|
|
|
|
const int32_t RETENTION_DAYS_DEFAULT = 7;
|
|
|
|
void PlayerEventLogs::SetSettingsDefaults()
|
|
{
|
|
m_settings[PlayerEvent::GM_COMMAND].event_enabled = 1;
|
|
m_settings[PlayerEvent::ZONING].event_enabled = 1;
|
|
m_settings[PlayerEvent::AA_GAIN].event_enabled = 1;
|
|
m_settings[PlayerEvent::AA_PURCHASE].event_enabled = 1;
|
|
m_settings[PlayerEvent::FORAGE_SUCCESS].event_enabled = 0;
|
|
m_settings[PlayerEvent::FORAGE_FAILURE].event_enabled = 0;
|
|
m_settings[PlayerEvent::FISH_SUCCESS].event_enabled = 0;
|
|
m_settings[PlayerEvent::FISH_FAILURE].event_enabled = 0;
|
|
m_settings[PlayerEvent::ITEM_DESTROY].event_enabled = 1;
|
|
m_settings[PlayerEvent::WENT_ONLINE].event_enabled = 0;
|
|
m_settings[PlayerEvent::WENT_OFFLINE].event_enabled = 0;
|
|
m_settings[PlayerEvent::LEVEL_GAIN].event_enabled = 1;
|
|
m_settings[PlayerEvent::LEVEL_LOSS].event_enabled = 1;
|
|
m_settings[PlayerEvent::LOOT_ITEM].event_enabled = 1;
|
|
m_settings[PlayerEvent::MERCHANT_PURCHASE].event_enabled = 1;
|
|
m_settings[PlayerEvent::MERCHANT_SELL].event_enabled = 1;
|
|
m_settings[PlayerEvent::GROUP_JOIN].event_enabled = 0;
|
|
m_settings[PlayerEvent::GROUP_LEAVE].event_enabled = 0;
|
|
m_settings[PlayerEvent::RAID_JOIN].event_enabled = 0;
|
|
m_settings[PlayerEvent::RAID_LEAVE].event_enabled = 0;
|
|
m_settings[PlayerEvent::GROUNDSPAWN_PICKUP].event_enabled = 1;
|
|
m_settings[PlayerEvent::NPC_HANDIN].event_enabled = 1;
|
|
m_settings[PlayerEvent::SKILL_UP].event_enabled = 0;
|
|
m_settings[PlayerEvent::TASK_ACCEPT].event_enabled = 1;
|
|
m_settings[PlayerEvent::TASK_UPDATE].event_enabled = 1;
|
|
m_settings[PlayerEvent::TASK_COMPLETE].event_enabled = 1;
|
|
m_settings[PlayerEvent::TRADE].event_enabled = 1;
|
|
m_settings[PlayerEvent::GIVE_ITEM].event_enabled = 1;
|
|
m_settings[PlayerEvent::SAY].event_enabled = 0;
|
|
m_settings[PlayerEvent::REZ_ACCEPTED].event_enabled = 1;
|
|
m_settings[PlayerEvent::DEATH].event_enabled = 1;
|
|
m_settings[PlayerEvent::COMBINE_FAILURE].event_enabled = 1;
|
|
m_settings[PlayerEvent::COMBINE_SUCCESS].event_enabled = 1;
|
|
m_settings[PlayerEvent::DROPPED_ITEM].event_enabled = 1;
|
|
m_settings[PlayerEvent::SPLIT_MONEY].event_enabled = 1;
|
|
m_settings[PlayerEvent::DZ_JOIN].event_enabled = 1;
|
|
m_settings[PlayerEvent::DZ_LEAVE].event_enabled = 1;
|
|
m_settings[PlayerEvent::TRADER_PURCHASE].event_enabled = 1;
|
|
m_settings[PlayerEvent::TRADER_SELL].event_enabled = 1;
|
|
m_settings[PlayerEvent::BANDOLIER_CREATE].event_enabled = 0;
|
|
m_settings[PlayerEvent::BANDOLIER_SWAP].event_enabled = 0;
|
|
m_settings[PlayerEvent::DISCOVER_ITEM].event_enabled = 1;
|
|
m_settings[PlayerEvent::POSSIBLE_HACK].event_enabled = 1;
|
|
m_settings[PlayerEvent::KILLED_NPC].event_enabled = 0;
|
|
m_settings[PlayerEvent::KILLED_NAMED_NPC].event_enabled = 1;
|
|
m_settings[PlayerEvent::KILLED_RAID_NPC].event_enabled = 1;
|
|
m_settings[PlayerEvent::ITEM_CREATION].event_enabled = 1;
|
|
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM].event_enabled = 1;
|
|
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_PLAT].event_enabled = 1;
|
|
m_settings[PlayerEvent::PARCEL_SEND].event_enabled = 1;
|
|
m_settings[PlayerEvent::PARCEL_RETRIEVE].event_enabled = 1;
|
|
m_settings[PlayerEvent::PARCEL_DELETE].event_enabled = 1;
|
|
m_settings[PlayerEvent::BARTER_TRANSACTION].event_enabled = 1;
|
|
m_settings[PlayerEvent::EVOLVE_ITEM].event_enabled = 1;
|
|
m_settings[PlayerEvent::SPEECH].event_enabled = 0;
|
|
|
|
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
|
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
|
|
}
|
|
}
|
|
|
|
void PlayerEventLogs::LoadEtlIds()
|
|
{
|
|
auto e = [&](auto p) -> bool {
|
|
for (PlayerEventLogSettingsRepository::PlayerEventLogSettings const &c: m_settings) {
|
|
if (c.id == p) {
|
|
return c.etl_enabled ? true : false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
m_etl_settings.clear();
|
|
m_etl_settings = {
|
|
{
|
|
PlayerEvent::LOOT_ITEM,
|
|
{
|
|
.enabled = e(PlayerEvent::LOOT_ITEM),
|
|
.table_name = "player_event_loot_items",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventLootItemsRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::MERCHANT_SELL,
|
|
{
|
|
.enabled = e(PlayerEvent::MERCHANT_SELL),
|
|
.table_name = "player_event_merchant_sell",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventMerchantSellRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::MERCHANT_PURCHASE,
|
|
{
|
|
.enabled = e(PlayerEvent::MERCHANT_PURCHASE),
|
|
.table_name = "player_event_merchant_purchase",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventMerchantPurchaseRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::NPC_HANDIN,
|
|
{
|
|
.enabled = e(PlayerEvent::NPC_HANDIN),
|
|
.table_name = "player_event_npc_handin",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventNpcHandinRepository::TableName()))
|
|
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::TRADE,
|
|
{
|
|
.enabled = e(PlayerEvent::TRADE),
|
|
.table_name = "player_event_trade",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventTradeRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::SPEECH,
|
|
{
|
|
.enabled = e(PlayerEvent::SPEECH),
|
|
.table_name = "player_event_speech",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventSpeechRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::KILLED_NPC,
|
|
{
|
|
.enabled = e(PlayerEvent::KILLED_NPC),
|
|
.table_name = "player_event_killed_npc",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventKilledNpcRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::KILLED_NAMED_NPC,
|
|
{
|
|
.enabled = e(PlayerEvent::KILLED_NAMED_NPC),
|
|
.table_name = "player_event_killed_named_npc",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventKilledNamedNpcRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::KILLED_RAID_NPC,
|
|
{
|
|
.enabled = e(PlayerEvent::KILLED_RAID_NPC),
|
|
.table_name = "player_event_killed_raid_npc",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventKilledRaidNpcRepository::TableName()))
|
|
}
|
|
},
|
|
{
|
|
PlayerEvent::AA_PURCHASE,
|
|
{
|
|
.enabled = e(PlayerEvent::AA_PURCHASE),
|
|
.table_name = "player_event_aa_purchase",
|
|
.next_id = static_cast<int64>(m_database->GetNextTableId(PlayerEventAaPurchaseRepository::TableName()))
|
|
}
|
|
}
|
|
};
|
|
|
|
for (auto &e: m_etl_settings) {
|
|
LogPlayerEventsDetail(
|
|
"ETL Settings [{}] Enabled [{}] Table [{}] NextId [{}]",
|
|
PlayerEvent::EventName[e.first],
|
|
e.second.enabled,
|
|
e.second.table_name,
|
|
e.second.next_id
|
|
);
|
|
}
|
|
}
|
|
|
|
bool PlayerEventLogs::LoadDatabaseConnection()
|
|
{
|
|
const auto c = EQEmuConfig::get();
|
|
|
|
LogInfo(
|
|
"Connecting to MySQL for PlayerEvents [{}]@[{}]:[{}]",
|
|
c->DatabaseUsername.c_str(),
|
|
c->DatabaseHost.c_str(),
|
|
c->DatabasePort
|
|
);
|
|
|
|
if (!player_event_database.Connect(
|
|
c->DatabaseHost.c_str(),
|
|
c->DatabaseUsername.c_str(),
|
|
c->DatabasePassword.c_str(),
|
|
c->DatabaseDB.c_str(),
|
|
c->DatabasePort
|
|
)) {
|
|
LogError("Cannot continue without a database connection for player events.");
|
|
return false;
|
|
}
|
|
|
|
SetDatabase(&player_event_database);
|
|
return true;
|
|
}
|