[Logging] Implement Player Event Logging system (#2833)

* Plumbing

* Batch processing in world

* Cleanup

* Cleanup

* Update player_event_logs.cpp

* Add player zoning event

* Use generics

* Comments

* Add events

* Add more events

* AA_GAIN, AA_PURCHASE, FORAGE_SUCCESS, FORAGE_FAILURE

* FISH_SUCCESS, FISH_FAILURE, ITEM_DESTROY

* Add charges to ITEM_DESTROY

* WENT_ONLINE, WENT_OFFLINE

* LEVEL_GAIN, LEVEL_LOSS

* LOOT_ITEM

* MERCHANT_PURCHASE

* MERCHANT_SELL

* SKILL_UP

* Add events

* Add more events

* TASK_ACCEPT, TASK_COMPLETE, and TASK_UPDATE

* GROUNDSPAWN_PICKUP

* SAY

* REZ_ACCEPTED

* COMBINE_FAILURE and COMBINE_SUCCESS

* DROPPED_ITEM

* DEATH

* SPLIT_MONEY

* TRADER_PURCHASE and TRADER_SELL

* DISCOVER_ITEM

* Convert GM_COMMAND to use new macro

* Convert ZONING event to use macro

* Revert some code changes

* Revert "Revert some code changes"

This reverts commit d53682f997e89a053a660761085913245db91e9d.

* Add cereal generation support to repositories

* TRADE

* Formatting

* Cleanup

* Relocate discord_manager to discord folder

* Discord sending plumbing

* Rename UCS's Database class to UCSDatabase to be more specific and not collide with base Database class for repository usage

* More discord sending plumbing

* More discord message formatting work

* More discord formatting work

* Discord formatting of events

* Format WENT_ONLINE, WENT_OFFLINE

* Add merchant purchase event

* Handle Discord MERCHANT_SELL formatter

* Update player_event_discord_formatter.cpp

* Tweaks

* Implement retention truncation

* Put mutex locking on batch queue, put processor on its own thread

* Process on initial bootup

* Implement optional QS processing, implement keepalive from world to QS

* Reload player event settings when logs are reloaded in game

* Set settings defaults

* Update player_event_logs.cpp

* Update player_event_logs.cpp

* Set retention days on boot

* Update player_event_logs.cpp

* Player Handin Event Testing.

Testing player handin stuff.

* Cleanup.

* Finish NPC Handin.

* set a reference to the client inside of the trade object as well for plugins to process

* Fix for windows _inline

* Bump to cpp20 default, ignore excessive warnings on windows

* Bump FMT to 6.1.2 for cpp20 compat and swap fmt::join for Strings::Join

* Windows compile fixes

* Update CMakeLists.txt

* Update CMakeLists.txt

* Update CMakeLists.txt

* Create 2022_12_19_player_events_tables.sql

* [Formatters] Work on Discord Formatters

* Handin money.

* Format header

* [Formatters] Work on Discord Formatters

* Format

* Format

* [Formatters] More Formatter work, need to test further.

* [Formatters] More Work on Formatters.

* Add missing #endif

* [Formatters] Work on Formatters, fix Bot formatting in ^create help

* NPC Handin Discord Formatter

* Update player_event_logs.cpp

* Discover Item Discord Formatter

* Dropped Item Discord Formatter

* Split Money Discord Formatter

* Trader Discord Formatters

* Cleanup.

* Trade Event Discord Formatter Groundwork

* SAY don't record GM commands

* GM_Command don't record #help

* Update player_event_logs.cpp

* Fill in more event data

* Post rebase fixes

* Post rebase fix

* Discord formatting adjustments

* Add event deprecation or unimplemented tag support

* Trade events

* Add return money and sanity checks.

* Update schema

* Update ucs.cpp

* Update client.cpp

* Update 2022_12_19_player_events_tables.sql

* Implement archive single line

* Replace hackers table and functions with PossibleHack player event

* Replace very old eventlog table since the same events are covered by player event logs

* Update bot_command.cpp

* Record NPC kill events ALL / Named / Raid

* Add BatchEventProcessIntervalSeconds rule

* Naming

* Update CMakeLists.txt

* Update database_schema.h

* Remove logging function and methods

* DB version

* Cleanup SendPlayerHandinEvent

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
Co-authored-by: Aeadoin <109764533+Aeadoin@users.noreply.github.com>
This commit is contained in:
Chris Miles
2023-02-12 21:31:01 -06:00
committed by GitHub
parent 1cc32d92cf
commit d9f545a5ec
92 changed files with 6480 additions and 1391 deletions
+97 -35
View File
@@ -24,8 +24,10 @@
#include "worldserver.h"
#include "zonedb.h"
#include "../common/zone_store.h"
#include "../common/events/player_event_logs.h"
#include "bot.h"
#include "../common/events/player_event_logs.h"
extern WorldServer worldserver;
@@ -803,17 +805,12 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
safe_delete(inst);
// discover item and any augments
if((RuleB(Character, EnableDiscoveredItems)) && !GetGM()) {
if(!IsDiscovered(item_id)) {
DiscoverItem(item_id);
}
/*
// Augments should have been discovered prior to being placed on an item.
for (int iter = AUG_BEGIN; iter < EQ::constants::ITEM_COMMON_SIZE; ++iter) {
if(augments[iter] && !IsDiscovered(augments[iter]))
DiscoverItem(augments[iter]);
}
*/
if (
RuleB(Character, EnableDiscoveredItems) &&
!GetGM() &&
!IsDiscovered(item_id)
) {
DiscoverItem(item_id);
}
return true;
@@ -822,11 +819,14 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
// Drop item from inventory to ground (generally only dropped from SLOT_CURSOR)
void Client::DropItem(int16 slot_id, bool recurse)
{
LogInventory("[{}] (char_id: [{}]) Attempting to drop item from slot [{}] on the ground",
GetCleanName(), CharacterID(), slot_id);
LogInventory(
"[{}] (char_id: [{}]) Attempting to drop item from slot [{}] on the ground",
GetCleanName(),
CharacterID(),
slot_id
);
if(GetInv().CheckNoDrop(slot_id, recurse) && !CanTradeFVNoDropItem())
{
if (GetInv().CheckNoDrop(slot_id, recurse) && !CanTradeFVNoDropItem()) {
auto invalid_drop = m_inv.GetItem(slot_id);
if (!invalid_drop) {
LogInventory("Error in InventoryProfile::CheckNoDrop() - returned 'true' for empty slot");
@@ -850,48 +850,84 @@ void Client::DropItem(int16 slot_id, bool recurse)
}
invalid_drop = nullptr;
database.SetHackerFlag(AccountName(), GetCleanName(), "Tried to drop an item on the ground that was nodrop!");
std::string message = fmt::format(
"Tried to drop an item on the ground that was no-drop! item_name [{}] item_id ({})",
invalid_drop->GetItem()->Name,
invalid_drop->GetItem()->ID
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
GetInv().DeleteItem(slot_id);
return;
}
// Take control of item in client inventory
EQ::ItemInstance *inst = m_inv.PopItem(slot_id);
if(inst) {
auto* inst = m_inv.PopItem(slot_id);
if (inst) {
if (LogSys.log_settings[Logs::Inventory].is_category_enabled) {
LogInventory("DropItem() Processing - full item parse:");
LogInventory("depth: 0, Item: [{}] (id: [{}]), IsDroppable: [{}]",
(inst->GetItem() ? inst->GetItem()->Name : "null data"), inst->GetID(), (inst->IsDroppable(false) ? "true" : "false"));
LogInventory(
"depth: 0, Item: [{}] (id: [{}]), IsDroppable: [{}]",
(inst->GetItem() ? inst->GetItem()->Name : "null data"),
inst->GetID(),
(inst->IsDroppable(false) ? "true" : "false")
);
if (!inst->IsDroppable(false))
if (!inst->IsDroppable(false)) {
LogError("Non-droppable item being processed for drop by [{}]", GetCleanName());
}
for (auto iter1 : *inst->GetContents()) { // depth 1
LogInventory("-depth: 1, Item: [{}] (id: [{}]), IsDroppable: [{}]",
(iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), (iter1.second->IsDroppable(false) ? "true" : "false"));
LogInventory(
"-depth: 1, Item: [{}] (id: [{}]), IsDroppable: [{}]",
(iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"),
iter1.second->GetID(),
(iter1.second->IsDroppable(false) ? "true" : "false")
);
if (!iter1.second->IsDroppable(false))
if (!iter1.second->IsDroppable(false)) {
LogError("Non-droppable item being processed for drop by [{}]", GetCleanName());
}
for (auto iter2 : *iter1.second->GetContents()) { // depth 2
LogInventory("--depth: 2, Item: [{}] (id: [{}]), IsDroppable: [{}]",
(iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), (iter2.second->IsDroppable(false) ? "true" : "false"));
LogInventory(
"--depth: 2, Item: [{}] (id: [{}]), IsDroppable: [{}]",
(iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"),
iter2.second->GetID(),
(iter2.second->IsDroppable(false) ? "true" : "false")
);
if (!iter2.second->IsDroppable(false))
if (!iter2.second->IsDroppable(false)) {
LogError("Non-droppable item being processed for drop by [{}]", GetCleanName());
}
}
}
}
if (player_event_logs.IsEventEnabled(PlayerEvent::DROPPED_ITEM)) {
auto e = PlayerEvent::DroppedItemEvent{
.item_id = inst->GetID(),
.item_name = inst->GetItem()->Name,
.slot_id = slot_id,
.charges = (uint32) inst->GetCharges()
};
RecordPlayerEventLog(PlayerEvent::DROPPED_ITEM, e);
}
int i = parse->EventItem(EVENT_DROP_ITEM, this, inst, nullptr, "", slot_id);
if(i != 0) {
if (i != 0) {
LogInventory("Item drop handled by [EVENT_DROP_ITEM]");
safe_delete(inst);
}
} else {
// Item doesn't exist in inventory!
LogInventory("DropItem() - No item found in slot [{}]", slot_id);
Message(Chat::Red, "Error: Item not found in slot %i", slot_id);
Message(
Chat::Red,
fmt::format(
"Error: Item not found in slot {}.",
slot_id
).c_str()
);
return;
}
@@ -904,15 +940,16 @@ void Client::DropItem(int16 slot_id, bool recurse)
database.SaveInventory(CharacterID(), nullptr, slot_id);
}
if(!inst)
if (!inst) {
return;
}
// Package as zone object
auto object = new Object(this, inst);
entity_list.AddObject(object, true);
object->StartDecay();
LogInventory("Item drop handled ut assolet");
LogInventory("[{}] dropped [{}] from slot [{}]", GetCleanName(), inst->GetItem()->Name, slot_id);
DropItemQS(inst, false);
safe_delete(inst);
@@ -1822,6 +1859,18 @@ bool Client::SwapItem(MoveItem_Struct* move_in) {
MessageString(Chat::Loot, 290);
parse->EventItem(EVENT_DESTROY_ITEM, this, test_inst, nullptr, "", 0);
DeleteItemInInventory(EQ::invslot::slotCursor, 0, true);
if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
auto e = PlayerEvent::DestroyItemEvent{
.item_id = test_inst->GetItem()->ID,
.item_name = test_inst->GetItem()->Name,
.charges = test_inst->GetCharges(),
.reason = "Duplicate lore item",
};
RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e);
}
}
}
return true;
@@ -1835,6 +1884,16 @@ bool Client::SwapItem(MoveItem_Struct* move_in) {
EQ::ItemInstance *inst = m_inv.GetItem(EQ::invslot::slotCursor);
if(inst) {
parse->EventItem(EVENT_DESTROY_ITEM, this, inst, nullptr, "", 0);
if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
auto e = PlayerEvent::DestroyItemEvent{
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
.charges = inst->GetCharges(),
.reason = "Client destroy cursor",
};
RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e);
}
}
DeleteItemInInventory(move_in->from_slot);
@@ -1867,10 +1926,13 @@ bool Client::SwapItem(MoveItem_Struct* move_in) {
if(!banker || distance > USE_NPC_RANGE2)
{
auto hacked_string = fmt::format("Player tried to make use of a banker(items) but {} is "
"non-existant or too far away ({} units).",
banker ? banker->GetName() : "UNKNOWN NPC", distance);
database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName());
auto message = fmt::format(
"Player tried to make use of a banker (items) but banker [{}] is "
"non-existent or too far away [{}] units",
banker ? banker->GetName() : "UNKNOWN NPC", distance
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
Kick("Inventory desync"); // Kicking player to avoid item loss do to client and server inventories not being sync'd
return false;
}