Core/Housing: stage 34 snapshot Initiative reward delivery
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
#include "HousingMgr.h"
|
#include "HousingMgr.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
#include <limits>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
namespace Housing
|
namespace Housing
|
||||||
@@ -16,8 +17,11 @@ namespace Housing
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr uint32 MaxArchivedRewardsPerNeighborhood = 32;
|
constexpr uint32 MaxArchivedRewardsPerNeighborhood = 32;
|
||||||
|
constexpr uint32 MaxRewardEntriesPerClaim = 64;
|
||||||
constexpr uint8 RewardClaimPending = 0;
|
constexpr uint8 RewardClaimPending = 0;
|
||||||
constexpr uint8 RewardClaimCompleted = 1;
|
constexpr uint8 RewardClaimCompleted = 1;
|
||||||
|
constexpr uint8 RewardGrantPending = 0;
|
||||||
|
constexpr uint8 RewardGrantDelivered = 1;
|
||||||
|
|
||||||
struct ArchivedRewardState
|
struct ArchivedRewardState
|
||||||
{
|
{
|
||||||
@@ -41,6 +45,14 @@ namespace Housing
|
|||||||
uint64 CompletedAt = 0;
|
uint64 CompletedAt = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RewardCatalogEntry
|
||||||
|
{
|
||||||
|
uint32 EntryId = 0;
|
||||||
|
InitiativeRewardGrantType Type = InitiativeRewardGrantType::Item;
|
||||||
|
uint32 AssetId = 0;
|
||||||
|
uint32 Quantity = 0;
|
||||||
|
};
|
||||||
|
|
||||||
std::mutex InitiativeRewardClaimMutex;
|
std::mutex InitiativeRewardClaimMutex;
|
||||||
|
|
||||||
bool PlayerOwnsNeighborhood(Player const* player, ObjectGuid const& neighborhoodGuid)
|
bool PlayerOwnsNeighborhood(Player const* player, ObjectGuid const& neighborhoodGuid)
|
||||||
@@ -55,6 +67,12 @@ namespace Housing
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsSupportedGrantType(uint8 value)
|
||||||
|
{
|
||||||
|
return value == uint8(InitiativeRewardGrantType::Item) ||
|
||||||
|
value == uint8(InitiativeRewardGrantType::Currency);
|
||||||
|
}
|
||||||
|
|
||||||
bool LoadArchivedReward(ObjectGuid const& neighborhoodGuid, uint64 generationId, ArchivedRewardState& state)
|
bool LoadArchivedReward(ObjectGuid const& neighborhoodGuid, uint64 generationId, ArchivedRewardState& state)
|
||||||
{
|
{
|
||||||
state = {};
|
state = {};
|
||||||
@@ -132,6 +150,75 @@ namespace Housing
|
|||||||
return state.GenerationId != 0 && state.InitiativeId != 0 && state.CharacterGuid != 0;
|
return state.GenerationId != 0 && state.InitiativeId != 0 && state.CharacterGuid != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InitiativeRewardClaimResult LoadCatalogEntries(uint16 rewardTier, std::vector<RewardCatalogEntry>& entries)
|
||||||
|
{
|
||||||
|
entries.clear();
|
||||||
|
QueryResult result = CharacterDatabase.PQuery(
|
||||||
|
"SELECT EntryID, GrantType, AssetID, Quantity FROM housing_initiative_reward_catalog "
|
||||||
|
"WHERE RewardTier = {} AND `Enabled` = 1 ORDER BY EntryID LIMIT {}",
|
||||||
|
uint32(rewardTier), MaxRewardEntriesPerClaim + 1);
|
||||||
|
if (!result)
|
||||||
|
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (entries.size() >= MaxRewardEntriesPerClaim)
|
||||||
|
return InitiativeRewardClaimResult::SafetyLimitExceeded;
|
||||||
|
|
||||||
|
Field* fields = result->Fetch();
|
||||||
|
uint8 const grantType = fields[1].GetUInt8();
|
||||||
|
RewardCatalogEntry entry;
|
||||||
|
entry.EntryId = fields[0].GetUInt32();
|
||||||
|
entry.AssetId = fields[2].GetUInt32();
|
||||||
|
entry.Quantity = fields[3].GetUInt32();
|
||||||
|
|
||||||
|
if (!entry.EntryId || !IsSupportedGrantType(grantType) || !entry.AssetId || !entry.Quantity ||
|
||||||
|
entry.Quantity > uint32(std::numeric_limits<int32>::max()))
|
||||||
|
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||||
|
|
||||||
|
entry.Type = InitiativeRewardGrantType(grantType);
|
||||||
|
entries.push_back(entry);
|
||||||
|
} while (result->NextRow());
|
||||||
|
|
||||||
|
return entries.empty() ? InitiativeRewardClaimResult::InvalidCatalog : InitiativeRewardClaimResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitiativeRewardClaimResult LoadDeliverySnapshot(uint64 claimId, std::vector<InitiativeRewardGrant>& grants)
|
||||||
|
{
|
||||||
|
grants.clear();
|
||||||
|
QueryResult result = CharacterDatabase.PQuery(
|
||||||
|
"SELECT EntryID, GrantType, AssetID, Quantity, State "
|
||||||
|
"FROM housing_initiative_reward_delivery WHERE ClaimID = {} ORDER BY EntryID LIMIT {}",
|
||||||
|
claimId, MaxRewardEntriesPerClaim + 1);
|
||||||
|
if (!result)
|
||||||
|
return InitiativeRewardClaimResult::Success;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (grants.size() >= MaxRewardEntriesPerClaim)
|
||||||
|
return InitiativeRewardClaimResult::SafetyLimitExceeded;
|
||||||
|
|
||||||
|
Field* fields = result->Fetch();
|
||||||
|
uint8 const grantType = fields[1].GetUInt8();
|
||||||
|
uint8 const state = fields[4].GetUInt8();
|
||||||
|
InitiativeRewardGrant grant;
|
||||||
|
grant.EntryId = fields[0].GetUInt32();
|
||||||
|
grant.AssetId = fields[2].GetUInt32();
|
||||||
|
grant.Quantity = fields[3].GetUInt32();
|
||||||
|
|
||||||
|
if (!grant.EntryId || !IsSupportedGrantType(grantType) || !grant.AssetId || !grant.Quantity ||
|
||||||
|
grant.Quantity > uint32(std::numeric_limits<int32>::max()) ||
|
||||||
|
(state != RewardGrantPending && state != RewardGrantDelivered))
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
|
||||||
|
grant.Type = InitiativeRewardGrantType(grantType);
|
||||||
|
grant.Delivered = state == RewardGrantDelivered;
|
||||||
|
grants.push_back(grant);
|
||||||
|
} while (result->NextRow());
|
||||||
|
|
||||||
|
return InitiativeRewardClaimResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
InitiativeRewardClaimResult PopulateReservation(Player const* player, ArchivedRewardState const& reward,
|
InitiativeRewardClaimResult PopulateReservation(Player const* player, ArchivedRewardState const& reward,
|
||||||
RewardClaimState const& claim, InitiativeRewardClaimReservation& reservation)
|
RewardClaimState const& claim, InitiativeRewardClaimReservation& reservation)
|
||||||
{
|
{
|
||||||
@@ -270,6 +357,132 @@ namespace Housing
|
|||||||
return PopulateReservation(player, verifiedReward, claim, reservation);
|
return PopulateReservation(player, verifiedReward, claim, reservation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InitiativeRewardClaimResult PrepareArchivedInitiativeRewardDelivery(Player const* player,
|
||||||
|
uint64 claimId, std::vector<InitiativeRewardGrant>& grants)
|
||||||
|
{
|
||||||
|
grants.clear();
|
||||||
|
if (!player || !claimId)
|
||||||
|
return InitiativeRewardClaimResult::InvalidRequest;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(InitiativeRewardClaimMutex);
|
||||||
|
|
||||||
|
RewardClaimState claim;
|
||||||
|
if (!LoadClaimById(claimId, claim))
|
||||||
|
return InitiativeRewardClaimResult::NoReward;
|
||||||
|
if (claim.CharacterGuid != player->GetGUID().GetCounter())
|
||||||
|
return InitiativeRewardClaimResult::PermissionDenied;
|
||||||
|
if (claim.State == RewardClaimCompleted)
|
||||||
|
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||||
|
|
||||||
|
ArchivedRewardState reward;
|
||||||
|
if (!LoadArchivedReward(claim.NeighborhoodGuid, claim.GenerationId, reward) ||
|
||||||
|
reward.InitiativeId != claim.InitiativeId)
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
if (!reward.RewardAvailable)
|
||||||
|
return InitiativeRewardClaimResult::NoReward;
|
||||||
|
if (reward.RewardClaimed)
|
||||||
|
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||||
|
|
||||||
|
InitiativeRewardClaimResult const existingSnapshot = LoadDeliverySnapshot(claimId, grants);
|
||||||
|
if (existingSnapshot != InitiativeRewardClaimResult::Success)
|
||||||
|
return existingSnapshot;
|
||||||
|
if (!grants.empty())
|
||||||
|
return InitiativeRewardClaimResult::Success;
|
||||||
|
|
||||||
|
std::vector<RewardCatalogEntry> catalog;
|
||||||
|
InitiativeRewardClaimResult const catalogResult = LoadCatalogEntries(reward.RewardTier, catalog);
|
||||||
|
if (catalogResult != InitiativeRewardClaimResult::Success)
|
||||||
|
return catalogResult;
|
||||||
|
|
||||||
|
uint64 const now = uint64(GameTime::GetGameTime());
|
||||||
|
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||||
|
for (RewardCatalogEntry const& entry : catalog)
|
||||||
|
{
|
||||||
|
transaction->PAppend(
|
||||||
|
"INSERT INTO housing_initiative_reward_delivery "
|
||||||
|
"(ClaimID, EntryID, GrantType, AssetID, Quantity, State, CreatedAt, UpdatedAt, DeliveredAt) "
|
||||||
|
"VALUES ({}, {}, {}, {}, {}, {}, {}, {}, 0) "
|
||||||
|
"ON DUPLICATE KEY UPDATE ClaimID = VALUES(ClaimID)",
|
||||||
|
claimId, entry.EntryId, uint32(entry.Type), entry.AssetId, entry.Quantity,
|
||||||
|
uint32(RewardGrantPending), now, now);
|
||||||
|
}
|
||||||
|
CharacterDatabase.DirectCommitTransaction(transaction);
|
||||||
|
|
||||||
|
InitiativeRewardClaimResult const snapshotResult = LoadDeliverySnapshot(claimId, grants);
|
||||||
|
if (snapshotResult != InitiativeRewardClaimResult::Success)
|
||||||
|
return snapshotResult;
|
||||||
|
if (grants.size() != catalog.size())
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < grants.size(); ++i)
|
||||||
|
{
|
||||||
|
RewardCatalogEntry const& expected = catalog[i];
|
||||||
|
InitiativeRewardGrant const& actual = grants[i];
|
||||||
|
if (actual.EntryId != expected.EntryId || actual.Type != expected.Type ||
|
||||||
|
actual.AssetId != expected.AssetId || actual.Quantity != expected.Quantity || actual.Delivered)
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InitiativeRewardClaimResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitiativeRewardClaimResult MarkArchivedInitiativeRewardGrantDelivered(Player const* player,
|
||||||
|
uint64 claimId, uint32 entryId)
|
||||||
|
{
|
||||||
|
if (!player || !claimId || !entryId)
|
||||||
|
return InitiativeRewardClaimResult::InvalidRequest;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(InitiativeRewardClaimMutex);
|
||||||
|
|
||||||
|
RewardClaimState claim;
|
||||||
|
if (!LoadClaimById(claimId, claim))
|
||||||
|
return InitiativeRewardClaimResult::NoReward;
|
||||||
|
if (claim.CharacterGuid != player->GetGUID().GetCounter())
|
||||||
|
return InitiativeRewardClaimResult::PermissionDenied;
|
||||||
|
if (claim.State == RewardClaimCompleted)
|
||||||
|
return InitiativeRewardClaimResult::AlreadyClaimed;
|
||||||
|
|
||||||
|
QueryResult row = CharacterDatabase.PQuery(
|
||||||
|
"SELECT GrantType, AssetID, Quantity, State FROM housing_initiative_reward_delivery "
|
||||||
|
"WHERE ClaimID = {} AND EntryID = {} LIMIT 1",
|
||||||
|
claimId, entryId);
|
||||||
|
if (!row)
|
||||||
|
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||||
|
|
||||||
|
Field* fields = row->Fetch();
|
||||||
|
uint8 const grantType = fields[0].GetUInt8();
|
||||||
|
uint32 const assetId = fields[1].GetUInt32();
|
||||||
|
uint32 const quantity = fields[2].GetUInt32();
|
||||||
|
uint8 const state = fields[3].GetUInt8();
|
||||||
|
if (!IsSupportedGrantType(grantType) || !assetId || !quantity ||
|
||||||
|
quantity > uint32(std::numeric_limits<int32>::max()) ||
|
||||||
|
(state != RewardGrantPending && state != RewardGrantDelivered))
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
|
||||||
|
if (state == RewardGrantDelivered)
|
||||||
|
return InitiativeRewardClaimResult::Success;
|
||||||
|
|
||||||
|
uint64 const now = uint64(GameTime::GetGameTime());
|
||||||
|
CharacterDatabase.DirectPExecute(
|
||||||
|
"UPDATE housing_initiative_reward_delivery SET State = {}, UpdatedAt = {}, "
|
||||||
|
"DeliveredAt = IF(DeliveredAt = 0, {}, DeliveredAt) "
|
||||||
|
"WHERE ClaimID = {} AND EntryID = {} AND State = {}",
|
||||||
|
uint32(RewardGrantDelivered), now, now, claimId, entryId, uint32(RewardGrantPending));
|
||||||
|
|
||||||
|
QueryResult verification = CharacterDatabase.PQuery(
|
||||||
|
"SELECT State, DeliveredAt FROM housing_initiative_reward_delivery "
|
||||||
|
"WHERE ClaimID = {} AND EntryID = {} LIMIT 1",
|
||||||
|
claimId, entryId);
|
||||||
|
if (!verification)
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
|
||||||
|
Field* verified = verification->Fetch();
|
||||||
|
if (verified[0].GetUInt8() != RewardGrantDelivered || !verified[1].GetUInt64())
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
|
||||||
|
return InitiativeRewardClaimResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
InitiativeRewardClaimResult FinalizeArchivedInitiativeReward(Player const* player, uint64 claimId)
|
InitiativeRewardClaimResult FinalizeArchivedInitiativeReward(Player const* player, uint64 claimId)
|
||||||
{
|
{
|
||||||
if (!player || !claimId)
|
if (!player || !claimId)
|
||||||
@@ -295,6 +508,22 @@ namespace Housing
|
|||||||
if (!reward.RewardAvailable)
|
if (!reward.RewardAvailable)
|
||||||
return InitiativeRewardClaimResult::NoReward;
|
return InitiativeRewardClaimResult::NoReward;
|
||||||
|
|
||||||
|
QueryResult deliveryCount = CharacterDatabase.PQuery(
|
||||||
|
"SELECT COUNT(*), SUM(State = {}) FROM housing_initiative_reward_delivery WHERE ClaimID = {}",
|
||||||
|
uint32(RewardGrantPending), claimId);
|
||||||
|
if (!deliveryCount)
|
||||||
|
return InitiativeRewardClaimResult::PersistenceError;
|
||||||
|
|
||||||
|
Field* deliveryFields = deliveryCount->Fetch();
|
||||||
|
uint64 const totalEntries = deliveryFields[0].GetUInt64();
|
||||||
|
uint64 const pendingEntries = deliveryFields[1].GetUInt64();
|
||||||
|
if (!totalEntries)
|
||||||
|
return InitiativeRewardClaimResult::InvalidCatalog;
|
||||||
|
if (totalEntries > MaxRewardEntriesPerClaim)
|
||||||
|
return InitiativeRewardClaimResult::SafetyLimitExceeded;
|
||||||
|
if (pendingEntries)
|
||||||
|
return InitiativeRewardClaimResult::DeliveryIncomplete;
|
||||||
|
|
||||||
uint64 const now = uint64(GameTime::GetGameTime());
|
uint64 const now = uint64(GameTime::GetGameTime());
|
||||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||||
transaction->PAppend(
|
transaction->PAppend(
|
||||||
|
|||||||
Reference in New Issue
Block a user