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

* Plumbing

* Batch processing in world

* Cleanup

* Cleanup

* Update player_event_logs.cpp

* Add player zoning event

* Use generics

* Comments

* Add events

* Add more events

* AA_GAIN, AA_PURCHASE, FORAGE_SUCCESS, FORAGE_FAILURE

* FISH_SUCCESS, FISH_FAILURE, ITEM_DESTROY

* Add charges to ITEM_DESTROY

* WENT_ONLINE, WENT_OFFLINE

* LEVEL_GAIN, LEVEL_LOSS

* LOOT_ITEM

* MERCHANT_PURCHASE

* MERCHANT_SELL

* SKILL_UP

* Add events

* Add more events

* TASK_ACCEPT, TASK_COMPLETE, and TASK_UPDATE

* GROUNDSPAWN_PICKUP

* SAY

* REZ_ACCEPTED

* COMBINE_FAILURE and COMBINE_SUCCESS

* DROPPED_ITEM

* DEATH

* SPLIT_MONEY

* TRADER_PURCHASE and TRADER_SELL

* DISCOVER_ITEM

* Convert GM_COMMAND to use new macro

* Convert ZONING event to use macro

* Revert some code changes

* Revert "Revert some code changes"

This reverts commit d53682f997e89a053a660761085913245db91e9d.

* Add cereal generation support to repositories

* TRADE

* Formatting

* Cleanup

* Relocate discord_manager to discord folder

* Discord sending plumbing

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

* More discord sending plumbing

* More discord message formatting work

* More discord formatting work

* Discord formatting of events

* Format WENT_ONLINE, WENT_OFFLINE

* Add merchant purchase event

* Handle Discord MERCHANT_SELL formatter

* Update player_event_discord_formatter.cpp

* Tweaks

* Implement retention truncation

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

* Process on initial bootup

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

* Reload player event settings when logs are reloaded in game

* Set settings defaults

* Update player_event_logs.cpp

* Update player_event_logs.cpp

* Set retention days on boot

* Update player_event_logs.cpp

* Player Handin Event Testing.

Testing player handin stuff.

* Cleanup.

* Finish NPC Handin.

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

* Fix for windows _inline

* Bump to cpp20 default, ignore excessive warnings on windows

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

* Windows compile fixes

* Update CMakeLists.txt

* Update CMakeLists.txt

* Update CMakeLists.txt

* Create 2022_12_19_player_events_tables.sql

* [Formatters] Work on Discord Formatters

* Handin money.

* Format header

* [Formatters] Work on Discord Formatters

* Format

* Format

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

* [Formatters] More Work on Formatters.

* Add missing #endif

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

* NPC Handin Discord Formatter

* Update player_event_logs.cpp

* Discover Item Discord Formatter

* Dropped Item Discord Formatter

* Split Money Discord Formatter

* Trader Discord Formatters

* Cleanup.

* Trade Event Discord Formatter Groundwork

* SAY don't record GM commands

* GM_Command don't record #help

* Update player_event_logs.cpp

* Fill in more event data

* Post rebase fixes

* Post rebase fix

* Discord formatting adjustments

* Add event deprecation or unimplemented tag support

* Trade events

* Add return money and sanity checks.

* Update schema

* Update ucs.cpp

* Update client.cpp

* Update 2022_12_19_player_events_tables.sql

* Implement archive single line

* Replace hackers table and functions with PossibleHack player event

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

* Update bot_command.cpp

* Record NPC kill events ALL / Named / Raid

* Add BatchEventProcessIntervalSeconds rule

* Naming

* Update CMakeLists.txt

* Update database_schema.h

* Remove logging function and methods

* DB version

* Cleanup SendPlayerHandinEvent

---------

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