feat(Core/Commands): allow .additem to remove items from offline players (#26714)

This commit is contained in:
Andrew 2026-08-07 23:32:10 -03:00 committed by GitHub
parent f89fe30551
commit 766607448f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 112 additions and 18 deletions

View file

@ -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);

View file

@ -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,

View file

@ -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<std::pair<ObjectGuid::LowType, uint32>> stacks;
uint32 totalCount = 0;
if (result)
{
do
{
Field* fields = result->Fetch();
stacks.emplace_back(fields[0].Get<uint32>(), fields[1].Get<uint32>());
totalCount += fields[1].Get<uint32>();
} 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;