diff --git a/changelog.txt b/changelog.txt index c13ee4b05..0242a6d1d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 10/28/2014 == +Uleat: Added Client::InterrogateInventory(). Can be invoked by #interrogateinv and is also called when Handle_OP_MoveItem() calls for SwapItemResync() + == 10/22/2014 == Uleat: Fix for stacking items in a world object..added a new command option: #peekinv world - will show world container contents, if one is in use by target. diff --git a/common/features.h b/common/features.h index 3047475e1..237a78290 100644 --- a/common/features.h +++ b/common/features.h @@ -261,7 +261,8 @@ enum { commandChangeFlags = 200, //ability to set/refresh flags commandBanPlayers = 100, //can set bans on players commandChangeDatarate = 201, //edit client's data rate - commandZoneToCoords = 0 //can #zone with coords + commandZoneToCoords = 0, //can #zone with coords + commandInterrogateInv = 100 //below this == only log on error state and self-only target dump }; //default states for logging flag on NPCs and clients (having NPCs on by default is prolly a bad idea) diff --git a/zone/client.cpp b/zone/client.cpp index b8527204f..f1fef3b23 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -327,6 +327,8 @@ Client::Client(EQStreamInterface* ieqs) EngagedRaidTarget = false; SavedRaidRestTimer = 0; + + interrogateinv_flag = false; } Client::~Client() { diff --git a/zone/client.h b/zone/client.h index e31c15598..598fc8bce 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1212,6 +1212,11 @@ public: void ShowNumHits(); // work around function for numhits not showing on buffs + void TripInterrogateInvState() { interrogateinv_flag = true; } + bool GetInterrogateInvState() { return interrogateinv_flag; } + + bool InterrogateInventory(Client* requester, bool log, bool silent, bool allowtrip, bool& error, bool autolog = true); + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); @@ -1524,6 +1529,11 @@ private: std::map accountflags; uint8 initial_respawn_selection; + + bool interrogateinv_flag; // used to minimize log spamming by players + + void InterrogateInventory_(bool errorcheck, Client* requester, int16 head, int16 index, const ItemInst* inst, const ItemInst* parent, bool log, bool silent, bool &error, int depth); + bool InterrogateInventory_error(int16 head, int16 index, const ItemInst* inst, const ItemInst* parent, int depth); }; #endif diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index d929118b0..db6edf703 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -9915,7 +9915,14 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) if (mi_hack) { Message(15, "Caution: Illegal use of inaccessable bag slots!"); } - if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) { SwapItemResync(mi); } + if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) { + SwapItemResync(mi); + + bool error = false; + InterrogateInventory(this, false, true, false, error, false); + if (error) + InterrogateInventory(this, true, false, true, error); + } return; } diff --git a/zone/command.cpp b/zone/command.cpp index c1121932c..f7225e2ee 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -259,6 +259,7 @@ int command_init(void) { command_add("appearance","[type] [value] - Send an appearance packet for you or your target",150,command_appearance) || command_add("nukeitem","[itemid] - Remove itemid from your player target's inventory",150,command_nukeitem) || command_add("peekinv","[worn/inv/cursor/trib/bank/trade/world/all] - Print out contents of your player target's inventory",100,command_peekinv) || + command_add("interrogateinv","- use [help] argument for available options",0,command_interrogateinv) || command_add("findnpctype","[search criteria] - Search database NPC types",100,command_findnpctype) || command_add("findzone","[search criteria] - Search database zones",100,command_findzone) || command_add("fz",nullptr,100, command_findzone) || @@ -3002,12 +3003,80 @@ void command_peekinv(Client *c, const Seperator *sep) if (!bFound) { - c->Message(0, "Usage: #peekinv [worn|cursor|inv|bank|trade|trib|all]"); + c->Message(0, "Usage: #peekinv [worn|inv|cursor|trib|bank|trade|world|all]"); c->Message(0, " Displays a portion of the targeted user's inventory"); c->Message(0, " Caution: 'all' is a lot of information!"); } } +void command_interrogateinv(Client *c, const Seperator *sep) +{ + // 'command_interrogateinv' is an in-memory inventory interrogation tool only. + // + // it does not verify against actual database entries..but, the output can be + // used to verify that something has been corrupted in a player's inventory. + // any error condition should be assumed that the item in question will be + // lost when the player logs out or zones (or incurrs any action that will + // consume the Client-Inventory object instance in question.) + // + // any item instances located at a greater depth than a reported error should + // be treated as an error themselves regardless of whether they report as the + // same or not. + + if (strcasecmp(sep->arg[1], "help") == 0) { + if (c->Admin() < commandInterrogateInv) { + c->Message(0, "Usage: #interrogateinv"); + c->Message(0, " Displays your inventory's current in-memory nested storage references"); + } + else { + c->Message(0, "Usage: #interrogateinv [log] [silent]"); + c->Message(0, " Displays your or your Player target inventory's current in-memory nested storage references"); + c->Message(0, " [log] - Logs interrogation to file"); + c->Message(0, " [silent] - Omits the in-game message portion of the interrogation"); + } + return; + } + + Client* target = nullptr; + std::map instmap; + bool log = false; + bool silent = false; + bool error = false; + bool allowtrip = false; + + if (c->Admin() < commandInterrogateInv) { + if (c->GetInterrogateInvState()) { + c->Message(13, "The last use of #interrogateinv on this inventory instance discovered an error..."); + c->Message(13, "Logging out, zoning or re-arranging items at this point will result in item loss!"); + return; + } + target = c; + allowtrip = true; + } + else { + if (c->GetTarget() == nullptr) { + target = c; + } + else if (c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); + } + else { + c->Message(1, "Use of this command is limited to Client entities"); + return; + } + + if (strcasecmp(sep->arg[1], "log") == 0) + log = true; + if (strcasecmp(sep->arg[2], "silent") == 0) + silent = true; + } + + bool success = target->InterrogateInventory(c, log, silent, allowtrip, error); + + if (!success) + c->Message(13, "An unknown error occurred while processing Client::InterrogateInventory()"); +} + void command_findnpctype(Client *c, const Seperator *sep) { if(sep->arg[1][0] == 0) { diff --git a/zone/command.h b/zone/command.h index c02c127dc..db069b57c 100644 --- a/zone/command.h +++ b/zone/command.h @@ -152,6 +152,7 @@ void command_heal(Client *c, const Seperator *sep); void command_appearance(Client *c, const Seperator *sep); void command_nukeitem(Client *c, const Seperator *sep); void command_peekinv(Client *c, const Seperator *sep); +void command_interrogateinv(Client *c, const Seperator *sep); void command_findnpctype(Client *c, const Seperator *sep); void command_findzone(Client *c, const Seperator *sep); void command_viewnpctype(Client *c, const Seperator *sep); diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 6a80aefe1..8cc9e4cff 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -2732,6 +2732,211 @@ bool Client::MoveItemToInventory(ItemInst *ItemToReturn, bool UpdateClient) { return true; } +bool Client::InterrogateInventory(Client* requester, bool log, bool silent, bool allowtrip, bool& error, bool autolog) +{ + if (!requester) + return false; + + std::map instmap; + + // build reference map + for (int16 index = MAIN_BEGIN; index < EmuConstants::MAP_POSSESSIONS_SIZE; ++index) + if (m_inv[index]) + instmap[index] = m_inv[index]; + for (int16 index = EmuConstants::TRIBUTE_BEGIN; index <= EmuConstants::TRIBUTE_END; ++index) + if (m_inv[index]) + instmap[index] = m_inv[index]; + for (int16 index = EmuConstants::BANK_BEGIN; index <= EmuConstants::BANK_END; ++index) + if (m_inv[index]) + instmap[index] = m_inv[index]; + for (int16 index = EmuConstants::SHARED_BANK_BEGIN; index <= EmuConstants::SHARED_BANK_END; ++index) + if (m_inv[index]) + instmap[index] = m_inv[index]; + for (int16 index = EmuConstants::TRADE_BEGIN; index <= EmuConstants::TRADE_END; ++index) + if (m_inv[index]) + instmap[index] = m_inv[index]; + + if (Object* tsobject = GetTradeskillObject()) + for (int16 index = MAIN_BEGIN; index < EmuConstants::MAP_WORLD_SIZE; ++index) + if (tsobject->GetItem(index)) + instmap[EmuConstants::WORLD_BEGIN + index] = tsobject->GetItem(index); + + int limbo = 0; + for (iter_queue cursor_itr = m_inv.cursor_begin(); cursor_itr != m_inv.cursor_end(); ++cursor_itr, ++limbo) { + if (cursor_itr == m_inv.cursor_begin()) // m_inv.cursor_begin() is referenced as MainCursor in MapPossessions above + continue; + + instmap[8000 + limbo] = *cursor_itr; + } + + if (m_inv[MainPowerSource]) + instmap[MainPowerSource] = m_inv[MainPowerSource]; + + // call InterrogateInventory_ for error check + for (std::map::iterator instmap_itr = instmap.begin(); (instmap_itr != instmap.end()) && (!error); ++instmap_itr) + InterrogateInventory_(true, requester, instmap_itr->first, INVALID_INDEX, instmap_itr->second, nullptr, log, silent, error, 0); + + if (autolog && error && (!log)) + log = true; + + if (log) + _log(INVENTORY__ERROR, "Client::InterrogateInventory() called for %s by %s with an error state of %s", GetName(), requester->GetName(), (error ? "TRUE" : "FALSE")); + if (!silent) + requester->Message(1, "--- Inventory Interrogation Report for %s (requested by: %s, error state: %s) ---", GetName(), requester->GetName(), (error ? "TRUE" : "FALSE")); + + // call InterrogateInventory_ for report + for (std::map::iterator instmap_itr = instmap.begin(); (instmap_itr != instmap.end()); ++instmap_itr) + InterrogateInventory_(false, requester, instmap_itr->first, INVALID_INDEX, instmap_itr->second, nullptr, log, silent, error, 0); + + if (error) { + Message(13, "An error has been discovered in your inventory!"); + Message(13, "Do not log out, zone or re-arrange items until this"); + Message(13, "issue has been resolved or item loss may occur!"); + + if (allowtrip) + TripInterrogateInvState(); + } + + if (log) { + _log(INVENTORY__ERROR, "Target interrogate inventory flag: %s", (GetInterrogateInvState() ? "TRUE" : "FALSE")); + _log(INVENTORY__ERROR, "Client::InterrogateInventory() -- End"); + } + if (!silent) { + requester->Message(1, "Target interrogation flag: %s", (GetInterrogateInvState() ? "TRUE" : "FALSE")); + requester->Message(1, "--- End of Interrogation Report ---"); + } + + instmap.clear(); + + return true; +} + +void Client::InterrogateInventory_(bool errorcheck, Client* requester, int16 head, int16 index, const ItemInst* inst, const ItemInst* parent, bool log, bool silent, bool &error, int depth) +{ + if (depth >= 10) { + _log(INVENTORY__ERROR, "Client::InterrogateInventory_() - Recursion count has exceeded the maximum allowable (You have a REALLY BIG PROBLEM!!)"); + return; + } + + if (errorcheck) { + if (InterrogateInventory_error(head, index, inst, parent, depth)) { + error = true; + } + else { + if (inst) + for (int16 sub = SUB_BEGIN; (sub < EmuConstants::ITEM_CONTAINER_SIZE) && (!error); ++sub) // treat any ItemInst as having the max internal slots available + if (inst->GetItem(sub)) + InterrogateInventory_(true, requester, head, sub, inst->GetItem(sub), inst, log, silent, error, depth + 1); + } + } + else { + bool localerror = InterrogateInventory_error(head, index, inst, parent, depth); + std::string i; + std::string p; + std::string e; + + if (inst) { i = StringFormat("%s (class: %u | augtype: %u)", inst->GetItem()->Name, inst->GetItem()->ItemClass, inst->GetItem()->AugType); } + else { i = "NONE"; } + if (parent) { p = StringFormat("%s (class: %u | augtype: %u), index: %i", parent->GetItem()->Name, parent->GetItem()->ItemClass, parent->GetItem()->AugType, index); } + else { p = "NONE"; } + if (localerror) { e = " [ERROR]"; } + else { e = ""; } + + if (log) + _log(INVENTORY__ERROR, "Head: %i, Depth: %i, Instance: %s, Parent: %s%s", + head, depth, i.c_str(), p.c_str(), e.c_str()); + if (!silent) + requester->Message(1, "%i:%i - inst: %s - parent: %s%s", + head, depth, i.c_str(), p.c_str(), e.c_str()); + + if (inst) + for (int16 sub = SUB_BEGIN; (sub < EmuConstants::ITEM_CONTAINER_SIZE); ++sub) + if (inst->GetItem(sub)) + InterrogateInventory_(false, requester, head, sub, inst->GetItem(sub), inst, log, silent, error, depth + 1); + } + + return; +} + +bool Client::InterrogateInventory_error(int16 head, int16 index, const ItemInst* inst, const ItemInst* parent, int depth) +{ + // very basic error checking - can be elaborated upon if more in-depth testing is needed... + + if ( + (head >= EmuConstants::EQUIPMENT_BEGIN && head <= EmuConstants::EQUIPMENT_END) || + (head >= EmuConstants::TRIBUTE_BEGIN && head <= EmuConstants::TRIBUTE_END) || + (head >= EmuConstants::WORLD_BEGIN && head <= EmuConstants::WORLD_END) || + (head >= 8000 && head <= 8101) || + (head == MainPowerSource)) { + switch (depth) + { + case 0: // requirement: inst is extant + if (!inst) + return true; + break; + case 1: // requirement: parent is common and inst is augment + if ((!parent) || (!inst)) + return true; + if (!parent->IsType(ItemClassCommon)) + return true; + if (index >= EmuConstants::ITEM_COMMON_SIZE) + return true; + break; + default: // requirement: none (something bad happened...) + return true; + } + } + else if ( + (head >= EmuConstants::GENERAL_BEGIN && head <= EmuConstants::GENERAL_END) || + (head == MainCursor) || + (head >= EmuConstants::BANK_BEGIN && head <= EmuConstants::BANK_END) || + (head >= EmuConstants::SHARED_BANK_BEGIN && head <= EmuConstants::SHARED_BANK_END) || + (head >= EmuConstants::TRADE_BEGIN && head <= EmuConstants::TRADE_END)) { + switch (depth) + { + case 0: // requirement: inst is extant + if (!inst) + return true; + break; + case 1: // requirement: parent is common and inst is augment ..or.. parent is container and inst is extant + if ((!parent) || (!inst)) + return true; + if (parent->IsType(ItemClassContainer)) + break; + if (parent->IsType(ItemClassBook)) + return true; + if (parent->IsType(ItemClassCommon)) { + if (!(inst->GetItem()->AugType > 0)) + return true; + if (index >= EmuConstants::ITEM_COMMON_SIZE) + return true; + } + break; + case 2: // requirement: parent is common and inst is augment + if ((!parent) || (!inst)) + return true; + if (parent->IsType(ItemClassContainer)) + return true; + if (parent->IsType(ItemClassBook)) + return true; + if (parent->IsType(ItemClassCommon)) { + if (!(inst->GetItem()->AugType > 0)) + return true; + if (index >= EmuConstants::ITEM_COMMON_SIZE) + return true; + } + break; + default: // requirement: none (something bad happened again...) + return true; + } + } + else { + return true; + } + + return false; +} + void Inventory::SetCustomItemData(uint32 character_id, int16 slot_id, std::string identifier, std::string value) { ItemInst *inst = GetItem(slot_id); if(inst) {