diff --git a/common/ruletypes.h b/common/ruletypes.h index c89213368..fd5ce8a7d 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -692,6 +692,7 @@ RULE_CATEGORY_END() RULE_CATEGORY(QueryServ) RULE_BOOL(QueryServ, PlayerLogChat, false) // Logs Player Chat RULE_BOOL(QueryServ, PlayerLogTrades, false) // Logs Player Trades +RULE_BOOL(QueryServ, PlayerDropItems, false) // Logs Player dropping items RULE_BOOL(QueryServ, PlayerLogHandins, false) // Logs Player Handins RULE_BOOL(QueryServ, PlayerLogNPCKills, false) // Logs Player NPC Kills RULE_BOOL(QueryServ, PlayerLogDeletes, false) // Logs Player Deletes diff --git a/common/servertalk.h b/common/servertalk.h index e1d019fc4..c5bee9ef8 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -203,6 +203,7 @@ #define ServerOP_CZSignalNPC 0x5017 #define ServerOP_CZSetEntityVariableByNPCTypeID 0x5018 #define ServerOP_WWMarquee 0x5019 +#define ServerOP_QSPlayerDropItem 0x5020 /* Query Serv Generic Packet Flag/Type Enumeration */ enum { QSG_LFGuild = 0 }; @@ -1140,6 +1141,27 @@ struct QSPlayerLogTrade_Struct { QSTradeItems_Struct items[0]; }; +struct QSDropItems_Struct { + uint32 item_id; + uint16 charges; + uint32 aug_1; + uint32 aug_2; + uint32 aug_3; + uint32 aug_4; + uint32 aug_5; +}; + +struct QSPlayerDropItem_Struct { + uint32 char_id; + bool pickup; // 0 drop, 1 pickup + uint32 zone_id; + int x; + int y; + int z; + uint16 _detail_count; + QSDropItems_Struct items[0]; +}; + struct QSHandinItems_Struct { char action_type[7]; // handin, return or reward uint16 char_slot; diff --git a/queryserv/database.cpp b/queryserv/database.cpp index c4e185360..fcda884e4 100644 --- a/queryserv/database.cpp +++ b/queryserv/database.cpp @@ -123,6 +123,39 @@ void Database::AddSpeech(const char* from, const char* to, const char* message, } +void Database::LogPlayerDropItem(QSPlayerDropItem_Struct* QS) { + + std::string query = StringFormat("INSERT INTO `qs_player_drop_record` SET `time` = NOW(), " + "`char_id` = '%i', `pickup` = '%i', " + "`zone_id` = '%i', `x` = '%i', `y` = '%i', `z` = '%i' ", + QS->char_id, QS->pickup, QS->zone_id, QS->x, QS->y, QS->z); + + auto results = QueryDatabase(query); + if (!results.Success()) { + Log(Logs::Detail, Logs::QS_Server, "Failed Drop Record Insert: %s", results.ErrorMessage().c_str()); + Log(Logs::Detail, Logs::QS_Server, "%s", query.c_str()); + } + + if (QS->_detail_count == 0) + return; + + int lastIndex = results.LastInsertedID(); + + for (int i = 0; i < QS->_detail_count; i++) { + query = StringFormat("INSERT INTO `qs_player_drop_record_entries` SET `event_id` = '%i', " + "`item_id` = '%i', `charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', " + "`aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'", + lastIndex, QS->items[i].item_id, + QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, + QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5); + results = QueryDatabase(query); + if (!results.Success()) { + Log(Logs::Detail, Logs::QS_Server, "Failed Drop Record Entry Insert: %s", results.ErrorMessage().c_str()); + Log(Logs::Detail, Logs::QS_Server, "%s", query.c_str()); + } + } +} + void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 detailCount) { std::string query = StringFormat("INSERT INTO `qs_player_trade_record` SET `time` = NOW(), " diff --git a/queryserv/database.h b/queryserv/database.h index b2d32341b..13815c575 100644 --- a/queryserv/database.h +++ b/queryserv/database.h @@ -45,6 +45,7 @@ public: void AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type); void LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount); + void LogPlayerDropItem(QSPlayerDropItem_Struct* QS); void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount); void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members); void LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items); diff --git a/queryserv/worldserver.cpp b/queryserv/worldserver.cpp index 686c92326..a03d23b1e 100644 --- a/queryserv/worldserver.cpp +++ b/queryserv/worldserver.cpp @@ -98,6 +98,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) database.LogPlayerTrade(QS, QS->_detail_count); break; } + case ServerOP_QSPlayerDropItem: { + QSPlayerDropItem_Struct *QS = (QSPlayerDropItem_Struct *) p.Data(); + database.LogPlayerDropItem(QS); + break; + } case ServerOP_QSPlayerLogHandins: { QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct*)p.Data(); database.LogPlayerHandin(QS, QS->_detail_count); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 263aeee2f..7d638d100 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1295,6 +1295,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_QSPlayerLogDeletes: case ServerOP_QSPlayerLogMoves: case ServerOP_QSPlayerLogMerchantTransactions: + case ServerOP_QSPlayerDropItem: { QSLink.SendPacket(pack); break; diff --git a/zone/client.h b/zone/client.h index ead552f73..393ede129 100644 --- a/zone/client.h +++ b/zone/client.h @@ -876,6 +876,7 @@ public: void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); void DropItem(int16 slot_id, bool recurse = true); + void DropItemQS(EQEmu::ItemInstance* inst, bool pickup); int GetItemLinkHash(const EQEmu::ItemInstance* inst); // move to ItemData..or make use of the pre-calculated database field diff --git a/zone/inventory.cpp b/zone/inventory.cpp index d375d94b6..902268e2c 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -688,10 +688,79 @@ void Client::DropItem(int16 slot_id, bool recurse) object->StartDecay(); Log(Logs::General, Logs::Inventory, "Item drop handled ut assolet"); + DropItemQS(inst, false); safe_delete(inst); } +void Client::DropItemQS(EQEmu::ItemInstance* inst, bool pickup) { + if (RuleB(QueryServ, PlayerDropItems)) { + QSPlayerDropItem_Struct qs_audit; + std::list event_details; + memset(&qs_audit, 0, sizeof(QSPlayerDropItem_Struct)); + + qs_audit.char_id = this->character_id; + qs_audit.pickup = pickup; + qs_audit.zone_id = this->GetZoneID(); + qs_audit.x = (int) this->GetX(); + qs_audit.y = (int) this->GetY(); + qs_audit.z = (int) this->GetZ(); + + if (inst) { + auto detail = new QSDropItems_Struct; + detail->item_id = inst->GetID(); + detail->charges = inst->IsClassBag() ? 1 : inst->GetCharges(); + detail->aug_1 = inst->GetAugmentItemID(1); + detail->aug_2 = inst->GetAugmentItemID(2); + detail->aug_3 = inst->GetAugmentItemID(3); + detail->aug_4 = inst->GetAugmentItemID(4); + detail->aug_5 = inst->GetAugmentItemID(5); + event_details.push_back(detail); + + if (inst->IsClassBag()) { + for (uint8 sub_slot = EQEmu::invbag::SLOT_BEGIN; (sub_slot <= EQEmu::invbag::SLOT_END); ++sub_slot) { // this is to catch ALL items + const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot); + if (bag_inst) { + detail = new QSDropItems_Struct; + detail->item_id = bag_inst->GetID(); + detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); + detail->aug_1 = bag_inst->GetAugmentItemID(1); + detail->aug_2 = bag_inst->GetAugmentItemID(2); + detail->aug_3 = bag_inst->GetAugmentItemID(3); + detail->aug_4 = bag_inst->GetAugmentItemID(4); + detail->aug_5 = bag_inst->GetAugmentItemID(5); + event_details.push_back(detail); + } + } + } + } + qs_audit._detail_count = event_details.size(); + + auto qs_pack = new ServerPacket( + ServerOP_QSPlayerDropItem, + sizeof(QSPlayerDropItem_Struct) + + (sizeof(QSDropItems_Struct) * qs_audit._detail_count)); + QSPlayerDropItem_Struct* qs_buf = (QSPlayerDropItem_Struct*) qs_pack->pBuffer; + + memcpy(qs_buf, &qs_audit, sizeof(QSPlayerDropItem_Struct)); + + int offset = 0; + + for (auto iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSDropItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); + } + + event_details.clear(); + + if (worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); + } +} + // Drop inst void Client::DropInst(const EQEmu::ItemInstance* inst) { diff --git a/zone/object.cpp b/zone/object.cpp index 3ad4a6a9c..4306c6642 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -528,6 +528,8 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) if(cursordelete) // delete the item if it's a duplicate lore. We have to do this because the client expects the item packet sender->DeleteItemInInventory(EQEmu::invslot::slotCursor); + sender->DropItemQS(m_inst, true); + if(!m_ground_spawn) safe_delete(m_inst);