|
@@ -14,10 +14,11 @@
|
|
|
#include "../build_data/runtime.h"
|
|
#include "../build_data/runtime.h"
|
|
|
#include "runtime.h"
|
|
#include "runtime.h"
|
|
|
#include "state.h"
|
|
#include "state.h"
|
|
|
|
|
+#include "state_account_transaction_helpers.h"
|
|
|
#include "storage/internal.h"
|
|
#include "storage/internal.h"
|
|
|
|
|
|
|
|
namespace sunrise::state {
|
|
namespace sunrise::state {
|
|
|
-namespace {
|
|
|
|
|
|
|
+namespace runtime::detail {
|
|
|
|
|
|
|
|
namespace authored_inventory = account::inventory;
|
|
namespace authored_inventory = account::inventory;
|
|
|
namespace item_details = build_data::items::details;
|
|
namespace item_details = build_data::items::details;
|
|
@@ -27,26 +28,9 @@ namespace family4_loadout = middleware::datagen::family4::loadout;
|
|
|
/** First SOID reserved for item instances created by this local runtime. */
|
|
/** First SOID reserved for item instances created by this local runtime. */
|
|
|
constexpr std::uint64_t kFirstGeneratedItemSoid = 0x4000000000000001ULL;
|
|
constexpr std::uint64_t kFirstGeneratedItemSoid = 0x4000000000000001ULL;
|
|
|
|
|
|
|
|
-/** Historical profile definitions returned by ordinary gear dismantles. */
|
|
|
|
|
-constexpr std::uint32_t kLegendaryShardsDefinitionHash = 0x3CF2E8E2U;
|
|
|
|
|
-constexpr std::uint32_t kGunsmithMaterialsDefinitionHash = 0x28D6AC07U;
|
|
|
|
|
-/** Base gear payout for the supported pre-Beyond-Light client. */
|
|
|
|
|
-constexpr std::int32_t kLegendaryShardDismantleQuantity = 4;
|
|
|
|
|
-constexpr std::int32_t kGunsmithMaterialDismantleQuantity = 3;
|
|
|
|
|
/** Equipment slots 0-2 are weapons and 3-7 are class-specific armor. */
|
|
/** Equipment slots 0-2 are weapons and 3-7 are class-specific armor. */
|
|
|
constexpr std::uint8_t kGearEquipmentSlotCount = 8;
|
|
constexpr std::uint8_t kGearEquipmentSlotCount = 8;
|
|
|
|
|
|
|
|
-struct DismantleRewardPolicy {
|
|
|
|
|
- std::uint32_t definitionHash{};
|
|
|
|
|
- std::int32_t quantity{};
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-constexpr std::array<DismantleRewardPolicy, kDismantleRewardCapacity>
|
|
|
|
|
- kGearDismantleRewards{{
|
|
|
|
|
- {kLegendaryShardsDefinitionHash, kLegendaryShardDismantleQuantity},
|
|
|
|
|
- {kGunsmithMaterialsDefinitionHash, kGunsmithMaterialDismantleQuantity},
|
|
|
|
|
- }};
|
|
|
|
|
-
|
|
|
|
|
/** Writes one exhaustive equipment-transaction checkpoint to the persistent diagnostic log. */
|
|
/** Writes one exhaustive equipment-transaction checkpoint to the persistent diagnostic log. */
|
|
|
void report_equipment(std::string_view stage,
|
|
void report_equipment(std::string_view stage,
|
|
|
std::string_view result,
|
|
std::string_view result,
|
|
@@ -196,11 +180,11 @@ same_profile_inventory(const AccountState& account,
|
|
|
|
|
|
|
|
/** @return True when two fixed profile views, including their empty tails, are identical. */
|
|
/** @return True when two fixed profile views, including their empty tails, are identical. */
|
|
|
[[nodiscard]] bool same_profile_views(
|
|
[[nodiscard]] bool same_profile_views(
|
|
|
- const std::array<authored_inventory::ProfileItem,
|
|
|
|
|
- authored_inventory::kProfileItemCapacity>& left,
|
|
|
|
|
|
|
+ const std::array<authored_inventory::ProfileItem, authored_inventory::kProfileItemCapacity>&
|
|
|
|
|
+ left,
|
|
|
std::size_t leftCount,
|
|
std::size_t leftCount,
|
|
|
- const std::array<authored_inventory::ProfileItem,
|
|
|
|
|
- authored_inventory::kProfileItemCapacity>& right,
|
|
|
|
|
|
|
+ const std::array<authored_inventory::ProfileItem, authored_inventory::kProfileItemCapacity>&
|
|
|
|
|
+ right,
|
|
|
std::size_t rightCount) noexcept {
|
|
std::size_t rightCount) noexcept {
|
|
|
if (leftCount != rightCount) {
|
|
if (leftCount != rightCount) {
|
|
|
return false;
|
|
return false;
|
|
@@ -759,14 +743,6 @@ void report_socket_plug(std::string_view stage,
|
|
|
return semanticIndex < authored_inventory::kEquipmentSlotCount;
|
|
return semanticIndex < authored_inventory::kEquipmentSlotCount;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** One instance's unique native row and equipped marker in a resolved character loadout. */
|
|
|
|
|
-struct ResolvedPosition {
|
|
|
|
|
- std::uint16_t inventoryRow{};
|
|
|
|
|
- std::uint8_t equipmentSlot{};
|
|
|
|
|
- bool equipped{};
|
|
|
|
|
- std::int32_t mutationSerial{};
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
/** Finds one instance exactly once in a checked, row-sorted loadout. */
|
|
/** Finds one instance exactly once in a checked, row-sorted loadout. */
|
|
|
[[nodiscard]] bool find_resolved_position(const family4_loadout::ResolvedLoadout& loadout,
|
|
[[nodiscard]] bool find_resolved_position(const family4_loadout::ResolvedLoadout& loadout,
|
|
|
std::uint64_t instanceSoid,
|
|
std::uint64_t instanceSoid,
|
|
@@ -1535,15 +1511,17 @@ character_item_at(CharacterState& character, const CharacterItemLocation& locati
|
|
|
/**
|
|
/**
|
|
|
* Credits the supported client's ordinary weapon/armor dismantle payout.
|
|
* Credits the supported client's ordinary weapon/armor dismantle payout.
|
|
|
*
|
|
*
|
|
|
- * Capped stacks lose only the overflowing part, matching normal profile-inventory behavior.
|
|
|
|
|
- * Every credited row receives a new mutation serial so the account observer can display it.
|
|
|
|
|
|
|
+ * Capped stacks
|
|
|
|
|
+ * lose only the overflowing part, matching normal profile-inventory behavior.
|
|
|
|
|
+ * Every credited row
|
|
|
|
|
+ * receives a new mutation serial so the account observer can display it.
|
|
|
*/
|
|
*/
|
|
|
-[[nodiscard]] bool apply_dismantle_rewards(
|
|
|
|
|
- const AccountState& before,
|
|
|
|
|
- std::uint8_t equipmentSlot,
|
|
|
|
|
- AccountState& after,
|
|
|
|
|
- std::array<DismantleReward, kDismantleRewardCapacity>& rewards,
|
|
|
|
|
- std::size_t& rewardCount) noexcept {
|
|
|
|
|
|
|
+[[nodiscard]] bool
|
|
|
|
|
+apply_dismantle_rewards(const AccountState& before,
|
|
|
|
|
+ std::uint8_t equipmentSlot,
|
|
|
|
|
+ AccountState& after,
|
|
|
|
|
+ std::array<DismantleReward, kDismantleRewardCapacity>& rewards,
|
|
|
|
|
+ std::size_t& rewardCount) noexcept {
|
|
|
after = before;
|
|
after = before;
|
|
|
rewards = {};
|
|
rewards = {};
|
|
|
rewardCount = 0;
|
|
rewardCount = 0;
|
|
@@ -1560,7 +1538,8 @@ character_item_at(CharacterState& character, const CharacterItemLocation& locati
|
|
|
(std::max)(greatestMutationSerial, before.profileItems[index].mutationSerial);
|
|
(std::max)(greatestMutationSerial, before.profileItems[index].mutationSerial);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for (const DismantleRewardPolicy& policy : kGearDismantleRewards) {
|
|
|
|
|
|
|
+ for (std::size_t policyIndex = 0; policyIndex < before.dismantleRewardCount; ++policyIndex) {
|
|
|
|
|
+ const DismantleRewardPolicy& policy = before.dismantleRewards[policyIndex];
|
|
|
build_data::items::Definition definition{};
|
|
build_data::items::Definition definition{};
|
|
|
item_details::Definition detail{};
|
|
item_details::Definition detail{};
|
|
|
inventory_buckets::Descriptor bucket{};
|
|
inventory_buckets::Descriptor bucket{};
|
|
@@ -1752,8 +1731,7 @@ character_item_at(CharacterState& character, const CharacterItemLocation& locati
|
|
|
|
|
|
|
|
build_data::items::Definition dismantledDefinition{};
|
|
build_data::items::Definition dismantledDefinition{};
|
|
|
item_details::Definition dismantledDetail{};
|
|
item_details::Definition dismantledDetail{};
|
|
|
- if (!build_data::find_item_definition_hash(dismantledItem.definitionHash,
|
|
|
|
|
- dismantledDefinition)
|
|
|
|
|
|
|
+ if (!build_data::find_item_definition_hash(dismantledItem.definitionHash, dismantledDefinition)
|
|
|
|| dismantledDefinition.definitionHash != dismantledItem.definitionHash
|
|
|| dismantledDefinition.definitionHash != dismantledItem.definitionHash
|
|
|
|| !build_data::find_configured_item_detail(dismantledDefinition.definitionIndex,
|
|
|| !build_data::find_configured_item_detail(dismantledDefinition.definitionIndex,
|
|
|
dismantledDetail)
|
|
dismantledDetail)
|
|
@@ -1819,8 +1797,7 @@ character_item_at(CharacterState& character, const CharacterItemLocation& locati
|
|
|
|| left.inventoryIndex != right.inventoryIndex
|
|
|| left.inventoryIndex != right.inventoryIndex
|
|
|
|| left.movedInventoryItemCount != right.movedInventoryItemCount
|
|
|| left.movedInventoryItemCount != right.movedInventoryItemCount
|
|
|
|| left.rewardCount != right.rewardCount || left.inventoryRow != right.inventoryRow
|
|
|| left.rewardCount != right.rewardCount || left.inventoryRow != right.inventoryRow
|
|
|
- || left.equipmentSlot != right.equipmentSlot
|
|
|
|
|
- || left.profileChanged != right.profileChanged
|
|
|
|
|
|
|
+ || left.equipmentSlot != right.equipmentSlot || left.profileChanged != right.profileChanged
|
|
|
|| !same_stationary_item(left.dismantledItem, right.dismantledItem)
|
|
|| !same_stationary_item(left.dismantledItem, right.dismantledItem)
|
|
|
|| !same_character(left.beforeCharacter, right.beforeCharacter)
|
|
|| !same_character(left.beforeCharacter, right.beforeCharacter)
|
|
|
|| !same_character(left.afterCharacter, right.afterCharacter)
|
|
|| !same_character(left.afterCharacter, right.afterCharacter)
|
|
@@ -1851,8 +1828,7 @@ character_item_at(CharacterState& character, const CharacterItemLocation& locati
|
|
|
|| mutation.dismantledInstanceSoid == 0
|
|
|| mutation.dismantledInstanceSoid == 0
|
|
|
|| mutation.dismantledItem.instanceSoid != mutation.dismantledInstanceSoid
|
|
|| mutation.dismantledItem.instanceSoid != mutation.dismantledInstanceSoid
|
|
|
|| mutation.dismantledItem.definitionHash == authored_inventory::kNoDefinitionHash
|
|
|| mutation.dismantledItem.definitionHash == authored_inventory::kNoDefinitionHash
|
|
|
- || mutation.characterIndex >= current.characterCount
|
|
|
|
|
- || mutation.expectedInventoryCount == 0
|
|
|
|
|
|
|
+ || mutation.characterIndex >= current.characterCount || mutation.expectedInventoryCount == 0
|
|
|
|| mutation.expectedInventoryCount > authored_inventory::kCharacterItemCapacity
|
|
|| mutation.expectedInventoryCount > authored_inventory::kCharacterItemCapacity
|
|
|
|| mutation.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|| mutation.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|
|| mutation.afterProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|| mutation.afterProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
@@ -1918,7 +1894,9 @@ character_item_at(CharacterState& character, const CharacterItemLocation& locati
|
|
|
&& !identity_uses_soid(after, mutation.dismantledInstanceSoid);
|
|
&& !identity_uses_soid(after, mutation.dismantledInstanceSoid);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-} // namespace
|
|
|
|
|
|
|
+} // namespace runtime::detail
|
|
|
|
|
+
|
|
|
|
|
+using namespace runtime::detail;
|
|
|
|
|
|
|
|
/** Stores the active account key without publishing an incomplete account. */
|
|
/** Stores the active account key without publishing an incomplete account. */
|
|
|
bool set_primary_soid(std::uint64_t primarySoid) noexcept {
|
|
bool set_primary_soid(std::uint64_t primarySoid) noexcept {
|
|
@@ -2285,1175 +2263,6 @@ bool commit_equipment_swap(PendingEquipmentSwap& mutation) noexcept {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** Prepares one native-row-checked selected-character inventory insertion. */
|
|
|
|
|
-bool prepare_item_acquisition(std::uint16_t collectibleIndex,
|
|
|
|
|
- std::uint32_t definitionHash,
|
|
|
|
|
- PendingItemAcquisition& mutation) noexcept {
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const AccountState account = account_snapshot();
|
|
|
|
|
- build_data::collectibles::Definition collectible{};
|
|
|
|
|
- build_data::items::Definition grantedDefinition{};
|
|
|
|
|
- if (definitionHash == authored_inventory::kNoDefinitionHash || !account::valid(account)
|
|
|
|
|
- || !valid_profile_inventory(account)
|
|
|
|
|
- || !build_data::find_collectible_definition(collectibleIndex, collectible)
|
|
|
|
|
- || collectible.itemDefinitionIndex
|
|
|
|
|
- == build_data::collectibles::kUnavailableItemDefinitionIndex
|
|
|
|
|
- || !build_data::find_item_definition_index(collectible.itemDefinitionIndex,
|
|
|
|
|
- grantedDefinition)
|
|
|
|
|
- || grantedDefinition.definitionHash != definitionHash) {
|
|
|
|
|
- report_acquisition("prepare", "fail", "input", definitionHash, 0, 0, 0, 0, 0, 0);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- AccountState chargedAccount{};
|
|
|
|
|
- bool profileChanged = false;
|
|
|
|
|
- if (!apply_collection_materials(account, collectible, chargedAccount, profileChanged)) {
|
|
|
|
|
- report_acquisition("prepare", "fail", "materials", definitionHash, 0, 0, 0, 0, 0, 0);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- std::size_t characterIndex = account.characterCount;
|
|
|
|
|
- for (std::size_t index = 0; index < account.characterCount; ++index) {
|
|
|
|
|
- if (account.characters[index].selected) {
|
|
|
|
|
- characterIndex = index;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (characterIndex == account.characterCount) {
|
|
|
|
|
- report_acquisition("prepare", "fail", "selection", definitionHash, 0, 0, 0, 0, 0, 0);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const CharacterState& before = account.characters[characterIndex];
|
|
|
|
|
- if (before.inventory.count >= before.inventory.values.size()
|
|
|
|
|
- || before.nextInventorySerial
|
|
|
|
|
- >= static_cast<std::uint32_t>((std::numeric_limits<std::int32_t>::max)())) {
|
|
|
|
|
- report_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "state_capacity",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- before.soid,
|
|
|
|
|
- 0,
|
|
|
|
|
- before.inventory.count,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- before.nextInventorySerial);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- std::uint64_t instanceSoid = 0;
|
|
|
|
|
- if (!next_item_instance_soid(account, instanceSoid)) {
|
|
|
|
|
- report_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "soid",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- before.soid,
|
|
|
|
|
- 0,
|
|
|
|
|
- before.inventory.count,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- before.nextInventorySerial);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- CharacterState after = before;
|
|
|
|
|
- const std::size_t inventoryIndex = after.inventory.count;
|
|
|
|
|
- authored_inventory::Item acquired{};
|
|
|
|
|
- acquired.instanceSoid = instanceSoid;
|
|
|
|
|
- acquired.definitionHash = definitionHash;
|
|
|
|
|
- acquired.level = acquisition_level(before);
|
|
|
|
|
- acquired.quantity = 1;
|
|
|
|
|
- acquired.mutationSerial = static_cast<std::int32_t>(after.nextInventorySerial++);
|
|
|
|
|
- acquired.sockets.policy = authored_inventory::SocketPolicy::nativeDefaults;
|
|
|
|
|
- after.inventory.values[inventoryIndex] = acquired;
|
|
|
|
|
- ++after.inventory.count;
|
|
|
|
|
-
|
|
|
|
|
- AccountState candidate = chargedAccount;
|
|
|
|
|
- candidate.characters[characterIndex] = after;
|
|
|
|
|
- family4_loadout::ResolvedLoadout resolved{};
|
|
|
|
|
- std::uint16_t inventoryRow = 0;
|
|
|
|
|
- std::uint8_t equipmentSlot = 0;
|
|
|
|
|
- if (!account::valid(candidate) || identity_uses_soid(candidate, instanceSoid)
|
|
|
|
|
- || !family4_loadout::resolve(candidate, characterIndex, resolved)
|
|
|
|
|
- || !find_acquired_row(resolved, instanceSoid, inventoryRow, equipmentSlot)) {
|
|
|
|
|
- report_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "resolve_or_bucket_full",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- before.soid,
|
|
|
|
|
- instanceSoid,
|
|
|
|
|
- inventoryIndex,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- after.nextInventorySerial);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- mutation.beforeCharacter = before;
|
|
|
|
|
- mutation.afterCharacter = after;
|
|
|
|
|
- mutation.beforeProfileItems = account.profileItems;
|
|
|
|
|
- mutation.afterProfileItems = chargedAccount.profileItems;
|
|
|
|
|
- mutation.accountSoid = account.primarySoid;
|
|
|
|
|
- mutation.characterSoid = before.soid;
|
|
|
|
|
- mutation.acquiredInstanceSoid = instanceSoid;
|
|
|
|
|
- mutation.acquiredDefinitionHash = definitionHash;
|
|
|
|
|
- mutation.materialRequirementSetHash = collectible.materialRequirementSetHash;
|
|
|
|
|
- mutation.expectedNextInventorySerial = before.nextInventorySerial;
|
|
|
|
|
- mutation.characterIndex = characterIndex;
|
|
|
|
|
- mutation.expectedInventoryCount = before.inventory.count;
|
|
|
|
|
- mutation.expectedProfileItemCount = account.profileItemCount;
|
|
|
|
|
- mutation.afterProfileItemCount = chargedAccount.profileItemCount;
|
|
|
|
|
- mutation.inventoryIndex = inventoryIndex;
|
|
|
|
|
- mutation.collectibleIndex = collectibleIndex;
|
|
|
|
|
- mutation.inventoryRow = inventoryRow;
|
|
|
|
|
- mutation.equipmentSlot = equipmentSlot;
|
|
|
|
|
- mutation.materialRequirementCount = collectible.materialRequirementCount;
|
|
|
|
|
- mutation.profileChanged = profileChanged;
|
|
|
|
|
- mutation.prepared = true;
|
|
|
|
|
- report_acquisition("prepare",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- before.soid,
|
|
|
|
|
- instanceSoid,
|
|
|
|
|
- inventoryIndex,
|
|
|
|
|
- inventoryRow,
|
|
|
|
|
- equipmentSlot,
|
|
|
|
|
- after.nextInventorySerial);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Produces the full account after-image while a prepared character pull remains current. */
|
|
|
|
|
-bool preview_item_acquisition(const PendingItemAcquisition& mutation,
|
|
|
|
|
- AccountState& after) noexcept {
|
|
|
|
|
- after = {};
|
|
|
|
|
- if (!mutation.prepared || mutation.accountSoid == 0 || mutation.characterSoid == 0
|
|
|
|
|
- || mutation.acquiredInstanceSoid == 0 || mutation.characterIndex >= kCharacterCapacity
|
|
|
|
|
- || mutation.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|
|
|
- || mutation.afterProfileItemCount > authored_inventory::kProfileItemCapacity) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- const AccountState current = account_snapshot();
|
|
|
|
|
- if (mutation.characterIndex >= current.characterCount
|
|
|
|
|
- || current.primarySoid != mutation.accountSoid
|
|
|
|
|
- || !same_character(current.characters[mutation.characterIndex], mutation.beforeCharacter)
|
|
|
|
|
- || !same_profile_inventory(
|
|
|
|
|
- current, mutation.beforeProfileItems, mutation.expectedProfileItemCount)) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- after = current;
|
|
|
|
|
- after.profileItems = mutation.afterProfileItems;
|
|
|
|
|
- after.profileItemCount = mutation.afterProfileItemCount;
|
|
|
|
|
- after.characters[mutation.characterIndex] = mutation.afterCharacter;
|
|
|
|
|
- family4_loadout::ResolvedLoadout resolved{};
|
|
|
|
|
- std::uint16_t checkedRow = 0;
|
|
|
|
|
- std::uint8_t checkedSlot = 0;
|
|
|
|
|
- return account::valid(after) && valid_profile_inventory(after)
|
|
|
|
|
- && family4_loadout::resolve(after, mutation.characterIndex, resolved)
|
|
|
|
|
- && find_acquired_row(resolved, mutation.acquiredInstanceSoid, checkedRow, checkedSlot)
|
|
|
|
|
- && checkedRow == mutation.inventoryRow && checkedSlot == mutation.equipmentSlot;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Commits one prepared insertion only while its prepare-time loadout remains current. */
|
|
|
|
|
-bool commit_item_acquisition(PendingItemAcquisition& mutation) noexcept {
|
|
|
|
|
- const PendingItemAcquisition prepared = mutation;
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const auto fail = [&prepared](std::string_view reason) noexcept {
|
|
|
|
|
- report_acquisition("commit",
|
|
|
|
|
- "fail",
|
|
|
|
|
- reason,
|
|
|
|
|
- prepared.acquiredDefinitionHash,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.acquiredInstanceSoid,
|
|
|
|
|
- prepared.inventoryIndex,
|
|
|
|
|
- prepared.inventoryRow,
|
|
|
|
|
- prepared.equipmentSlot,
|
|
|
|
|
- prepared.afterCharacter.nextInventorySerial);
|
|
|
|
|
- return false;
|
|
|
|
|
- };
|
|
|
|
|
- if (!prepared.prepared || prepared.characterSoid == 0 || prepared.acquiredInstanceSoid == 0
|
|
|
|
|
- || prepared.accountSoid == 0
|
|
|
|
|
- || prepared.acquiredDefinitionHash == authored_inventory::kNoDefinitionHash
|
|
|
|
|
- || prepared.characterIndex >= kCharacterCapacity
|
|
|
|
|
- || prepared.expectedInventoryCount >= authored_inventory::kCharacterItemCapacity
|
|
|
|
|
- || prepared.inventoryIndex != prepared.expectedInventoryCount
|
|
|
|
|
- || prepared.afterCharacter.inventory.count != prepared.expectedInventoryCount + 1U
|
|
|
|
|
- || prepared.inventoryIndex >= prepared.afterCharacter.inventory.count
|
|
|
|
|
- || prepared.afterCharacter.inventory.values[prepared.inventoryIndex].instanceSoid
|
|
|
|
|
- != prepared.acquiredInstanceSoid
|
|
|
|
|
- || prepared.afterCharacter.inventory.values[prepared.inventoryIndex].definitionHash
|
|
|
|
|
- != prepared.acquiredDefinitionHash
|
|
|
|
|
- || prepared.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|
|
|
- || prepared.afterProfileItemCount > authored_inventory::kProfileItemCapacity) {
|
|
|
|
|
- return fail("mutation");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- build_data::collectibles::Definition collectible{};
|
|
|
|
|
- if (!build_data::find_collectible_definition(prepared.collectibleIndex, collectible)
|
|
|
|
|
- || collectible.itemDefinitionIndex
|
|
|
|
|
- == build_data::collectibles::kUnavailableItemDefinitionIndex
|
|
|
|
|
- || collectible.materialRequirementSetHash != prepared.materialRequirementSetHash
|
|
|
|
|
- || collectible.materialRequirementCount != prepared.materialRequirementCount) {
|
|
|
|
|
- return fail("collectible");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- report_acquisition("commit_begin",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- prepared.acquiredDefinitionHash,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.acquiredInstanceSoid,
|
|
|
|
|
- prepared.inventoryIndex,
|
|
|
|
|
- prepared.inventoryRow,
|
|
|
|
|
- prepared.equipmentSlot,
|
|
|
|
|
- prepared.afterCharacter.nextInventorySerial);
|
|
|
|
|
-
|
|
|
|
|
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- AccountState candidate = runtime::storage::g_state.account;
|
|
|
|
|
- if (prepared.characterIndex >= candidate.characterCount
|
|
|
|
|
- || candidate.primarySoid != prepared.accountSoid
|
|
|
|
|
- || !same_profile_inventory(
|
|
|
|
|
- candidate, prepared.beforeProfileItems, prepared.expectedProfileItemCount)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("account");
|
|
|
|
|
- }
|
|
|
|
|
- CharacterState& character = candidate.characters[prepared.characterIndex];
|
|
|
|
|
- if (!character.selected || character.soid != prepared.characterSoid
|
|
|
|
|
- || !same_character(character, prepared.beforeCharacter)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("stale");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- candidate.profileItems = prepared.afterProfileItems;
|
|
|
|
|
- candidate.profileItemCount = prepared.afterProfileItemCount;
|
|
|
|
|
- character = prepared.afterCharacter;
|
|
|
|
|
- family4_loadout::ResolvedLoadout resolved{};
|
|
|
|
|
- std::uint16_t checkedRow = 0;
|
|
|
|
|
- std::uint8_t checkedSlot = 0;
|
|
|
|
|
- if (!account::valid(candidate) || !valid_profile_inventory(candidate)
|
|
|
|
|
- || identity_uses_soid(candidate, prepared.acquiredInstanceSoid)
|
|
|
|
|
- || !family4_loadout::resolve(candidate, prepared.characterIndex, resolved)
|
|
|
|
|
- || !find_acquired_row(resolved, prepared.acquiredInstanceSoid, checkedRow, checkedSlot)
|
|
|
|
|
- || checkedRow != prepared.inventoryRow || checkedSlot != prepared.equipmentSlot) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("resolve");
|
|
|
|
|
- }
|
|
|
|
|
- runtime::storage::g_state.account = candidate;
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- report_acquisition("commit_end",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "published",
|
|
|
|
|
- prepared.acquiredDefinitionHash,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.acquiredInstanceSoid,
|
|
|
|
|
- prepared.inventoryIndex,
|
|
|
|
|
- prepared.inventoryRow,
|
|
|
|
|
- prepared.equipmentSlot,
|
|
|
|
|
- prepared.afterCharacter.nextInventorySerial);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Prepares one checked profile-stack increment or append for a Collections pull. */
|
|
|
|
|
-bool prepare_profile_item_acquisition(std::uint16_t collectibleIndex,
|
|
|
|
|
- std::uint32_t definitionHash,
|
|
|
|
|
- PendingProfileItemAcquisition& mutation) noexcept {
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const AccountState account = account_snapshot();
|
|
|
|
|
- build_data::collectibles::Definition collectible{};
|
|
|
|
|
- build_data::items::Definition item{};
|
|
|
|
|
- item_details::Definition detail{};
|
|
|
|
|
- inventory_buckets::Descriptor bucket{};
|
|
|
|
|
- if (definitionHash == authored_inventory::kNoDefinitionHash || !account::valid(account)
|
|
|
|
|
- || !valid_profile_inventory(account)
|
|
|
|
|
- || !build_data::find_collectible_definition(collectibleIndex, collectible)
|
|
|
|
|
- || collectible.itemDefinitionIndex
|
|
|
|
|
- == build_data::collectibles::kUnavailableItemDefinitionIndex
|
|
|
|
|
- || !build_data::find_item_definition_hash(definitionHash, item)
|
|
|
|
|
- || item.definitionHash != definitionHash
|
|
|
|
|
- || item.definitionIndex != collectible.itemDefinitionIndex
|
|
|
|
|
- || !build_data::find_configured_item_detail(item.definitionIndex, detail)
|
|
|
|
|
- || detail.definitionIndex != item.definitionIndex || detail.definitionHash != definitionHash
|
|
|
|
|
- || detail.bucketId != item.bucketId
|
|
|
|
|
- || detail.instancedDefinitionState != item_details::InstancedDefinitionState::stackable
|
|
|
|
|
- || detail.maxStackSize <= 0
|
|
|
|
|
- || !build_data::find_inventory_bucket_descriptor(detail.bucketId, bucket)
|
|
|
|
|
- || bucket.arraySelector != inventory_buckets::ArraySelector::profile) {
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "definition_or_profile",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- account.profileItemCount,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- false);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- AccountState chargedAccount{};
|
|
|
|
|
- bool materialsChanged = false;
|
|
|
|
|
- if (!apply_collection_materials(account, collectible, chargedAccount, materialsChanged)) {
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "materials",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- 0,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- 0,
|
|
|
|
|
- account.profileItemCount,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- false);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- (void)materialsChanged;
|
|
|
|
|
- const bool actionSource =
|
|
|
|
|
- build_data::is_profile_action_source(item.definitionIndex, item.bucketId);
|
|
|
|
|
-
|
|
|
|
|
- std::size_t profileIndex = chargedAccount.profileItemCount;
|
|
|
|
|
- std::int32_t previousQuantity = 0;
|
|
|
|
|
- std::int32_t previousMutationSerial = 0;
|
|
|
|
|
- std::int32_t greatestMutationSerial = 0;
|
|
|
|
|
- bool appended = true;
|
|
|
|
|
- for (std::size_t index = 0; index < chargedAccount.profileItemCount; ++index) {
|
|
|
|
|
- greatestMutationSerial =
|
|
|
|
|
- (std::max)(greatestMutationSerial, chargedAccount.profileItems[index].mutationSerial);
|
|
|
|
|
- }
|
|
|
|
|
- for (std::size_t index = 0; index < chargedAccount.profileItemCount; ++index) {
|
|
|
|
|
- const authored_inventory::ProfileItem& existing = chargedAccount.profileItems[index];
|
|
|
|
|
- if (existing.definitionHash != definitionHash) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- if (existing.quantity > detail.maxStackSize) {
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "existing_quantity",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- existing.instanceSoid,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- index,
|
|
|
|
|
- chargedAccount.profileItemCount,
|
|
|
|
|
- existing.quantity,
|
|
|
|
|
- existing.quantity,
|
|
|
|
|
- false);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- if (appended && existing.quantity < detail.maxStackSize) {
|
|
|
|
|
- profileIndex = index;
|
|
|
|
|
- previousQuantity = existing.quantity;
|
|
|
|
|
- previousMutationSerial = existing.mutationSerial;
|
|
|
|
|
- appended = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (greatestMutationSerial == (std::numeric_limits<std::int32_t>::max)()
|
|
|
|
|
- || (appended && chargedAccount.profileItemCount >= chargedAccount.profileItems.size())) {
|
|
|
|
|
- report_profile_acquisition(
|
|
|
|
|
- "prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "state_capacity",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- appended ? 0 : chargedAccount.profileItems[profileIndex].instanceSoid,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- profileIndex,
|
|
|
|
|
- chargedAccount.profileItemCount,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- true);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- std::uint64_t acquiredInstanceSoid =
|
|
|
|
|
- appended ? 0 : chargedAccount.profileItems[profileIndex].instanceSoid;
|
|
|
|
|
- if (actionSource != (acquiredInstanceSoid != 0) && !appended) {
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "identity_policy",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- acquiredInstanceSoid,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- profileIndex,
|
|
|
|
|
- chargedAccount.profileItemCount,
|
|
|
|
|
- previousQuantity,
|
|
|
|
|
- previousQuantity,
|
|
|
|
|
- false);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- if (appended && actionSource
|
|
|
|
|
- && !next_profile_item_instance_soid(chargedAccount, acquiredInstanceSoid)) {
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "identity_capacity",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- acquiredInstanceSoid,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- profileIndex,
|
|
|
|
|
- chargedAccount.profileItemCount,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- true);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- AccountState after = chargedAccount;
|
|
|
|
|
- const std::int32_t acquiredMutationSerial = greatestMutationSerial + 1;
|
|
|
|
|
- if (appended) {
|
|
|
|
|
- after.profileItems[profileIndex] = {
|
|
|
|
|
- acquiredInstanceSoid, definitionHash, 1, acquiredMutationSerial};
|
|
|
|
|
- ++after.profileItemCount;
|
|
|
|
|
- } else {
|
|
|
|
|
- ++after.profileItems[profileIndex].quantity;
|
|
|
|
|
- after.profileItems[profileIndex].mutationSerial = acquiredMutationSerial;
|
|
|
|
|
- }
|
|
|
|
|
- const std::int32_t acquiredQuantity = after.profileItems[profileIndex].quantity;
|
|
|
|
|
- if (after.profileItems[profileIndex].instanceSoid != acquiredInstanceSoid
|
|
|
|
|
- || acquiredQuantity <= previousQuantity || acquiredQuantity > detail.maxStackSize
|
|
|
|
|
- || !account::valid(after) || !valid_profile_inventory(after)) {
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "bucket_full_or_quantity",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- acquiredInstanceSoid,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- profileIndex,
|
|
|
|
|
- after.profileItemCount,
|
|
|
|
|
- previousQuantity,
|
|
|
|
|
- acquiredQuantity,
|
|
|
|
|
- appended);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- mutation.beforeItems = account.profileItems;
|
|
|
|
|
- mutation.afterItems = after.profileItems;
|
|
|
|
|
- mutation.accountSoid = account.primarySoid;
|
|
|
|
|
- mutation.acquiredInstanceSoid = acquiredInstanceSoid;
|
|
|
|
|
- mutation.acquiredDefinitionHash = definitionHash;
|
|
|
|
|
- mutation.materialRequirementSetHash = collectible.materialRequirementSetHash;
|
|
|
|
|
- mutation.expectedItemCount = account.profileItemCount;
|
|
|
|
|
- mutation.afterItemCount = after.profileItemCount;
|
|
|
|
|
- mutation.profileIndex = profileIndex;
|
|
|
|
|
- mutation.previousQuantity = previousQuantity;
|
|
|
|
|
- mutation.acquiredQuantity = acquiredQuantity;
|
|
|
|
|
- mutation.previousMutationSerial = previousMutationSerial;
|
|
|
|
|
- mutation.acquiredMutationSerial = acquiredMutationSerial;
|
|
|
|
|
- mutation.collectibleIndex = collectibleIndex;
|
|
|
|
|
- mutation.bucketId = detail.bucketId;
|
|
|
|
|
- mutation.materialRequirementCount = collectible.materialRequirementCount;
|
|
|
|
|
- mutation.actionSource = actionSource;
|
|
|
|
|
- mutation.appended = appended;
|
|
|
|
|
- mutation.prepared = true;
|
|
|
|
|
- if (!valid_profile_mutation_shape(mutation)) {
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "mutation",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- acquiredInstanceSoid,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- profileIndex,
|
|
|
|
|
- after.profileItemCount,
|
|
|
|
|
- previousQuantity,
|
|
|
|
|
- acquiredQuantity,
|
|
|
|
|
- appended);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- report_profile_acquisition("prepare",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- definitionHash,
|
|
|
|
|
- account.primarySoid,
|
|
|
|
|
- acquiredInstanceSoid,
|
|
|
|
|
- detail.bucketId,
|
|
|
|
|
- profileIndex,
|
|
|
|
|
- after.profileItemCount,
|
|
|
|
|
- previousQuantity,
|
|
|
|
|
- acquiredQuantity,
|
|
|
|
|
- appended);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Produces the exact account after-image while the captured profile view is still current. */
|
|
|
|
|
-bool preview_profile_item_acquisition(const PendingProfileItemAcquisition& mutation,
|
|
|
|
|
- AccountState& after) noexcept {
|
|
|
|
|
- after = {};
|
|
|
|
|
- const AccountState current = account_snapshot();
|
|
|
|
|
- const bool ready = materialize_profile_acquisition(current, mutation, after);
|
|
|
|
|
- report_profile_acquisition("preview",
|
|
|
|
|
- ready ? "ok" : "fail",
|
|
|
|
|
- ready ? "ready" : "stale_or_invalid",
|
|
|
|
|
- mutation.acquiredDefinitionHash,
|
|
|
|
|
- mutation.accountSoid,
|
|
|
|
|
- mutation.acquiredInstanceSoid,
|
|
|
|
|
- mutation.bucketId,
|
|
|
|
|
- mutation.profileIndex,
|
|
|
|
|
- mutation.afterItemCount,
|
|
|
|
|
- mutation.previousQuantity,
|
|
|
|
|
- mutation.acquiredQuantity,
|
|
|
|
|
- mutation.appended);
|
|
|
|
|
- return ready;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Commits one profile-stack after-image only while its exact prepare-time view remains current. */
|
|
|
|
|
-bool commit_profile_item_acquisition(PendingProfileItemAcquisition& mutation) noexcept {
|
|
|
|
|
- const PendingProfileItemAcquisition prepared = mutation;
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- if (!valid_profile_mutation_shape(prepared)) {
|
|
|
|
|
- report_profile_acquisition("commit",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "mutation",
|
|
|
|
|
- prepared.acquiredDefinitionHash,
|
|
|
|
|
- prepared.accountSoid,
|
|
|
|
|
- prepared.acquiredInstanceSoid,
|
|
|
|
|
- prepared.bucketId,
|
|
|
|
|
- prepared.profileIndex,
|
|
|
|
|
- prepared.afterItemCount,
|
|
|
|
|
- prepared.previousQuantity,
|
|
|
|
|
- prepared.acquiredQuantity,
|
|
|
|
|
- prepared.appended);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- AccountState candidate{};
|
|
|
|
|
- const bool ready =
|
|
|
|
|
- materialize_profile_acquisition(runtime::storage::g_state.account, prepared, candidate);
|
|
|
|
|
- if (ready) {
|
|
|
|
|
- runtime::storage::g_state.account = candidate;
|
|
|
|
|
- }
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- report_profile_acquisition("commit",
|
|
|
|
|
- ready ? "ok" : "fail",
|
|
|
|
|
- ready ? "published" : "stale_or_invalid",
|
|
|
|
|
- prepared.acquiredDefinitionHash,
|
|
|
|
|
- prepared.accountSoid,
|
|
|
|
|
- prepared.acquiredInstanceSoid,
|
|
|
|
|
- prepared.bucketId,
|
|
|
|
|
- prepared.profileIndex,
|
|
|
|
|
- prepared.afterItemCount,
|
|
|
|
|
- prepared.previousQuantity,
|
|
|
|
|
- prepared.acquiredQuantity,
|
|
|
|
|
- prepared.appended);
|
|
|
|
|
- return ready;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Prepares one selected-character unequipped item removal without changing account State. */
|
|
|
|
|
-bool prepare_item_dismantle(std::uint64_t instanceSoid, PendingItemDismantle& mutation) noexcept {
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const AccountState account = account_snapshot();
|
|
|
|
|
- if (instanceSoid == 0 || !account::valid(account)) {
|
|
|
|
|
- report_dismantle("prepare", "fail", "input", 0, 0, instanceSoid, 0, 0, 0, 0, 0);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- std::size_t characterIndex = account.characterCount;
|
|
|
|
|
- for (std::size_t index = 0; index < account.characterCount; ++index) {
|
|
|
|
|
- if (account.characters[index].selected) {
|
|
|
|
|
- characterIndex = index;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (characterIndex >= account.characterCount
|
|
|
|
|
- || !stage_item_dismantle(account, characterIndex, instanceSoid, mutation)) {
|
|
|
|
|
- report_dismantle(
|
|
|
|
|
- "prepare", "fail", "ownership_or_resolve", 0, 0, instanceSoid, 0, 0, 0, 0, 0);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- report_dismantle("prepare",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- mutation.dismantledItem.definitionHash,
|
|
|
|
|
- mutation.characterSoid,
|
|
|
|
|
- mutation.dismantledInstanceSoid,
|
|
|
|
|
- mutation.inventoryIndex,
|
|
|
|
|
- mutation.inventoryRow,
|
|
|
|
|
- mutation.equipmentSlot,
|
|
|
|
|
- mutation.movedInventoryItemCount,
|
|
|
|
|
- mutation.afterCharacter.nextInventorySerial);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Produces the exact character-and-profile after-image while the captured views remain current. */
|
|
|
|
|
-bool preview_item_dismantle(const PendingItemDismantle& mutation,
|
|
|
|
|
- AccountState& after) noexcept {
|
|
|
|
|
- const AccountState current = account_snapshot();
|
|
|
|
|
- const bool ready = materialize_item_dismantle(current, mutation, after);
|
|
|
|
|
- report_dismantle("preview",
|
|
|
|
|
- ready ? "ok" : "fail",
|
|
|
|
|
- ready ? "ready" : "stale_or_invalid",
|
|
|
|
|
- mutation.dismantledItem.definitionHash,
|
|
|
|
|
- mutation.characterSoid,
|
|
|
|
|
- mutation.dismantledInstanceSoid,
|
|
|
|
|
- mutation.inventoryIndex,
|
|
|
|
|
- mutation.inventoryRow,
|
|
|
|
|
- mutation.equipmentSlot,
|
|
|
|
|
- mutation.movedInventoryItemCount,
|
|
|
|
|
- mutation.afterCharacter.nextInventorySerial);
|
|
|
|
|
- return ready;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Commits one prepared dismantle only while its complete account views are unchanged. */
|
|
|
|
|
-bool commit_item_dismantle(PendingItemDismantle& mutation) noexcept {
|
|
|
|
|
- const PendingItemDismantle prepared = mutation;
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const auto fail = [&prepared](std::string_view reason) noexcept {
|
|
|
|
|
- report_dismantle("commit",
|
|
|
|
|
- "fail",
|
|
|
|
|
- reason,
|
|
|
|
|
- prepared.dismantledItem.definitionHash,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.dismantledInstanceSoid,
|
|
|
|
|
- prepared.inventoryIndex,
|
|
|
|
|
- prepared.inventoryRow,
|
|
|
|
|
- prepared.equipmentSlot,
|
|
|
|
|
- prepared.movedInventoryItemCount,
|
|
|
|
|
- prepared.afterCharacter.nextInventorySerial);
|
|
|
|
|
- return false;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- report_dismantle("commit_begin",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- prepared.dismantledItem.definitionHash,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.dismantledInstanceSoid,
|
|
|
|
|
- prepared.inventoryIndex,
|
|
|
|
|
- prepared.inventoryRow,
|
|
|
|
|
- prepared.equipmentSlot,
|
|
|
|
|
- prepared.movedInventoryItemCount,
|
|
|
|
|
- prepared.afterCharacter.nextInventorySerial);
|
|
|
|
|
-
|
|
|
|
|
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- AccountState candidate{};
|
|
|
|
|
- const bool ready =
|
|
|
|
|
- materialize_item_dismantle(runtime::storage::g_state.account, prepared, candidate);
|
|
|
|
|
- if (ready) {
|
|
|
|
|
- runtime::storage::g_state.account = candidate;
|
|
|
|
|
- }
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
-
|
|
|
|
|
- if (!ready) {
|
|
|
|
|
- return fail("stale_or_invalid");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- report_dismantle("commit_end",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "published",
|
|
|
|
|
- prepared.dismantledItem.definitionHash,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.dismantledInstanceSoid,
|
|
|
|
|
- prepared.inventoryIndex,
|
|
|
|
|
- prepared.inventoryRow,
|
|
|
|
|
- prepared.equipmentSlot,
|
|
|
|
|
- prepared.movedInventoryItemCount,
|
|
|
|
|
- prepared.afterCharacter.nextInventorySerial);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Prepares one checked ordinary-socket selection without publishing account State. */
|
|
|
|
|
-bool prepare_socket_plug(std::uint64_t targetInstanceSoid,
|
|
|
|
|
- std::uint8_t socketLane,
|
|
|
|
|
- std::uint16_t plugDefinitionIndex,
|
|
|
|
|
- PendingSocketPlug& mutation) noexcept {
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const AccountState snapshot = account_snapshot();
|
|
|
|
|
- std::size_t characterIndex = snapshot.characterCount;
|
|
|
|
|
- if (targetInstanceSoid != 0 && account::valid(snapshot)) {
|
|
|
|
|
- for (std::size_t index = 0; index < snapshot.characterCount; ++index) {
|
|
|
|
|
- if (snapshot.characters[index].selected) {
|
|
|
|
|
- characterIndex = index;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (characterIndex >= snapshot.characterCount
|
|
|
|
|
- || !stage_socket_plug(snapshot,
|
|
|
|
|
- characterIndex,
|
|
|
|
|
- targetInstanceSoid,
|
|
|
|
|
- socketLane,
|
|
|
|
|
- plugDefinitionIndex,
|
|
|
|
|
- mutation)) {
|
|
|
|
|
- report_socket_plug("prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "ownership_definition_or_compatibility",
|
|
|
|
|
- 0,
|
|
|
|
|
- targetInstanceSoid,
|
|
|
|
|
- 0,
|
|
|
|
|
- socketLane,
|
|
|
|
|
- plugDefinitionIndex,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- false,
|
|
|
|
|
- 0);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- report_socket_plug("prepare",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- mutation.characterSoid,
|
|
|
|
|
- mutation.targetInstanceSoid,
|
|
|
|
|
- mutation.targetDefinitionIndex,
|
|
|
|
|
- mutation.socketLane,
|
|
|
|
|
- mutation.plugDefinitionIndex,
|
|
|
|
|
- mutation.targetBucketId,
|
|
|
|
|
- mutation.plugBucketId,
|
|
|
|
|
- mutation.targetEquipped,
|
|
|
|
|
- mutation.itemIndex);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Prepares a socket transition for the exact instance identity carried by opcode 1901. */
|
|
|
|
|
-bool prepare_character_selector_socket_plug(std::uint64_t itemSelector,
|
|
|
|
|
- std::uint8_t requestedSocketLane,
|
|
|
|
|
- std::uint16_t plugDefinitionIndex,
|
|
|
|
|
- PendingSocketPlug& mutation) noexcept {
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const AccountState snapshot = account_snapshot();
|
|
|
|
|
- std::size_t characterIndex = snapshot.characterCount;
|
|
|
|
|
- if (account::valid(snapshot)) {
|
|
|
|
|
- for (std::size_t index = 0; index < snapshot.characterCount; ++index) {
|
|
|
|
|
- if (snapshot.characters[index].selected) {
|
|
|
|
|
- characterIndex = index;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- constexpr std::uint64_t kSelectorStride = 4;
|
|
|
|
|
- constexpr std::uint64_t kInstanceIdentityMask = 0x3FFFFFFFFFFFFFFFULL;
|
|
|
|
|
- if (characterIndex >= snapshot.characterCount || itemSelector == 0
|
|
|
|
|
- || itemSelector % kSelectorStride != 0
|
|
|
|
|
- || itemSelector / kSelectorStride > kInstanceIdentityMask
|
|
|
|
|
- || requestedSocketLane >= authored_inventory::kPlugCapacity) {
|
|
|
|
|
- report_socket_plug("prepare_location",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "selection_or_slot",
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- requestedSocketLane,
|
|
|
|
|
- plugDefinitionIndex,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- false,
|
|
|
|
|
- static_cast<std::size_t>(itemSelector / kSelectorStride));
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- family4_loadout::ResolvedLoadout resolvedLoadout{};
|
|
|
|
|
- if (!family4_loadout::resolve(snapshot, characterIndex, resolvedLoadout)) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- const std::uint64_t identityToken = itemSelector / kSelectorStride;
|
|
|
|
|
- const family4_loadout::ResolvedItem* resolvedTarget = nullptr;
|
|
|
|
|
- for (std::size_t index = 0; index < resolvedLoadout.itemCount; ++index) {
|
|
|
|
|
- const family4_loadout::ResolvedItem& item = resolvedLoadout.items[index];
|
|
|
|
|
- if ((item.instance.instanceSoid & kInstanceIdentityMask) != identityToken) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- if (resolvedTarget != nullptr) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- resolvedTarget = &item;
|
|
|
|
|
- }
|
|
|
|
|
- const bool expectedEquipped = resolvedTarget != nullptr && resolvedTarget->equipped;
|
|
|
|
|
-
|
|
|
|
|
- std::uint8_t resolvedSocketLane = static_cast<std::uint8_t>(authored_inventory::kPlugCapacity);
|
|
|
|
|
- build_data::items::Definition targetDefinition{};
|
|
|
|
|
- item_details::Definition targetDetail{};
|
|
|
|
|
- bool ambiguousSocketLane = false;
|
|
|
|
|
- if (resolvedTarget != nullptr
|
|
|
|
|
- && build_data::find_item_definition_index(resolvedTarget->instance.baseDefinitionIndex,
|
|
|
|
|
- targetDefinition)
|
|
|
|
|
- && targetDefinition.definitionIndex == resolvedTarget->instance.baseDefinitionIndex
|
|
|
|
|
- && build_data::find_configured_item_detail(targetDefinition.definitionIndex, targetDetail)
|
|
|
|
|
- && targetDetail.definitionIndex == targetDefinition.definitionIndex
|
|
|
|
|
- && targetDetail.definitionHash == targetDefinition.definitionHash
|
|
|
|
|
- && targetDetail.bucketId == targetDefinition.bucketId
|
|
|
|
|
- && targetDetail.ordinarySocketState == item_details::OrdinarySocketState::present
|
|
|
|
|
- && targetDetail.ordinarySocketCount <= authored_inventory::kPlugCapacity) {
|
|
|
|
|
- // Most action kinds are the physical ordinary-socket lane. Prefer that exact lane when
|
|
|
|
|
- // its installed pool accepts the plug; this disambiguates armour items whose two mod
|
|
|
|
|
- // sockets intentionally expose the same pool. Some action kinds are semantic categories
|
|
|
|
|
- // instead (notably shaders), so retain the unique-compatible-lane fallback for those.
|
|
|
|
|
- if (requestedSocketLane < targetDetail.ordinarySocketCount
|
|
|
|
|
- && build_data::is_socket_plug_allowed(
|
|
|
|
|
- targetDefinition.definitionIndex, requestedSocketLane, plugDefinitionIndex)) {
|
|
|
|
|
- resolvedSocketLane = requestedSocketLane;
|
|
|
|
|
- } else {
|
|
|
|
|
- for (std::size_t lane = 0; lane < targetDetail.ordinarySocketCount; ++lane) {
|
|
|
|
|
- if (!build_data::is_socket_plug_allowed(targetDefinition.definitionIndex,
|
|
|
|
|
- static_cast<std::uint8_t>(lane),
|
|
|
|
|
- plugDefinitionIndex)) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- if (resolvedSocketLane < authored_inventory::kPlugCapacity) {
|
|
|
|
|
- ambiguousSocketLane = true;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- resolvedSocketLane = static_cast<std::uint8_t>(lane);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (resolvedTarget == nullptr || resolvedTarget->instance.instanceSoid == 0
|
|
|
|
|
- || ambiguousSocketLane || resolvedSocketLane >= authored_inventory::kPlugCapacity
|
|
|
|
|
- || !stage_socket_plug(snapshot,
|
|
|
|
|
- characterIndex,
|
|
|
|
|
- resolvedTarget->instance.instanceSoid,
|
|
|
|
|
- resolvedSocketLane,
|
|
|
|
|
- plugDefinitionIndex,
|
|
|
|
|
- mutation)
|
|
|
|
|
- || mutation.targetEquipped != expectedEquipped) {
|
|
|
|
|
- report_socket_plug("prepare_location",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "equipment_or_compatibility",
|
|
|
|
|
- snapshot.characters[characterIndex].soid,
|
|
|
|
|
- resolvedTarget != nullptr ? resolvedTarget->instance.instanceSoid : 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- resolvedSocketLane,
|
|
|
|
|
- plugDefinitionIndex,
|
|
|
|
|
- 0,
|
|
|
|
|
- 0,
|
|
|
|
|
- expectedEquipped,
|
|
|
|
|
- static_cast<std::size_t>(identityToken));
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- report_socket_plug("prepare_location",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- mutation.characterSoid,
|
|
|
|
|
- mutation.targetInstanceSoid,
|
|
|
|
|
- mutation.targetDefinitionIndex,
|
|
|
|
|
- mutation.socketLane,
|
|
|
|
|
- mutation.plugDefinitionIndex,
|
|
|
|
|
- mutation.targetBucketId,
|
|
|
|
|
- mutation.plugBucketId,
|
|
|
|
|
- mutation.targetEquipped,
|
|
|
|
|
- mutation.itemIndex);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Produces the complete account after-image while the prepared socket action remains current. */
|
|
|
|
|
-bool preview_socket_plug(const PendingSocketPlug& mutation, AccountState& after) noexcept {
|
|
|
|
|
- after = {};
|
|
|
|
|
- if (!mutation.prepared || mutation.accountSoid == 0 || mutation.characterSoid == 0
|
|
|
|
|
- || mutation.targetInstanceSoid == 0 || mutation.characterIndex >= kCharacterCapacity
|
|
|
|
|
- || mutation.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|
|
|
- || mutation.afterProfileItemCount > authored_inventory::kProfileItemCapacity) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- const AccountState current = account_snapshot();
|
|
|
|
|
- if (mutation.characterIndex >= current.characterCount
|
|
|
|
|
- || current.primarySoid != mutation.accountSoid
|
|
|
|
|
- || !same_character(current.characters[mutation.characterIndex], mutation.beforeCharacter)
|
|
|
|
|
- || !same_profile_inventory(
|
|
|
|
|
- current, mutation.beforeProfileItems, mutation.expectedProfileItemCount)) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- PendingSocketPlug canonical{};
|
|
|
|
|
- if (!stage_socket_plug(current,
|
|
|
|
|
- mutation.characterIndex,
|
|
|
|
|
- mutation.targetInstanceSoid,
|
|
|
|
|
- mutation.socketLane,
|
|
|
|
|
- mutation.plugDefinitionIndex,
|
|
|
|
|
- canonical)
|
|
|
|
|
- || canonical.accountSoid != mutation.accountSoid
|
|
|
|
|
- || canonical.characterSoid != mutation.characterSoid
|
|
|
|
|
- || canonical.targetDefinitionHash != mutation.targetDefinitionHash
|
|
|
|
|
- || canonical.plugDefinitionHash != mutation.plugDefinitionHash
|
|
|
|
|
- || canonical.materialRequirementSetHash != mutation.materialRequirementSetHash
|
|
|
|
|
- || canonical.characterIndex != mutation.characterIndex
|
|
|
|
|
- || canonical.expectedProfileItemCount != mutation.expectedProfileItemCount
|
|
|
|
|
- || canonical.afterProfileItemCount != mutation.afterProfileItemCount
|
|
|
|
|
- || canonical.itemIndex != mutation.itemIndex
|
|
|
|
|
- || canonical.targetDefinitionIndex != mutation.targetDefinitionIndex
|
|
|
|
|
- || canonical.plugDefinitionIndex != mutation.plugDefinitionIndex
|
|
|
|
|
- || canonical.materialRequirementSetIndex != mutation.materialRequirementSetIndex
|
|
|
|
|
- || canonical.socketLane != mutation.socketLane
|
|
|
|
|
- || canonical.targetBucketId != mutation.targetBucketId
|
|
|
|
|
- || canonical.plugBucketId != mutation.plugBucketId
|
|
|
|
|
- || canonical.materialRequirementCount != mutation.materialRequirementCount
|
|
|
|
|
- || canonical.profileChanged != mutation.profileChanged
|
|
|
|
|
- || canonical.targetEquipped != mutation.targetEquipped
|
|
|
|
|
- || !same_character(canonical.beforeCharacter, mutation.beforeCharacter)
|
|
|
|
|
- || !same_character(canonical.afterCharacter, mutation.afterCharacter)) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- after = current;
|
|
|
|
|
- after.profileItems = canonical.afterProfileItems;
|
|
|
|
|
- after.profileItemCount = canonical.afterProfileItemCount;
|
|
|
|
|
- after.characters[mutation.characterIndex] = canonical.afterCharacter;
|
|
|
|
|
- const bool balancesChanged = !same_profile_inventory(
|
|
|
|
|
- after, mutation.beforeProfileItems, mutation.expectedProfileItemCount);
|
|
|
|
|
- family4_loadout::ResolvedLoadout resolved{};
|
|
|
|
|
- return balancesChanged == mutation.profileChanged
|
|
|
|
|
- && same_profile_inventory(
|
|
|
|
|
- after, mutation.afterProfileItems, mutation.afterProfileItemCount)
|
|
|
|
|
- && account::valid(after) && valid_profile_inventory(after)
|
|
|
|
|
- && family4_loadout::resolve(after, mutation.characterIndex, resolved);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Commits one prepared socket-and-material after-image behind exact staleness guards. */
|
|
|
|
|
-bool commit_socket_plug(PendingSocketPlug& mutation) noexcept {
|
|
|
|
|
- const PendingSocketPlug prepared = mutation;
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const auto fail = [&prepared](std::string_view reason) noexcept {
|
|
|
|
|
- report_socket_plug("commit",
|
|
|
|
|
- "fail",
|
|
|
|
|
- reason,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.targetInstanceSoid,
|
|
|
|
|
- prepared.targetDefinitionIndex,
|
|
|
|
|
- prepared.socketLane,
|
|
|
|
|
- prepared.plugDefinitionIndex,
|
|
|
|
|
- prepared.targetBucketId,
|
|
|
|
|
- prepared.plugBucketId,
|
|
|
|
|
- prepared.targetEquipped,
|
|
|
|
|
- prepared.itemIndex);
|
|
|
|
|
- return false;
|
|
|
|
|
- };
|
|
|
|
|
- const std::size_t itemCapacity = prepared.targetEquipped
|
|
|
|
|
- ? authored_inventory::kEquipmentSlotCount
|
|
|
|
|
- : authored_inventory::kCharacterItemCapacity;
|
|
|
|
|
- if (!prepared.prepared || prepared.accountSoid == 0 || prepared.characterSoid == 0
|
|
|
|
|
- || prepared.targetInstanceSoid == 0
|
|
|
|
|
- || prepared.targetDefinitionHash == authored_inventory::kNoDefinitionHash
|
|
|
|
|
- || prepared.plugDefinitionHash == authored_inventory::kNoDefinitionHash
|
|
|
|
|
- || prepared.characterIndex >= kCharacterCapacity || prepared.itemIndex >= itemCapacity
|
|
|
|
|
- || prepared.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|
|
|
- || prepared.afterProfileItemCount > authored_inventory::kProfileItemCapacity
|
|
|
|
|
- || prepared.socketLane >= authored_inventory::kPlugCapacity
|
|
|
|
|
- || prepared.beforeCharacter.soid != prepared.characterSoid
|
|
|
|
|
- || prepared.afterCharacter.soid != prepared.characterSoid) {
|
|
|
|
|
- return fail("mutation");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- report_socket_plug("commit_begin",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.targetInstanceSoid,
|
|
|
|
|
- prepared.targetDefinitionIndex,
|
|
|
|
|
- prepared.socketLane,
|
|
|
|
|
- prepared.plugDefinitionIndex,
|
|
|
|
|
- prepared.targetBucketId,
|
|
|
|
|
- prepared.plugBucketId,
|
|
|
|
|
- prepared.targetEquipped,
|
|
|
|
|
- prepared.itemIndex);
|
|
|
|
|
-
|
|
|
|
|
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- AccountState candidate = runtime::storage::g_state.account;
|
|
|
|
|
- if (prepared.characterIndex >= candidate.characterCount
|
|
|
|
|
- || candidate.primarySoid != prepared.accountSoid
|
|
|
|
|
- || !same_profile_inventory(
|
|
|
|
|
- candidate, prepared.beforeProfileItems, prepared.expectedProfileItemCount)
|
|
|
|
|
- || !same_character(candidate.characters[prepared.characterIndex],
|
|
|
|
|
- prepared.beforeCharacter)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("stale");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- PendingSocketPlug canonical{};
|
|
|
|
|
- if (!stage_socket_plug(candidate,
|
|
|
|
|
- prepared.characterIndex,
|
|
|
|
|
- prepared.targetInstanceSoid,
|
|
|
|
|
- prepared.socketLane,
|
|
|
|
|
- prepared.plugDefinitionIndex,
|
|
|
|
|
- canonical)
|
|
|
|
|
- || canonical.characterSoid != prepared.characterSoid
|
|
|
|
|
- || canonical.accountSoid != prepared.accountSoid
|
|
|
|
|
- || canonical.targetDefinitionHash != prepared.targetDefinitionHash
|
|
|
|
|
- || canonical.plugDefinitionHash != prepared.plugDefinitionHash
|
|
|
|
|
- || canonical.materialRequirementSetHash != prepared.materialRequirementSetHash
|
|
|
|
|
- || canonical.characterIndex != prepared.characterIndex
|
|
|
|
|
- || canonical.expectedProfileItemCount != prepared.expectedProfileItemCount
|
|
|
|
|
- || canonical.afterProfileItemCount != prepared.afterProfileItemCount
|
|
|
|
|
- || canonical.itemIndex != prepared.itemIndex
|
|
|
|
|
- || canonical.targetDefinitionIndex != prepared.targetDefinitionIndex
|
|
|
|
|
- || canonical.plugDefinitionIndex != prepared.plugDefinitionIndex
|
|
|
|
|
- || canonical.materialRequirementSetIndex != prepared.materialRequirementSetIndex
|
|
|
|
|
- || canonical.socketLane != prepared.socketLane
|
|
|
|
|
- || canonical.targetBucketId != prepared.targetBucketId
|
|
|
|
|
- || canonical.plugBucketId != prepared.plugBucketId
|
|
|
|
|
- || canonical.materialRequirementCount != prepared.materialRequirementCount
|
|
|
|
|
- || canonical.profileChanged != prepared.profileChanged
|
|
|
|
|
- || canonical.targetEquipped != prepared.targetEquipped
|
|
|
|
|
- || !same_character(canonical.beforeCharacter, prepared.beforeCharacter)
|
|
|
|
|
- || !same_character(canonical.afterCharacter, prepared.afterCharacter)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("transition");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- candidate.profileItems = canonical.afterProfileItems;
|
|
|
|
|
- candidate.profileItemCount = canonical.afterProfileItemCount;
|
|
|
|
|
- if (!same_profile_inventory(
|
|
|
|
|
- candidate, prepared.afterProfileItems, prepared.afterProfileItemCount)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("materials");
|
|
|
|
|
- }
|
|
|
|
|
- candidate.characters[prepared.characterIndex] = canonical.afterCharacter;
|
|
|
|
|
- family4_loadout::ResolvedLoadout checked{};
|
|
|
|
|
- if (!account::valid(candidate) || !valid_profile_inventory(candidate)
|
|
|
|
|
- || !family4_loadout::resolve(candidate, prepared.characterIndex, checked)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("account_or_resolve");
|
|
|
|
|
- }
|
|
|
|
|
- runtime::storage::g_state.account = candidate;
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
-
|
|
|
|
|
- report_socket_plug("commit_end",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "published",
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.targetInstanceSoid,
|
|
|
|
|
- prepared.targetDefinitionIndex,
|
|
|
|
|
- prepared.socketLane,
|
|
|
|
|
- prepared.plugDefinitionIndex,
|
|
|
|
|
- prepared.targetBucketId,
|
|
|
|
|
- prepared.plugBucketId,
|
|
|
|
|
- prepared.targetEquipped,
|
|
|
|
|
- prepared.itemIndex);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Prepares one complete native item-state value for an owned selected-character instance. */
|
|
|
|
|
-bool prepare_item_state(std::uint64_t targetInstanceSoid,
|
|
|
|
|
- std::uint16_t targetDefinitionIndex,
|
|
|
|
|
- std::uint32_t flags,
|
|
|
|
|
- PendingItemState& mutation) noexcept {
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const AccountState snapshot = account_snapshot();
|
|
|
|
|
- std::size_t characterIndex = snapshot.characterCount;
|
|
|
|
|
- if (account::valid(snapshot)) {
|
|
|
|
|
- for (std::size_t index = 0; index < snapshot.characterCount; ++index) {
|
|
|
|
|
- if (snapshot.characters[index].selected) {
|
|
|
|
|
- characterIndex = index;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (characterIndex >= snapshot.characterCount
|
|
|
|
|
- || !stage_item_state(
|
|
|
|
|
- snapshot, characterIndex, targetInstanceSoid, targetDefinitionIndex, flags, mutation)) {
|
|
|
|
|
- report_item_state(
|
|
|
|
|
- "prepare",
|
|
|
|
|
- "fail",
|
|
|
|
|
- "selection_or_item",
|
|
|
|
|
- characterIndex < snapshot.characterCount ? snapshot.characters[characterIndex].soid : 0,
|
|
|
|
|
- targetInstanceSoid,
|
|
|
|
|
- targetDefinitionIndex,
|
|
|
|
|
- 0,
|
|
|
|
|
- flags,
|
|
|
|
|
- false,
|
|
|
|
|
- 0);
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- report_item_state("prepare",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "ready",
|
|
|
|
|
- mutation.characterSoid,
|
|
|
|
|
- mutation.targetInstanceSoid,
|
|
|
|
|
- mutation.targetDefinitionIndex,
|
|
|
|
|
- mutation.beforeFlags,
|
|
|
|
|
- mutation.afterFlags,
|
|
|
|
|
- mutation.targetEquipped,
|
|
|
|
|
- mutation.itemIndex);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/** Commits one prepared item-state after-image behind an exact character staleness guard. */
|
|
|
|
|
-bool commit_item_state(PendingItemState& mutation) noexcept {
|
|
|
|
|
- const PendingItemState prepared = mutation;
|
|
|
|
|
- mutation = {};
|
|
|
|
|
- const auto fail = [&prepared](std::string_view reason) noexcept {
|
|
|
|
|
- report_item_state("commit",
|
|
|
|
|
- "fail",
|
|
|
|
|
- reason,
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.targetInstanceSoid,
|
|
|
|
|
- prepared.targetDefinitionIndex,
|
|
|
|
|
- prepared.beforeFlags,
|
|
|
|
|
- prepared.afterFlags,
|
|
|
|
|
- prepared.targetEquipped,
|
|
|
|
|
- prepared.itemIndex);
|
|
|
|
|
- return false;
|
|
|
|
|
- };
|
|
|
|
|
- const std::size_t capacity = prepared.targetEquipped
|
|
|
|
|
- ? authored_inventory::kEquipmentSlotCount
|
|
|
|
|
- : authored_inventory::kCharacterItemCapacity;
|
|
|
|
|
- if (!prepared.prepared || prepared.characterSoid == 0 || prepared.targetInstanceSoid == 0
|
|
|
|
|
- || prepared.characterIndex >= kCharacterCapacity || prepared.itemIndex >= capacity
|
|
|
|
|
- || prepared.beforeCharacter.soid != prepared.characterSoid
|
|
|
|
|
- || prepared.afterCharacter.soid != prepared.characterSoid
|
|
|
|
|
- || prepared.beforeFlags == prepared.afterFlags) {
|
|
|
|
|
- return fail("mutation");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- AccountState candidate = runtime::storage::g_state.account;
|
|
|
|
|
- if (prepared.characterIndex >= candidate.characterCount
|
|
|
|
|
- || !same_character(candidate.characters[prepared.characterIndex],
|
|
|
|
|
- prepared.beforeCharacter)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("stale");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- PendingItemState canonical{};
|
|
|
|
|
- if (!stage_item_state(candidate,
|
|
|
|
|
- prepared.characterIndex,
|
|
|
|
|
- prepared.targetInstanceSoid,
|
|
|
|
|
- prepared.targetDefinitionIndex,
|
|
|
|
|
- prepared.afterFlags,
|
|
|
|
|
- canonical)
|
|
|
|
|
- || canonical.characterSoid != prepared.characterSoid
|
|
|
|
|
- || canonical.characterIndex != prepared.characterIndex
|
|
|
|
|
- || canonical.itemIndex != prepared.itemIndex
|
|
|
|
|
- || canonical.targetDefinitionIndex != prepared.targetDefinitionIndex
|
|
|
|
|
- || canonical.beforeFlags != prepared.beforeFlags
|
|
|
|
|
- || canonical.afterFlags != prepared.afterFlags
|
|
|
|
|
- || canonical.targetEquipped != prepared.targetEquipped
|
|
|
|
|
- || !same_character(canonical.beforeCharacter, prepared.beforeCharacter)
|
|
|
|
|
- || !same_character(canonical.afterCharacter, prepared.afterCharacter)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("transition");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- candidate.characters[prepared.characterIndex] = prepared.afterCharacter;
|
|
|
|
|
- family4_loadout::ResolvedLoadout checked{};
|
|
|
|
|
- if (!account::valid(candidate)
|
|
|
|
|
- || !family4_loadout::resolve(candidate, prepared.characterIndex, checked)) {
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
- return fail("account_or_resolve");
|
|
|
|
|
- }
|
|
|
|
|
- runtime::storage::g_state.account = candidate;
|
|
|
|
|
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
-
|
|
|
|
|
- report_item_state("commit",
|
|
|
|
|
- "ok",
|
|
|
|
|
- "published",
|
|
|
|
|
- prepared.characterSoid,
|
|
|
|
|
- prepared.targetInstanceSoid,
|
|
|
|
|
- prepared.targetDefinitionIndex,
|
|
|
|
|
- prepared.beforeFlags,
|
|
|
|
|
- prepared.afterFlags,
|
|
|
|
|
- prepared.targetEquipped,
|
|
|
|
|
- prepared.itemIndex);
|
|
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
/** @return A copy of the active account state, read under the lock. */
|
|
/** @return A copy of the active account state, read under the lock. */
|
|
|
AccountState account_snapshot() noexcept {
|
|
AccountState account_snapshot() noexcept {
|
|
|
AcquireSRWLockShared(&runtime::storage::g_stateLock);
|
|
AcquireSRWLockShared(&runtime::storage::g_stateLock);
|