Core/Housing: stage 23 make decor storage mutations atomic
This commit is contained in:
@@ -3,10 +3,14 @@
|
||||
*/
|
||||
|
||||
#include "HousingDecorSession.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "HousingMgr.h"
|
||||
#include "HousingPackets.h"
|
||||
#include "Player.h"
|
||||
#include "WorldSession.h"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Housing
|
||||
{
|
||||
@@ -138,4 +142,173 @@ namespace Housing
|
||||
|
||||
session->HandleHousingDecorSetDyeSlots(packet);
|
||||
}
|
||||
|
||||
void HandleDecorDeleteFromStorageStage23(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorDeleteFromStorage& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingDecorDeleteFromStorageResponse response;
|
||||
response.Result = ToDecorPacketResult(Result::Success);
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
std::vector<std::pair<ObjectGuid, uint32>> validatedDecor;
|
||||
validatedDecor.reserve(packet.DecorGUIDs.size());
|
||||
|
||||
// Validate the entire wire batch before beginning the transaction. Reject
|
||||
// duplicate GUIDs so one storage instance can never decrement its license twice.
|
||||
for (ObjectGuid const& decorGuid : packet.DecorGUIDs)
|
||||
{
|
||||
if (!IsDecorGuid(decorGuid) || !decorGuid.GetEntry() ||
|
||||
std::find_if(validatedDecor.begin(), validatedDecor.end(),
|
||||
[&decorGuid](auto const& entry) { return entry.first == decorGuid; }) != validatedDecor.end())
|
||||
{
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
break;
|
||||
}
|
||||
|
||||
QueryResult storedDecor = CharacterDatabase.PQuery(
|
||||
"SELECT i.DecorID FROM housing_decor_instance i "
|
||||
"INNER JOIN account_housing_decor a ON a.BattlenetAccountId = i.BattlenetAccountId "
|
||||
"AND a.DecorId = i.DecorID "
|
||||
"WHERE i.BattlenetAccountId = {} AND i.DecorGuid = '{}' "
|
||||
"AND i.PlacementStatus = 0 AND i.HouseGuid = '' AND a.Quantity > 0 LIMIT 1",
|
||||
battlenetAccountId, decorGuid.ToString());
|
||||
if (!storedDecor)
|
||||
{
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32 const decorId = storedDecor->Fetch()[0].GetUInt32();
|
||||
if (!decorId || decorId != decorGuid.GetEntry())
|
||||
{
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
break;
|
||||
}
|
||||
|
||||
validatedDecor.emplace_back(decorGuid, decorId);
|
||||
}
|
||||
|
||||
if (response.Result == ToDecorPacketResult(Result::Success) && !validatedDecor.empty())
|
||||
{
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
for (auto const& [decorGuid, decorId] : validatedDecor)
|
||||
{
|
||||
trans->PAppend(
|
||||
"DELETE FROM housing_decor_instance WHERE BattlenetAccountId = {} "
|
||||
"AND DecorGuid = '{}' AND DecorID = {} AND PlacementStatus = 0 AND HouseGuid = ''",
|
||||
battlenetAccountId, decorGuid.ToString(), decorId);
|
||||
trans->PAppend(
|
||||
"UPDATE account_housing_decor SET Quantity = Quantity - 1 "
|
||||
"WHERE BattlenetAccountId = {} AND DecorId = {} AND Quantity > 0",
|
||||
battlenetAccountId, decorId);
|
||||
}
|
||||
|
||||
trans->PAppend(
|
||||
"DELETE FROM account_housing_decor WHERE BattlenetAccountId = {} AND Quantity = 0",
|
||||
battlenetAccountId);
|
||||
CharacterDatabase.DirectCommitTransaction(trans);
|
||||
}
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
|
||||
void HandleDecorRedeemDeferredDecorStage23(WorldSession* session,
|
||||
WorldPackets::Housing::HousingDecorRedeemDeferredDecor& packet)
|
||||
{
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
WorldPackets::Housing::HousingRedeemDeferredDecorResponse response;
|
||||
response.TransactionID = packet.TransactionID;
|
||||
response.Result = ToDecorPacketResult(Result::InvalidDecorItem);
|
||||
|
||||
Player* player = session->GetPlayer();
|
||||
if (!player || !packet.DecorID || !packet.TransactionID)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 const battlenetAccountId = session->GetBattlenetAccountId();
|
||||
QueryResult existing = CharacterDatabase.PQuery(
|
||||
"SELECT DecorID, DecorGuid FROM housing_deferred_decor_redemption "
|
||||
"WHERE BattlenetAccountId = {} AND TransactionID = {} LIMIT 1",
|
||||
battlenetAccountId, packet.TransactionID);
|
||||
|
||||
if (existing)
|
||||
{
|
||||
Field* fields = existing->Fetch();
|
||||
uint32 const persistedDecorId = fields[0].GetUInt32();
|
||||
ObjectGuid const persistedDecorGuid = ObjectGuid::FromString(fields[1].GetString());
|
||||
if (persistedDecorId != packet.DecorID || !IsDecorGuid(persistedDecorGuid) ||
|
||||
persistedDecorGuid.GetEntry() != packet.DecorID)
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
response.DecorGUID = persistedDecorGuid;
|
||||
|
||||
// Heal only rows that can be reconstructed without guessing quantities.
|
||||
// Existing license quantities are preserved; a missing license is restored
|
||||
// to the minimum one implied by the persisted redemption record.
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
trans->PAppend(
|
||||
"INSERT INTO account_housing_decor (BattlenetAccountId, DecorId, Quantity) VALUES ({}, {}, 1) "
|
||||
"ON DUPLICATE KEY UPDATE Quantity = Quantity",
|
||||
battlenetAccountId, packet.DecorID);
|
||||
trans->PAppend(
|
||||
"INSERT INTO housing_decor_instance "
|
||||
"(BattlenetAccountId, DecorGuid, DecorID, HouseGuid, PlacementStatus, SourceType, SourceValue) "
|
||||
"VALUES ({}, '{}', {}, '', 0, 0, '') "
|
||||
"ON DUPLICATE KEY UPDATE DecorID = VALUES(DecorID)",
|
||||
battlenetAccountId, response.DecorGUID.ToString(), packet.DecorID);
|
||||
CharacterDatabase.DirectCommitTransaction(trans);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.DecorGUID = ObjectGuid::Create<HighGuid::Housing>(
|
||||
1, packet.DecorID, packet.TransactionID, player->GetGUID().GetCounter());
|
||||
if (!IsDecorGuid(response.DecorGUID))
|
||||
{
|
||||
session->SendPacket(response.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
// The redemption marker, licensed quantity and concrete storage instance
|
||||
// are one logical mutation and must either all persist or all roll back.
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
trans->PAppend(
|
||||
"INSERT INTO housing_deferred_decor_redemption "
|
||||
"(BattlenetAccountId, TransactionID, DecorID, DecorGuid) VALUES ({}, {}, {}, '{}')",
|
||||
battlenetAccountId, packet.TransactionID, packet.DecorID, response.DecorGUID.ToString());
|
||||
trans->PAppend(
|
||||
"INSERT INTO account_housing_decor (BattlenetAccountId, DecorId, Quantity) VALUES ({}, {}, 1) "
|
||||
"ON DUPLICATE KEY UPDATE Quantity = Quantity + 1",
|
||||
battlenetAccountId, packet.DecorID);
|
||||
trans->PAppend(
|
||||
"INSERT INTO housing_decor_instance "
|
||||
"(BattlenetAccountId, DecorGuid, DecorID, HouseGuid, PlacementStatus, SourceType, SourceValue) "
|
||||
"VALUES ({}, '{}', {}, '', 0, 0, '')",
|
||||
battlenetAccountId, response.DecorGUID.ToString(), packet.DecorID);
|
||||
CharacterDatabase.DirectCommitTransaction(trans);
|
||||
}
|
||||
|
||||
QueryResult committed = CharacterDatabase.PQuery(
|
||||
"SELECT 1 FROM housing_deferred_decor_redemption r "
|
||||
"INNER JOIN housing_decor_instance i ON i.BattlenetAccountId = r.BattlenetAccountId "
|
||||
"AND i.DecorGuid = r.DecorGuid AND i.DecorID = r.DecorID "
|
||||
"INNER JOIN account_housing_decor a ON a.BattlenetAccountId = r.BattlenetAccountId "
|
||||
"AND a.DecorId = r.DecorID AND a.Quantity > 0 "
|
||||
"WHERE r.BattlenetAccountId = {} AND r.TransactionID = {} "
|
||||
"AND r.DecorID = {} AND r.DecorGuid = '{}' LIMIT 1",
|
||||
battlenetAccountId, packet.TransactionID, packet.DecorID, response.DecorGUID.ToString());
|
||||
if (committed)
|
||||
response.Result = ToDecorPacketResult(Result::Success);
|
||||
|
||||
session->SendPacket(response.Write());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user