Core/Housing: stage 34 snapshot Initiative reward delivery
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "HousingMgr.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
namespace Housing
|
||||
@@ -16,8 +17,11 @@ namespace Housing
|
||||
namespace
|
||||
{
|
||||
constexpr uint32 MaxArchivedRewardsPerNeighborhood = 32;
|
||||
constexpr uint32 MaxRewardEntriesPerClaim = 64;
|
||||
constexpr uint8 RewardClaimPending = 0;
|
||||
constexpr uint8 RewardClaimCompleted = 1;
|
||||
constexpr uint8 RewardGrantPending = 0;
|
||||
constexpr uint8 RewardGrantDelivered = 1;
|
||||
|
||||
struct ArchivedRewardState
|
||||
{
|
||||
@@ -41,6 +45,14 @@ namespace Housing
|
||||
uint64 CompletedAt = 0;
|
||||
};
|
||||
|
||||
struct RewardCatalogEntry
|
||||
{
|
||||
uint32 EntryId = 0;
|
||||
InitiativeRewardGrantType Type = InitiativeRewardGrantType::Item;
|
||||
uint32 AssetId = 0;
|
||||
uint32 Quantity = 0;
|
||||
};
|
||||
|
||||
std::mutex InitiativeRewardClaimMutex;
|
||||
|
||||
bool PlayerOwnsNeighborhood(Player const* player, ObjectGuid const& neighborhoodGuid)
|
||||
@@ -55,6 +67,12 @@ namespace Housing
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsSupportedGrantType(uint8 value)
|
||||
{
|
||||
return value == uint8(InitiativeRewardGrantType::Item) ||
|
||||
value == uint8(InitiativeRewardGrantType::Currency);
|
||||
}
|
||||
|
||||
bool LoadArchivedReward(ObjectGuid const& neighborhoodGuid, uint64 generationId, ArchivedRewardState& state)
|
||||
{
|
||||
state = {};
|
||||
@@ -132,6 +150,75 @@ namespace Housing
|
||||
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,
|
||||
RewardClaimState const& claim, InitiativeRewardClaimReservation& reservation)
|
||||
{
|
||||
@@ -270,6 +357,132 @@ namespace Housing
|
||||
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)
|
||||
{
|
||||
if (!player || !claimId)
|
||||
@@ -295,6 +508,22 @@ namespace Housing
|
||||
if (!reward.RewardAvailable)
|
||||
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());
|
||||
CharacterDatabaseTransaction transaction = CharacterDatabase.BeginTransaction();
|
||||
transaction->PAppend(
|
||||
|
||||
Reference in New Issue
Block a user