[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 6480 additions and 1391 deletions

View File

@ -33,9 +33,11 @@ SET(common_sources
eq_stream_proxy.cpp
eqtime.cpp
event_sub.cpp
events/player_event_logs.cpp
events/player_event_discord_formatter.cpp
expedition_lockout_timer.cpp
extprofile.cpp
discord_manager.cpp
discord/discord_manager.cpp
faction.cpp
file.cpp
guild_base.cpp
@ -198,7 +200,6 @@ SET(repositories
repositories/base/base_dynamic_zones_repository.h
repositories/base/base_dynamic_zone_members_repository.h
repositories/base/base_dynamic_zone_templates_repository.h
repositories/base/base_eventlog_repository.h
repositories/base/base_expeditions_repository.h
repositories/base/base_expedition_lockouts_repository.h
repositories/base/base_faction_association_repository.h
@ -218,7 +219,6 @@ SET(repositories
repositories/base/base_guilds_repository.h
repositories/base/base_guild_ranks_repository.h
repositories/base/base_guild_relations_repository.h
repositories/base/base_hackers_repository.h
repositories/base/base_horses_repository.h
repositories/base/base_instance_list_repository.h
repositories/base/base_instance_list_player_repository.h
@ -264,6 +264,8 @@ SET(repositories
repositories/base/base_pets_equipmentset_repository.h
repositories/base/base_pets_equipmentset_entries_repository.h
repositories/base/base_player_titlesets_repository.h
repositories/base/base_player_event_log_settings_repository.h
repositories/base/base_player_event_logs_repository.h
repositories/base/base_quest_globals_repository.h
repositories/base/base_raid_details_repository.h
repositories/base/base_raid_members_repository.h
@ -376,7 +378,6 @@ SET(repositories
repositories/dynamic_zones_repository.h
repositories/dynamic_zone_members_repository.h
repositories/dynamic_zone_templates_repository.h
repositories/eventlog_repository.h
repositories/expeditions_repository.h
repositories/expedition_lockouts_repository.h
repositories/faction_association_repository.h
@ -396,7 +397,6 @@ SET(repositories
repositories/guilds_repository.h
repositories/guild_ranks_repository.h
repositories/guild_relations_repository.h
repositories/hackers_repository.h
repositories/horses_repository.h
repositories/instance_list_repository.h
repositories/instance_list_player_repository.h
@ -442,6 +442,8 @@ SET(repositories
repositories/pets_equipmentset_repository.h
repositories/pets_equipmentset_entries_repository.h
repositories/player_titlesets_repository.h
repositories/player_event_log_settings_repository.h
repositories/player_event_logs_repository.h
repositories/quest_globals_repository.h
repositories/raid_details_repository.h
repositories/raid_members_repository.h
@ -507,7 +509,7 @@ SET(common_headers
dbcore.h
deity.h
discord/discord.h
discord_manager.h
discord/discord_manager.h
dynamic_zone_base.h
emu_constants.h
emu_limits.h
@ -530,6 +532,9 @@ SET(common_headers
eq_stream_locator.h
eq_stream_proxy.h
eqtime.h
events/player_event_logs.h
events/player_event_discord_formatter.h
events/player_events.h
errmsg.h
event_sub.h
expedition_lockout_timer.h
@ -608,6 +613,7 @@ SET(common_headers
event/event_loop.h
event/task.h
event/timer.h
json/json_archive_single_line.h
json/json.h
json/json-forwards.h
net/console_server.h
@ -661,8 +667,7 @@ SET(common_headers
StackWalker/StackWalker.h
util/memory_stream.h
util/directory.h
util/uuid.h
)
util/uuid.h)
SOURCE_GROUP(Event FILES
event/event_loop.h

View File

@ -1281,44 +1281,6 @@ bool Database::MoveCharacterToZone(const char *charname, uint32 zone_id)
return results.RowsAffected() != 0;
}
bool Database::SetHackerFlag(const char* accountname, const char* charactername, const char* hacked) {
std::string query = StringFormat("INSERT INTO `hackers` (account, name, hacked) values('%s','%s','%s')", accountname, charactername, hacked);
auto results = QueryDatabase(query);
if (!results.Success()) {
return false;
}
return results.RowsAffected() != 0;
}
bool Database::SetMQDetectionFlag(const char* accountname, const char* charactername, const char* hacked, const char* zone) {
//Utilize the "hacker" table, but also give zone information.
std::string query = StringFormat("INSERT INTO hackers(account,name,hacked,zone) values('%s','%s','%s','%s')", accountname, charactername, hacked, zone);
auto results = QueryDatabase(query);
if (!results.Success())
{
return false;
}
return results.RowsAffected() != 0;
}
bool Database::SetMQDetectionFlag(const char* accountname, const char* charactername, const std::string &hacked, const char* zone) {
//Utilize the "hacker" table, but also give zone information.
auto query = fmt::format("INSERT INTO hackers(account, name, hacked, zone) values('{}', '{}', '{}', '{}')",
accountname, charactername, hacked, zone);
auto results = QueryDatabase(query);
if (!results.Success())
{
return false;
}
return results.RowsAffected() != 0;
}
uint8 Database::GetRaceSkill(uint8 skillid, uint8 in_race)
{
uint16 race_cap = 0;

View File

@ -108,9 +108,6 @@ public:
bool MoveCharacterToZone(uint32 character_id, uint32 zone_id);
bool ReserveName(uint32 account_id, char *name);
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct *pp);
bool SetHackerFlag(const char *accountname, const char *charactername, const char *hacked);
bool SetMQDetectionFlag(const char *accountname, const char *charactername, const char *hacked, const char *zone);
bool SetMQDetectionFlag(const char *accountname, const char *charactername, const std::string &hacked, const char *zone);
bool UpdateName(const char *oldname, const char *newname);
bool CopyCharacter(
const std::string& source_character_name,

View File

@ -321,13 +321,11 @@ namespace DatabaseSchema {
"discord_webhooks",
"dynamic_zone_members",
"dynamic_zones",
"eventlog",
"expedition_lockouts",
"expeditions",
"gm_ips",
"group_id",
"group_leaders",
"hackers",
"instance_list",
"ip_exemptions",
"item_tick",
@ -343,6 +341,8 @@ namespace DatabaseSchema {
"respawn_times",
"saylink",
"server_scheduled_events",
"player_event_log_settings",
"player_event_logs"
"shared_task_activity_state",
"shared_task_dynamic_zones",
"shared_task_members",

View File

@ -1,22 +1,17 @@
#include <cereal/archives/json.hpp>
#include <cereal/archives/binary.hpp>
#include "discord.h"
#include "../http/httplib.h"
#include "../json/json.h"
#include "../strings.h"
#include "../eqemu_logsys.h"
#include "../events/player_event_logs.h"
constexpr int MAX_RETRIES = 10;
void Discord::SendWebhookMessage(const std::string &message, const std::string &webhook_url)
{
// validate
if (webhook_url.empty()) {
LogDiscord("[webhook_url] is empty");
return;
}
// validate
if (webhook_url.find("http://") == std::string::npos && webhook_url.find("https://") == std::string::npos) {
LogDiscord("[webhook_url] [{}] does not contain a valid http/s prefix.", webhook_url);
if (!ValidateWebhookUrl(webhook_url)) {
return;
}
@ -28,7 +23,7 @@ void Discord::SendWebhookMessage(const std::string &message, const std::string &
std::string endpoint = Strings::Replace(webhook_url, base_url, "");
// client
httplib::Client cli(base_url.c_str());
httplib::Client cli(base_url);
cli.set_connection_timeout(0, 15000000); // 15 sec
cli.set_read_timeout(15, 0); // 15 seconds
cli.set_write_timeout(15, 0); // 15 seconds
@ -46,9 +41,9 @@ void Discord::SendWebhookMessage(const std::string &message, const std::string &
int retries = 0;
int retry_timer = 1000;
while (retry) {
if (auto res = cli.Post(endpoint.c_str(), payload.str(), "application/json")) {
if (auto res = cli.Post(endpoint, payload.str(), "application/json")) {
if (res->status != 200 && res->status != 204) {
LogError("Code [{}] Error [{}]", res->status, res->body);
LogError("[Discord Client] Code [{}] Error [{}]", res->status, res->body);
}
if (res->status == 429) {
if (!res->body.empty()) {
@ -81,6 +76,74 @@ void Discord::SendWebhookMessage(const std::string &message, const std::string &
}
}
void Discord::SendPlayerEventMessage(
const PlayerEvent::PlayerEventContainer &e,
const std::string &webhook_url
)
{
if (!ValidateWebhookUrl(webhook_url)) {
return;
}
auto s = Strings::Split(webhook_url, '/');
// url
std::string base_url = fmt::format("{}//{}", s[0], s[2]);
std::string endpoint = Strings::Replace(webhook_url, base_url, "");
// client
httplib::Client cli(base_url);
cli.set_connection_timeout(0, 15000000); // 15 sec
cli.set_read_timeout(15, 0); // 15 seconds
cli.set_write_timeout(15, 0); // 15 seconds
httplib::Headers headers = {
{"Content-Type", "application/json"}
};
std::string payload = PlayerEventLogs::GetDiscordPayloadFromEvent(e);
if (payload.empty()) {
return;
}
bool retry = true;
int retries = 0;
int retry_timer = 1000;
while (retry) {
if (auto res = cli.Post(endpoint, payload, "application/json")) {
if (res->status != 200 && res->status != 204) {
LogError("Code [{}] Error [{}]", res->status, res->body);
}
if (res->status == 429) {
if (!res->body.empty()) {
std::stringstream ss(res->body);
Json::Value response;
try {
ss >> response;
}
catch (std::exception const &ex) {
LogDiscord("JSON serialization failure [{}] via [{}]", ex.what(), res->body);
}
retry_timer = std::stoi(response["retry_after"].asString()) + 500;
}
LogDiscord("Rate limited... retrying message in [{}ms]", retry_timer);
std::this_thread::sleep_for(std::chrono::milliseconds(retry_timer + 500));
}
if (res->status == 204) {
retry = false;
}
if (retries > MAX_RETRIES) {
LogDiscord("Retries exceeded for player event message");
retry = false;
}
retries++;
}
}
}
std::string Discord::FormatDiscordMessage(uint16 category_id, const std::string &message)
{
if (category_id == Logs::LogCategory::MySQLQuery) {
@ -89,3 +152,20 @@ std::string Discord::FormatDiscordMessage(uint16 category_id, const std::string
return message + "\n";
}
bool Discord::ValidateWebhookUrl(const std::string &webhook_url)
{
// validate
if (webhook_url.empty()) {
LogDiscord("[webhook_url] is empty");
return false;
}
// validate
if (!Strings::Contains(webhook_url, "http://") && !Strings::Contains(webhook_url, "https://")) {
LogDiscord("[webhook_url] [{}] does not contain a valid http/s prefix.", webhook_url);
return false;
}
return true;
}

View File

@ -4,11 +4,16 @@
#include <string>
#include "../types.h"
#include "../http/httplib.h"
#include "../repositories/player_event_logs_repository.h"
#include "../events/player_events.h"
class Discord {
public:
static void SendWebhookMessage(const std::string& message, const std::string& webhook_url);
static std::string FormatDiscordMessage(uint16 category_id, const std::string& message);
static void SendPlayerEventMessage(const PlayerEvent::PlayerEventContainer& e, const std::string &webhook_url);
static bool ValidateWebhookUrl(const std::string &webhook_url);
};

View File

@ -1,7 +1,6 @@
#include "discord_manager.h"
#include "../common/discord/discord.h"
#include "../common/eqemu_logsys.h"
#include "../common/strings.h"
#include "../../common/discord/discord.h"
#include "../events/player_event_logs.h"
void DiscordManager::QueueWebhookMessage(uint32 webhook_id, const std::string &message)
{
@ -55,7 +54,6 @@ void DiscordManager::ProcessMessageQueue()
message = "";
}
}
// final flush
if (!message.empty()) {
Discord::SendWebhookMessage(
@ -67,3 +65,11 @@ void DiscordManager::ProcessMessageQueue()
webhook_message_queue.clear();
webhook_queue_lock.unlock();
}
void DiscordManager::QueuePlayerEventMessage(const PlayerEvent::PlayerEventContainer& e)
{
auto w = player_event_logs.GetDiscordWebhookUrlFromEventType(e.player_event_log.event_type_id);
if (!w.empty()) {
Discord::SendPlayerEventMessage(e, w);
}
}

View File

@ -4,12 +4,15 @@
#include <mutex>
#include <map>
#include <vector>
#include "../common/types.h"
#include "../../common/types.h"
#include "../repositories/player_event_logs_repository.h"
#include "../events/player_events.h"
class DiscordManager {
public:
void QueueWebhookMessage(uint32 webhook_id, const std::string& message);
void ProcessMessageQueue();
void QueuePlayerEventMessage(const PlayerEvent::PlayerEventContainer& e);
private:
std::mutex webhook_queue_lock{};
std::map<uint32, std::vector<std::string>> webhook_message_queue{};

View File

@ -1017,14 +1017,13 @@ enum Anonymity : uint8
Roleplaying
};
enum ZoningMessage : int8
{
ZoneNoMessage = 0,
ZoneSuccess = 1,
ZoneNotReady = -1,
ZoneValidPC = -2,
ZoneStoryZone = -3,
ZoneNoExpansion = -6,
enum ZoningMessage : int8 {
ZoneNoMessage = 0,
ZoneSuccess = 1,
ZoneNotReady = -1,
ZoneValidPC = -2,
ZoneStoryZone = -3,
ZoneNoExpansion = -6,
ZoneNoExperience = -7
};
@ -1040,4 +1039,10 @@ enum class RecipeCountType : uint8
#define ALT_CURRENCY_ID_RADIANT 4
#define ALT_CURRENCY_ID_EBON 5
enum ResurrectionActions
{
Decline,
Accept
};
#endif /*COMMON_EQ_CONSTANTS_H*/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,214 @@
#ifndef EQEMU_PLAYER_EVENT_DISCORD_FORMATTER_H
#define EQEMU_PLAYER_EVENT_DISCORD_FORMATTER_H
#include <string>
#include "player_events.h"
#include "../repositories/base/base_player_event_logs_repository.h"
#include <cereal/archives/json.hpp>
#include <cereal/types/vector.hpp>
struct DiscordField {
std::string name;
std::string value;
bool is_inline;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(name),
CEREAL_NVP(value),
cereal::make_nvp("inline", is_inline)
);
}
};
struct DiscordAuthor {
std::string name;
std::string icon_url;
std::string url;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(name),
CEREAL_NVP(icon_url),
CEREAL_NVP(url)
);
}
};
struct DiscordEmbed {
std::vector<DiscordField> fields;
std::string title;
std::string description;
std::string timestamp;
DiscordAuthor author;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(fields),
CEREAL_NVP(title),
CEREAL_NVP(description),
CEREAL_NVP(timestamp),
CEREAL_NVP(author)
);
}
};
struct DiscordWebhook {
std::vector<DiscordEmbed> embeds;
std::string content;
std::string avatar_url;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(embeds),
CEREAL_NVP(avatar_url),
CEREAL_NVP(content)
);
}
};
class PlayerEventDiscordFormatter {
public:
static std::string GetCurrentTimestamp();
static std::string FormatEventSay(const PlayerEvent::PlayerEventContainer &c, const PlayerEvent::SayEvent &e);
static std::string
FormatGMCommand(const PlayerEvent::PlayerEventContainer &c, const PlayerEvent::GMCommandEvent &e);
static void BuildDiscordField(
std::vector<DiscordField> *f,
const std::string &name,
const std::string &value,
bool is_inline = true
);
static void BuildBaseEmbed(
std::vector<DiscordEmbed> *e,
const std::vector<DiscordField> &f,
PlayerEvent::PlayerEventContainer c
);
static std::string FormatWithNodata(const PlayerEvent::PlayerEventContainer &c);
static std::string FormatAAGainedEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::AAGainedEvent &e
);
static std::string FormatAAPurchasedEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::AAPurchasedEvent &e
);
static std::string FormatDeathEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::DeathEvent &e
);
static std::string FormatFishSuccessEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::FishSuccessEvent &e
);
static std::string FormatForageSuccessEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::ForageSuccessEvent &e
);
static std::string FormatDestroyItemEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::DestroyItemEvent &e
);
static std::string FormatDiscoverItemEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::DiscoverItemEvent &e
);
static std::string FormatDroppedItemEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::DroppedItemEvent &e
);
static std::string FormatLevelGainedEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::LevelGainedEvent &e
);
static std::string FormatLevelLostEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::LevelLostEvent &e
);
static std::string FormatLootItemEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::LootItemEvent &e
);
static std::string FormatGroundSpawnPickupEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::GroundSpawnPickupEvent &e
);
static std::string FormatMerchantPurchaseEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::MerchantPurchaseEvent &e
);
static std::string FormatMerchantSellEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::MerchantSellEvent &e
);
static std::string FormatNPCHandinEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::HandinEvent &e
);
static std::string FormatSkillUpEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::SkillUpEvent &e
);
static std::string FormatTaskAcceptEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::TaskAcceptEvent &e
);
static std::string FormatTaskCompleteEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::TaskCompleteEvent &e
);
static std::string FormatTaskUpdateEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::TaskUpdateEvent &e
);
static std::string FormatTradeEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::TradeEvent &e
);
static std::string FormatTraderPurchaseEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::TraderPurchaseEvent &e
);
static std::string FormatTraderSellEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::TraderSellEvent &e
);
static std::string FormatResurrectAcceptEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::ResurrectAcceptEvent &e
);
static std::string FormatSplitMoneyEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::SplitMoneyEvent &e
);
static std::string FormatCombineEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::CombineEvent &e
);
static std::string FormatZoningEvent(
const PlayerEvent::PlayerEventContainer &c,
const PlayerEvent::ZoningEvent &e
);
static DiscordWebhook BuildDiscordWebhook(
const PlayerEvent::PlayerEventContainer &p,
std::vector<DiscordEmbed> &embeds
);
};
#endif //EQEMU_PLAYER_EVENT_DISCORD_FORMATTER_H

View File

@ -0,0 +1,703 @@
#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) {
m_settings[e.id] = e;
db.emplace_back(e.id);
}
// 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;
PlayerEventLogSettingsRepository::InsertOne(*m_database, c);
}
}
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()
{
if (m_record_batch_queue.empty()) {
return;
}
BenchTimer benchmark;
// flush many
PlayerEventLogsRepository::InsertMany(*m_database, m_record_batch_queue);
LogInfo(
"Processing batch player event log queue of [{}] took [{}]",
m_record_batch_queue.size(),
benchmark.elapsed()
);
// empty
m_batch_queue_lock.lock();
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: {
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_FAILURE: {
payload = PlayerEventDiscordFormatter::FormatWithNodata(e);
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::WENT_ONLINE:
case PlayerEvent::WENT_OFFLINE: {
payload = PlayerEventDiscordFormatter::FormatWithNodata(e);
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 UCS depending on rule Logging:PlayerEventsQSProcess
void PlayerEventLogs::Process()
{
if (m_process_batch_events_timer.Check()) {
ProcessBatchQueue();
}
if (m_process_retention_truncation_timer.Check()) {
ProcessRetentionTruncation();
}
}
void PlayerEventLogs::ProcessRetentionTruncation()
{
LogInfo("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 = 1;
m_settings[PlayerEvent::KILLED_NAMED_NPC].event_enabled = 1;
m_settings[PlayerEvent::KILLED_RAID_NPC].event_enabled = 1;
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
}
}

View File

@ -0,0 +1,85 @@
#ifndef EQEMU_PLAYER_EVENT_LOGS_H
#define EQEMU_PLAYER_EVENT_LOGS_H
#include "../repositories/player_event_log_settings_repository.h"
#include "player_events.h"
#include "../servertalk.h"
#include "../repositories/player_event_logs_repository.h"
#include "../timer.h"
#include "../json/json_archive_single_line.h"
#include <cereal/archives/json.hpp>
#include <mutex>
class PlayerEventLogs {
public:
void Init();
void ReloadSettings();
PlayerEventLogs *SetDatabase(Database *db);
bool ValidateDatabaseConnection();
bool IsEventEnabled(PlayerEvent::EventType event);
void Process();
// batch queue
void AddToQueue(const PlayerEventLogsRepository::PlayerEventLogs &logs);
// main event record generic function
// can ingest any struct event types
template<typename T>
std::unique_ptr<ServerPacket> RecordEvent(
PlayerEvent::EventType t,
const PlayerEvent::PlayerEvent &p,
T e
)
{
auto n = PlayerEventLogsRepository::NewEntity();
FillPlayerEvent(p, n);
n.event_type_id = t;
std::stringstream ss;
{
cereal::JSONOutputArchiveSingleLine ar(ss);
e.serialize(ar);
}
n.event_type_name = PlayerEvent::EventName[t];
n.event_data = Strings::Contains(ss.str(), "noop") ? "{}" : ss.str();
n.created_at = std::time(nullptr);
auto c = PlayerEvent::PlayerEventContainer{
.player_event = p,
.player_event_log = n
};
return BuildPlayerEventPacket(c);
}
[[nodiscard]] const PlayerEventLogSettingsRepository::PlayerEventLogSettings *GetSettings() const;
bool IsEventDiscordEnabled(int32_t event_type_id);
std::string GetDiscordWebhookUrlFromEventType(int32_t event_type_id);
static std::string GetDiscordPayloadFromEvent(const PlayerEvent::PlayerEventContainer &e);
private:
Database *m_database; // reference to database
PlayerEventLogSettingsRepository::PlayerEventLogSettings m_settings[PlayerEvent::EventType::MAX]{};
// batch queue is used to record events in batch
std::vector<PlayerEventLogsRepository::PlayerEventLogs> m_record_batch_queue{};
static void FillPlayerEvent(const PlayerEvent::PlayerEvent &p, PlayerEventLogsRepository::PlayerEventLogs &n);
static std::unique_ptr<ServerPacket>
BuildPlayerEventPacket(const PlayerEvent::PlayerEventContainer &e);
// timers
Timer m_process_batch_events_timer; // events processing timer
Timer m_process_retention_truncation_timer; // timer for truncating events based on retention settings
// processing
std::mutex m_batch_queue_lock{};
void ProcessBatchQueue();
void ProcessRetentionTruncation();
void SetSettingsDefaults();
};
extern PlayerEventLogs player_event_logs;
#endif //EQEMU_PLAYER_EVENT_LOGS_H

View File

@ -0,0 +1,935 @@
#ifndef EQEMU_PLAYER_EVENTS_H
#define EQEMU_PLAYER_EVENTS_H
#include <string>
#include <cereal/cereal.hpp>
#include "../types.h"
#include "../repositories/player_event_logs_repository.h"
namespace PlayerEvent {
enum EventType {
GM_COMMAND = 1,
ZONING,
AA_GAIN,
AA_PURCHASE,
FORAGE_SUCCESS,
FORAGE_FAILURE,
FISH_SUCCESS,
FISH_FAILURE,
ITEM_DESTROY,
WENT_ONLINE,
WENT_OFFLINE,
LEVEL_GAIN,
LEVEL_LOSS,
LOOT_ITEM,
MERCHANT_PURCHASE,
MERCHANT_SELL,
GROUP_JOIN, // unimplemented
GROUP_LEAVE, // unimplemented
RAID_JOIN, // unimplemented
RAID_LEAVE, // unimplemented
GROUNDSPAWN_PICKUP,
NPC_HANDIN,
SKILL_UP,
TASK_ACCEPT,
TASK_UPDATE,
TASK_COMPLETE,
TRADE,
GIVE_ITEM, // unimplemented
SAY,
REZ_ACCEPTED,
DEATH,
COMBINE_FAILURE,
COMBINE_SUCCESS,
DROPPED_ITEM,
SPLIT_MONEY,
DZ_JOIN, // unimplemented
DZ_LEAVE, // unimplemented
TRADER_PURCHASE,
TRADER_SELL,
BANDOLIER_CREATE, // unimplemented
BANDOLIER_SWAP, // unimplemented
DISCOVER_ITEM,
POSSIBLE_HACK,
KILLED_NPC,
KILLED_NAMED_NPC,
KILLED_RAID_NPC,
MAX // dont remove
};
// Don't ever remove items, even if they are deprecated
// If event is deprecated just tag (Deprecated) in the name
// If event is unimplemented just tag (Unimplemented) in the name
// Events don't get saved to the database if unimplemented or deprecated
// Events tagged as deprecated will get automatically removed
static const char *EventName[PlayerEvent::MAX] = {
"None",
"GM Command",
"Zoning",
"AA Gain",
"AA Purchase",
"Forage Success",
"Forage Failure",
"Fish Success",
"Fish Failure",
"Item Destroy",
"Went Online",
"Went Offline",
"Level Gain",
"Level Loss",
"Loot Item",
"Merchant Purchase",
"Merchant Sell",
"Group Join (Unimplemented)",
"Group Leave (Unimplemented)",
"Raid Join (Unimplemented)",
"Raid Leave (Unimplemented)",
"Groundspawn Pickup",
"NPC Handin",
"Skill Up",
"Task Accept",
"Task Update",
"Task Complete",
"Trade",
"Given Item (Unimplemented)",
"Say",
"Rez Accepted",
"Death",
"Combine Failure",
"Combine Success",
"Dropped Item",
"Split Money",
"DZ Join (Unimplemented)",
"DZ Leave (Unimplemented)",
"Trader Purchase",
"Trader Sell",
"Bandolier Create (Unimplemented)",
"Bandolier Swap (Unimplemented)",
"Discover Item",
"Possible Hack",
"Killed NPC",
"Killed Named NPC",
"Killed Raid NPC"
};
// Generic struct used by all events
struct PlayerEvent {
int64 account_id;
std::string account_name;
int64 character_id;
std::string character_name;
int64 guild_id;
std::string guild_name;
int zone_id;
std::string zone_short_name;
std::string zone_long_name;
int instance_id;
float x;
float y;
float z;
float heading;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(account_id),
CEREAL_NVP(account_name),
CEREAL_NVP(character_id),
CEREAL_NVP(character_name),
CEREAL_NVP(guild_id),
CEREAL_NVP(guild_name),
CEREAL_NVP(zone_id),
CEREAL_NVP(zone_short_name),
CEREAL_NVP(zone_long_name),
CEREAL_NVP(instance_id),
CEREAL_NVP(x),
CEREAL_NVP(y),
CEREAL_NVP(z),
CEREAL_NVP(heading)
);
}
};
// contains metadata in use for things like log/discord formatters
// along with the actual event to be persisted
struct PlayerEventContainer {
PlayerEvent player_event;
PlayerEventLogsRepository::PlayerEventLogs player_event_log;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(player_event),
CEREAL_NVP(player_event_log)
);
}
};
// used in events with no extra data
struct EmptyEvent {
std::string noop; // noop, gets discard upstream
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(noop)
);
}
};
// used in Trade event
struct TradeItem {
int64 item_id;
std::string item_name;
int32 slot;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(slot)
);
}
};
// used in Trade event
class TradeItemEntry {
public:
uint16 slot;
uint32 item_id;
std::string item_name;
uint16 charges;
uint32 aug_1_item_id;
std::string aug_1_item_name;
uint32 aug_2_item_id;
std::string aug_2_item_name;
uint32 aug_3_item_id;
std::string aug_3_item_name;
uint32 aug_4_item_id;
std::string aug_4_item_name;
uint32 aug_5_item_id;
std::string aug_5_item_name;
uint32 aug_6_item_id;
std::string aug_6_item_name;
bool in_bag;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(slot),
CEREAL_NVP(item_id),
CEREAL_NVP(charges),
CEREAL_NVP(aug_1_item_id),
CEREAL_NVP(aug_2_item_id),
CEREAL_NVP(aug_3_item_id),
CEREAL_NVP(aug_4_item_id),
CEREAL_NVP(aug_5_item_id),
CEREAL_NVP(in_bag)
);
}
};
/**
* Events
*/
struct Money {
int32 platinum;
int32 gold;
int32 silver;
int32 copper;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(platinum),
CEREAL_NVP(gold),
CEREAL_NVP(silver),
CEREAL_NVP(copper)
);
}
};
struct TradeEvent {
uint32 character_1_id;
std::string character_1_name;
uint32 character_2_id;
std::string character_2_name;
Money character_1_give_money;
Money character_2_give_money;
std::vector<TradeItemEntry> character_1_give_items;
std::vector<TradeItemEntry> character_2_give_items;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(character_1_id),
CEREAL_NVP(character_1_name),
CEREAL_NVP(character_2_id),
CEREAL_NVP(character_2_name),
CEREAL_NVP(character_1_give_money),
CEREAL_NVP(character_2_give_money),
CEREAL_NVP(character_1_give_items),
CEREAL_NVP(character_2_give_items)
);
}
};
struct GMCommandEvent {
std::string message;
std::string target;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(message),
CEREAL_NVP(target)
);
}
};
struct ZoningEvent {
std::string from_zone_long_name;
std::string from_zone_short_name;
int32 from_zone_id;
int32 from_instance_id;
int32 from_instance_version;
std::string to_zone_long_name;
std::string to_zone_short_name;
int32 to_zone_id;
int32 to_instance_id;
int32 to_instance_version;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(from_zone_long_name),
CEREAL_NVP(from_zone_short_name),
CEREAL_NVP(from_zone_id),
CEREAL_NVP(from_instance_id),
CEREAL_NVP(from_instance_version),
CEREAL_NVP(to_zone_long_name),
CEREAL_NVP(to_zone_short_name),
CEREAL_NVP(to_zone_id),
CEREAL_NVP(to_instance_id),
CEREAL_NVP(to_instance_version)
);
}
};
struct AAGainedEvent {
uint32 aa_gained;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(CEREAL_NVP(aa_gained));
}
};
struct AAPurchasedEvent {
int32 aa_id;
int32 aa_cost;
int32 aa_previous_id;
int32 aa_next_id;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(aa_id),
CEREAL_NVP(aa_cost),
CEREAL_NVP(aa_previous_id),
CEREAL_NVP(aa_next_id)
);
}
};
struct ForageSuccessEvent {
uint32 item_id;
std::string item_name;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name)
);
}
};
struct FishSuccessEvent {
uint32 item_id;
std::string item_name;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name)
);
}
};
struct DestroyItemEvent {
uint32 item_id;
std::string item_name;
int16 charges;
std::string reason;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(reason),
CEREAL_NVP(charges)
);
}
};
struct LevelGainedEvent {
uint32 from_level;
uint8 to_level;
int levels_gained;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(from_level),
CEREAL_NVP(to_level),
CEREAL_NVP(levels_gained)
);
}
};
struct LevelLostEvent {
uint32 from_level;
uint8 to_level;
int levels_lost;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(from_level),
CEREAL_NVP(to_level),
CEREAL_NVP(levels_lost)
);
}
};
struct LootItemEvent {
uint32 item_id;
std::string item_name;
int16 charges;
uint32 npc_id;
std::string corpse_name;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(charges),
CEREAL_NVP(npc_id),
CEREAL_NVP(corpse_name)
);
}
};
struct MerchantPurchaseEvent {
uint32 npc_id;
std::string merchant_name;
uint32 merchant_type;
uint32 item_id;
std::string item_name;
int16 charges;
uint32 cost;
uint32 alternate_currency_id;
uint64 player_money_balance;
uint64 player_currency_balance;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(npc_id),
CEREAL_NVP(merchant_name),
CEREAL_NVP(merchant_type),
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(charges),
CEREAL_NVP(cost),
CEREAL_NVP(alternate_currency_id),
CEREAL_NVP(player_money_balance),
CEREAL_NVP(player_currency_balance)
);
}
};
struct MerchantSellEvent {
uint32 npc_id;
std::string merchant_name;
uint32 merchant_type;
uint32 item_id;
std::string item_name;
int16 charges;
uint32 cost;
uint32 alternate_currency_id;
uint64 player_money_balance;
uint64 player_currency_balance;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(npc_id),
CEREAL_NVP(merchant_name),
CEREAL_NVP(merchant_type),
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(charges),
CEREAL_NVP(cost),
CEREAL_NVP(alternate_currency_id),
CEREAL_NVP(player_money_balance),
CEREAL_NVP(player_currency_balance)
);
}
};
struct SkillUpEvent {
uint32 skill_id;
int value;
int16 max_skill;
std::string against_who;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(skill_id),
CEREAL_NVP(value),
CEREAL_NVP(max_skill),
CEREAL_NVP(against_who)
);
}
};
struct TaskAcceptEvent {
uint32 npc_id;
std::string npc_name;
uint32 task_id;
std::string task_name;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(npc_id),
CEREAL_NVP(npc_name),
CEREAL_NVP(task_id),
CEREAL_NVP(task_name)
);
}
};
struct TaskUpdateEvent {
uint32 task_id;
std::string task_name;
uint32 activity_id;
uint32 done_count;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(task_id),
CEREAL_NVP(task_name),
CEREAL_NVP(activity_id),
CEREAL_NVP(done_count)
);
}
};
struct TaskCompleteEvent {
uint32 task_id;
std::string task_name;
uint32 activity_id;
uint32 done_count;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(task_id),
CEREAL_NVP(task_name),
CEREAL_NVP(activity_id),
CEREAL_NVP(done_count)
);
}
};
struct GroundSpawnPickupEvent {
uint32 item_id;
std::string item_name;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name)
);
}
};
struct SayEvent {
std::string message;
std::string target;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(message),
CEREAL_NVP(target)
);
}
};
struct ResurrectAcceptEvent {
std::string resurrecter_name;
std::string spell_name;
uint32 spell_id;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(resurrecter_name),
CEREAL_NVP(spell_name),
CEREAL_NVP(spell_id)
);
}
};
struct CombineEvent {
uint32 recipe_id;
std::string recipe_name;
uint32 made_count;
uint32 tradeskill_id;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(recipe_id),
CEREAL_NVP(recipe_name),
CEREAL_NVP(made_count),
CEREAL_NVP(tradeskill_id)
);
}
};
struct DroppedItemEvent {
uint32 item_id;
std::string item_name;
int16 slot_id;
uint32 charges;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(slot_id),
CEREAL_NVP(charges)
);
}
};
struct DeathEvent {
uint32 killer_id;
std::string killer_name;
int64 damage;
uint32 spell_id;
std::string spell_name;
int skill_id;
std::string skill_name;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(killer_id),
CEREAL_NVP(killer_name),
CEREAL_NVP(damage),
CEREAL_NVP(spell_id),
CEREAL_NVP(spell_name),
CEREAL_NVP(skill_id),
CEREAL_NVP(skill_name)
);
}
};
struct SplitMoneyEvent {
uint32 copper;
uint32 silver;
uint32 gold;
uint32 platinum;
uint64 player_money_balance;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(copper),
CEREAL_NVP(silver),
CEREAL_NVP(gold),
CEREAL_NVP(platinum),
CEREAL_NVP(player_money_balance)
);
}
};
struct TraderPurchaseEvent {
uint32 item_id;
std::string item_name;
uint32 trader_id;
std::string trader_name;
uint32 price;
uint32 charges;
uint32 total_cost;
uint64 player_money_balance;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(trader_id),
CEREAL_NVP(trader_name),
CEREAL_NVP(price),
CEREAL_NVP(charges),
CEREAL_NVP(total_cost),
CEREAL_NVP(player_money_balance)
);
}
};
struct TraderSellEvent {
uint32 item_id;
std::string item_name;
uint32 buyer_id;
std::string buyer_name;
uint32 price;
uint32 charges;
uint32 total_cost;
uint64 player_money_balance;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(buyer_id),
CEREAL_NVP(buyer_name),
CEREAL_NVP(price),
CEREAL_NVP(charges),
CEREAL_NVP(total_cost),
CEREAL_NVP(player_money_balance)
);
}
};
struct DiscoverItemEvent {
uint32 item_id;
std::string item_name;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name)
);
}
};
class HandinEntry {
public:
uint32 item_id;
std::string item_name;
uint16 charges;
bool attuned;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_name),
CEREAL_NVP(charges),
CEREAL_NVP(attuned)
);
}
};
class HandinMoney {
public:
uint32 copper;
uint32 silver;
uint32 gold;
uint32 platinum;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(copper),
CEREAL_NVP(silver),
CEREAL_NVP(gold),
CEREAL_NVP(platinum)
);
}
};
struct HandinEvent {
uint32 npc_id;
std::string npc_name;
std::vector<HandinEntry> handin_items;
HandinMoney handin_money;
std::vector<HandinEntry> return_items;
HandinMoney return_money;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(npc_id),
CEREAL_NVP(npc_name),
CEREAL_NVP(handin_items),
CEREAL_NVP(handin_money),
CEREAL_NVP(return_items),
CEREAL_NVP(return_money)
);
}
};
struct PossibleHackEvent {
std::string message;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(message)
);
}
};
struct KilledNPCEvent {
uint32 npc_id;
std::string npc_name;
uint32 combat_time_seconds;
uint64 total_damage_per_second_taken;
uint64 total_heal_per_second_taken;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(npc_id),
CEREAL_NVP(npc_name),
CEREAL_NVP(combat_time_seconds),
CEREAL_NVP(total_damage_per_second_taken),
CEREAL_NVP(total_heal_per_second_taken)
);
}
};
}
#endif //EQEMU_PLAYER_EVENTS_H
#define RecordPlayerEventLog(event_type, event_data) do {\
if (player_event_logs.IsEventEnabled(event_type)) {\
worldserver.SendPacket(\
player_event_logs.RecordEvent(\
event_type,\
GetPlayerEvent(),\
event_data\
).get()\
);\
}\
} while (0)
#define RecordPlayerEventLogWithClient(c, event_type, event_data) do {\
if (player_event_logs.IsEventEnabled(event_type)) {\
worldserver.SendPacket(\
player_event_logs.RecordEvent(\
event_type,\
(c)->GetPlayerEvent(),\
event_data\
).get()\
);\
}\
} while (0)

File diff suppressed because it is too large Load Diff

View File

@ -22,26 +22,31 @@
EQEmuExePlatform exe_platform = ExePlatformNone;
void RegisterExecutablePlatform(EQEmuExePlatform p) {
void RegisterExecutablePlatform(EQEmuExePlatform p)
{
exe_platform = p;
}
const EQEmuExePlatform& GetExecutablePlatform() {
const EQEmuExePlatform &GetExecutablePlatform()
{
return exe_platform;
}
/**
* @return
*/
int GetExecutablePlatformInt(){
int GetExecutablePlatformInt()
{
return exe_platform;
}
/**
* Returns platform name by string
*
* @return
*/
bool IsWorld()
{
return exe_platform == EQEmuExePlatform::ExePlatformWorld;
}
bool IsQueryServ()
{
return exe_platform == EQEmuExePlatform::ExePlatformQueryServ;
}
std::string GetPlatformName()
{
switch (GetExecutablePlatformInt()) {

View File

@ -44,5 +44,7 @@ void RegisterExecutablePlatform(EQEmuExePlatform p);
const EQEmuExePlatform& GetExecutablePlatform();
int GetExecutablePlatformInt();
std::string GetPlatformName();
bool IsWorld();
bool IsQueryServ();
#endif

View File

@ -1,412 +0,0 @@
/**
* DO NOT MODIFY THIS FILE
*
* This repository was automatically generated and is NOT to be modified directly.
* Any repository modifications are meant to be made to the repository extending the base.
* Any modifications to base repositories are to be made by the generator only
*
* @generator ./utils/scripts/generators/repository-generator.pl
* @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories
*/
#ifndef EQEMU_BASE_EVENTLOG_REPOSITORY_H
#define EQEMU_BASE_EVENTLOG_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
class BaseEventlogRepository {
public:
struct Eventlog {
uint32_t id;
std::string accountname;
uint32_t accountid;
int32_t status;
std::string charname;
std::string target;
std::string time;
std::string descriptiontype;
std::string description;
int32_t event_nid;
};
static std::string PrimaryKey()
{
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"accountname",
"accountid",
"status",
"charname",
"target",
"time",
"descriptiontype",
"description",
"event_nid",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"id",
"accountname",
"accountid",
"status",
"charname",
"target",
"time",
"descriptiontype",
"description",
"event_nid",
};
}
static std::string ColumnsRaw()
{
return std::string(Strings::Implode(", ", Columns()));
}
static std::string SelectColumnsRaw()
{
return std::string(Strings::Implode(", ", SelectColumns()));
}
static std::string TableName()
{
return std::string("eventlog");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
SelectColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static Eventlog NewEntity()
{
Eventlog e{};
e.id = 0;
e.accountname = "";
e.accountid = 0;
e.status = 0;
e.charname = "";
e.target = "None";
e.time = std::time(nullptr);
e.descriptiontype = "";
e.description = "";
e.event_nid = 0;
return e;
}
static Eventlog GetEventlog(
const std::vector<Eventlog> &eventlogs,
int eventlog_id
)
{
for (auto &eventlog : eventlogs) {
if (eventlog.id == eventlog_id) {
return eventlog;
}
}
return NewEntity();
}
static Eventlog FindOne(
Database& db,
int eventlog_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE id = {} LIMIT 1",
BaseSelect(),
eventlog_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
Eventlog e{};
e.id = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.accountname = row[1] ? row[1] : "";
e.accountid = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
e.status = static_cast<int32_t>(atoi(row[3]));
e.charname = row[4] ? row[4] : "";
e.target = row[5] ? row[5] : "";
e.time = row[6] ? row[6] : "";
e.descriptiontype = row[7] ? row[7] : "";
e.description = row[8] ? row[8] : "";
e.event_nid = static_cast<int32_t>(atoi(row[9]));
return e;
}
return NewEntity();
}
static int DeleteOne(
Database& db,
int eventlog_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
eventlog_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
Database& db,
const Eventlog &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[1] + " = '" + Strings::Escape(e.accountname) + "'");
v.push_back(columns[2] + " = " + std::to_string(e.accountid));
v.push_back(columns[3] + " = " + std::to_string(e.status));
v.push_back(columns[4] + " = '" + Strings::Escape(e.charname) + "'");
v.push_back(columns[5] + " = '" + Strings::Escape(e.target) + "'");
v.push_back(columns[6] + " = '" + Strings::Escape(e.time) + "'");
v.push_back(columns[7] + " = '" + Strings::Escape(e.descriptiontype) + "'");
v.push_back(columns[8] + " = '" + Strings::Escape(e.description) + "'");
v.push_back(columns[9] + " = " + std::to_string(e.event_nid));
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static Eventlog InsertOne(
Database& db,
Eventlog e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back("'" + Strings::Escape(e.accountname) + "'");
v.push_back(std::to_string(e.accountid));
v.push_back(std::to_string(e.status));
v.push_back("'" + Strings::Escape(e.charname) + "'");
v.push_back("'" + Strings::Escape(e.target) + "'");
v.push_back("'" + Strings::Escape(e.time) + "'");
v.push_back("'" + Strings::Escape(e.descriptiontype) + "'");
v.push_back("'" + Strings::Escape(e.description) + "'");
v.push_back(std::to_string(e.event_nid));
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseInsert(),
Strings::Implode(",", v)
)
);
if (results.Success()) {
e.id = results.LastInsertedID();
return e;
}
e = NewEntity();
return e;
}
static int InsertMany(
Database& db,
const std::vector<Eventlog> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back("'" + Strings::Escape(e.accountname) + "'");
v.push_back(std::to_string(e.accountid));
v.push_back(std::to_string(e.status));
v.push_back("'" + Strings::Escape(e.charname) + "'");
v.push_back("'" + Strings::Escape(e.target) + "'");
v.push_back("'" + Strings::Escape(e.time) + "'");
v.push_back("'" + Strings::Escape(e.descriptiontype) + "'");
v.push_back("'" + Strings::Escape(e.description) + "'");
v.push_back(std::to_string(e.event_nid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<Eventlog> All(Database& db)
{
std::vector<Eventlog> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
Eventlog e{};
e.id = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.accountname = row[1] ? row[1] : "";
e.accountid = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
e.status = static_cast<int32_t>(atoi(row[3]));
e.charname = row[4] ? row[4] : "";
e.target = row[5] ? row[5] : "";
e.time = row[6] ? row[6] : "";
e.descriptiontype = row[7] ? row[7] : "";
e.description = row[8] ? row[8] : "";
e.event_nid = static_cast<int32_t>(atoi(row[9]));
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<Eventlog> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<Eventlog> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {}",
BaseSelect(),
where_filter
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
Eventlog e{};
e.id = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.accountname = row[1] ? row[1] : "";
e.accountid = static_cast<uint32_t>(strtoul(row[2], nullptr, 10));
e.status = static_cast<int32_t>(atoi(row[3]));
e.charname = row[4] ? row[4] : "";
e.target = row[5] ? row[5] : "";
e.time = row[6] ? row[6] : "";
e.descriptiontype = row[7] ? row[7] : "";
e.description = row[8] ? row[8] : "";
e.event_nid = static_cast<int32_t>(atoi(row[9]));
all_entries.push_back(e);
}
return all_entries;
}
static int DeleteWhere(Database& db, const std::string &where_filter)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {}",
TableName(),
where_filter
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int Truncate(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"TRUNCATE TABLE {}",
TableName()
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int64 GetMaxId(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COALESCE(MAX({}), 0) FROM {}",
PrimaryKey(),
TableName()
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static int64 Count(Database& db, const std::string &where_filter = "")
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COUNT(*) FROM {} {}",
TableName(),
(where_filter.empty() ? "" : "WHERE " + where_filter)
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
};
#endif //EQEMU_BASE_EVENTLOG_REPOSITORY_H

View File

@ -9,22 +9,21 @@
* @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories
*/
#ifndef EQEMU_BASE_HACKERS_REPOSITORY_H
#define EQEMU_BASE_HACKERS_REPOSITORY_H
#ifndef EQEMU_BASE_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H
#define EQEMU_BASE_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
class BaseHackersRepository {
class BasePlayerEventLogSettingsRepository {
public:
struct Hackers {
int32_t id;
std::string account;
std::string name;
std::string hacked;
std::string zone;
std::string date;
struct PlayerEventLogSettings {
int64_t id;
std::string event_name;
int8_t event_enabled;
int32_t retention_days;
int32_t discord_webhook_id;
};
static std::string PrimaryKey()
@ -36,11 +35,10 @@ public:
{
return {
"id",
"account",
"name",
"hacked",
"zone",
"date",
"event_name",
"event_enabled",
"retention_days",
"discord_webhook_id",
};
}
@ -48,11 +46,10 @@ public:
{
return {
"id",
"account",
"name",
"hacked",
"zone",
"date",
"event_name",
"event_enabled",
"retention_days",
"discord_webhook_id",
};
}
@ -68,7 +65,7 @@ public:
static std::string TableName()
{
return std::string("hackers");
return std::string("player_event_log_settings");
}
static std::string BaseSelect()
@ -89,57 +86,56 @@ public:
);
}
static Hackers NewEntity()
static PlayerEventLogSettings NewEntity()
{
Hackers e{};
PlayerEventLogSettings e{};
e.id = 0;
e.account = "";
e.name = "";
e.hacked = "";
e.zone = "";
e.date = std::time(nullptr);
e.id = 0;
e.event_name = "";
e.event_enabled = 0;
e.retention_days = 0;
e.discord_webhook_id = 0;
return e;
}
static Hackers GetHackers(
const std::vector<Hackers> &hackerss,
int hackers_id
static PlayerEventLogSettings GetPlayerEventLogSettings(
const std::vector<PlayerEventLogSettings> &player_event_log_settingss,
int player_event_log_settings_id
)
{
for (auto &hackers : hackerss) {
if (hackers.id == hackers_id) {
return hackers;
for (auto &player_event_log_settings : player_event_log_settingss) {
if (player_event_log_settings.id == player_event_log_settings_id) {
return player_event_log_settings;
}
}
return NewEntity();
}
static Hackers FindOne(
static PlayerEventLogSettings FindOne(
Database& db,
int hackers_id
int player_event_log_settings_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE id = {} LIMIT 1",
"{} WHERE {} = {} LIMIT 1",
BaseSelect(),
hackers_id
PrimaryKey(),
player_event_log_settings_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
Hackers e{};
PlayerEventLogSettings e{};
e.id = static_cast<int32_t>(atoi(row[0]));
e.account = row[1] ? row[1] : "";
e.name = row[2] ? row[2] : "";
e.hacked = row[3] ? row[3] : "";
e.zone = row[4] ? row[4] : "";
e.date = row[5] ? row[5] : "";
e.id = strtoll(row[0], nullptr, 10);
e.event_name = row[1] ? row[1] : "";
e.event_enabled = static_cast<int8_t>(atoi(row[2]));
e.retention_days = static_cast<int32_t>(atoi(row[3]));
e.discord_webhook_id = static_cast<int32_t>(atoi(row[4]));
return e;
}
@ -149,7 +145,7 @@ public:
static int DeleteOne(
Database& db,
int hackers_id
int player_event_log_settings_id
)
{
auto results = db.QueryDatabase(
@ -157,7 +153,7 @@ public:
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
hackers_id
player_event_log_settings_id
)
);
@ -166,18 +162,18 @@ public:
static int UpdateOne(
Database& db,
const Hackers &e
const PlayerEventLogSettings &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[1] + " = '" + Strings::Escape(e.account) + "'");
v.push_back(columns[2] + " = '" + Strings::Escape(e.name) + "'");
v.push_back(columns[3] + " = '" + Strings::Escape(e.hacked) + "'");
v.push_back(columns[4] + " = '" + Strings::Escape(e.zone) + "'");
v.push_back(columns[5] + " = '" + Strings::Escape(e.date) + "'");
v.push_back(columns[0] + " = " + std::to_string(e.id));
v.push_back(columns[1] + " = '" + Strings::Escape(e.event_name) + "'");
v.push_back(columns[2] + " = " + std::to_string(e.event_enabled));
v.push_back(columns[3] + " = " + std::to_string(e.retention_days));
v.push_back(columns[4] + " = " + std::to_string(e.discord_webhook_id));
auto results = db.QueryDatabase(
fmt::format(
@ -192,19 +188,18 @@ public:
return (results.Success() ? results.RowsAffected() : 0);
}
static Hackers InsertOne(
static PlayerEventLogSettings InsertOne(
Database& db,
Hackers e
PlayerEventLogSettings e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back("'" + Strings::Escape(e.account) + "'");
v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back("'" + Strings::Escape(e.hacked) + "'");
v.push_back("'" + Strings::Escape(e.zone) + "'");
v.push_back("'" + Strings::Escape(e.date) + "'");
v.push_back("'" + Strings::Escape(e.event_name) + "'");
v.push_back(std::to_string(e.event_enabled));
v.push_back(std::to_string(e.retention_days));
v.push_back(std::to_string(e.discord_webhook_id));
auto results = db.QueryDatabase(
fmt::format(
@ -226,7 +221,7 @@ public:
static int InsertMany(
Database& db,
const std::vector<Hackers> &entries
const std::vector<PlayerEventLogSettings> &entries
)
{
std::vector<std::string> insert_chunks;
@ -235,11 +230,10 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back("'" + Strings::Escape(e.account) + "'");
v.push_back("'" + Strings::Escape(e.name) + "'");
v.push_back("'" + Strings::Escape(e.hacked) + "'");
v.push_back("'" + Strings::Escape(e.zone) + "'");
v.push_back("'" + Strings::Escape(e.date) + "'");
v.push_back("'" + Strings::Escape(e.event_name) + "'");
v.push_back(std::to_string(e.event_enabled));
v.push_back(std::to_string(e.retention_days));
v.push_back(std::to_string(e.discord_webhook_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@ -257,9 +251,9 @@ public:
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<Hackers> All(Database& db)
static std::vector<PlayerEventLogSettings> All(Database& db)
{
std::vector<Hackers> all_entries;
std::vector<PlayerEventLogSettings> all_entries;
auto results = db.QueryDatabase(
fmt::format(
@ -271,14 +265,13 @@ public:
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
Hackers e{};
PlayerEventLogSettings e{};
e.id = static_cast<int32_t>(atoi(row[0]));
e.account = row[1] ? row[1] : "";
e.name = row[2] ? row[2] : "";
e.hacked = row[3] ? row[3] : "";
e.zone = row[4] ? row[4] : "";
e.date = row[5] ? row[5] : "";
e.id = strtoll(row[0], nullptr, 10);
e.event_name = row[1] ? row[1] : "";
e.event_enabled = static_cast<int8_t>(atoi(row[2]));
e.retention_days = static_cast<int32_t>(atoi(row[3]));
e.discord_webhook_id = static_cast<int32_t>(atoi(row[4]));
all_entries.push_back(e);
}
@ -286,9 +279,9 @@ public:
return all_entries;
}
static std::vector<Hackers> GetWhere(Database& db, const std::string &where_filter)
static std::vector<PlayerEventLogSettings> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<Hackers> all_entries;
std::vector<PlayerEventLogSettings> all_entries;
auto results = db.QueryDatabase(
fmt::format(
@ -301,14 +294,13 @@ public:
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
Hackers e{};
PlayerEventLogSettings e{};
e.id = static_cast<int32_t>(atoi(row[0]));
e.account = row[1] ? row[1] : "";
e.name = row[2] ? row[2] : "";
e.hacked = row[3] ? row[3] : "";
e.zone = row[4] ? row[4] : "";
e.date = row[5] ? row[5] : "";
e.id = strtoll(row[0], nullptr, 10);
e.event_name = row[1] ? row[1] : "";
e.event_enabled = static_cast<int8_t>(atoi(row[2]));
e.retention_days = static_cast<int32_t>(atoi(row[3]));
e.discord_webhook_id = static_cast<int32_t>(atoi(row[4]));
all_entries.push_back(e);
}
@ -369,4 +361,4 @@ public:
};
#endif //EQEMU_BASE_HACKERS_REPOSITORY_H
#endif //EQEMU_BASE_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H

View File

@ -0,0 +1,465 @@
/**
* DO NOT MODIFY THIS FILE
*
* This repository was automatically generated and is NOT to be modified directly.
* Any repository modifications are meant to be made to the repository extending the base.
* Any modifications to base repositories are to be made by the generator only
*
* @generator ./utils/scripts/generators/repository-generator.pl
* @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories
*/
#ifndef EQEMU_BASE_PLAYER_EVENT_LOGS_REPOSITORY_H
#define EQEMU_BASE_PLAYER_EVENT_LOGS_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
#include <cereal/cereal.hpp>
class BasePlayerEventLogsRepository {
public:
struct PlayerEventLogs {
int64_t id;
int64_t account_id;
int64_t character_id;
int32_t zone_id;
int32_t instance_id;
float x;
float y;
float z;
float heading;
int32_t event_type_id;
std::string event_type_name;
std::string event_data;
time_t created_at;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(id),
CEREAL_NVP(account_id),
CEREAL_NVP(character_id),
CEREAL_NVP(zone_id),
CEREAL_NVP(instance_id),
CEREAL_NVP(x),
CEREAL_NVP(y),
CEREAL_NVP(z),
CEREAL_NVP(heading),
CEREAL_NVP(event_type_id),
CEREAL_NVP(event_type_name),
CEREAL_NVP(event_data),
CEREAL_NVP(created_at)
);
}
};
static std::string PrimaryKey()
{
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"account_id",
"character_id",
"zone_id",
"instance_id",
"x",
"y",
"z",
"heading",
"event_type_id",
"event_type_name",
"event_data",
"created_at",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"id",
"account_id",
"character_id",
"zone_id",
"instance_id",
"x",
"y",
"z",
"heading",
"event_type_id",
"event_type_name",
"event_data",
"UNIX_TIMESTAMP(created_at)",
};
}
static std::string ColumnsRaw()
{
return std::string(Strings::Implode(", ", Columns()));
}
static std::string SelectColumnsRaw()
{
return std::string(Strings::Implode(", ", SelectColumns()));
}
static std::string TableName()
{
return std::string("player_event_logs");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
SelectColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static PlayerEventLogs NewEntity()
{
PlayerEventLogs e{};
e.id = 0;
e.account_id = 0;
e.character_id = 0;
e.zone_id = 0;
e.instance_id = 0;
e.x = 0;
e.y = 0;
e.z = 0;
e.heading = 0;
e.event_type_id = 0;
e.event_type_name = "";
e.event_data = "";
e.created_at = 0;
return e;
}
static PlayerEventLogs GetPlayerEventLogs(
const std::vector<PlayerEventLogs> &player_event_logss,
int player_event_logs_id
)
{
for (auto &player_event_logs : player_event_logss) {
if (player_event_logs.id == player_event_logs_id) {
return player_event_logs;
}
}
return NewEntity();
}
static PlayerEventLogs FindOne(
Database& db,
int player_event_logs_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {} = {} LIMIT 1",
BaseSelect(),
PrimaryKey(),
player_event_logs_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
PlayerEventLogs e{};
e.id = strtoll(row[0], nullptr, 10);
e.account_id = strtoll(row[1], nullptr, 10);
e.character_id = strtoll(row[2], nullptr, 10);
e.zone_id = static_cast<int32_t>(atoi(row[3]));
e.instance_id = static_cast<int32_t>(atoi(row[4]));
e.x = strtof(row[5], nullptr);
e.y = strtof(row[6], nullptr);
e.z = strtof(row[7], nullptr);
e.heading = strtof(row[8], nullptr);
e.event_type_id = static_cast<int32_t>(atoi(row[9]));
e.event_type_name = row[10] ? row[10] : "";
e.event_data = row[11] ? row[11] : "";
e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10);
return e;
}
return NewEntity();
}
static int DeleteOne(
Database& db,
int player_event_logs_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
player_event_logs_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
Database& db,
const PlayerEventLogs &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[1] + " = " + std::to_string(e.account_id));
v.push_back(columns[2] + " = " + std::to_string(e.character_id));
v.push_back(columns[3] + " = " + std::to_string(e.zone_id));
v.push_back(columns[4] + " = " + std::to_string(e.instance_id));
v.push_back(columns[5] + " = " + std::to_string(e.x));
v.push_back(columns[6] + " = " + std::to_string(e.y));
v.push_back(columns[7] + " = " + std::to_string(e.z));
v.push_back(columns[8] + " = " + std::to_string(e.heading));
v.push_back(columns[9] + " = " + std::to_string(e.event_type_id));
v.push_back(columns[10] + " = '" + Strings::Escape(e.event_type_name) + "'");
v.push_back(columns[11] + " = '" + Strings::Escape(e.event_data) + "'");
v.push_back(columns[12] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static PlayerEventLogs InsertOne(
Database& db,
PlayerEventLogs e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.account_id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.zone_id));
v.push_back(std::to_string(e.instance_id));
v.push_back(std::to_string(e.x));
v.push_back(std::to_string(e.y));
v.push_back(std::to_string(e.z));
v.push_back(std::to_string(e.heading));
v.push_back(std::to_string(e.event_type_id));
v.push_back("'" + Strings::Escape(e.event_type_name) + "'");
v.push_back("'" + Strings::Escape(e.event_data) + "'");
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseInsert(),
Strings::Implode(",", v)
)
);
if (results.Success()) {
e.id = results.LastInsertedID();
return e;
}
e = NewEntity();
return e;
}
static int InsertMany(
Database& db,
const std::vector<PlayerEventLogs> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.account_id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.zone_id));
v.push_back(std::to_string(e.instance_id));
v.push_back(std::to_string(e.x));
v.push_back(std::to_string(e.y));
v.push_back(std::to_string(e.z));
v.push_back(std::to_string(e.heading));
v.push_back(std::to_string(e.event_type_id));
v.push_back("'" + Strings::Escape(e.event_type_name) + "'");
v.push_back("'" + Strings::Escape(e.event_data) + "'");
v.push_back("FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<PlayerEventLogs> All(Database& db)
{
std::vector<PlayerEventLogs> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
PlayerEventLogs e{};
e.id = strtoll(row[0], nullptr, 10);
e.account_id = strtoll(row[1], nullptr, 10);
e.character_id = strtoll(row[2], nullptr, 10);
e.zone_id = static_cast<int32_t>(atoi(row[3]));
e.instance_id = static_cast<int32_t>(atoi(row[4]));
e.x = strtof(row[5], nullptr);
e.y = strtof(row[6], nullptr);
e.z = strtof(row[7], nullptr);
e.heading = strtof(row[8], nullptr);
e.event_type_id = static_cast<int32_t>(atoi(row[9]));
e.event_type_name = row[10] ? row[10] : "";
e.event_data = row[11] ? row[11] : "";
e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10);
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<PlayerEventLogs> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<PlayerEventLogs> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {}",
BaseSelect(),
where_filter
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
PlayerEventLogs e{};
e.id = strtoll(row[0], nullptr, 10);
e.account_id = strtoll(row[1], nullptr, 10);
e.character_id = strtoll(row[2], nullptr, 10);
e.zone_id = static_cast<int32_t>(atoi(row[3]));
e.instance_id = static_cast<int32_t>(atoi(row[4]));
e.x = strtof(row[5], nullptr);
e.y = strtof(row[6], nullptr);
e.z = strtof(row[7], nullptr);
e.heading = strtof(row[8], nullptr);
e.event_type_id = static_cast<int32_t>(atoi(row[9]));
e.event_type_name = row[10] ? row[10] : "";
e.event_data = row[11] ? row[11] : "";
e.created_at = strtoll(row[12] ? row[12] : "-1", nullptr, 10);
all_entries.push_back(e);
}
return all_entries;
}
static int DeleteWhere(Database& db, const std::string &where_filter)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {}",
TableName(),
where_filter
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int Truncate(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"TRUNCATE TABLE {}",
TableName()
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int64 GetMaxId(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COALESCE(MAX({}), 0) FROM {}",
PrimaryKey(),
TableName()
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static int64 Count(Database& db, const std::string &where_filter = "")
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COUNT(*) FROM {} {}",
TableName(),
(where_filter.empty() ? "" : "WHERE " + where_filter)
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
};
#endif //EQEMU_BASE_PLAYER_EVENT_LOGS_REPOSITORY_H

View File

@ -5,6 +5,8 @@
#include "../strings.h"
#include "base/base_character_data_repository.h"
class CharacterDataRepository: public BaseCharacterDataRepository {
public:
@ -44,7 +46,6 @@ public:
*/
// Custom extended repository methods here
};
#endif //EQEMU_CHARACTER_DATA_REPOSITORY_H

View File

@ -172,7 +172,7 @@ public:
DELETE FROM {}
WHERE dynamic_zone_id IN ({});
),
TableName(), fmt::join(dynamic_zone_ids, ",")
TableName(), Strings::Join(dynamic_zone_ids, ",")
));
}
}

View File

@ -75,7 +75,7 @@ public:
FROM expedition_lockouts
WHERE expedition_id IN ({})
),
fmt::join(expedition_ids, ",")
Strings::Join(expedition_ids, ",")
));
all_entries.reserve(results.RowCount());

View File

@ -62,7 +62,7 @@ public:
std::vector<CharacterExpedition> entries;
auto joined_character_names = fmt::format("'{}'", fmt::join(character_names, "','"));
auto joined_character_names = fmt::format("'{}'", Strings::Join(character_names, "','"));
auto results = db.QueryDatabase(fmt::format(SQL(
SELECT

View File

@ -1,11 +1,11 @@
#ifndef EQEMU_EVENTLOG_REPOSITORY_H
#define EQEMU_EVENTLOG_REPOSITORY_H
#ifndef EQEMU_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H
#define EQEMU_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_eventlog_repository.h"
#include "base/base_player_event_log_settings_repository.h"
class EventlogRepository: public BaseEventlogRepository {
class PlayerEventLogSettingsRepository: public BasePlayerEventLogSettingsRepository {
public:
/**
@ -32,10 +32,10 @@ public:
*
* Example custom methods in a repository
*
* EventlogRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* EventlogRepository::GetWhereNeverExpires()
* EventlogRepository::GetWhereXAndY()
* EventlogRepository::DeleteWhereXAndY()
* PlayerEventLogSettingsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* PlayerEventLogSettingsRepository::GetWhereNeverExpires()
* PlayerEventLogSettingsRepository::GetWhereXAndY()
* PlayerEventLogSettingsRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
@ -47,4 +47,4 @@ public:
};
#endif //EQEMU_EVENTLOG_REPOSITORY_H
#endif //EQEMU_PLAYER_EVENT_LOG_SETTINGS_REPOSITORY_H

View File

@ -1,11 +1,11 @@
#ifndef EQEMU_HACKERS_REPOSITORY_H
#define EQEMU_HACKERS_REPOSITORY_H
#ifndef EQEMU_PLAYER_EVENT_LOGS_REPOSITORY_H
#define EQEMU_PLAYER_EVENT_LOGS_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_hackers_repository.h"
#include "base/base_player_event_logs_repository.h"
class HackersRepository: public BaseHackersRepository {
class PlayerEventLogsRepository: public BasePlayerEventLogsRepository {
public:
/**
@ -32,10 +32,10 @@ public:
*
* Example custom methods in a repository
*
* HackersRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* HackersRepository::GetWhereNeverExpires()
* HackersRepository::GetWhereXAndY()
* HackersRepository::DeleteWhereXAndY()
* PlayerEventLogsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* PlayerEventLogsRepository::GetWhereNeverExpires()
* PlayerEventLogsRepository::GetWhereXAndY()
* PlayerEventLogsRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
@ -47,4 +47,4 @@ public:
};
#endif //EQEMU_HACKERS_REPOSITORY_H
#endif //EQEMU_PLAYER_EVENT_LOGS_REPOSITORY_H

View File

@ -778,6 +778,8 @@ RULE_CATEGORY_END()
RULE_CATEGORY(Logging)
RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...")
RULE_BOOL(Logging, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output")
RULE_BOOL(Logging, PlayerEventsQSProcess, false, "Have query server process player events instead of world. Useful when wanting to use a dedicated server and database for processing player events on separate disk")
RULE_INT(Logging, BatchPlayerEventProcessIntervalSeconds, 5, "This is the interval in which player events are processed in world or qs")
RULE_CATEGORY_END()
RULE_CATEGORY(HotReload)

View File

@ -281,6 +281,9 @@
#define ServerOP_QSSendQuery 0x5006
#define ServerOP_QSPlayerDropItem 0x5007
// player events
#define ServerOP_PlayerEvent 0x5100
enum {
CZUpdateType_Character,
CZUpdateType_Group,
@ -1308,10 +1311,10 @@ struct Server_Speech_Struct {
char message[0];
};
struct QSTradeItems_Struct {
uint32 from_id;
struct PlayerLogTradeItemsEntry_Struct {
uint32 from_character_id;
uint16 from_slot;
uint32 to_id;
uint32 to_character_id;
uint16 to_slot;
uint32 item_id;
uint16 charges;
@ -1322,15 +1325,15 @@ struct QSTradeItems_Struct {
uint32 aug_5;
};
struct QSPlayerLogTrade_Struct {
uint32 char1_id;
MoneyUpdate_Struct char1_money;
uint16 char1_count;
uint32 char2_id;
MoneyUpdate_Struct char2_money;
uint16 char2_count;
uint16 _detail_count;
QSTradeItems_Struct items[0];
struct PlayerLogTrade_Struct {
uint32 character_1_id;
MoneyUpdate_Struct character_1_money;
uint16 character_1_item_count;
uint32 character_2_id;
MoneyUpdate_Struct character_2_money;
uint16 character_2_item_count;
uint16 _detail_count;
PlayerLogTradeItemsEntry_Struct item_entries[0];
};
struct QSDropItems_Struct {
@ -1806,6 +1809,11 @@ struct ServerDzCreateSerialized_Struct {
char cereal_data[0];
};
struct ServerSendPlayerEvent_Struct {
uint32_t cereal_size;
char cereal_data[0];
};
struct ServerFlagUpdate_Struct {
uint32 account_id;
int16 admin;

View File

@ -225,6 +225,20 @@ std::string Strings::Join(const std::vector<std::string> &ar, const std::string
return ret;
}
std::string Strings::Join(const std::vector<uint32_t> &ar, const std::string &delim)
{
std::string ret;
for (size_t i = 0; i < ar.size(); ++i) {
if (i != 0) {
ret += delim;
}
ret += std::to_string(ar[i]);
}
return ret;
}
void
Strings::FindReplace(std::string &string_subject, const std::string &search_string, const std::string &replace_string)
{

View File

@ -108,6 +108,7 @@ public:
static std::string GetBetween(const std::string &s, std::string start_delim, std::string stop_delim);
static std::string Implode(std::string glue, std::vector<std::string> src);
static std::string Join(const std::vector<std::string> &ar, const std::string &delim);
static std::string Join(const std::vector<uint32_t> &ar, const std::string &delim);
static std::string MillisecondsToTime(int duration);
static std::string Money(uint32 platinum, uint32 gold = 0, uint32 silver = 0, uint32 copper = 0);
static std::string NumberToWords(unsigned long long int n);

View File

@ -16,7 +16,6 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Disgrace: for windows compile
#ifndef WIN32
#include <sys/time.h>

View File

@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9219
#define CURRENT_BINARY_DATABASE_VERSION 9220
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9037
#endif

View File

@ -50,7 +50,6 @@
#include "../common/strings.h"
#include "../common/servertalk.h"
void QSDatabase::AddSpeech(
const char *from,
const char *to,
@ -125,7 +124,7 @@ void QSDatabase::LogPlayerDropItem(QSPlayerDropItem_Struct *QS)
}
}
void QSDatabase::LogPlayerTrade(QSPlayerLogTrade_Struct *QS, uint32 detailCount)
void QSDatabase::LogPlayerTrade(PlayerLogTrade_Struct *QS, uint32 detailCount)
{
std::string query = StringFormat(
@ -134,10 +133,10 @@ void QSDatabase::LogPlayerTrade(QSPlayerLogTrade_Struct *QS, uint32 detailCount)
"`char1_sp` = '%i', `char1_cp` = '%i', `char1_items` = '%i', "
"`char2_id` = '%i', `char2_pp` = '%i', `char2_gp` = '%i', "
"`char2_sp` = '%i', `char2_cp` = '%i', `char2_items` = '%i'",
QS->char1_id, QS->char1_money.platinum, QS->char1_money.gold,
QS->char1_money.silver, QS->char1_money.copper, QS->char1_count,
QS->char2_id, QS->char2_money.platinum, QS->char2_money.gold,
QS->char2_money.silver, QS->char2_money.copper, QS->char2_count
QS->character_1_id, QS->character_1_money.platinum, QS->character_1_money.gold,
QS->character_1_money.silver, QS->character_1_money.copper, QS->character_1_item_count,
QS->character_2_id, QS->character_2_money.platinum, QS->character_2_money.gold,
QS->character_2_money.silver, QS->character_2_money.copper, QS->character_2_item_count
);
auto results = QueryDatabase(query);
if (!results.Success()) {
@ -157,10 +156,10 @@ void QSDatabase::LogPlayerTrade(QSPlayerLogTrade_Struct *QS, uint32 detailCount)
"`from_id` = '%i', `from_slot` = '%i', `to_id` = '%i', `to_slot` = '%i', "
"`item_id` = '%i', `charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', "
"`aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'",
lastIndex, QS->items[i].from_id, QS->items[i].from_slot,
QS->items[i].to_id, QS->items[i].to_slot, QS->items[i].item_id,
QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2,
QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5
lastIndex, QS->item_entries[i].from_character_id, QS->item_entries[i].from_slot,
QS->item_entries[i].to_character_id, QS->item_entries[i].to_slot, QS->item_entries[i].item_id,
QS->item_entries[i].charges, QS->item_entries[i].aug_1, QS->item_entries[i].aug_2,
QS->item_entries[i].aug_3, QS->item_entries[i].aug_4, QS->item_entries[i].aug_5
);
results = QueryDatabase(query);
if (!results.Success()) {

View File

@ -39,7 +39,7 @@
class QSDatabase : public Database {
public:
void AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type);
void LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount);
void LogPlayerTrade(PlayerLogTrade_Struct* QS, uint32 DetailCount);
void LogPlayerDropItem(QSPlayerDropItem_Struct* QS);
void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount);
void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members);

View File

@ -100,7 +100,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
break;
}
case ServerOP_QSPlayerLogTrades: {
QSPlayerLogTrade_Struct *QS = (QSPlayerLogTrade_Struct *) p.Data();
PlayerLogTrade_Struct *QS = (PlayerLogTrade_Struct *) p.Data();
database.LogPlayerTrade(QS, QS->_detail_count);
break;
}

View File

@ -30,6 +30,7 @@
#include "../common/database.h"
#include "clientlist.h"
#include "chatchannel.h"
#include "../common/shareddb.h"
#include <string>
#include <vector>
#include <map>

View File

@ -37,9 +37,10 @@
#include "../common/net/tcp_server.h"
#include "../common/net/servertalk_client_connection.h"
#include "../common/discord_manager.h"
#include "../common/discord/discord_manager.h"
#include "../common/path_manager.h"
#include "../common/zone_store.h"
#include "../common/events/player_event_logs.h"
ChatChannelList *ChannelList;
Clientlist *g_Clientlist;
@ -49,6 +50,7 @@ WorldServer *worldserver = nullptr;
DiscordManager discord_manager;
PathManager path;
ZoneStore zone_store;
PlayerEventLogs player_event_logs;
const ucsconfig *Config;
@ -93,7 +95,7 @@ void CatchSignal(int sig_num) {
}
}
void DiscordQueueListener() {
void PlayerEventQueueListener() {
while (caught_loop == 0) {
discord_manager.ProcessMessageQueue();
Sleep(100);
@ -177,7 +179,7 @@ int main() {
std::signal(SIGKILL, CatchSignal);
std::signal(SIGSEGV, CatchSignal);
std::thread(DiscordQueueListener).detach();
std::thread(PlayerEventQueueListener).detach();
worldserver = new WorldServer;

View File

@ -26,7 +26,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "clientlist.h"
#include "ucsconfig.h"
#include "database.h"
#include "../common/discord_manager.h"
#include "../common/discord/discord_manager.h"
#include "../common/events/player_event_logs.h"
#include <iostream>
#include <string.h>
@ -76,6 +77,18 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p)
}
case ServerOP_ReloadLogs: {
LogSys.LoadLogDatabaseSettings();
player_event_logs.ReloadSettings();
break;
}
case ServerOP_PlayerEvent: {
auto n = PlayerEvent::PlayerEventContainer{};
auto s = (ServerSendPlayerEvent_Struct*) pack->pBuffer;
EQ::Util::MemoryStreamReader ss(s->cereal_data, s->cereal_size);
cereal::BinaryInputArchive archive(ss);
archive(n);
discord_manager.QueuePlayerEventMessage(n);
break;
}
case ServerOP_DiscordWebhookMessage: {

View File

@ -473,6 +473,7 @@
9217|2023_01_15_chatchannel_reserved_names.sql|SHOW TABLES LIKE 'chatchannel_reserved_names'|empty|
9218|2023_01_24_item_recast.sql|show columns from character_item_recast like '%recast_type%'|contains|smallint
9219|2023_01_29_merchant_status_requirements.sql|SHOW COLUMNS FROM merchantlist LIKE 'min_status'|empty|
9220|2022_12_19_player_events_tables.sql|SHOW TABLES LIKE 'player_event_logs'|empty|
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1,34 @@
CREATE TABLE `player_event_log_settings`
(
`id` bigint(20) NOT NULL,
`event_name` varchar(100) DEFAULT NULL,
`event_enabled` tinyint(1) DEFAULT NULL,
`retention_days` int(11) DEFAULT 0,
`discord_webhook_id` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `player_event_logs`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_id` bigint(20) DEFAULT NULL,
`character_id` bigint(20) DEFAULT NULL,
`zone_id` int(11) DEFAULT NULL,
`instance_id` int(11) DEFAULT NULL,
`x` float DEFAULT NULL,
`y` float DEFAULT NULL,
`z` float DEFAULT NULL,
`heading` float DEFAULT NULL,
`event_type_id` int(11) DEFAULT NULL,
`event_type_name` varchar(255) DEFAULT NULL,
`event_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`event_data`)),
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `event_created_at` (`event_type_id`,`created_at`),
KEY `zone_id` (`zone_id`),
KEY `character_id` (`character_id`,`zone_id`) USING BTREE,
KEY `created_at` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
DROP TABLE `hackers`;
DROP TABLE `eventlog`;

View File

@ -1,4 +1,6 @@
#include "../../common/zone_store.h"
#include <cereal/archives/json.hpp>
#include <cereal/types/vector.hpp>
#include "../../common/events/player_events.h"
void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std::string &description)
{
@ -8,14 +10,5 @@ void WorldserverCLI::TestCommand(int argc, char **argv, argh::parser &cmd, std::
return;
}
zone_store.LoadZones(database);
const char* zonename = ZoneName(0);
if (zonename == 0) {
LogInfo("Zone name is 0");
}
if (zonename == nullptr) {
LogInfo("Zone name is nullptr");
}
}

View File

@ -49,6 +49,8 @@
#include "sof_char_create_data.h"
#include "../common/zone_store.h"
#include "../common/repositories/account_repository.h"
#include "../common/repositories/player_event_logs_repository.h"
#include "../common/events/player_event_logs.h"
#include <iostream>
#include <iomanip>
@ -818,7 +820,8 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) {
zone_id = database.MoveCharacterToBind(charid, 4);
} else {
LogInfo("[{}] is trying to go home before they're able.", char_name);
database.SetHackerFlag(GetAccountName(), char_name, "MQGoHome: player tried to go home before they were able.");
RecordPossibleHack("[MQGoHome] player tried to go home before they were able");
eqs->Close();
return true;
}
@ -844,7 +847,8 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) {
database.MoveCharacterToZone(charid, zone_id);
} else {
LogInfo("[{}] is trying to go to the Tutorial but they are not allowed.", char_name);
database.SetHackerFlag(GetAccountName(), char_name, "MQTutorial: player tried to enter the tutorial without having tutorial enabled for this character.");
RecordPossibleHack("[MQTutorial] player tried to enter the tutorial without having tutorial enabled for this character");
eqs->Close();
return true;
}
@ -2360,3 +2364,24 @@ bool Client::StoreCharacter(
return true;
}
void Client::RecordPossibleHack(const std::string& message)
{
if (player_event_logs.IsEventEnabled(PlayerEvent::POSSIBLE_HACK)) {
auto event = PlayerEvent::PossibleHackEvent{.message = message};
std::stringstream ss;
{
cereal::JSONOutputArchiveSingleLine ar(ss);
event.serialize(ar);
}
auto e = PlayerEventLogsRepository::NewEntity();
e.character_id = charid;
e.account_id = GetCLE() ? GetAccountID() : 0;
e.event_type_id = PlayerEvent::POSSIBLE_HACK;
e.event_type_name = PlayerEvent::EventName[PlayerEvent::POSSIBLE_HACK];
e.event_data = ss.str();
e.created_at = std::time(nullptr);
PlayerEventLogsRepository::InsertOne(database, e);
}
}

View File

@ -121,6 +121,7 @@ private:
EQStreamInterface* eqs;
bool CanTradeFVNoDropItem();
void RecordPossibleHack(const std::string& message);
};
bool CheckCharCreateInfoSoF(CharCreate_Struct *cc);

View File

@ -28,9 +28,9 @@ void DynamicZoneManager::PurgeExpiredDynamicZones()
LogDynamicZones("Purging [{}] dynamic zone(s)", dz_ids.size());
DynamicZoneMembersRepository::DeleteWhere(database,
fmt::format("dynamic_zone_id IN ({})", fmt::join(dz_ids, ",")));
fmt::format("dynamic_zone_id IN ({})", Strings::Join(dz_ids, ",")));
DynamicZonesRepository::DeleteWhere(database,
fmt::format("id IN ({})", fmt::join(dz_ids, ",")));
fmt::format("id IN ({})", Strings::Join(dz_ids, ",")));
}
}
@ -145,7 +145,7 @@ void DynamicZoneManager::Process()
// need to look up expedition ids until lockouts are moved to dynamic zones
std::vector<uint32_t> expedition_ids;
auto expeditions = ExpeditionsRepository::GetWhere(database,
fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ",")));
fmt::format("dynamic_zone_id IN ({})", Strings::Join(dynamic_zone_ids, ",")));
if (!expeditions.empty())
{
@ -154,14 +154,14 @@ void DynamicZoneManager::Process()
expedition_ids.emplace_back(expedition.id);
}
ExpeditionLockoutsRepository::DeleteWhere(database,
fmt::format("expedition_id IN ({})", fmt::join(expedition_ids, ",")));
fmt::format("expedition_id IN ({})", Strings::Join(expedition_ids, ",")));
}
ExpeditionsRepository::DeleteWhere(database,
fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ",")));
fmt::format("dynamic_zone_id IN ({})", Strings::Join(dynamic_zone_ids, ",")));
DynamicZoneMembersRepository::RemoveAllMembers(database, dynamic_zone_ids);
DynamicZonesRepository::DeleteWhere(database,
fmt::format("id IN ({})", fmt::join(dynamic_zone_ids, ",")));
fmt::format("id IN ({})", Strings::Join(dynamic_zone_ids, ",")));
}
}

View File

@ -50,11 +50,11 @@ void ExpeditionDatabase::PurgeExpiredExpeditions()
auto results = database.QueryDatabase(query);
if (results.Success())
{
std::vector<uint32_t> expedition_ids;
std::vector<std::string> expedition_ids;
std::vector<uint32_t> dynamic_zone_ids;
for (auto row = results.begin(); row != results.end(); ++row)
{
expedition_ids.emplace_back(static_cast<uint32_t>(strtoul(row[0], nullptr, 10)));
expedition_ids.emplace_back(row[0]);
dynamic_zone_ids.emplace_back(static_cast<uint32_t>(strtoul(row[1], nullptr, 10)));
}

View File

@ -58,6 +58,7 @@
#include "../common/unix.h"
#include <sys/sem.h>
#include <thread>
#if not defined (FREEBSD) && not defined (DARWIN)
union semun {
@ -96,6 +97,7 @@ union semun {
#include "shared_task_manager.h"
#include "world_boot.h"
#include "../common/path_manager.h"
#include "../common/events/player_event_logs.h"
ZoneStore zone_store;
@ -118,6 +120,7 @@ EQEmuLogSys LogSys;
WorldContentService content_service;
WebInterfaceList web_interface;
PathManager path;
PlayerEventLogs player_event_logs;
void CatchSignal(int sig_num);
@ -128,6 +131,13 @@ inline void UpdateWindowTitle(std::string new_title)
#endif
}
void PlayerEventQueueListener() {
while (RunLoops) {
player_event_logs.Process();
Sleep(1000);
}
}
/**
* World process entrypoint
*
@ -371,6 +381,13 @@ int main(int argc, char **argv)
}
);
player_event_logs.SetDatabase(&database)->Init();
if (!RuleB(Logging, PlayerEventsQSProcess)) {
LogInfo("[PlayerEventQueueListener] Booting queue processor");
std::thread(PlayerEventQueueListener).detach();
}
auto loop_fn = [&](EQ::Timer* t) {
Timer::SetCurrentTime();

View File

@ -22,6 +22,7 @@ void QueryServConnection::AddConnection(std::shared_ptr<EQ::Net::ServertalkServe
connection->OnMessage(ServerOP_QueryServGeneric, std::bind(&QueryServConnection::HandleGenericMessage, this, std::placeholders::_1, std::placeholders::_2));
connection->OnMessage(ServerOP_LFGuildUpdate, std::bind(&QueryServConnection::HandleLFGuildUpdateMessage, this, std::placeholders::_1, std::placeholders::_2));
m_streams.insert(std::make_pair(connection->GetUUID(), connection));
m_keepalive = std::make_unique<EQ::Timer>(1000, true, std::bind(&QueryServConnection::OnKeepAlive, this, std::placeholders::_1));
}
void QueryServConnection::RemoveConnection(std::shared_ptr<EQ::Net::ServertalkServerConnection> connection)
@ -51,4 +52,10 @@ bool QueryServConnection::SendPacket(ServerPacket* pack)
}
return true;
}
}
void QueryServConnection::OnKeepAlive(EQ::Timer *t)
{
ServerPacket pack(ServerOP_KeepAlive, 0);
SendPacket(&pack);
}

View File

@ -4,6 +4,7 @@
#include "../common/types.h"
#include "../common/net/servertalk_server.h"
#include "../common/servertalk.h"
#include "../common/event/timer.h"
class QueryServConnection
{
@ -14,8 +15,10 @@ public:
void HandleGenericMessage(uint16_t opcode, EQ::Net::Packet &p);
void HandleLFGuildUpdateMessage(uint16_t opcode, EQ::Net::Packet &p);
bool SendPacket(ServerPacket* pack);
void OnKeepAlive(EQ::Timer *t);
private:
std::map<std::string, std::shared_ptr<EQ::Net::ServertalkServerConnection>> m_streams;
std::unique_ptr<EQ::Timer> m_keepalive;
};
#endif /*QueryServ_H_*/

View File

@ -312,7 +312,7 @@ void SharedTaskManager::LoadSharedTaskState()
shared_task_character_data = CharacterDataRepository::GetWhere(
*m_database,
fmt::format("id IN ({})", fmt::join(character_ids, ","))
fmt::format("id IN ({})", Strings::Join(character_ids, ","))
);
}
@ -1294,7 +1294,7 @@ std::vector<CharacterTaskTimersRepository::CharacterTaskTimers> SharedTaskManage
OR (timer_group > 0 AND timer_type = {} AND timer_group = {}))
AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1
),
fmt::join(character_ids, ","),
Strings::Join(character_ids, ","),
task.id,
static_cast<int>(TaskTimerType::Replay),
task.replay_timer_group,
@ -1632,7 +1632,7 @@ void SharedTaskManager::AddReplayTimers(SharedTask *s)
s->GetTaskData().id,
s->GetTaskData().replay_timer_group,
static_cast<int>(TaskTimerType::Replay),
fmt::join(s->member_id_history, ",")
Strings::Join(s->member_id_history, ",")
));
CharacterTaskTimersRepository::InsertMany(*m_database, task_timers);

View File

@ -323,7 +323,7 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack)
}
}
std::string player_list = fmt::format("{}", fmt::join(player_names, ", "));
std::string player_list = fmt::format("{}", Strings::Join(player_names, ", "));
client_list.SendCharacterMessageID(buf->source_character_id, Chat::Yellow, TaskStr::MEMBERS_PRINT, {player_list});
}

View File

@ -43,6 +43,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/shared_tasks.h"
#include "shared_task_manager.h"
#include "../common/content/world_content_service.h"
#include "../common/repositories/player_event_logs_repository.h"
#include "../common/events/player_event_logs.h"
extern ClientList client_list;
extern GroupLFPList LFPGroupList;
@ -369,6 +371,30 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_PlayerEvent: {
auto n = PlayerEvent::PlayerEventContainer{};
auto s = (ServerSendPlayerEvent_Struct *) pack->pBuffer;
EQ::Util::MemoryStreamReader ss(s->cereal_data, s->cereal_size);
cereal::BinaryInputArchive archive(ss);
archive(n);
// by default process events in world
// if set, process events in queryserver
// if you want to offload event recording to a dedicated QS instance
if (!RuleB(Logging, PlayerEventsQSProcess)) {
player_event_logs.AddToQueue(n.player_event_log);
}
else {
QSLink.SendPacket(pack);
}
// if discord enabled for event, ship to UCS to process
if (player_event_logs.IsEventDiscordEnabled(n.player_event_log.event_type_id)) {
UCSLink.SendPacket(pack);
}
break;
}
case ServerOP_DetailsChange: {
if (pack->size != sizeof(ServerRaidGeneralAction_Struct)) {
break;
@ -1356,6 +1382,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
zoneserver_list.SendPacket(pack);
UCSLink.SendPacket(pack);
LogSys.LoadLogDatabaseSettings();
player_event_logs.ReloadSettings();
break;
}
case ServerOP_ReloadTasks: {

View File

@ -23,6 +23,7 @@ Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
#include "../common/races.h"
#include "../common/spdat.h"
#include "../common/strings.h"
#include "../common/events/player_event_logs.h"
#include "aa.h"
#include "client.h"
#include "corpse.h"
@ -35,9 +36,11 @@ Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
#include "titles.h"
#include "zonedb.h"
#include "../common/zone_store.h"
#include "worldserver.h"
#include "bot.h"
extern WorldServer worldserver;
extern QueryServ* QServ;
void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, uint32 duration_override, bool followme, bool sticktarg, uint16 *eye_id) {
@ -1180,6 +1183,17 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost
SendAlternateAdvancementPoints();
SendAlternateAdvancementStats();
if (player_event_logs.IsEventEnabled(PlayerEvent::AA_PURCHASE)) {
auto e = PlayerEvent::AAPurchasedEvent{
.aa_id = rank->id,
.aa_cost = cost,
.aa_previous_id = rank->prev_id,
.aa_next_id = rank->next_id
};
RecordPlayerEventLog(PlayerEvent::AA_PURCHASE, e);
}
if (rank->prev) {
MessageString(
Chat::Yellow,

View File

@ -25,6 +25,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/strings.h"
#include "../common/data_verification.h"
#include "../common/misc_functions.h"
#include "../common/events/player_event_logs.h"
#include "queryserv.h"
#include "quest_parser_collection.h"
#include "string_ids.h"
@ -2064,6 +2065,20 @@ bool Client::Death(Mob* killerMob, int64 damage, uint16 spell, EQ::skills::Skill
QServ->PlayerLogEvent(Player_Log_Deaths, CharacterID(), event_desc);
}
if (player_event_logs.IsEventEnabled(PlayerEvent::DEATH)) {
auto e = PlayerEvent::DeathEvent{
.killer_id = killerMob ? static_cast<uint32>(killerMob->GetID()) : static_cast<uint32>(0),
.killer_name = killerMob ? killerMob->GetCleanName() : "No Killer",
.damage = damage,
.spell_id = spell,
.spell_name = IsValidSpell(spell) ? spells[spell].name : "No Spell",
.skill_id = static_cast<int>(attack_skill),
.skill_name = !EQ::skills::GetSkillName(attack_skill).empty() ? EQ::skills::GetSkillName(attack_skill) : "No Skill",
};
RecordPlayerEventLog(PlayerEvent::DEATH, e);
}
std::vector<std::any> args = { new_corpse };
parse->EventPlayer(EVENT_DEATH_COMPLETE, this, export_string, 0, &args);
return true;
@ -2518,6 +2533,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
if (kr->members[i].member != nullptr && kr->members[i].member->IsClient()) { // If Group Member is Client
Client *c = kr->members[i].member;
parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0);
c->RecordKilledNPCEvent(this);
if (RuleB(NPC, EnableMeritBasedFaction))
c->SetFactionLevel(c->CharacterID(), GetNPCFactionID(), c->GetBaseClass(), c->GetBaseRace(), c->GetDeity());
@ -2565,6 +2581,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
if (kg->members[i] != nullptr && kg->members[i]->IsClient()) { // If Group Member is Client
Client *c = kg->members[i]->CastToClient();
parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0);
c->RecordKilledNPCEvent(this);
if (RuleB(NPC, EnableMeritBasedFaction))
c->SetFactionLevel(c->CharacterID(), GetNPCFactionID(), c->GetBaseClass(), c->GetBaseRace(), c->GetDeity());
@ -2612,6 +2629,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
/* Send the EVENT_KILLED_MERIT event */
parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0);
give_exp_client->RecordKilledNPCEvent(this);
if (RuleB(NPC, EnableMeritBasedFaction))
give_exp_client->SetFactionLevel(give_exp_client->CharacterID(), GetNPCFactionID(), give_exp_client->GetBaseClass(),
@ -2789,7 +2807,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
std::vector<std::any> args = { corpse };
parse->EventNPC(EVENT_DEATH_COMPLETE, this, oos, export_string, 0, &args);
combat_record.Stop();
m_combat_record.Stop();
/* Zone controller process EVENT_DEATH_ZONE (Death events) */
args.push_back(this);

View File

@ -1624,8 +1624,6 @@ int bot_command_real_dispatch(Client *c, const char *message)
{
Seperator sep(message, ' ', 10, 100, true); // "three word argument" should be considered 1 arg
bot_command_log_command(c, message);
std::string cstr(sep.arg[0]+1);
if(bot_command_list.count(cstr) != 1) {
@ -1659,77 +1657,6 @@ int bot_command_real_dispatch(Client *c, const char *message)
}
void bot_command_log_command(Client *c, const char *message)
{
int admin = c->Admin();
bool continueevents = false;
switch (zone->loglevelvar){ //catch failsafe
case 9: { // log only LeadGM
if (
admin >= AccountStatus::GMLeadAdmin &&
admin < AccountStatus::GMMgmt
) {
continueevents = true;
}
break;
}
case 8: { // log only GM
if (
admin >= AccountStatus::GMAdmin &&
admin < AccountStatus::GMLeadAdmin
) {
continueevents = true;
}
break;
}
case 1: {
if (admin >= AccountStatus::GMMgmt) {
continueevents = true;
}
break;
}
case 2: {
if (admin >= AccountStatus::GMLeadAdmin) {
continueevents = true;
}
break;
}
case 3: {
if (admin >= AccountStatus::GMAdmin) {
continueevents = true;
}
break;
}
case 4: {
if (admin >= AccountStatus::QuestTroupe) {
continueevents = true;
}
break;
}
case 5: {
if (admin >= AccountStatus::ApprenticeGuide) {
continueevents = true;
}
break;
}
case 6: {
if (admin >= AccountStatus::Steward) {
continueevents = true;
}
break;
}
case 7: {
continueevents = true;
break;
}
}
if (continueevents)
database.logevents(c->AccountName(), c->AccountID(), admin,c->GetName(), c->GetTarget()?c->GetTarget()->GetName():"None", "BotCommand", message, 1);
}
/*
* helper functions by use
*/

View File

@ -543,7 +543,6 @@ void bot_command_deinit(void);
int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function);
int bot_command_not_avail(Client *c, const char *message);
int bot_command_real_dispatch(Client *c, char const *message);
void bot_command_log_command(Client *c, const char *message);
// Bot Commands
void bot_command_actionable(Client *c, const Seperator *sep);

View File

@ -1,6 +1,10 @@
#include "cheat_manager.h"
#include "client.h"
#include "quest_parser_collection.h"
#include "../common/events/player_event_logs.h"
#include "worldserver.h"
extern WorldServer worldserver;
void CheatManager::SetClient(Client *cli)
{
@ -36,12 +40,9 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position2.z,
Distance(position1, position2)
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
std::string export_string = fmt::format(
"{} {} {}",
@ -65,12 +66,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position2.z,
Distance(position1, position2)
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
std::string export_string = fmt::format(
"{} {} {}",
@ -91,12 +87,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
}
break;
@ -109,12 +100,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
}
break;
@ -129,12 +115,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
}
}
@ -149,12 +130,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
}
break;
@ -167,12 +143,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
}
break;
@ -185,12 +156,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
}
break;
@ -199,17 +165,13 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
if (RuleB(Cheat, EnableMQGhostDetector) &&
((m_target->Admin() < RuleI(Cheat, MQGhostExemptStatus) ||
(RuleI(Cheat, MQGhostExemptStatus)) == -1))) {
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
"Packet blocking detected.",
zone->GetShortName()
);
LogCheat(
std::string message = fmt::format(
"[MQGhost] [{}] [{}] was caught not sending the proper packets as regularly as they were suppose to.",
m_target->AccountName(),
m_target->GetName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat("{}", message);
}
break;
case MQFastMem:
@ -222,12 +184,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
}
break;
@ -238,12 +195,7 @@ void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3
position1.y,
position1.z
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
LogCheat(fmt::runtime(message));
break;
}

View File

@ -66,6 +66,9 @@ extern volatile bool RunLoops;
#include "../common/repositories/character_spells_repository.h"
#include "../common/repositories/character_disciplines_repository.h"
#include "../common/repositories/character_data_repository.h"
#include "../common/repositories/discovered_items_repository.h"
#include "../common/events/player_events.h"
#include "../common/events/player_event_logs.h"
extern QueryServ* QServ;
@ -1122,6 +1125,17 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
break;
}
case ChatChannel_Say: { /* Say */
if (player_event_logs.IsEventEnabled(PlayerEvent::SAY)) {
std::string msg = message;
if (!msg.empty() && msg.at(0) != '#' && msg.at(0) != '^') {
auto e = PlayerEvent::SayEvent{
.message = message,
.target = GetTarget() ? GetTarget()->GetCleanName() : ""
};
RecordPlayerEventLog(PlayerEvent::SAY, e);
}
}
if (message[0] == COMMAND_CHAR) {
if (command_dispatch(this, message, false) == -2) {
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
@ -2542,6 +2556,17 @@ bool Client::CheckIncreaseSkill(EQ::skills::SkillType skillid, Mob *against_who,
0
);
parse->EventPlayer(EVENT_SKILL_UP, this, export_string, 0);
if (player_event_logs.IsEventEnabled(PlayerEvent::SKILL_UP)) {
auto e = PlayerEvent::SkillUpEvent{
.skill_id = static_cast<uint32>(skillid),
.value = (skillval + 1),
.max_skill = static_cast<int16>(maxskill),
.against_who = (against_who) ? against_who->GetCleanName() : GetCleanName(),
};
RecordPlayerEventLog(PlayerEvent::SKILL_UP, e);
}
LogSkills("Skill [{}] at value [{}] successfully gain with [{}] chance (mod [{}])", skillid, skillval, Chance, chancemodi);
return true;
} else {
@ -2764,35 +2789,6 @@ void Client::MemorizeSpell(uint32 slot,uint32 spellid,uint32 scribing, uint32 re
safe_delete(outapp);
}
void Client::LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const EQ::ItemData* item, bool buying)
{
if(!player || !merchant || !item)
return;
std::string LogText = "Qty: ";
char Buffer[255];
memset(Buffer, 0, sizeof(Buffer));
snprintf(Buffer, sizeof(Buffer)-1, "%3i", quantity);
LogText += Buffer;
snprintf(Buffer, sizeof(Buffer)-1, "%10i", price);
LogText += " TotalValue: ";
LogText += Buffer;
snprintf(Buffer, sizeof(Buffer)-1, " ItemID: %7i", item->ID);
LogText += Buffer;
LogText += " ";
snprintf(Buffer, sizeof(Buffer)-1, " %s", item->Name);
LogText += Buffer;
if (buying==true) {
database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Buying from Merchant",LogText.c_str(),2);
}
else {
database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Selling to Merchant",LogText.c_str(),3);
}
}
void Client::Disarm(Client* disarmer, int chance) {
int16 slot = EQ::invslot::SLOT_INVALID;
const EQ::ItemInstance *inst = GetInv().GetItem(EQ::invslot::slotPrimary);
@ -4044,36 +4040,42 @@ void Client::KeyRingList()
}
}
bool Client::IsDiscovered(uint32 itemid) {
std::string query = StringFormat("SELECT count(*) FROM discovered_items WHERE item_id = '%lu'", itemid);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
bool Client::IsDiscovered(uint32 item_id) {
const auto& l = DiscoveredItemsRepository::GetWhere(
database,
fmt::format(
"item_id = {}",
item_id
)
);
if (l.empty()) {
return false;
}
auto row = results.begin();
if (!atoi(row[0]))
return false;
return true;
}
void Client::DiscoverItem(uint32 itemid) {
void Client::DiscoverItem(uint32 item_id) {
auto e = DiscoveredItemsRepository::NewEntity();
std::string query = StringFormat("INSERT INTO discovered_items "
"SET item_id = %lu, char_name = '%s', "
"discovered_date = UNIX_TIMESTAMP(), account_status = %i",
itemid, GetName(), Admin());
auto results = database.QueryDatabase(query);
e.account_status = Admin();
e.char_name = GetCleanName();
e.discovered_date = std::time(nullptr);
e.item_id = item_id;
auto* inst = database.CreateItem(itemid);
auto d = DiscoveredItemsRepository::InsertOne(database, e);
std::vector<std::any> args;
parse->EventPlayer(EVENT_DISCOVER_ITEM, this, "", item_id);
args.emplace_back(inst);
if (player_event_logs.IsEventEnabled(PlayerEvent::DISCOVER_ITEM)) {
const auto* item = database.GetItem(item_id);
parse->EventPlayer(EVENT_DISCOVER_ITEM, this, "", itemid, &args);
auto e = PlayerEvent::DiscoverItemEvent{
.item_id = item_id,
.item_name = item->Name,
};
RecordPlayerEventLog(PlayerEvent::DISCOVER_ITEM, e);
}
}
void Client::UpdateLFP() {
@ -6870,7 +6872,7 @@ void Client::SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount)
SendAlternateCurrencyValue(currency_id);
}
void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method)
int Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method)
{
/* Added via Quest, rest of the logging methods may be done inline due to information available in that area of the code */
@ -6883,12 +6885,12 @@ void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 me
}
if(amount == 0) {
return;
return 0;
}
if(!alternate_currency_loaded) {
alternate_currency_queued_operations.push(std::make_pair(currency_id, amount));
return;
return 0;
}
int new_value = 0;
@ -6907,6 +6909,8 @@ void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 me
database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_value);
}
SendAlternateCurrencyValue(currency_id);
return new_value;
}
void Client::SendAlternateCurrencyValues()
@ -11859,7 +11863,7 @@ void Client::SendPath(Mob* target)
target->CastToClient()->Trader ||
target->CastToClient()->Buyer
)
) {
) {
Message(
Chat::Yellow,
fmt::format(
@ -11894,7 +11898,8 @@ void Client::SendPath(Mob* target)
points.push_back(a);
points.push_back(b);
} else {
}
else {
glm::vec3 path_start(
GetX(),
GetY(),
@ -11907,8 +11912,8 @@ void Client::SendPath(Mob* target)
target->GetZ() + (target->GetSize() < 6.0 ? 6 : target->GetSize()) * HEAD_POSITION
);
bool partial = false;
bool stuck = false;
bool partial = false;
bool stuck = false;
auto path_list = zone->pathing->FindRoute(path_start, path_end, partial, stuck);
if (path_list.empty() || partial) {
@ -11939,7 +11944,7 @@ void Client::SendPath(Mob* target)
p.z = GetZ();
points.push_back(p);
for (const auto& n : path_list) {
for (const auto &n: path_list) {
if (n.teleport) {
leads_to_teleporter = true;
break;
@ -11967,10 +11972,201 @@ void Client::SendPath(Mob* target)
SendPathPacket(points);
}
void Client::UseAugmentContainer(int container_slot) {
void Client::UseAugmentContainer(int container_slot)
{
auto in_augment = new AugmentItem_Struct[sizeof(AugmentItem_Struct)];
in_augment->container_slot = container_slot;
in_augment->augment_slot = -1;
in_augment->augment_slot = -1;
Object::HandleAugmentation(this, in_augment, nullptr);
safe_delete_array(in_augment);
}
PlayerEvent::PlayerEvent Client::GetPlayerEvent()
{
auto e = PlayerEvent::PlayerEvent{};
e.account_id = AccountID();
e.character_id = CharacterID();
e.character_name = GetCleanName();
e.x = GetX();
e.y = GetY();
e.z = GetZ();
e.heading = GetHeading();
e.zone_id = GetZoneID();
e.zone_short_name = zone ? zone->GetShortName() : "";
e.zone_long_name = zone ? zone->GetLongName() : "";
e.instance_id = GetInstanceID();
e.guild_id = GuildID();
e.guild_name = guild_mgr.GetGuildName(GuildID());
e.account_name = AccountName();
return e;
}
void Client::PlayerTradeEventLog(Trade *t, Trade *t2)
{
Client *trader = t->GetOwner()->CastToClient();
Client *trader2 = t2->GetOwner()->CastToClient();
uint8 t_item_count = 0;
uint8 t2_item_count = 0;
auto money_t = PlayerEvent::Money{
.platinum = t->pp,
.gold = t->gp,
.silver = t->sp,
.copper = t->cp,
};
auto money_t2 = PlayerEvent::Money{
.platinum = t2->pp,
.gold = t2->gp,
.silver = t2->sp,
.copper = t2->cp,
};
// trader 1 item count
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) {
if (trader->GetInv().GetItem(i)) {
t_item_count++;
}
}
// trader 2 item count
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) {
if (trader2->GetInv().GetItem(i)) {
t2_item_count++;
}
}
std::vector<PlayerEvent::TradeItemEntry> t_entries = {};
t_entries.reserve(t_item_count);
if (t_item_count > 0) {
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) {
const EQ::ItemInstance *inst = trader->GetInv().GetItem(i);
if (inst) {
t_entries.emplace_back(
PlayerEvent::TradeItemEntry{
.slot = i,
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
.charges = static_cast<uint16>(inst->GetCharges()),
.aug_1_item_id = inst->GetAugmentItemID(0),
.aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "",
.aug_2_item_id = inst->GetAugmentItemID(1),
.aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "",
.aug_3_item_id = inst->GetAugmentItemID(2),
.aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "",
.aug_4_item_id = inst->GetAugmentItemID(3),
.aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "",
.aug_5_item_id = inst->GetAugmentItemID(4),
.aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "",
.aug_6_item_id = inst->GetAugmentItemID(5),
.aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "",
.in_bag = false,
}
);
if (inst->IsClassBag()) {
for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) {
inst = trader->GetInv().GetItem(i, j);
if (inst) {
t_entries.emplace_back(
PlayerEvent::TradeItemEntry{
.slot = j,
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
.charges = static_cast<uint16>(inst->GetCharges()),
.aug_1_item_id = inst->GetAugmentItemID(0),
.aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "",
.aug_2_item_id = inst->GetAugmentItemID(1),
.aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "",
.aug_3_item_id = inst->GetAugmentItemID(2),
.aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "",
.aug_4_item_id = inst->GetAugmentItemID(3),
.aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "",
.aug_5_item_id = inst->GetAugmentItemID(4),
.aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "",
.aug_6_item_id = inst->GetAugmentItemID(5),
.aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "",
.in_bag = true,
}
);
}
}
}
}
}
}
std::vector<PlayerEvent::TradeItemEntry> t2_entries = {};
t_entries.reserve(t2_item_count);
if (t2_item_count > 0) {
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) {
const EQ::ItemInstance *inst = trader2->GetInv().GetItem(i);
if (inst) {
t2_entries.emplace_back(
PlayerEvent::TradeItemEntry{
.slot = i,
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
.charges = static_cast<uint16>(inst->GetCharges()),
.aug_1_item_id = inst->GetAugmentItemID(0),
.aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "",
.aug_2_item_id = inst->GetAugmentItemID(1),
.aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "",
.aug_3_item_id = inst->GetAugmentItemID(2),
.aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "",
.aug_4_item_id = inst->GetAugmentItemID(3),
.aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "",
.aug_5_item_id = inst->GetAugmentItemID(4),
.aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "",
.aug_6_item_id = inst->GetAugmentItemID(5),
.aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "",
.in_bag = false,
}
);
if (inst->IsClassBag()) {
for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) {
inst = trader2->GetInv().GetItem(i, j);
if (inst) {
t2_entries.emplace_back(
PlayerEvent::TradeItemEntry{
.slot = j,
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
.charges = static_cast<uint16>(inst->GetCharges()),
.aug_1_item_id = inst->GetAugmentItemID(0),
.aug_1_item_name = inst->GetAugment(0) ? inst->GetAugment(0)->GetItem()->Name : "",
.aug_2_item_id = inst->GetAugmentItemID(1),
.aug_2_item_name = inst->GetAugment(1) ? inst->GetAugment(1)->GetItem()->Name : "",
.aug_3_item_id = inst->GetAugmentItemID(2),
.aug_3_item_name = inst->GetAugment(2) ? inst->GetAugment(2)->GetItem()->Name : "",
.aug_4_item_id = inst->GetAugmentItemID(3),
.aug_4_item_name = inst->GetAugment(3) ? inst->GetAugment(3)->GetItem()->Name : "",
.aug_5_item_id = inst->GetAugmentItemID(4),
.aug_5_item_name = inst->GetAugment(4) ? inst->GetAugment(4)->GetItem()->Name : "",
.aug_6_item_id = inst->GetAugmentItemID(5),
.aug_6_item_name = inst->GetAugment(5) ? inst->GetAugment(5)->GetItem()->Name : "",
.in_bag = true,
}
);
}
}
}
}
}
}
auto e = PlayerEvent::TradeEvent{
.character_1_id = trader->CharacterID(),
.character_1_name = trader->GetCleanName(),
.character_2_id = trader2->CharacterID(),
.character_2_name = trader2->GetCleanName(),
.character_1_give_money = money_t,
.character_2_give_money = money_t2,
.character_1_give_items = t_entries,
.character_2_give_items = t2_entries
};
RecordPlayerEventLogWithClient(trader, PlayerEvent::TRADE, e);
RecordPlayerEventLogWithClient(trader2, PlayerEvent::TRADE, e);
}

View File

@ -67,6 +67,7 @@ namespace EQ
#include "task_manager.h"
#include "task_client_state.h"
#include "cheat_manager.h"
#include "../common/events/player_events.h"
#ifdef _WINDOWS
// since windows defines these within windef.h (which windows.h include)
@ -329,7 +330,6 @@ public:
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
virtual bool Process();
void ProcessPackets();
void LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const EQ::ItemData* item, bool buying);
void QueuePacket(const EQApplicationPacket* app, bool ack_req = true, CLIENT_CONN_STATUS = CLIENT_CONNECTINGALL, eqFilterType filter=FilterNone);
void FastQueuePacket(EQApplicationPacket** app, bool ack_req = true, CLIENT_CONN_STATUS = CLIENT_CONNECTINGALL);
void ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_skill, const char* orig_message, const char* targetname = nullptr, bool is_silent = false);
@ -1487,7 +1487,7 @@ public:
void ConsentCorpses(std::string consent_name, bool deny = false);
void SendAltCurrencies();
void SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount);
void AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method = 0);
int AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method = 0);
void SendAlternateCurrencyValues();
void SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null = true);
uint32 GetAlternateCurrencyValue(uint32 currency_id) const;
@ -1679,6 +1679,8 @@ public:
std::string GetGuildPublicNote();
PlayerEvent::PlayerEvent GetPlayerEvent();
void RecordKilledNPCEvent(NPC *n);
protected:
friend class Mob;
void CalcItemBonuses(StatBonuses* newbon);
@ -2086,6 +2088,7 @@ private:
bool CanTradeFVNoDropItem();
void SendMobPositions();
void PlayerTradeEventLog(Trade *t, Trade *t2);
};
#endif

View File

@ -71,6 +71,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/repositories/account_repository.h"
#include "bot.h"
#include "../common/events/player_event_logs.h"
extern QueryServ* QServ;
extern Zone* zone;
@ -790,6 +791,7 @@ void Client::CompleteConnect()
/* This sub event is for if a player logs in for the first time since entering world. */
if (firstlogon == 1) {
parse->EventPlayer(EVENT_CONNECT, this, "", 0);
RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{});
/* QS: PlayerLogConnectDisconnect */
if (RuleB(QueryServ, PlayerLogConnectDisconnect)) {
std::string event_desc = StringFormat("Connect :: Logged into zoneid:%i instid:%i", GetZoneID(), GetInstanceID());
@ -2240,9 +2242,6 @@ void Client::Handle_OP_AdventureMerchantSell(const EQApplicationPacket *app)
return;
}
if (RuleB(EventLog, RecordSellToMerchant))
LogMerchant(this, vendor, ams_in->charges, price, item, false);
if (!inst->IsStackable())
{
DeleteItemInInventory(ams_in->slot);
@ -2600,10 +2599,28 @@ void Client::Handle_OP_AltCurrencyPurchase(const EQApplicationPacket *app)
);
parse->EventPlayer(EVENT_ALT_CURRENCY_MERCHANT_BUY, this, export_string, 0);
AddAlternateCurrencyValue(alt_cur_id, -((int32)cost));
int16 charges = 1;
if (item->MaxCharges != 0)
uint64 current_balance = AddAlternateCurrencyValue(alt_cur_id, -((int32) cost));
int16 charges = 1;
if (item->MaxCharges != 0) {
charges = item->MaxCharges;
}
if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) {
auto e = PlayerEvent::MerchantPurchaseEvent{
.npc_id = tar->GetNPCTypeID(),
.merchant_name = tar->GetCleanName(),
.merchant_type = tar->MerchantType,
.item_id = item->ID,
.item_name = item->Name,
.charges = charges,
.cost = cost,
.alternate_currency_id = alt_cur_id,
.player_money_balance = GetCarriedMoney(),
.player_currency_balance = current_balance,
};
RecordPlayerEventLog(PlayerEvent::MERCHANT_PURCHASE, e);
}
EQ::ItemInstance *inst = database.CreateItem(item, charges);
if (!AutoPutLootInInventory(*inst, true, true))
@ -2774,7 +2791,25 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app)
parse->EventPlayer(EVENT_ALT_CURRENCY_MERCHANT_SELL, this, export_string, 0);
FastQueuePacket(&outapp);
AddAlternateCurrencyValue(alt_cur_id, cost);
uint64 new_balance = AddAlternateCurrencyValue(alt_cur_id, cost);
if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_SELL)) {
auto e = PlayerEvent::MerchantSellEvent{
.npc_id = tar->GetNPCTypeID(),
.merchant_name = tar->GetCleanName(),
.merchant_type = tar->CastToNPC()->MerchantType,
.item_id = item->ID,
.item_name = item->Name,
.charges = static_cast<int16>(sell->charges),
.cost = cost,
.alternate_currency_id = 0,
.player_money_balance = GetCarriedMoney(),
.player_currency_balance = new_balance,
};
RecordPlayerEventLog(PlayerEvent::MERCHANT_SELL, e);
}
Save(1);
}
}
@ -3441,10 +3476,10 @@ void Client::Handle_OP_BankerChange(const EQApplicationPacket *app)
if (!banker || distance > USE_NPC_RANGE2)
{
auto hacked_string = fmt::format(
auto message = fmt::format(
"Player tried to make use of a banker(money) but {} is non-existant or too far away ({} units).",
banker ? banker->GetName() : "UNKNOWN NPC", distance);
database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
return;
}
@ -4164,7 +4199,8 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app)
const EQ::ItemData* item = inst->GetItem();
if (item->Click.Effect != (uint32)castspell->spell_id)
{
database.SetMQDetectionFlag(account_name, name, "OP_CastSpell with item, tried to cast a different spell.", zone->GetShortName());
std::string message = fmt::format("OP_CastSpell with item, tried to cast a different spell than what was on item - item spell id [{}] attempted [{}]", item->Click.Effect, (uint32)castspell->spell_id);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
InterruptSpell(castspell->spell_id); //CHEATER!!
return;
}
@ -4205,7 +4241,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app)
}
else
{
database.SetMQDetectionFlag(account_name, name, "OP_CastSpell with item, did not meet req level.", zone->GetShortName());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "OP_CastSpell with item, did not meet req level."});
Message(Chat::Red, "Error: level not high enough.", castspell->inventoryslot);
InterruptSpell(castspell->spell_id);
}
@ -5145,8 +5181,8 @@ void Client::Handle_OP_ControlBoat(const EQApplicationPacket *app)
if (!boat->IsNPC() || !boat->IsControllableBoat())
{
auto hacked_string = fmt::format("OP_Control Boat was sent against {} which is of race {}", boat->GetName(), boat->GetRace());
database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName());
auto message = fmt::format("OP_Control Boat was sent against {} which is of race {}", boat->GetName(), boat->GetRace());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
return;
}
@ -5415,6 +5451,17 @@ void Client::Handle_OP_DeleteItem(const EQApplicationPacket *app)
}
DeleteItemInInventory(alc->from_slot, 1);
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 deleted",
};
RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e);
}
return;
}
@ -5466,8 +5513,9 @@ void Client::Handle_OP_Disarm(const EQApplicationPacket *app) {
return;
if (pmob->GetID() != GetID()) {
// Client sent a disarm request with an originator ID not matching their own ID.
auto hack_str = fmt::format("Player {} ({}) sent OP_Disarm with source ID of: {}", GetCleanName(), GetID(), pmob->GetID());
database.SetMQDetectionFlag(account_name, name, hack_str, zone->GetShortName());
auto message = fmt::format("Player {} ({}) sent OP_Disarm with source ID of: {}", GetCleanName(), GetID(), pmob->GetID());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
return;
}
// No disarm on corpses
@ -6194,7 +6242,7 @@ void Client::Handle_OP_GMBecomeNPC(const EQApplicationPacket *app)
{
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/becomenpc");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /becomenpc when they shouldn't be able to"});
return;
}
if (app->size != sizeof(BecomeNPC_Struct)) {
@ -6234,7 +6282,7 @@ void Client::Handle_OP_GMDelCorpse(const EQApplicationPacket *app)
return;
if (Admin() < commandEditPlayerCorpses) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/delcorpse");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /delcorpse"});
return;
}
GMDelCorpse_Struct* dc = (GMDelCorpse_Struct *)app->pBuffer;
@ -6255,7 +6303,6 @@ void Client::Handle_OP_GMEmoteZone(const EQApplicationPacket *app)
{
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/emote");
return;
}
if (app->size != sizeof(GMEmoteZone_Struct)) {
@ -6288,7 +6335,7 @@ void Client::Handle_OP_GMFind(const EQApplicationPacket *app)
{
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/find");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /find"});
return;
}
if (app->size != sizeof(GMSummon_Struct)) {
@ -6326,7 +6373,7 @@ void Client::Handle_OP_GMGoto(const EQApplicationPacket *app)
}
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/goto");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /goto"});
return;
}
GMSummon_Struct* gmg = (GMSummon_Struct*)app->pBuffer;
@ -6353,7 +6400,7 @@ void Client::Handle_OP_GMHideMe(const EQApplicationPacket *app)
{
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/hideme");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /hideme"});
return;
}
if (app->size != sizeof(SpawnAppearance_Struct)) {
@ -6373,7 +6420,7 @@ void Client::Handle_OP_GMKick(const EQApplicationPacket *app)
return;
if (Admin() < minStatusToKick) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/kick");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /kick"});
return;
}
GMKick_Struct* gmk = (GMKick_Struct *)app->pBuffer;
@ -6403,7 +6450,7 @@ void Client::Handle_OP_GMKill(const EQApplicationPacket *app)
{
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/kill");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /kill"});
return;
}
if (app->size != sizeof(GMKill_Struct)) {
@ -6455,7 +6502,7 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app)
else {
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(client->account_name, client->name, "/lastname");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /lastname"});
return;
}
else
@ -6480,7 +6527,7 @@ void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
const GMName_Struct* gmn = (const GMName_Struct *)app->pBuffer;
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/name");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /name"});
return;
}
Client* client = entity_list.GetClientByName(gmn->oldname);
@ -6623,7 +6670,7 @@ void Client::Handle_OP_GMToggle(const EQApplicationPacket *app)
}
if (Admin() < minStatusToUseGMCommands) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/toggle");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /toggle"});
return;
}
GMToggle_Struct *ts = (GMToggle_Struct *)app->pBuffer;
@ -6670,7 +6717,7 @@ void Client::Handle_OP_GMZoneRequest(const EQApplicationPacket *app)
}
if (Admin() < minStatusToBeGM) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/zone");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /zone"});
return;
}
@ -6726,7 +6773,7 @@ void Client::Handle_OP_GMZoneRequest2(const EQApplicationPacket *app)
{
if (Admin() < minStatusToBeGM) {
Message(Chat::Red, "Your account has been reported for hacking.");
database.SetHackerFlag(account_name, name, "/zone");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Used /zone"});
return;
}
if (app->size < sizeof(uint32)) {
@ -8400,9 +8447,12 @@ void Client::Handle_OP_Illusion(const EQApplicationPacket *app)
return;
}
if (!GetGM())
{
database.SetMQDetectionFlag(AccountName(), GetName(), "OP_Illusion sent by non Game Master.", zone->GetShortName());
if (!GetGM()) {
RecordPlayerEventLog(
PlayerEvent::POSSIBLE_HACK,
PlayerEvent::PossibleHackEvent{.message = "OP_Illusion sent by non Game Master"}
);
return;
}
@ -10199,13 +10249,13 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app)
{
const EQ::ItemInstance *itm_from = GetInv().GetItem(mi->from_slot);
const EQ::ItemInstance *itm_to = GetInv().GetItem(mi->to_slot);
auto detect = fmt::format("Player issued a move item from {}(item id {}) to {}(item id {}) while casting {}.",
auto message = fmt::format("Player issued a move item from {}(item id {}) to {}(item id {}) while casting {}.",
mi->from_slot,
itm_from ? itm_from->GetID() : 0,
mi->to_slot,
itm_to ? itm_to->GetID() : 0,
casting_spell_id);
database.SetMQDetectionFlag(AccountName(), GetName(), detect, zone->GetShortName());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
Kick("Inventory desync"); // Kick client to prevent client and server from getting out-of-sync inventory slots
return;
}
@ -11188,7 +11238,7 @@ void Client::Handle_OP_PickPocket(const EQApplicationPacket *app)
if (!p_timers.Expired(&database, pTimerBeggingPickPocket, false))
{
Message(Chat::Red, "Ability recovery time not yet met.");
database.SetMQDetectionFlag(AccountName(), GetName(), "OP_PickPocket was sent again too quickly.", zone->GetShortName());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "OP_PickPocket was sent again too quickly."});
return;
}
PickPocket_Struct* pick_in = (PickPocket_Struct*)app->pBuffer;
@ -11210,7 +11260,7 @@ void Client::Handle_OP_PickPocket(const EQApplicationPacket *app)
}
else if (Distance(GetPosition(), victim->GetPosition()) > 20) {
Message(Chat::Red, "Attempt to pickpocket out of range detected.");
database.SetMQDetectionFlag(AccountName(), GetName(), "OP_PickPocket was sent from outside combat range.", zone->GetShortName());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "OP_PickPocket was sent from outside combat range"});
}
else if (victim->IsNPC()) {
auto body = victim->GetBodyType();
@ -12696,16 +12746,27 @@ void Client::Handle_OP_RezzAnswer(const EQApplicationPacket *app)
{
VERIFY_PACKET_LENGTH(OP_RezzAnswer, app, Resurrect_Struct);
const Resurrect_Struct* ra = (const Resurrect_Struct*)app->pBuffer;
const auto* r = (const Resurrect_Struct*) app->pBuffer;
LogSpells("[Client::Handle_OP_RezzAnswer] Received OP_RezzAnswer from client. Pendingrezzexp is [{}] action is [{}]",
PendingRezzXP, ra->action ? "ACCEPT" : "DECLINE");
LogSpells(
"[Client::Handle_OP_RezzAnswer] Received OP_RezzAnswer from client. Pendingrezzexp is [{}] action is [{}]",
PendingRezzXP,
r->action ? "ACCEPT" : "DECLINE"
);
OPRezzAnswer(ra->action, ra->spellid, ra->zone_id, ra->instance_id, ra->x, ra->y, ra->z);
OPRezzAnswer(r->action, r->spellid, r->zone_id, r->instance_id, r->x, r->y, r->z);
if (r->action == ResurrectionActions::Accept) {
if (player_event_logs.IsEventEnabled(PlayerEvent::REZ_ACCEPTED)) {
auto e = PlayerEvent::ResurrectAcceptEvent{
.resurrecter_name = r->rezzer_name,
.spell_name = spells[r->spellid].name,
.spell_id = r->spellid,
};
RecordPlayerEventLog(PlayerEvent::REZ_ACCEPTED, e);
}
if (ra->action == 1)
{
EQApplicationPacket* outapp = app->Copy();
// Send the OP_RezzComplete to the world server. This finds it's way to the zone that
// the rezzed corpse is in to mark the corpse as rezzed.
@ -12713,7 +12774,6 @@ void Client::Handle_OP_RezzAnswer(const EQApplicationPacket *app)
worldserver.RezzPlayer(outapp, 0, 0, OP_RezzComplete);
safe_delete(outapp);
}
return;
}
void Client::Handle_OP_Sacrifice(const EQApplicationPacket *app)
@ -13236,10 +13296,19 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
if (!TakeMoneyFromPP(mpo->price))
{
auto hacker_str = fmt::format("Vendor Cheat: attempted to buy {} of {}: {} that cost {} cp but only has {} pp {} gp {} sp {} cp",
mpo->quantity, item->ID, item->Name,
mpo->price, m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper);
database.SetMQDetectionFlag(AccountName(), GetName(), Strings::Escape(hacker_str), zone->GetShortName());
auto message = fmt::format(
"Vendor Cheat attempted to buy qty [{}] of item_id [{}] item_name[{}] that cost [{}] copper but only has platinum [{}] gold [{}] silver [{}] copper [{}]",
mpo->quantity,
item->ID,
item->Name,
mpo->price,
m_pp.platinum,
m_pp.gold,
m_pp.silver,
m_pp.copper
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
safe_delete(outapp);
safe_delete(inst);
return;
@ -13304,6 +13373,23 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
safe_delete(inst);
safe_delete(outapp);
if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) {
auto e = PlayerEvent::MerchantPurchaseEvent{
.npc_id = tmp->GetNPCTypeID(),
.merchant_name = tmp->GetCleanName(),
.merchant_type = tmp->CastToNPC()->MerchantType,
.item_id = item->ID,
.item_name = item->Name,
.charges = static_cast<int16>(mpo->quantity),
.cost = mpo->price,
.alternate_currency_id = 0,
.player_money_balance = GetCarriedMoney(),
.player_currency_balance = 0,
};
RecordPlayerEventLog(PlayerEvent::MERCHANT_PURCHASE, e);
}
// start QS code
// stacking purchases not supported at this time - entire process will need some work to catch them properly
if (RuleB(QueryServ, PlayerLogMerchantTransactions)) {
@ -13359,9 +13445,6 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
}
// end QS code
if (RuleB(EventLog, RecordBuyFromMerchant))
LogMerchant(this, tmp, mpo->quantity, mpo->price, item, true);
const auto& export_string = fmt::format(
"{} {} {} {} {}",
tmp->GetNPCTypeID(),
@ -13372,10 +13455,24 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
);
parse->EventPlayer(EVENT_MERCHANT_BUY, this, export_string, 0);
if ((RuleB(Character, EnableDiscoveredItems)))
{
if (!GetGM() && !IsDiscovered(item_id))
DiscoverItem(item_id);
if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_PURCHASE)) {
auto e = PlayerEvent::MerchantPurchaseEvent{
.npc_id = tmp->GetNPCTypeID(),
.merchant_name = tmp->GetCleanName(),
.merchant_type = tmp->CastToNPC()->MerchantType,
.item_id = item->ID,
.item_name = item->Name,
.charges = static_cast<int16>(mpo->quantity),
.cost = mpo->price,
.alternate_currency_id = 0,
.player_money_balance = GetCarriedMoney(),
.player_currency_balance = 0,
};
RecordPlayerEventLog(PlayerEvent::MERCHANT_PURCHASE, e);
}
if (RuleB(Character, EnableDiscoveredItems) && !GetGM() && !IsDiscovered(item_id)) {
DiscoverItem(item_id);
}
t1.stop();
@ -13460,9 +13557,6 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
else
mp->quantity = 1;
if (RuleB(EventLog, RecordSellToMerchant))
LogMerchant(this, vendor, mp->quantity, price, item, false);
int charges = mp->quantity;
if (vendor->GetKeepsSoldItems()) {
@ -13475,7 +13569,7 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
charges,
true
)
) > 0) {
) > 0) {
EQ::ItemInstance *inst2 = inst->Clone();
while (true) {
@ -13557,6 +13651,22 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
);
parse->EventPlayer(EVENT_MERCHANT_SELL, this, export_string, 0);
if (player_event_logs.IsEventEnabled(PlayerEvent::MERCHANT_SELL)) {
auto e = PlayerEvent::MerchantSellEvent{
.npc_id = vendor->GetNPCTypeID(),
.merchant_name = vendor->GetCleanName(),
.merchant_type = vendor->CastToNPC()->MerchantType,
.item_id = item->ID,
.item_name = item->Name,
.charges = static_cast<int16>(mp->quantity),
.cost = price,
.alternate_currency_id = 0,
.player_money_balance = GetCarriedMoney(),
.player_currency_balance = 0,
};
RecordPlayerEventLog(PlayerEvent::MERCHANT_SELL, e);
}
// Now remove the item from the player, this happens regardless of outcome
DeleteItemInInventory(
@ -13747,8 +13857,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
{
if (ClientVersion() < EQ::versions::ClientVersion::SoF)
{
auto hack_str = fmt::format("Player sent OP_SpawnAppearance with AT_Invis: {}", sa->parameter);
database.SetMQDetectionFlag(account_name, name, hack_str, zone->GetShortName());
auto message = fmt::format("Player sent OP_SpawnAppearance with AT_Invis [{}]", sa->parameter);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
}
}
return;
@ -13847,8 +13957,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
{
if (!HasSkill(EQ::skills::SkillSneak))
{
auto hack_str = fmt::format("Player sent OP_SpawnAppearance with AT_Sneak: {}", sa->parameter);
database.SetMQDetectionFlag(account_name, name, hack_str, zone->GetShortName());
auto message = fmt::format("Player sent OP_SpawnAppearance with AT_Sneak [{}]", sa->parameter);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
}
return;
}
@ -13857,8 +13967,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
}
else if (sa->type == AT_Size)
{
auto hack_str = fmt::format("Player sent OP_SpawnAppearance with AT_Size: {}", sa->parameter);
database.SetMQDetectionFlag(account_name, name, hack_str, zone->GetShortName());
auto message = fmt::format("Player sent OP_SpawnAppearance with AT_Size [{}]", sa->parameter);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
}
else if (sa->type == AT_Light) // client emitting light (lightstone, shiny shield)
{
@ -14201,9 +14311,14 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
else if (GetTarget()->GetBodyType() == BT_NoTarget2 || GetTarget()->GetBodyType() == BT_Special
|| GetTarget()->GetBodyType() == BT_NoTarget)
{
auto hacker_str = fmt::format("{} attempting to target something untargetable, {} bodytype: {}",
GetName(), GetTarget()->GetName(), (int)GetTarget()->GetBodyType());
database.SetMQDetectionFlag(AccountName(), GetName(), Strings::Escape(hacker_str), zone->GetShortName());
auto message = fmt::format(
"[{}] attempting to target something untargetable [{}] bodytype [{}]",
GetName(),
GetTarget()->GetName(),
(int) GetTarget()->GetBodyType()
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
SetTarget((Mob*)nullptr);
return;
}
@ -14232,15 +14347,16 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
{
if (DistanceSquared(m_Position, GetTarget()->GetPosition()) > (zone->newzone_data.maxclip*zone->newzone_data.maxclip))
{
auto hacker_str = fmt::format(
"{} attempting to target something beyond the clip plane of {:.2f} "
auto message = fmt::format(
"[{}] attempting to target something beyond the clip plane of {:.2f} "
"units, from ({:.2f}, {:.2f}, {:.2f}) to {} ({:.2f}, {:.2f}, "
"{:.2f})",
GetName(),
(zone->newzone_data.maxclip * zone->newzone_data.maxclip), GetX(),
GetY(), GetZ(), GetTarget()->GetName(), GetTarget()->GetX(),
GetTarget()->GetY(), GetTarget()->GetZ());
database.SetMQDetectionFlag(AccountName(), GetName(), Strings::Escape(hacker_str), zone->GetShortName());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
SetTarget(nullptr);
return;
}
@ -14248,13 +14364,21 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
}
else if (DistanceSquared(m_Position, GetTarget()->GetPosition()) > (zone->newzone_data.maxclip*zone->newzone_data.maxclip))
{
auto hacker_str =
fmt::format("{} attempting to target something beyond the clip plane of {:.2f} "
"units, from ({:.2f}, {:.2f}, {:.2f}) to {} ({:.2f}, {:.2f}, {:.2f})",
GetName(), (zone->newzone_data.maxclip * zone->newzone_data.maxclip),
GetX(), GetY(), GetZ(), GetTarget()->GetName(), GetTarget()->GetX(),
GetTarget()->GetY(), GetTarget()->GetZ());
database.SetMQDetectionFlag(AccountName(), GetName(), Strings::Escape(hacker_str), zone->GetShortName());
auto message = fmt::format(
"{} attempting to target something beyond the clip plane of {:.2f} "
"units, from ({:.2f}, {:.2f}, {:.2f}) to {} ({:.2f}, {:.2f}, {:.2f})",
GetName(),
(zone->newzone_data.maxclip * zone->newzone_data.maxclip),
GetX(),
GetY(),
GetZ(),
GetTarget()->GetName(),
GetTarget()->GetX(),
GetTarget()->GetY(),
GetTarget()->GetZ()
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
SetTarget(nullptr);
return;
}
@ -14411,16 +14535,14 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app)
// TODO: query (other) as a hacker
}
else {
// Audit trade to database for both trade streams
other->trade->LogTrade();
trade->LogTrade();
other->PlayerTradeEventLog(other->trade, trade);
// start QS code
if (RuleB(QueryServ, PlayerLogTrades)) {
QSPlayerLogTrade_Struct event_entry;
PlayerLogTrade_Struct event_entry;
std::list<void*> event_details;
memset(&event_entry, 0, sizeof(QSPlayerLogTrade_Struct));
memset(&event_entry, 0, sizeof(PlayerLogTrade_Struct));
// Perform actual trade
FinishTrade(other, true, &event_entry, &event_details);
@ -14430,18 +14552,18 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app)
auto qs_pack = new ServerPacket(
ServerOP_QSPlayerLogTrades,
sizeof(QSPlayerLogTrade_Struct) +
(sizeof(QSTradeItems_Struct) * event_entry._detail_count));
QSPlayerLogTrade_Struct* qs_buf = (QSPlayerLogTrade_Struct*)qs_pack->pBuffer;
sizeof(PlayerLogTrade_Struct) +
(sizeof(PlayerLogTradeItemsEntry_Struct) * event_entry._detail_count));
PlayerLogTrade_Struct* qs_buf = (PlayerLogTrade_Struct*)qs_pack->pBuffer;
memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogTrade_Struct));
memcpy(qs_buf, &event_entry, sizeof(PlayerLogTrade_Struct));
int offset = 0;
for (auto iter = event_details.begin(); iter != event_details.end();
++iter, ++offset) {
QSTradeItems_Struct* detail = reinterpret_cast<QSTradeItems_Struct*>(*iter);
qs_buf->items[offset] = *detail;
PlayerLogTradeItemsEntry_Struct* detail = reinterpret_cast<PlayerLogTradeItemsEntry_Struct*>(*iter);
qs_buf->item_entries[offset] = *detail;
safe_delete(detail);
}
@ -15767,3 +15889,32 @@ void Client::SendMobPositions()
}
safe_delete(p);
}
struct RecordKillCheck {
PlayerEvent::EventType event;
bool check;
};
void Client::RecordKilledNPCEvent(NPC *n)
{
bool is_named = Strings::Contains(n->GetName(), "#") && !n->IsRaidTarget();
std::vector<RecordKillCheck> checks = {
RecordKillCheck{.event = PlayerEvent::KILLED_NPC, .check = true},
RecordKillCheck{.event = PlayerEvent::KILLED_NAMED_NPC, .check = is_named},
RecordKillCheck{.event = PlayerEvent::KILLED_RAID_NPC, .check = n->IsRaidTarget()},
};
for (auto &c: checks) {
if (c.check && player_event_logs.IsEventEnabled(c.event)) {
auto e = PlayerEvent::KilledNPCEvent{
.npc_id = n->GetNPCTypeID(),
.npc_name = n->GetCleanName(),
.combat_time_seconds = static_cast<uint32>(n->GetCombatRecord().TimeInCombat()),
.total_damage_per_second_taken = static_cast<uint64>(n->GetCombatRecord().GetDamageReceivedPerSecond()),
.total_heal_per_second_taken = static_cast<uint64>(n->GetCombatRecord().GetHealedReceivedPerSecond()),
};
RecordPlayerEventLog(c.event, e);
}
}
}

View File

@ -55,6 +55,7 @@
#include "zone.h"
#include "zonedb.h"
#include "../common/zone_store.h"
#include "../common/events/player_event_logs.h"
extern QueryServ* QServ;
extern Zone* zone;
@ -184,6 +185,7 @@ bool Client::Process() {
SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Offline);
parse->EventPlayer(EVENT_DISCONNECT, this, "", 0);
RecordPlayerEventLog(PlayerEvent::WENT_OFFLINE, PlayerEvent::EmptyEvent{});
return false; //delete client
}
@ -542,7 +544,7 @@ bool Client::Process() {
if (client_state == DISCONNECTED) {
OnDisconnect(true);
std::cout << "Client disconnected (cs=d): " << GetName() << std::endl;
database.SetMQDetectionFlag(AccountName(), GetName(), "/MQInstantCamp: Possible instant camp disconnect.", zone->GetShortName());
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "/MQInstantCamp: Possible instant camp disconnect"});
return false;
}
@ -693,6 +695,7 @@ void Client::OnDisconnect(bool hard_disconnect) {
MyRaid->MemberZoned(this);
parse->EventPlayer(EVENT_DISCONNECT, this, "", 0);
RecordPlayerEventLog(PlayerEvent::WENT_OFFLINE, PlayerEvent::EmptyEvent{});
/* QS: PlayerLogConnectDisconnect */
if (RuleB(QueryServ, PlayerLogConnectDisconnect)){
@ -1155,12 +1158,8 @@ void Client::OPMemorizeSpell(const EQApplicationPacket* app)
if (HasSpellScribed(m->spell_id)) {
MemSpell(m->spell_id, m->slot);
} else {
database.SetMQDetectionFlag(
AccountName(),
GetName(),
"OP_MemorizeSpell but we don't have this spell scribed...",
zone->GetShortName()
);
std::string message = fmt::format("OP_MemorizeSpell [{}] but we don't have this spell scribed", m->spell_id);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
}
break;
}
@ -1299,10 +1298,13 @@ void Client::OPMoveCoin(const EQApplicationPacket* app)
NPC *banker = entity_list.GetClosestBanker(this, distance);
if(!banker || distance > USE_NPC_RANGE2)
{
auto hacked_string = fmt::format("Player tried to make use of a banker(coin move) 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 (coin move) but "
"banker [{}] is non-existent or too far away [{}] units",
banker ? banker->GetName() : "UNKNOWN NPC", distance
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
return;
}
@ -1330,11 +1332,13 @@ void Client::OPMoveCoin(const EQApplicationPacket* app)
NPC *banker = entity_list.GetClosestBanker(this, distance);
if(!banker || distance > USE_NPC_RANGE2)
{
auto hacked_string =
fmt::format("Player tried to make use of a banker(shared coin move) 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 (shared coin move) but banker [{}] is "
"non-existent or too far away [{}] units",
banker ? banker->GetName() : "UNKNOWN NPC", distance
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
return;
}
if(mc->cointype1 == COINTYPE_PP) // there's only platinum here
@ -1386,10 +1390,13 @@ void Client::OPMoveCoin(const EQApplicationPacket* app)
NPC *banker = entity_list.GetClosestBanker(this, distance);
if(!banker || distance > USE_NPC_RANGE2)
{
auto hacked_string = fmt::format("Player tried to make use of a banker(coin move) 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(coin move) but "
"banker [{}] is non-existent or too far away [{}] units",
banker ? banker->GetName() : "UNKNOWN NPC", distance
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
return;
}
switch(mc->cointype2)
@ -1429,11 +1436,13 @@ void Client::OPMoveCoin(const EQApplicationPacket* app)
NPC *banker = entity_list.GetClosestBanker(this, distance);
if(!banker || distance > USE_NPC_RANGE2)
{
auto hacked_string =
fmt::format("Player tried to make use of a banker(shared coin move) 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 (shared coin move) but banker [{}] is "
"non-existent or too far away [{}] units",
banker ? banker->GetName() : "UNKNOWN NPC", distance
);
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
return;
}
if(mc->cointype2 == COINTYPE_PP) // there's only platinum here

View File

@ -4,50 +4,51 @@
void CombatRecord::Start(std::string in_mob_name)
{
start_time = std::time(nullptr);
end_time = 0;
damage_received = 0;
heal_received = 0;
mob_name = in_mob_name;
m_start_time = std::time(nullptr);
m_end_time = 0;
m_damage_received = 0;
m_heal_received = 0;
m_mob_name = in_mob_name;
}
void CombatRecord::Stop()
{
end_time = std::time(nullptr);
m_end_time = std::time(nullptr);
double time_in_combat = TimeInCombat();
LogCombatRecord(
"[Summary] Mob [{}] [Received] DPS [{:.0f}] Heal/s [{:.0f}] Duration [{}] ({}s)",
mob_name,
time_in_combat > 0 ? (damage_received / time_in_combat) : damage_received,
time_in_combat > 0 ? (heal_received / time_in_combat) : heal_received,
m_mob_name,
GetDamageReceivedPerSecond(),
GetHealedReceivedPerSecond(),
time_in_combat > 0 ? Strings::SecondsToTime(time_in_combat) : "",
time_in_combat
);
}
bool CombatRecord::InCombat()
bool CombatRecord::InCombat() const
{
return start_time > 0;
return m_start_time > 0;
}
void CombatRecord::ProcessHPEvent(int64 hp, int64 current_hp)
{
// damage
if (hp < current_hp) {
damage_received = damage_received + std::llabs(current_hp - hp);
m_damage_received = m_damage_received + std::llabs(current_hp - hp);
}
// heal
if (hp > current_hp && current_hp > 0) {
heal_received = heal_received + std::llabs(current_hp - hp);
m_heal_received = m_heal_received + std::llabs(current_hp - hp);
}
LogCombatRecordDetail(
"damage_received [{}] heal_received [{}] current_hp [{}] hp [{}] calc [{}]",
damage_received,
heal_received,
m_damage_received,
m_heal_received,
current_hp,
hp,
std::llabs(current_hp - hp)
@ -56,5 +57,27 @@ void CombatRecord::ProcessHPEvent(int64 hp, int64 current_hp)
double CombatRecord::TimeInCombat() const
{
return difftime(end_time, start_time);
return m_end_time > m_start_time ? difftime(m_end_time, m_start_time) : 0;
}
float CombatRecord::GetDamageReceivedPerSecond() const
{
double time_in_combat = TimeInCombat();
return time_in_combat > 0 ? (m_damage_received / time_in_combat) : m_damage_received;
}
float CombatRecord::GetHealedReceivedPerSecond() const
{
double time_in_combat = TimeInCombat();
return time_in_combat > 0 ? (m_heal_received / time_in_combat) : m_heal_received;
}
int64 CombatRecord::GetDamageReceived() const
{
return m_damage_received;
}
int64 CombatRecord::GetHealReceived() const
{
return m_heal_received;
}

View File

@ -9,15 +9,19 @@ class CombatRecord {
public:
void Start(std::string in_mob_name);
void Stop();
bool InCombat();
bool InCombat() const;
void ProcessHPEvent(int64 hp, int64 current_hp);
double TimeInCombat() const;
int64 GetDamageReceived() const;
int64 GetHealReceived() const;
float GetDamageReceivedPerSecond() const;
float GetHealedReceivedPerSecond() const;
private:
std::string mob_name;
time_t start_time = 0;
time_t end_time = 0;
int64 damage_received = 0;
int64 heal_received = 0;
std::string m_mob_name;
time_t m_start_time = 0;
time_t m_end_time = 0;
int64 m_damage_received = 0;
int64 m_heal_received = 0;
};
#endif //EQEMU_COMBAT_RECORD_H

View File

@ -36,6 +36,7 @@
#include "fastmath.h"
#include "mob_movement_manager.h"
#include "npc_scale_manager.h"
#include "../common/events/player_event_logs.h"
extern QueryServ* QServ;
extern WorldServer worldserver;
@ -551,8 +552,6 @@ int command_realdispatch(Client *c, std::string message, bool ignore_status)
{
Seperator sep(message.c_str(), ' ', 10, 100, true); // "three word argument" should be considered 1 arg
command_logcommand(c, message.c_str());
std::string cstr(sep.arg[0] + 1);
if (commandlist.count(cstr) != 1) {
@ -593,6 +592,15 @@ int command_realdispatch(Client *c, std::string message, bool ignore_status)
parse->EventPlayer(EVENT_GM_COMMAND, c, message, 0);
if (player_event_logs.IsEventEnabled(PlayerEvent::GM_COMMAND) && message != "#help") {
auto e = PlayerEvent::GMCommandEvent{
.message = message,
.target = c->GetTarget() ? c->GetTarget()->GetName() : "NONE"
};
RecordPlayerEventLogWithClient(c, PlayerEvent::GM_COMMAND, e);
}
cur->function(c, &sep); // Dispatch C++ Command
return 0;
@ -1035,7 +1043,6 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/listpetition.cpp"
#include "gm_commands/lootsim.cpp"
#include "gm_commands/loc.cpp"
#include "gm_commands/logcommand.cpp"
#include "gm_commands/logs.cpp"
#include "gm_commands/makepet.cpp"
#include "gm_commands/mana.cpp"

View File

@ -26,7 +26,6 @@ void command_deinit(void);
int command_add(std::string command_name, std::string description, uint8 admin, CmdFuncPtr function);
int command_notavail(Client *c, std::string message, bool ignore_status);
int command_realdispatch(Client *c, std::string message, bool ignore_status);
void command_logcommand(Client *c, std::string message);
uint8 GetCommandStatus(Client *c, std::string command_name);
void ListModifyNPCStatMap(Client *c);
std::map<std::string, std::string> GetModifyNPCStatMap();

View File

@ -848,11 +848,6 @@ public:
// Add item from cursor slot to trade bucket (automatically does bag data too)
void AddEntity(uint16 trade_slot_id, uint32 stack_size);
// Audit trade
void LogTrade();
void DumpTrade();
public:
// Object state
@ -868,6 +863,8 @@ private:
uint32 with_id;
Mob* owner;
public:
Mob *GetOwner() const;
};
struct ExtraAttackOptions {

View File

@ -49,6 +49,7 @@ Child of the Mob class.
#include "quest_parser_collection.h"
#include "string_ids.h"
#include "worldserver.h"
#include "../common/events/player_event_logs.h"
#include <iostream>
@ -1443,6 +1444,18 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app)
prevent_loot = true;
}
if (player_event_logs.IsEventEnabled(PlayerEvent::LOOT_ITEM) && !IsPlayerCorpse()) {
auto e = PlayerEvent::LootItemEvent{
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
.charges = inst->GetCharges(),
.npc_id = GetNPCTypeID(),
.corpse_name = EntityList::RemoveNumbers(corpse_name)
};
RecordPlayerEventLogWithClient(client, PlayerEvent::LOOT_ITEM, e);
}
if (!IsPlayerCorpse())
{
// dynamic zones may prevent looting by non-members or based on lockouts
@ -1471,9 +1484,14 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app)
// safe to ACK now
client->QueuePacket(app);
if (!IsPlayerCorpse() && RuleB(Character, EnableDiscoveredItems)) {
if (client && !client->GetGM() && !client->IsDiscovered(inst->GetItem()->ID))
client->DiscoverItem(inst->GetItem()->ID);
if (
!IsPlayerCorpse() &&
RuleB(Character, EnableDiscoveredItems) &&
client &&
!client->GetGM() &&
!client->IsDiscovered(inst->GetItem()->ID)
) {
client->DiscoverItem(inst->GetItem()->ID);
}
if (zone->adv_data) {

View File

@ -4091,6 +4091,11 @@ int8 Perl__GetRecipeSuccessCount(uint32 recipe_id, uint32 item_id)
return content_db.GetRecipeComponentCount(RecipeCountType::Success, recipe_id, item_id);
}
void Perl__send_player_handin_event()
{
quest_manager.SendPlayerHandinEvent();
}
void perl_register_quest()
{
perl::interpreter perl(PERL_GET_THX);
@ -4632,6 +4637,7 @@ void perl_register_quest()
package.add("scribespells", (int(*)(int, int))&Perl__scribespells);
package.add("secondstotime", &Perl__secondstotime);
package.add("selfcast", &Perl__selfcast);
package.add("send_player_handin_event", &Perl__send_player_handin_event);
package.add("setaaexpmodifierbycharid", (void(*)(uint32, uint32, double))&Perl__setaaexpmodifierbycharid);
package.add("setaaexpmodifierbycharid", (void(*)(uint32, uint32, double, int16))&Perl__setaaexpmodifierbycharid);
package.add("set_proximity", (void(*)(float, float, float, float))&Perl__set_proximity);

View File

@ -25,6 +25,8 @@ Eglin
#include <perlbind/perlbind.h>
namespace perl = perlbind;
#undef Null
#ifdef WIN32
#define snprintf _snprintf
#endif

View File

@ -34,6 +34,10 @@
#include "../common/data_verification.h"
#include "bot.h"
#include "../common/events/player_event_logs.h"
#include "worldserver.h"
extern WorldServer worldserver;
extern QueryServ* QServ;
@ -728,6 +732,8 @@ void Client::SetEXP(uint64 set_exp, uint64 set_aaxp, bool isrezzexp) {
parse->EventPlayer(EVENT_AA_GAIN, this, export_string, 0);
RecordPlayerEventLog(PlayerEvent::AA_GAIN, PlayerEvent::AAGainedEvent{gained});
/* QS: PlayerLogAARate */
if (RuleB(QueryServ, PlayerLogAARate)){
int add_points = (m_pp.aapoints - last_unspentAA);
@ -867,8 +873,19 @@ void Client::SetLevel(uint8 set_level, bool command)
}
if (set_level > m_pp.level) {
const auto export_string = fmt::format("{}", (set_level - m_pp.level));
int levels_gained = (set_level - m_pp.level);
const auto export_string = fmt::format("{}", levels_gained);
parse->EventPlayer(EVENT_LEVEL_UP, this, export_string, 0);
if (player_event_logs.IsEventEnabled(PlayerEvent::LEVEL_GAIN)) {
auto e = PlayerEvent::LevelGainedEvent{
.from_level = m_pp.level,
.to_level = set_level,
.levels_gained = levels_gained
};
RecordPlayerEventLog(PlayerEvent::LEVEL_GAIN, e);
}
if (RuleB(QueryServ, PlayerLogLevels)) {
const auto event_desc = fmt::format(
@ -881,8 +898,18 @@ void Client::SetLevel(uint8 set_level, bool command)
QServ->PlayerLogEvent(Player_Log_Levels, CharacterID(), event_desc);
}
} else if (set_level < m_pp.level) {
const auto export_string = fmt::format("{}", (m_pp.level - set_level));
int levels_lost = (m_pp.level - set_level);
const auto export_string = fmt::format("{}", levels_lost);
parse->EventPlayer(EVENT_LEVEL_DOWN, this, export_string, 0);
if (player_event_logs.IsEventEnabled(PlayerEvent::LEVEL_LOSS)) {
auto e = PlayerEvent::LevelLostEvent{
.from_level = m_pp.level,
.to_level = set_level,
.levels_lost = levels_lost
};
RecordPlayerEventLog(PlayerEvent::LEVEL_LOSS, e);
}
if (RuleB(QueryServ, PlayerLogLevels)) {
const auto event_desc = fmt::format(

View File

@ -29,6 +29,7 @@
#include <string>
#include <unordered_map>
#include <vector>
#include <cassert>
class Client;
class EQApplicationPacket;

View File

@ -32,6 +32,10 @@
#include "zonedb.h"
#include "../common/zone_store.h"
#include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/events/player_event_logs.h"
#include "worldserver.h"
extern WorldServer worldserver;
#include <iostream>
@ -377,6 +381,15 @@ void Client::GoFish()
std::vector<std::any> args;
args.push_back(inst);
parse->EventPlayer(EVENT_FISH_SUCCESS, this, "", inst->GetID(), &args);
if (player_event_logs.IsEventEnabled(PlayerEvent::FISH_SUCCESS)) {
auto e = PlayerEvent::FishSuccessEvent{
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
};
RecordPlayerEventLog(PlayerEvent::FISH_SUCCESS, e);
}
}
}
}
@ -396,6 +409,7 @@ void Client::GoFish()
}
parse->EventPlayer(EVENT_FISH_FAILURE, this, "", 0);
RecordPlayerEventLog(PlayerEvent::FISH_FAILURE, PlayerEvent::EmptyEvent{});
}
//chance to break fishing pole...
@ -497,6 +511,14 @@ void Client::ForageItem(bool guarantee) {
std::vector<std::any> args;
args.push_back(inst);
parse->EventPlayer(EVENT_FORAGE_SUCCESS, this, "", inst->GetID(), &args);
if (player_event_logs.IsEventEnabled(PlayerEvent::FORAGE_SUCCESS)) {
auto e = PlayerEvent::ForageSuccessEvent{
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name
};
RecordPlayerEventLog(PlayerEvent::FORAGE_SUCCESS, e);
}
}
}
@ -508,6 +530,7 @@ void Client::ForageItem(bool guarantee) {
} else {
MessageString(Chat::Skills, FORAGE_FAILED);
parse->EventPlayer(EVENT_FORAGE_FAILURE, this, "", 0);
RecordPlayerEventLog(PlayerEvent::FORAGE_FAILURE, PlayerEvent::EmptyEvent{});
}
CheckIncreaseSkill(EQ::skills::SkillForage, nullptr, 5);

View File

@ -1,89 +0,0 @@
#include "../client.h"
void command_logcommand(Client *c, std::string message)
{
int admin = c->Admin();
bool log = false;
switch (zone->loglevelvar) { //catch failsafe
case 9: { // log only LeadGM
if (
admin >= AccountStatus::GMLeadAdmin &&
admin < AccountStatus::GMMgmt
) {
log = true;
}
break;
}
case 8: { // log only GM
if (
admin >= AccountStatus::GMAdmin &&
admin < AccountStatus::GMLeadAdmin
) {
log = true;
}
break;
}
case 1: {
if (admin >= AccountStatus::GMMgmt) {
log = true;
}
break;
}
case 2: {
if (admin >= AccountStatus::GMLeadAdmin) {
log = true;
}
break;
}
case 3: {
if (admin >= AccountStatus::GMAdmin) {
log = true;
}
break;
}
case 4: {
if (admin >= AccountStatus::QuestTroupe) {
log = true;
}
break;
}
case 5: {
if (admin >= AccountStatus::ApprenticeGuide) {
log = true;
}
break;
}
case 6: {
if (admin >= AccountStatus::Steward) {
log = true;
}
break;
}
case 7: {
log = true;
break;
}
}
if (log) {
database.logevents(
c->AccountName(),
c->AccountID(),
admin,
c->GetName(),
c->GetTarget() ? c->GetTarget()->GetName() : "None",
"Command",
message.c_str(),
1
);
}
}

View File

@ -26,6 +26,7 @@
#include "../common/strings.h"
#include "worldserver.h"
#include "string_ids.h"
#include "../common/events/player_event_logs.h"
extern EntityList entity_list;
extern WorldServer worldserver;
@ -177,6 +178,18 @@ void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinu
true
);
if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) {
auto e = PlayerEvent::SplitMoneyEvent{
.copper = copper_split,
.silver = silver_split,
.gold = gold_split,
.platinum = platinum_split,
.player_money_balance = members[i]->CastToClient()->GetCarriedMoney(),
};
RecordPlayerEventLogWithClient(members[i]->CastToClient(), PlayerEvent::SPLIT_MONEY, e);
}
members[i]->CastToClient()->MessageString(
Chat::MoneySplit,
YOU_RECEIVE_AS_SPLIT,

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;
}

View File

@ -3803,6 +3803,11 @@ int8 lua_get_recipe_success_count(uint32 recipe_id, uint32 item_id)
return content_db.GetRecipeComponentCount(RecipeCountType::Success, recipe_id, item_id);
}
void lua_send_player_handin_event()
{
quest_manager.SendPlayerHandinEvent();
}
#define LuaCreateNPCParse(name, c_type, default_value) do { \
cur = table[#name]; \
if(luabind::type(cur) != LUA_TNIL) { \
@ -4331,6 +4336,7 @@ luabind::scope lua_register_general() {
luabind::def("get_recipe_fail_count", (int8(*)(uint32,uint32))&lua_get_recipe_fail_count),
luabind::def("get_recipe_salvage_count", (int8(*)(uint32,uint32))&lua_get_recipe_salvage_count),
luabind::def("get_recipe_success_count", (int8(*)(uint32,uint32))&lua_get_recipe_success_count),
luabind::def("send_player_handin_event", (void(*)(void))&lua_send_player_handin_event),
/*
Cross Zone
*/

View File

@ -59,7 +59,7 @@ void handle_npc_event_trade(
uint32 extra_data,
std::vector<std::any> *extra_pointers
) {
Lua_Client l_client(reinterpret_cast<Client*>(init));
Lua_Client l_client(reinterpret_cast<Client *>(init));
luabind::adl::object l_client_o = luabind::adl::object(L, l_client);
l_client_o.push(L);
lua_setfield(L, -2, "other");
@ -105,6 +105,10 @@ void handle_npc_event_trade(
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "copper");
// set a reference to the client inside of the trade object as well for plugins to process
l_client_o.push(L);
lua_setfield(L, -2, "other");
lua_setfield(L, -2, "trade");
}

View File

@ -89,6 +89,7 @@ extern volatile bool is_zone_loaded;
#include "zone_event_scheduler.h"
#include "../common/file.h"
#include "../common/path_manager.h"
#include "../common/events/player_event_logs.h"
EntityList entity_list;
WorldServer worldserver;
@ -107,6 +108,7 @@ EQEmuLogSys LogSys;
ZoneEventScheduler event_scheduler;
WorldContentService content_service;
PathManager path;
PlayerEventLogs player_event_logs;
const SPDat_Spell_Struct* spells;
int32 SPDAT_RECORDS = -1;
@ -266,6 +268,8 @@ int main(int argc, char** argv) {
->SetGMSayHandler(&Zone::GMSayHookCallBackProcess)
->StartFileLogs();
player_event_logs.SetDatabase(&database)->Init();
/* Guilds */
guild_mgr.SetDatabase(&database);
GuildBanks = nullptr;

View File

@ -1741,7 +1741,9 @@ protected:
bool spawned_in_water;
bool is_boat;
CombatRecord combat_record{};
CombatRecord m_combat_record{};
public:
const CombatRecord &GetCombatRecord() const;
public:
bool GetWasSpawnedInWater() const;

View File

@ -1929,7 +1929,7 @@ void Mob::AI_Event_Engaged(Mob *attacker, bool yell_for_help)
}
std::string mob_name = GetCleanName();
combat_record.Start(mob_name);
m_combat_record.Start(mob_name);
CastToNPC()->SetCombatEvent(true);
}
}
@ -1970,7 +1970,7 @@ void Mob::AI_Event_NoLongerEngaged() {
CastToNPC()->DoNPCEmote(EQ::constants::EmoteEventTypes::LeaveCombat, emoteid);
}
combat_record.Stop();
m_combat_record.Stop();
CastToNPC()->SetCombatEvent(false);
}
}

View File

@ -25,9 +25,11 @@
#include "object.h"
#include "quest_parser_collection.h"
#include "worldserver.h"
#include "zonedb.h"
#include "../common/zone_store.h"
#include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/events/player_event_logs.h"
#include <iostream>
@ -37,6 +39,7 @@ const char DEFAULT_OBJECT_NAME_SUFFIX[] = "_ACTORDEF";
extern Zone* zone;
extern EntityList entity_list;
extern WorldServer worldserver;
// Loading object from database
Object::Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& object, const EQ::ItemInstance* inst)
@ -517,6 +520,14 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object)
}
}
if (player_event_logs.IsEventEnabled(PlayerEvent::GROUNDSPAWN_PICKUP)) {
auto e = PlayerEvent::GroundSpawnPickupEvent{
.item_id = item->ID,
.item_name = item->Name,
};
RecordPlayerEventLogWithClient(sender, PlayerEvent::GROUNDSPAWN_PICKUP, e);
}
std::string export_string = fmt::format("{}", item->ID);
std::vector<std::any> args;
args.push_back(m_inst);
@ -542,12 +553,13 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object)
sender->SendItemPacket(EQ::invslot::slotCursor, m_inst, ItemPacketTrade);
// Could be an undiscovered ground_spawn
if (m_ground_spawn && (RuleB(Character, EnableDiscoveredItems)))
{
if (!sender->GetGM() && !sender->IsDiscovered(item->ID))
{
sender->DiscoverItem(item->ID);
}
if (
m_ground_spawn &&
RuleB(Character, EnableDiscoveredItems) &&
!sender->GetGM() &&
!sender->IsDiscovered(item->ID)
) {
sender->DiscoverItem(item->ID);
}
if(cursordelete) // delete the item if it's a duplicate lore. We have to do this because the client expects the item packet

View File

@ -23,6 +23,7 @@
#include "../common/spdat.h"
#include "../common/strings.h"
#include "../common/say_link.h"
#include "../common/events/player_event_logs.h"
#include "entity.h"
#include "event_codes.h"
@ -2431,6 +2432,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level
);
parse->EventPlayer(EVENT_BOT_CREATE, initiator, export_string, 0);
return true;
}
}
@ -2940,7 +2942,6 @@ std::string QuestManager::varlink(
return linker.GenerateLink();
}
std::string QuestManager::getitemname(uint32 item_id) {
const EQ::ItemData* item_data = database.GetItem(item_id);
if (!item_data) {
@ -3986,3 +3987,171 @@ int8 QuestManager::DoesAugmentFit(EQ::ItemInstance* inst, uint32 augment_id, uin
return inst->AvailableAugmentSlot(aug_inst->AugType);
}
void QuestManager::SendPlayerHandinEvent() {
QuestManagerCurrentQuestVars();
if (!owner || !owner->IsNPC() || !initiator) {
return;
}
if (
!initiator->EntityVariableExists("HANDIN_ITEMS") &&
!initiator->EntityVariableExists("HANDIN_MONEY") &&
!initiator->EntityVariableExists("RETURN_ITEMS") &&
!initiator->EntityVariableExists("RETURN_MONEY")
) {
return;
}
auto handin_items = initiator->GetEntityVariable("HANDIN_ITEMS");
auto return_items = initiator->GetEntityVariable("RETURN_ITEMS");
auto handin_money = initiator->GetEntityVariable("HANDIN_MONEY");
auto return_money = initiator->GetEntityVariable("RETURN_MONEY");
std::vector<PlayerEvent::HandinEntry> hi = {};
std::vector<PlayerEvent::HandinEntry> ri = {};
PlayerEvent::HandinMoney hm{};
PlayerEvent::HandinMoney rm{};
// Handin Items
if (!handin_items.empty()) {
if (Strings::Contains(handin_items, ",")) {
const auto handin_data = Strings::Split(handin_items, ",");
for (const auto &h: handin_data) {
const auto item_data = Strings::Split(h, "-");
if (
item_data.size() == 3 &&
Strings::IsNumber(item_data[0]) &&
Strings::IsNumber(item_data[1]) &&
Strings::IsNumber(item_data[2])
) {
const auto item_id = static_cast<uint32>(std::stoul(item_data[0]));
if (item_id != 0) {
const auto *item = database.GetItem(item_id);
hi.emplace_back(
PlayerEvent::HandinEntry{
.item_id = item_id,
.item_name = item->Name,
.charges = static_cast<uint16>(std::stoul(item_data[1])),
.attuned = std::stoi(item_data[2]) ? true : false
}
);
}
}
}
}
else if (Strings::Contains(handin_items, "|")) {
const auto item_data = Strings::Split(handin_items, "|");
if (
item_data.size() == 3 &&
Strings::IsNumber(item_data[0]) &&
Strings::IsNumber(item_data[1]) &&
Strings::IsNumber(item_data[2])
) {
const auto item_id = static_cast<uint32>(std::stoul(item_data[0]));
const auto *item = database.GetItem(item_id);
hi.emplace_back(
PlayerEvent::HandinEntry{
.item_id = item_id,
.item_name = item->Name,
.charges = static_cast<uint16>(std::stoul(item_data[1])),
.attuned = std::stoi(item_data[2]) ? true : false
}
);
}
}
}
// Handin Money
if (!handin_money.empty()) {
const auto hms = Strings::Split(handin_money, "|");
hm.copper = static_cast<uint32>(std::stoul(hms[0]));
hm.silver = static_cast<uint32>(std::stoul(hms[1]));
hm.gold = static_cast<uint32>(std::stoul(hms[2]));
hm.platinum = static_cast<uint32>(std::stoul(hms[3]));
}
// Return Items
if (!return_items.empty()) {
if (Strings::Contains(return_items, ",")) {
const auto return_data = Strings::Split(return_items, ",");
for (const auto &r: return_data) {
const auto item_data = Strings::Split(r, "|");
if (
item_data.size() == 3 &&
Strings::IsNumber(item_data[0]) &&
Strings::IsNumber(item_data[1]) &&
Strings::IsNumber(item_data[2])
) {
const auto item_id = static_cast<uint32>(std::stoul(item_data[0]));
const auto *item = database.GetItem(item_id);
ri.emplace_back(
PlayerEvent::HandinEntry{
.item_id = item_id,
.item_name = item->Name,
.charges = static_cast<uint16>(std::stoul(item_data[1])),
.attuned = std::stoi(item_data[2]) ? true : false
}
);
}
}
}
else if (Strings::Contains(return_items, "|")) {
const auto item_data = Strings::Split(return_items, "|");
if (
item_data.size() == 3 &&
Strings::IsNumber(item_data[0]) &&
Strings::IsNumber(item_data[1]) &&
Strings::IsNumber(item_data[2])
) {
const auto item_id = static_cast<uint32>(std::stoul(item_data[0]));
const auto *item = database.GetItem(item_id);
ri.emplace_back(
PlayerEvent::HandinEntry{
.item_id = item_id,
.item_name = item->Name,
.charges = static_cast<uint16>(std::stoul(item_data[1])),
.attuned = std::stoi(item_data[2]) ? true : false
}
);
}
}
}
// Return Money
if (!return_money.empty()) {
const auto rms = Strings::Split(return_money, "|");
rm.copper = static_cast<uint32>(std::stoul(rms[0]));
rm.silver = static_cast<uint32>(std::stoul(rms[1]));
rm.gold = static_cast<uint32>(std::stoul(rms[2]));
rm.platinum = static_cast<uint32>(std::stoul(rms[3]));
}
initiator->DeleteEntityVariable("HANDIN_ITEMS");
initiator->DeleteEntityVariable("HANDIN_MONEY");
initiator->DeleteEntityVariable("RETURN_ITEMS");
initiator->DeleteEntityVariable("RETURN_MONEY");
if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN)) {
auto e = PlayerEvent::HandinEvent{
.npc_id = owner->CastToNPC()->GetNPCTypeID(),
.npc_name = owner->GetCleanName(),
.handin_items = hi,
.handin_money = hm,
.return_items = ri,
.return_money = rm
};
RecordPlayerEventLogWithClient(initiator, PlayerEvent::NPC_HANDIN, e);
}
}

View File

@ -347,6 +347,7 @@ public:
bool HasRecipeLearned(uint32 recipe_id);
bool DoAugmentSlotsMatch(uint32 item_one, uint32 item_two);
int8 DoesAugmentFit(EQ::ItemInstance* inst, uint32 augment_id, uint8 augment_slot = 255);
void SendPlayerHandinEvent();
Bot *GetBot() const;
Client *GetInitiator() const;

View File

@ -17,6 +17,7 @@
*/
#include "../common/strings.h"
#include "../common/events/player_event_logs.h"
#include "client.h"
#include "entity.h"
@ -814,6 +815,18 @@ void Raid::SplitMoney(uint32 gid, uint32 copper, uint32 silver, uint32 gold, uin
true
);
if (player_event_logs.IsEventEnabled(PlayerEvent::SPLIT_MONEY)) {
auto e = PlayerEvent::SplitMoneyEvent{
.copper = copper_split,
.silver = silver_split,
.gold = gold_split,
.platinum = platinum_split,
.player_money_balance = members[i].member->GetCarriedMoney(),
};
RecordPlayerEventLogWithClient(members[i].member, PlayerEvent::SPLIT_MONEY, e);
}
members[i].member->MessageString(
Chat::MoneySplit,
YOU_RECEIVE_AS_SPLIT,

View File

@ -77,6 +77,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
#include "../common/strings.h"
#include "../common/data_verification.h"
#include "../common/misc_functions.h"
#include "../common/events/player_event_logs.h"
#include "data_bucket.h"
#include "quest_parser_collection.h"
@ -6914,10 +6915,13 @@ bool Mob::CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot) {
if (itm && itm->GetItem()->Classes != 65535) {
if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && !(itm->GetItem()->Classes & bitmask)) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are casting a spell from an item that requires equipping but shouldn't let them equip it
LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) which they shouldn't be able to equip!",
CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item with an invalid class");
std::string message = fmt::format(
"Attempted to click an equip-only effect on item_name [{}] item_id [{}] which they shouldn't be able to equip!",
itm->GetItem()->Name,
itm->GetItem()->ID
);
RecordPlayerEventLogWithClient(CastToClient(), PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
}
else {
MessageString(Chat::Red, MUST_EQUIP_ITEM);
@ -6926,10 +6930,13 @@ bool Mob::CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot) {
}
if ((itm->GetItem()->Click.Type == EQ::item::ItemEffectClick2) && !(itm->GetItem()->Classes & bitmask)) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are casting a spell from an item that they don't meet the race/class requirements to cast
LogError("HACKER: [{}] (account: [{}]) attempted to click a race/class restricted effect on item [{}] (id: [{}]) which they shouldn't be able to click!",
CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking race/class restricted item with an invalid class");
std::string message = fmt::format(
"Attempted to click a race/class restricted effect on item_name [{}] item_id [{}] which they shouldn't be able to click!",
itm->GetItem()->Name,
itm->GetItem()->ID
);
RecordPlayerEventLogWithClient(CastToClient(), PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
}
else {
if (CastToClient()->ClientVersion() >= EQ::versions::ClientVersion::RoF)
@ -6947,9 +6954,13 @@ bool Mob::CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot) {
}
if (itm && (itm->GetItem()->Click.Type == EQ::item::ItemEffectEquipClick) && inventory_slot > EQ::invslot::EQUIPMENT_END) {
if (CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoF) {
// They are attempting to cast a must equip clicky without having it equipped
LogError("HACKER: [{}] (account: [{}]) attempted to click an equip-only effect on item [{}] (id: [{}]) without equiping it!", CastToClient()->GetCleanName(), CastToClient()->AccountName(), itm->GetItem()->Name, itm->GetItem()->ID);
database.SetHackerFlag(CastToClient()->AccountName(), CastToClient()->GetCleanName(), "Clicking equip-only item without equiping it");
std::string message = fmt::format(
"Attempted to click an equip-only effect on item_name [{}] item_id [{}] without equipping it!",
itm->GetItem()->Name,
itm->GetItem()->ID
);
RecordPlayerEventLogWithClient(CastToClient(), PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
}
else {
MessageString(Chat::Red, MUST_EQUIP_ITEM);
@ -6975,8 +6986,8 @@ void Mob::SetHP(int64 hp)
return;
}
if (combat_record.InCombat()) {
combat_record.ProcessHPEvent(hp, current_hp);
if (m_combat_record.InCombat()) {
m_combat_record.ProcessHPEvent(hp, current_hp);
}
current_hp = hp;
@ -6984,8 +6995,8 @@ void Mob::SetHP(int64 hp)
void Mob::DrawDebugCoordinateNode(std::string node_name, const glm::vec4 vec)
{
NPC* node = nullptr;
for (const auto& n : entity_list.GetNPCList()) {
NPC *node = nullptr;
for (const auto &n: entity_list.GetNPCList()) {
if (n.second->GetCleanName() == node_name) {
node = n.second;
break;
@ -6995,3 +7006,8 @@ void Mob::DrawDebugCoordinateNode(std::string node_name, const glm::vec4 vec)
node = NPC::SpawnNodeNPC(node_name, "", GetPosition());
}
}
const CombatRecord &Mob::GetCombatRecord() const
{
return m_combat_record;
}

View File

@ -14,6 +14,7 @@
#include "worldserver.h"
#include "dynamic_zone.h"
#include "string_ids.h"
#include "../common/events/player_event_logs.h"
#define EBON_CRYSTAL 40902
#define RADIANT_CRYSTAL 40903
@ -925,6 +926,16 @@ int ClientTaskState::IncrementDoneCount(
int event_res = DispatchEventTaskComplete(client, *info, activity_id);
if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_COMPLETE)) {
auto e = PlayerEvent::TaskCompleteEvent{
.task_id = static_cast<uint32>(info->task_id),
.task_name = task_manager->GetTaskName(static_cast<uint32>(info->task_id)),
.activity_id = static_cast<uint32>(info->activity[activity_id].activity_id),
.done_count = static_cast<uint32>(info->activity[activity_id].done_count)
};
RecordPlayerEventLogWithClient(client, PlayerEvent::TASK_COMPLETE, e);
}
/* QS: PlayerLogTaskUpdates :: Complete */
if (RuleB(QueryServ, PlayerLogTaskUpdates)) {
std::string event_desc = StringFormat(
@ -963,6 +974,16 @@ int ClientTaskState::IncrementDoneCount(
activity_id,
task_index
);
if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_UPDATE)) {
auto e = PlayerEvent::TaskUpdateEvent{
.task_id = static_cast<uint32>(info->task_id),
.task_name = task_manager->GetTaskName(static_cast<uint32>(info->task_id)),
.activity_id = static_cast<uint32>(info->activity[activity_id].activity_id),
.done_count = static_cast<uint32>(info->activity[activity_id].done_count)
};
RecordPlayerEventLogWithClient(client, PlayerEvent::TASK_UPDATE, e);
}
}
task_manager->SaveClientState(client, this);
@ -2134,6 +2155,26 @@ void ClientTaskState::AcceptNewTask(
NPC *npc = entity_list.GetID(npc_type_id)->CastToNPC();
if (npc) {
parse->EventNPC(EVENT_TASK_ACCEPTED, npc, client, export_string, 0);
if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_ACCEPT)) {
auto e = PlayerEvent::TaskAcceptEvent{
.npc_id = static_cast<uint32>(npc_type_id),
.npc_name = npc->GetCleanName(),
.task_id = static_cast<uint32>(task_id),
.task_name = task_manager->GetTaskName(static_cast<uint32>(task_id)),
};
RecordPlayerEventLogWithClient(client, PlayerEvent::TASK_ACCEPT, e);
}
} else {
if (player_event_logs.IsEventEnabled(PlayerEvent::TASK_ACCEPT)) {
auto e = PlayerEvent::TaskAcceptEvent{
.npc_id = 0,
.npc_name = "No NPC",
.task_id = static_cast<uint32>(task_id),
.task_name = task_manager->GetTaskName(static_cast<uint32>(task_id)),
};
RecordPlayerEventLogWithClient(client, PlayerEvent::TASK_ACCEPT, e);
}
}
parse->EventPlayer(EVENT_TASK_ACCEPTED, client, export_string, 0);
}

View File

@ -700,7 +700,7 @@ void TaskManager::SharedTaskSelector(Client* client, Mob* mob, const std::vector
if (request.group_type != SharedTaskRequestGroupType::Solo) {
auto shared_task_members = SharedTaskMembersRepository::GetWhere(
database,
fmt::format("character_id IN ({}) LIMIT 1", fmt::join(request.character_ids, ",")));
fmt::format("character_id IN ({}) LIMIT 1", Strings::Join(request.character_ids, ",")));
if (!shared_task_members.empty()) {
validation_failed = true;

View File

@ -17,6 +17,7 @@
*/
#include "../common/global_define.h"
#include "../common/events/player_event_logs.h"
#include <stdlib.h>
#include <list>
@ -33,12 +34,14 @@
#include "string_ids.h"
#include "titles.h"
#include "zonedb.h"
#include "worldserver.h"
#include "../common/repositories/char_recipe_list_repository.h"
#include "../common/zone_store.h"
#include "../common/repositories/tradeskill_recipe_repository.h"
#include "../common/repositories/tradeskill_recipe_entries_repository.h"
extern QueryServ* QServ;
extern WorldServer worldserver;
static const EQ::skills::SkillType TradeskillUnknown = EQ::skills::Skill1HBlunt; /* an arbitrary non-tradeskill */
@ -488,8 +491,28 @@ void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine, Ob
}
if (success) {
if (player_event_logs.IsEventEnabled(PlayerEvent::COMBINE_SUCCESS)) {
auto e = PlayerEvent::CombineEvent{
.recipe_id = spec.recipe_id,
.recipe_name = spec.name,
.made_count = spec.madecount,
.tradeskill_id = (uint32)spec.tradeskill
};
RecordPlayerEventLogWithClient(user, PlayerEvent::COMBINE_SUCCESS, e);
}
parse->EventPlayer(EVENT_COMBINE_SUCCESS, user, spec.name, spec.recipe_id);
} else {
if (player_event_logs.IsEventEnabled(PlayerEvent::COMBINE_FAILURE)) {
auto e = PlayerEvent::CombineEvent{
.recipe_id = spec.recipe_id,
.recipe_name = spec.name,
.made_count = spec.madecount,
.tradeskill_id = (uint32)spec.tradeskill
};
RecordPlayerEventLogWithClient(user, PlayerEvent::COMBINE_FAILURE, e);
}
parse->EventPlayer(EVENT_COMBINE_FAILURE, user, spec.name, spec.recipe_id);
}
}

View File

@ -21,6 +21,7 @@
#include "../common/rulesys.h"
#include "../common/strings.h"
#include "../common/misc_functions.h"
#include "../common/events/player_event_logs.h"
#include "client.h"
#include "entity.h"
@ -187,149 +188,9 @@ void Trade::SendItemData(const EQ::ItemInstance* inst, int16 dest_slot_id)
}
}
// Audit trade: The part logged is what travels owner -> with
void Trade::LogTrade()
Mob *Trade::GetOwner() const
{
Mob* with = With();
if (!owner->IsClient() || !with)
return; // Should never happen
Client* trader = owner->CastToClient();
bool logtrade = false;
int admin_level = 0;
uint8 item_count = 0;
if (zone->tradevar != 0) {
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) {
if (trader->GetInv().GetItem(i))
item_count++;
}
if ((cp + sp + gp + pp) || item_count) {
admin_level = trader->Admin();
} else {
admin_level = (AccountStatus::Max + 1);
}
if (zone->tradevar == 7) {
logtrade = true;
} else if (
admin_level >= AccountStatus::Steward &&
admin_level < AccountStatus::ApprenticeGuide
) {
if (zone->tradevar < 8 && zone->tradevar > 5) {
logtrade = true;
}
} else if (admin_level <= AccountStatus::ApprenticeGuide) {
if (zone->tradevar < 8 && zone->tradevar > 4) {
logtrade = true;
}
} else if (admin_level <= AccountStatus::QuestTroupe) {
if (zone->tradevar < 8 && zone->tradevar > 3) {
logtrade = true;
}
} else if (admin_level <= AccountStatus::GMAdmin) {
if (zone->tradevar < 9 && zone->tradevar > 2) {
logtrade = true;
}
} else if (admin_level <= AccountStatus::GMLeadAdmin) {
if ((zone->tradevar < 8 && zone->tradevar > 1) || zone->tradevar == 9) {
logtrade = true;
}
} else if (admin_level <= AccountStatus::Max){
if (zone->tradevar < 8 && zone->tradevar > 0) {
logtrade = true;
}
}
}
if (logtrade) {
char logtext[1000] = {0};
uint32 cash = 0;
bool comma = false;
// Log items offered by owner
cash = cp + sp + gp + pp;
if ((cash>0) || (item_count>0)) {
sprintf(logtext, "%s gave %s ", trader->GetName(), with->GetName());
if (item_count > 0) {
strcat(logtext, "items {");
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) {
const EQ::ItemInstance* inst = trader->GetInv().GetItem(i);
if (!comma)
comma = true;
else {
if (inst)
strcat(logtext, ",");
}
if (inst) {
char item_num[15] = {0};
sprintf(item_num, "%i", inst->GetItem()->ID);
strcat(logtext, item_num);
if (inst->IsClassBag()) {
for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) {
inst = trader->GetInv().GetItem(i, j);
if (inst) {
strcat(logtext, ",");
sprintf(item_num, "%i", inst->GetItem()->ID);
strcat(logtext, item_num);
}
}
}
}
}
}
if (cash > 0) {
char money[100] = {0};
sprintf(money, " %ipp, %igp, %isp, %icp", trader->trade->pp, trader->trade->gp, trader->trade->sp, trader->trade->cp);
strcat(logtext, money);
}
database.logevents(trader->AccountName(), trader->AccountID(),
trader->Admin(), trader->GetName(), with->GetName(), "Trade", logtext, 6);
}
}
}
void Trade::DumpTrade()
{
Mob* with = With();
LogTrading("Dumping trade data: [{}] in TradeState [{}] with [{}]",
owner->GetName(), state, ((with==nullptr)?"(null)":with->GetName()));
if (!owner->IsClient())
return;
Client* trader = owner->CastToClient();
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; i++) {
const EQ::ItemInstance* inst = trader->GetInv().GetItem(i);
if (inst) {
LogTrading("Item [{}] (Charges=[{}], Slot=[{}], IsBag=[{}])",
inst->GetItem()->ID, inst->GetCharges(),
i, ((inst->IsClassBag()) ? "True" : "False"));
if (inst->IsClassBag()) {
for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) {
inst = trader->GetInv().GetItem(i, j);
if (inst) {
LogTrading("\tBagItem [{}] (Charges=[{}], Slot=[{}])",
inst->GetItem()->ID, inst->GetCharges(),
EQ::InventoryProfile::CalcSlotId(i, j));
}
}
}
}
}
LogTrading("\tpp:[{}], gp:[{}], sp:[{}], cp:[{}]", pp, gp, sp, cp);
return owner;
}
@ -458,8 +319,8 @@ void Client::ResetTrade() {
void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list<void*>* event_details) {
if(tradingWith && tradingWith->IsClient()) {
Client* other = tradingWith->CastToClient();
QSPlayerLogTrade_Struct* qs_audit = nullptr;
Client * other = tradingWith->CastToClient();
PlayerLogTrade_Struct * qs_audit = nullptr;
bool qs_log = false;
if(other) {
@ -470,24 +331,24 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
// step 0: pre-processing
// QS code
if (RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) {
qs_audit = (QSPlayerLogTrade_Struct*)event_entry;
qs_audit = (PlayerLogTrade_Struct*)event_entry;
qs_log = true;
if (finalizer) {
qs_audit->char2_id = character_id;
qs_audit->character_2_id = character_id;
qs_audit->char2_money.platinum = trade->pp;
qs_audit->char2_money.gold = trade->gp;
qs_audit->char2_money.silver = trade->sp;
qs_audit->char2_money.copper = trade->cp;
qs_audit->character_2_money.platinum = trade->pp;
qs_audit->character_2_money.gold = trade->gp;
qs_audit->character_2_money.silver = trade->sp;
qs_audit->character_2_money.copper = trade->cp;
}
else {
qs_audit->char1_id = character_id;
qs_audit->character_1_id = character_id;
qs_audit->char1_money.platinum = trade->pp;
qs_audit->char1_money.gold = trade->gp;
qs_audit->char1_money.silver = trade->sp;
qs_audit->char1_money.copper = trade->cp;
qs_audit->character_1_money.platinum = trade->pp;
qs_audit->character_1_money.gold = trade->gp;
qs_audit->character_1_money.silver = trade->sp;
qs_audit->character_1_money.copper = trade->cp;
}
}
@ -510,12 +371,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
if (other->PutItemInInventory(free_slot, *inst, true)) {
LogTrading("Container [{}] ([{}]) successfully transferred, deleting from trade slot", inst->GetItem()->Name, inst->GetItem()->ID);
if (qs_log) {
auto detail = new QSTradeItems_Struct;
auto detail = new PlayerLogTradeItemsEntry_Struct;
detail->from_id = character_id;
detail->from_slot = trade_slot;
detail->to_id = other->CharacterID();
detail->to_slot = free_slot;
detail->from_character_id = character_id;
detail->from_slot = trade_slot;
detail->to_character_id = other->CharacterID();
detail->to_slot = free_slot;
detail->item_id = inst->GetID();
detail->charges = 1;
detail->aug_1 = inst->GetAugmentItemID(1);
@ -527,20 +388,20 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
event_details->push_back(detail);
if (finalizer)
qs_audit->char2_count += detail->charges;
qs_audit->character_2_item_count += detail->charges;
else
qs_audit->char1_count += detail->charges;
qs_audit->character_1_item_count += detail->charges;
for (uint8 sub_slot = EQ::invbag::SLOT_BEGIN; (sub_slot <= EQ::invbag::SLOT_END); ++sub_slot) { // this is to catch ALL items
const EQ::ItemInstance* bag_inst = inst->GetItem(sub_slot);
if (bag_inst) {
detail = new QSTradeItems_Struct;
detail = new PlayerLogTradeItemsEntry_Struct;
detail->from_id = character_id;
detail->from_slot = EQ::InventoryProfile::CalcSlotId(trade_slot, sub_slot);
detail->to_id = other->CharacterID();
detail->to_slot = EQ::InventoryProfile::CalcSlotId(free_slot, sub_slot);
detail->from_character_id = character_id;
detail->from_slot = EQ::InventoryProfile::CalcSlotId(trade_slot, sub_slot);
detail->to_character_id = other->CharacterID();
detail->to_slot = EQ::InventoryProfile::CalcSlotId(free_slot, sub_slot);
detail->item_id = bag_inst->GetID();
detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges());
detail->aug_1 = bag_inst->GetAugmentItemID(1);
@ -552,9 +413,9 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
event_details->push_back(detail);
if (finalizer)
qs_audit->char2_count += detail->charges;
qs_audit->character_2_item_count += detail->charges;
else
qs_audit->char1_count += detail->charges;
qs_audit->character_1_item_count += detail->charges;
}
}
}
@ -620,12 +481,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
LogTrading("Partial stack [{}] ([{}]) successfully transferred, deleting [{}] charges from trade slot",
inst->GetItem()->Name, inst->GetItem()->ID, (old_charges - inst->GetCharges()));
if (qs_log) {
auto detail = new QSTradeItems_Struct;
auto detail = new PlayerLogTradeItemsEntry_Struct;
detail->from_id = character_id;
detail->from_slot = trade_slot;
detail->to_id = other->CharacterID();
detail->to_slot = partial_slot;
detail->from_character_id = character_id;
detail->from_slot = trade_slot;
detail->to_character_id = other->CharacterID();
detail->to_slot = partial_slot;
detail->item_id = inst->GetID();
detail->charges = (old_charges - inst->GetCharges());
detail->aug_1 = 0;
@ -637,9 +498,9 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
event_details->push_back(detail);
if (finalizer)
qs_audit->char2_count += detail->charges;
qs_audit->character_2_item_count += detail->charges;
else
qs_audit->char1_count += detail->charges;
qs_audit->character_1_item_count += detail->charges;
}
}
else {
@ -688,12 +549,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
}
if (qs_log) {
auto detail = new QSTradeItems_Struct;
auto detail = new PlayerLogTradeItemsEntry_Struct;
detail->from_id = character_id;
detail->from_slot = trade_slot;
detail->to_id = character_id;
detail->to_slot = bias_slot;
detail->from_character_id = character_id;
detail->from_slot = trade_slot;
detail->to_character_id = character_id;
detail->to_slot = bias_slot;
detail->item_id = inst->GetID();
detail->charges = (old_charges - inst->GetCharges());
detail->aug_1 = 0;
@ -728,12 +589,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
if (other->PutItemInInventory(free_slot, *inst, true)) {
LogTrading("Item [{}] ([{}]) successfully transferred, deleting from trade slot", inst->GetItem()->Name, inst->GetItem()->ID);
if (qs_log) {
auto detail = new QSTradeItems_Struct;
auto detail = new PlayerLogTradeItemsEntry_Struct;
detail->from_id = character_id;
detail->from_slot = trade_slot;
detail->to_id = other->CharacterID();
detail->to_slot = free_slot;
detail->from_character_id = character_id;
detail->from_slot = trade_slot;
detail->to_character_id = other->CharacterID();
detail->to_slot = free_slot;
detail->item_id = inst->GetID();
detail->charges = (!inst->IsStackable() ? 1 : inst->GetCharges());
detail->aug_1 = inst->GetAugmentItemID(1);
@ -745,21 +606,21 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
event_details->push_back(detail);
if (finalizer)
qs_audit->char2_count += detail->charges;
qs_audit->character_2_item_count += detail->charges;
else
qs_audit->char1_count += detail->charges;
qs_audit->character_1_item_count += detail->charges;
// 'step 3' should never really see containers..but, just in case...
for (uint8 sub_slot = EQ::invbag::SLOT_BEGIN; (sub_slot <= EQ::invbag::SLOT_END); ++sub_slot) { // this is to catch ALL items
const EQ::ItemInstance* bag_inst = inst->GetItem(sub_slot);
if (bag_inst) {
detail = new QSTradeItems_Struct;
detail = new PlayerLogTradeItemsEntry_Struct;
detail->from_id = character_id;
detail->from_slot = trade_slot;
detail->to_id = other->CharacterID();
detail->to_slot = free_slot;
detail->from_character_id = character_id;
detail->from_slot = trade_slot;
detail->to_character_id = other->CharacterID();
detail->to_slot = free_slot;
detail->item_id = bag_inst->GetID();
detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges());
detail->aug_1 = bag_inst->GetAugmentItemID(1);
@ -771,9 +632,9 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
event_details->push_back(detail);
if (finalizer)
qs_audit->char2_count += detail->charges;
qs_audit->character_2_item_count += detail->charges;
else
qs_audit->char1_count += detail->charges;
qs_audit->character_1_item_count += detail->charges;
}
}
}
@ -1697,7 +1558,7 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs, Client* Trader, const EQApplic
}
if(!TakeMoneyFromPP(TotalCost)) {
database.SetHackerFlag(account_name, name, "Attempted to buy something in bazaar but did not have enough money.");
RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = "Attempted to buy something in bazaar but did not have enough money."});
TradeRequestFailed(app);
safe_delete(outapp);
return;
@ -1715,6 +1576,37 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs, Client* Trader, const EQApplic
Trader->AddMoneyToPP(copper, silver, gold, platinum, true);
if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) {
auto e = PlayerEvent::TraderPurchaseEvent{
.item_id = BuyItem->GetID(),
.item_name = BuyItem->GetItem()->Name,
.trader_id = Trader->CharacterID(),
.trader_name = Trader->GetCleanName(),
.price = tbs->Price,
.charges = outtbs->Quantity,
.total_cost = (tbs->Price * outtbs->Quantity),
.player_money_balance = GetCarriedMoney(),
};
RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e);
}
if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_SELL)) {
auto e = PlayerEvent::TraderSellEvent{
.item_id = BuyItem->GetID(),
.item_name = BuyItem->GetItem()->Name,
.buyer_id = CharacterID(),
.buyer_name = GetCleanName(),
.price = tbs->Price,
.charges = outtbs->Quantity,
.total_cost = (tbs->Price * outtbs->Quantity),
.player_money_balance = Trader->GetCarriedMoney(),
};
RecordPlayerEventLogWithClient(Trader, PlayerEvent::TRADER_SELL, e);
}
LogTrading("Trader Received: [{}] Platinum, [{}] Gold, [{}] Silver, [{}] Copper", platinum, gold, silver, copper);
ReturnTraderReq(app, outtbs->Quantity, ItemID);

View File

@ -57,6 +57,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "shared_task_zone_messaging.h"
#include "dialogue_window.h"
#include "bot_command.h"
#include "queryserv.h"
#include "../common/events/player_event_logs.h"
extern EntityList entity_list;
extern Zone* zone;
@ -1958,8 +1960,9 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
}
case ServerOP_ReloadLogs:
{
zone->SendReloadMessage("Log Settings");
LogSys.LoadLogDatabaseSettings();
zone->SendReloadMessage("Log Settings");
LogSys.LoadLogDatabaseSettings();
player_event_logs.ReloadSettings();
break;
}
case ServerOP_ReloadMerchants: {

View File

@ -170,34 +170,6 @@ void ZoneDatabase::UpdateSpawn2Status(uint32 id, uint8 new_status)
QueryDatabase(query);
}
bool ZoneDatabase::logevents(const char* accountname,uint32 accountid,uint8 status,const char* charname, const char* target,const char* descriptiontype, const char* description,int event_nid){
uint32 len = strlen(description);
uint32 len2 = strlen(target);
auto descriptiontext = new char[2 * len + 1];
auto targetarr = new char[2 * len2 + 1];
memset(descriptiontext, 0, 2*len+1);
memset(targetarr, 0, 2*len2+1);
DoEscapeString(descriptiontext, description, len);
DoEscapeString(targetarr, target, len2);
std::string query = StringFormat("INSERT INTO eventlog (accountname, accountid, status, "
"charname, target, descriptiontype, description, event_nid) "
"VALUES('%s', %i, %i, '%s', '%s', '%s', '%s', '%i')",
accountname, accountid, status, charname, targetarr,
descriptiontype, descriptiontext, event_nid);
safe_delete_array(descriptiontext);
safe_delete_array(targetarr);
auto results = QueryDatabase(query);
if (!results.Success()) {
return false;
}
return true;
}
bool ZoneDatabase::SetSpecialAttkFlag(uint8 id, const char* flag) {
std::string query = StringFormat("UPDATE npc_types SET npcspecialattks='%s' WHERE id = %i;", flag, id);

View File

@ -612,7 +612,6 @@ public:
* PLEASE DO NOT ADD TO THIS COLLECTION OF CRAP UNLESS YOUR METHOD
* REALLY HAS NO BETTER SECTION
*/
bool logevents(const char* accountname,uint32 accountid,uint8 status,const char* charname,const char* target, const char* descriptiontype, const char* description,int event_nid);
uint32 GetKarma(uint32 acct_id);
void UpdateKarma(uint32 acct_id, uint32 amount);

View File

@ -38,6 +38,7 @@ extern Zone* zone;
#include "../common/repositories/character_peqzone_flags_repository.h"
#include "../common/repositories/zone_repository.h"
#include "../common/events/player_event_logs.h"
void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
@ -216,6 +217,22 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
return;
}
if (player_event_logs.IsEventEnabled(PlayerEvent::ZONING)) {
auto e = PlayerEvent::ZoningEvent{};
e.from_zone_long_name = zone->GetLongName();
e.from_zone_short_name = zone->GetShortName();
e.from_zone_id = zone->GetZoneID();
e.from_instance_id = zone->GetInstanceID();
e.from_instance_version = zone->GetInstanceVersion();
e.to_zone_long_name = ZoneLongName(target_zone_id);
e.to_zone_short_name = ZoneName(target_zone_id);
e.to_zone_id = target_zone_id;
e.to_instance_id = target_instance_id;
e.to_instance_version = target_instance_version;
RecordPlayerEventLog(PlayerEvent::ZONING, e);
}
//handle circumvention of zone restrictions
//we need the value when creating the outgoing packet as well.
uint8 ignore_restrictions = zonesummon_ignorerestrictions;