mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-16 01:01:30 +00:00
* [Feature] Add additional Guild Features This adds the following guild features and design pattern - the existing guild system was used - guild features are based on RoF2 within source with translaters used to converted between client differences - backward compatible with Ti and UF, and allows for mixed client servers - Guild Back for Ti and UF is based on RoF2 Permissions for banking if Guild Leader does not use Ti/UF - Guild Ranks and Permissions are enabled. - Guild Tributes are enabled. - Event logging via rules for donating tribute items and plat - Rules to limit Guild Tributes based on max level of server - Rewrote guild communications to client using specific opcodes -- Server no longer sends a guild member list on each zone -- Guild window is updated when a member levels, rank changes, zone changes, banker/alt status using individual opcodes -- When a member is removed or added to a guild, a single opcode is sent to each guild member -- This reduces network traffic considerably Known issues: - Visual bug only. Guild Tributes window will display a 0 for level if tribute is above max level rule setting. - Visual bug only. Guild Mgmt Window will not display an online member if the player has 'show offline' unchecked and a guild member zones within the Notes/Tribute tab. This is resolved by selecting and de-selecting the 'Show Offline' checkbox. * Updated RoF2 Guild Comms Updated RoF2 Guild Comms Update RoF2 Opcodes Rewrote RoF2 Guild Communications using specific opcodes. Added database changes - they are irreversible * Formatting * Update base_guild_members_repository.h * Format GuildInfo * Format GuildAction enum * Formatting in clientlist * quantity vs quantity * desc vs description * Format structs * Inline struct values * Formatting * Formatting * Formatting fixes * Formatting items * Formatting * Formatting * struct formatting updates * Updated formatting * Updated - std:string items - naming conventions - magic numbers * Repo refactors Other formatting updates * Remove test guild commands * Updated #guild info command * Add new repo methods for Neckolla ReplaceOne and ReplaceMany * Fix guild_tributes repo * Update database_update_manifest.cpp * Phase 1 of final testing with RoF2 -> RoF2. Next phase will be inter compatibility review * Remove #guild testing commands * Fix uf translator error Rewrite LoadGuilds * Use extended repository * FIx guild window on member add * LoadGuild Changes * Update guild_base.cpp * Few small fixes for display issue with UF * Update guild_base.cpp * Update guild_members_repository.h * Update zoneserver.cpp * Update guild.cpp * Update entity.h * Switch formatting * Formatting * Update worldserver.cpp * Switch formatting * Formatting switch statement * Update guild.cpp * Formatting in guild_base * We don't need to validate m_db everywhere * More formatting / spacing issues * Switch format * Update guild_base.cpp * Fix an UF issue displaying incorrect guildtag as <> * Updated several constants, fixed a few issues with Ti/UF and guild tributes not being removed or sent when a member is removed/disbands from a guild. * Formatting and logging updates * Fix for Loadguilds and permissions after repo updates. * Cleanup unnecessary m_db checks * Updated logging to use player_event_logs * Updated to use the single opcodes for guild traffic for Ti/UF/RoF2. Several enhancements for guild functionality for more reusable code and readability. * Update to fix Demote Self and guild invites declining when option set to not accept guild invites * Potential fix for guild notes/tribute display issues when client has 'Show Offline' unchecked. * Updates to fox recent master changes Updates to fix recent master changes * Updates in response to comments * Further Updates in response to comments * Comment updates and refactor for SendAppearance functions * Comment updates * Update client spawn process for show guild name Add show guild tag to default spawn process * Update to use zone spawn packets for RoF2 Removed several unused functions as a result Updated MemberRankUpdate to properly update guild_show on rank change. Updated OP_GuildURLAndChannel opcode for UF/RoF2 * Cleanup of world changes Created function for repetitive zonelist sendpackets to only booted zones Re-Inserted accidental delete of scanclosemobs * Fixes * Further world cleanup * Fix a few test guild bank cases for backward compat Removed a duplicate db call Fixed a fallthrough issue * Update guild_mgr.cpp * Cleanup --------- Co-authored-by: Akkadius <akkadius1@gmail.com>
707 lines
22 KiB
C++
707 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;
|
|
|
|
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
|
|
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
|
|
}
|
|
}
|