From 766607448f892c2a54ab87befa98670bffbd28e6 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Fri, 7 Aug 2026 23:32:10 -0300 Subject: [PATCH] feat(Core/Commands): allow .additem to remove items from offline players (#26714) --- .../Implementation/CharacterDatabase.cpp | 3 + .../Implementation/CharacterDatabase.h | 2 + src/server/scripts/Commands/cs_misc.cpp | 125 +++++++++++++++--- 3 files changed, 112 insertions(+), 18 deletions(-) diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 9b323b766..0ae0c0b84 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -140,6 +140,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_REP_ITEM_INSTANCE, "REPLACE INTO item_instance (itemEntry, owner_guid, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ITEM_INSTANCE, "UPDATE item_instance SET itemEntry = ?, owner_guid = ?, creatorGuid = ?, giftCreatorGuid = ?, count = ?, duration = ?, charges = ?, flags = ?, enchantments = ?, randomPropertyId = ?, durability = ?, playedTime = ?, text = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ITEM_INSTANCE_ON_LOAD, "UPDATE item_instance SET duration = ?, flags = ?, durability = ? WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_ITEM_COUNT, "UPDATE item_instance SET count = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_ITEM_INSTANCE, "DELETE FROM item_instance WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_ITEM_INSTANCE_BY_OWNER, "DELETE FROM item_instance WHERE owner_guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_GIFT_OWNER, "UPDATE character_gifts SET guid = ? WHERE item_guid = ?", CONNECTION_ASYNC); @@ -436,6 +437,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() "INNER JOIN item_instance ii ON ii.guid = ci.item " "LEFT JOIN character_inventory cb ON cb.item = ci.bag WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER, "SELECT ci.item FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE ii.itemEntry = ? AND ii.owner_guid = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_INVENTORY_STACKS_BY_ENTRY_AND_OWNER, "SELECT ci.item, ii.count FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item " + "LEFT JOIN character_inventory cb ON cb.bag = ci.item WHERE ii.itemEntry = ? AND ii.owner_guid = ? AND cb.item IS NULL", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_MAIL_ITEMS_BY_ENTRY, "SELECT mi.item_guid, m.sender, m.receiver, cs.account, cs.name, cr.account, cr.name " "FROM mail m INNER JOIN mail_items mi ON mi.mail_id = m.id INNER JOIN item_instance ii ON ii.guid = mi.item_guid " "INNER JOIN characters cs ON cs.guid = m.sender INNER JOIN characters cr ON cr.guid = m.receiver WHERE ii.itemEntry = ? LIMIT ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index e9fe3d705..843265341 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -124,6 +124,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_REP_ITEM_INSTANCE, CHAR_UPD_ITEM_INSTANCE, CHAR_UPD_ITEM_INSTANCE_ON_LOAD, + CHAR_UPD_ITEM_COUNT, CHAR_DEL_ITEM_INSTANCE, CHAR_DEL_ITEM_INSTANCE_BY_OWNER, CHAR_UPD_GIFT_OWNER, @@ -361,6 +362,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_GUILD_BANK_COUNT_ITEM, CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY, CHAR_SEL_CHAR_INVENTORY_ITEM_BY_ENTRY_AND_OWNER, + CHAR_SEL_CHAR_INVENTORY_STACKS_BY_ENTRY_AND_OWNER, CHAR_SEL_MAIL_ITEMS_BY_ENTRY, CHAR_SEL_AUCTIONHOUSE_ITEM_BY_ENTRY, CHAR_SEL_GUILD_BANK_ITEM_BY_ENTRY, diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index 6d8f75176..2598fbf75 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -1746,36 +1746,125 @@ public: Player* playerTarget = player->GetConnectedPlayer(); - if (!playerTarget) - return false; - // Subtract if (count < 0) { - // Only have scam check on player accounts - if (playerTarget->GetSession()->GetSecurity() == SEC_PLAYER) + uint32 removeCount = uint32(-count); + + if (playerTarget) { - if (!playerTarget->HasItemCount(itemId, 0)) + // Only have scam check on player accounts + if (playerTarget->GetSession()->GetSecurity() == SEC_PLAYER) { - // output that player don't have any items to destroy - handler->SendErrorMessage(LANG_REMOVEITEM_FAILURE, handler->GetNameLink(playerTarget), itemId); - return false; + if (!playerTarget->HasItemCount(itemId, 0)) + { + // output that player don't have any items to destroy + handler->SendErrorMessage(LANG_REMOVEITEM_FAILURE, handler->GetNameLink(playerTarget), itemId); + return false; + } + + if (!playerTarget->HasItemCount(itemId, removeCount)) + { + // output that player don't have as many items that you want to destroy + handler->SendErrorMessage(LANG_REMOVEITEM_ERROR, handler->GetNameLink(playerTarget), itemId); + return false; + } } - if (!playerTarget->HasItemCount(itemId, -count)) - { - // output that player don't have as many items that you want to destroy - handler->SendErrorMessage(LANG_REMOVEITEM_ERROR, handler->GetNameLink(playerTarget), itemId); - return false; - } + // output successful amount of destroyed items + playerTarget->DestroyItemCount(itemId, removeCount, true, false); + handler->PSendSysMessage(LANG_REMOVEITEM, itemId, removeCount, handler->GetNameLink(playerTarget)); + return true; } - // output successful amount of destroyed items - playerTarget->DestroyItemCount(itemId, -count, true, false); - handler->PSendSysMessage(LANG_REMOVEITEM, itemId, -count, handler->GetNameLink(playerTarget)); + // offline target: remove the items directly from the DB + if (handler->HasLowerSecurity(nullptr, player->GetGUID())) + return false; + + std::string nameLink = handler->playerLink(player->GetName()); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_INVENTORY_STACKS_BY_ENTRY_AND_OWNER); + stmt->SetData(0, itemId); + stmt->SetData(1, player->GetGUID().GetCounter()); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + + std::vector> stacks; + uint32 totalCount = 0; + if (result) + { + do + { + Field* fields = result->Fetch(); + stacks.emplace_back(fields[0].Get(), fields[1].Get()); + totalCount += fields[1].Get(); + } while (result->NextRow()); + } + + if (!totalCount) + { + handler->SendErrorMessage(LANG_REMOVEITEM_FAILURE, nameLink, itemId); + return false; + } + + // Only have scam check on player accounts + uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(player->GetGUID()); + if (AccountMgr::GetSecurity(accountId, realm.Id.Realm) == SEC_PLAYER && totalCount < removeCount) + { + handler->SendErrorMessage(LANG_REMOVEITEM_ERROR, nameLink, itemId); + return false; + } + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + uint32 remaining = removeCount; + for (auto const& [itemGuid, stackCount] : stacks) + { + if (!remaining) + break; + + if (stackCount <= remaining) + { + remaining -= stackCount; + Item::DeleteFromInventoryDB(trans, itemGuid); + Item::DeleteFromDB(trans, itemGuid); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT); + stmt->SetData(0, itemGuid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE); + stmt->SetData(0, itemGuid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_BOP_TRADE); + stmt->SetData(0, itemGuid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_CONTAINER); + stmt->SetData(0, itemGuid); + trans->Append(stmt); + } + else + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_COUNT); + stmt->SetData(0, stackCount - remaining); + stmt->SetData(1, itemGuid); + trans->Append(stmt); + remaining = 0; + } + } + CharacterDatabase.CommitTransaction(trans); + + handler->PSendSysMessage(LANG_REMOVEITEM, itemId, removeCount - remaining, nameLink); return true; } + // Adding items requires the target to be online + if (!playerTarget) + { + handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND); + return false; + } + // Adding items uint32 noSpaceForCount = 0;