mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
* Add Parcel Feature Add the parcel system for RoF2 client * Fixed a duplicate define * Reformat reformating and review changes * Further Formatting * Memory Mgmt Updates Refactored to using unique_ptr/make_unique/etc to avoid manual memory mgmt. Other format changes * Refactor db structure Refactor for db structure of parcels to character_parcels Removal of parcel_merchants Addition of npc_types.is_parcel_merchant Cleanup as a result * Refactor to use item id 99990 for money transfers. Removed the money string function as a result, though simplified the messaging related to money. Other updates based on feedback. * Move prune routine out of scheduler and into a world process. Removed RuleI from #define * Update * Update database.cpp * Update database_update_manifest.cpp * Update main.cpp * Update client_process.cpp * Update parcels.cpp * Remove parcel merchant content to optional sql instead of manifest. --------- Co-authored-by: Akkadius <akkadius1@gmail.com>
710 lines
22 KiB
C++
710 lines
22 KiB
C++
#include <cereal/archives/json.hpp>
|
|
#include "player_event_logs.h"
|
|
#include "player_event_discord_formatter.h"
|
|
#include "../platform.h"
|
|
#include "../rulesys.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);
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
BenchTimer benchmark;
|
|
|
|
// flush many
|
|
PlayerEventLogsRepository::InsertMany(*m_database, m_record_batch_queue);
|
|
LogPlayerEventsDetail(
|
|
"Processing batch player event log queue of [{}] took [{}]",
|
|
m_record_batch_queue.size(),
|
|
benchmark.elapsed()
|
|
);
|
|
|
|
// empty
|
|
m_record_batch_queue = {};
|
|
m_batch_queue_lock.unlock();
|
|
}
|
|
|
|
// adds a player event to the queue
|
|
void PlayerEventLogs::AddToQueue(const 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 (!LogSys.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 (!LogSys.GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url.empty()) {
|
|
return LogSys.GetDiscordWebhooks()[m_settings[event_type_id].discord_webhook_id].webhook_url;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
// 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: {
|
|
LogInfo(
|
|
"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_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()
|
|
{
|
|
LogPlayerEvents("Running truncation");
|
|
|
|
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
|
if (m_settings[i].retention_days > 0) {
|
|
int deleted_count = PlayerEventLogsRepository::DeleteWhere(
|
|
*m_database,
|
|
fmt::format(
|
|
"event_type_id = {} AND created_at < (NOW() - INTERVAL {} DAY)",
|
|
i,
|
|
m_settings[i].retention_days
|
|
)
|
|
);
|
|
|
|
if (deleted_count > 0) {
|
|
LogInfo(
|
|
"Truncated [{}] events of type [{}] ({}) older than [{}] days",
|
|
deleted_count,
|
|
PlayerEvent::EventName[i],
|
|
i,
|
|
m_settings[i].retention_days
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlayerEventLogs::ReloadSettings()
|
|
{
|
|
for (auto &e: PlayerEventLogSettingsRepository::All(*m_database)) {
|
|
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;
|
|
|
|
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
|
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
|
|
}
|
|
}
|