|
@@ -1,29 +1,87 @@
|
|
|
#include "queuez_outcome_staging.h"
|
|
#include "queuez_outcome_staging.h"
|
|
|
|
|
|
|
|
|
|
+#include <algorithm>
|
|
|
#include <limits>
|
|
#include <limits>
|
|
|
|
|
+#include <optional>
|
|
|
|
|
|
|
|
#include "../../../../core/logging/log.h"
|
|
#include "../../../../core/logging/log.h"
|
|
|
|
|
+#include "../../../../middleware/datagen/family4/loadout/loadout_resolver.h"
|
|
|
#include "../../../../middleware/secure_channel/runtime.h"
|
|
#include "../../../../middleware/secure_channel/runtime.h"
|
|
|
#include "queuez_state_validation.h"
|
|
#include "queuez_state_validation.h"
|
|
|
|
|
|
|
|
namespace sunrise::server::bap::encrypted::queuez {
|
|
namespace sunrise::server::bap::encrypted::queuez {
|
|
|
namespace {
|
|
namespace {
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * Stages the character upsert and appended resident an item-acquisition transaction promised.
|
|
|
|
|
- * Shared by the ordinary opcode-1820 transaction and a record-claim reward that resolved to a
|
|
|
|
|
- * character-bucket grant: both stage the exact same way once an `ItemAcquisitionTransaction` is
|
|
|
|
|
- * in hand.
|
|
|
|
|
- */
|
|
|
|
|
-[[nodiscard]] bool stage_item_acquisition_push(Scratch& scratch,
|
|
|
|
|
- const SessionState& before,
|
|
|
|
|
- const ItemAcquisitionTransaction& itemAcquisition,
|
|
|
|
|
- std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
- std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
- std::span<std::byte> response,
|
|
|
|
|
- std::size_t& written,
|
|
|
|
|
- SessionState& after) noexcept {
|
|
|
|
|
- const ItemAcquisition& acquisition = itemAcquisition.update;
|
|
|
|
|
|
|
+/** Extends the active feed overlay with the two identities touched by this equip. */
|
|
|
|
|
+[[nodiscard]] bool merge_equipment_presentation_rows(
|
|
|
|
|
+ std::span<const AcquisitionPresentationRow> existing,
|
|
|
|
|
+ const state::PendingEquipmentSwap& mutation,
|
|
|
|
|
+ std::array<AcquisitionPresentationRow, kAcquisitionPresentationRowCapacity>& output,
|
|
|
|
|
+ std::uint8_t& count) noexcept {
|
|
|
|
|
+ if (existing.size() > output.size()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::copy(existing.begin(), existing.end(), output.begin());
|
|
|
|
|
+ std::size_t used = existing.size();
|
|
|
|
|
+
|
|
|
|
|
+ const state::AccountState account = state::account_snapshot();
|
|
|
|
|
+ middleware::datagen::family4::loadout::ResolvedLoadout loadout{};
|
|
|
|
|
+ if (!mutation.prepared || mutation.characterIndex >= account.characterCount
|
|
|
|
|
+ || account.characters[mutation.characterIndex].soid != mutation.characterSoid
|
|
|
|
|
+ || !middleware::datagen::family4::loadout::resolve(
|
|
|
|
|
+ account, mutation.characterIndex, loadout)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const auto add = [&](std::uint64_t instanceSoid) noexcept -> bool {
|
|
|
|
|
+ if (instanceSoid == 0) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t index = 0; index < used; ++index) {
|
|
|
|
|
+ if (output[index].instanceSoid == instanceSoid) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (used >= output.size()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t index = 0; index < loadout.itemCount; ++index) {
|
|
|
|
|
+ const auto& item = loadout.items[index];
|
|
|
|
|
+ if (item.instance.instanceSoid != instanceSoid) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t prior = 0; prior < used; ++prior) {
|
|
|
|
|
+ if (output[prior].inventoryRow == item.inventoryRow) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ output[used++] = AcquisitionPresentationRow{
|
|
|
|
|
+ instanceSoid, static_cast<std::uint16_t>(item.inventoryRow)};
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (!add(mutation.requestedInstanceSoid) || !add(mutation.previousInstanceSoid)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ count = static_cast<std::uint8_t>(used);
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Stages the character upsert and appended resident an item acquisition promised. */
|
|
|
|
|
+[[nodiscard]] bool
|
|
|
|
|
+stage_item_acquisition_push(Scratch& scratch,
|
|
|
|
|
+ const SessionState& before,
|
|
|
|
|
+ const ItemAcquisition& acquisition,
|
|
|
|
|
+ const state::PendingItemAcquisition& pending,
|
|
|
|
|
+ std::optional<std::uint16_t> pendingSeasonReward,
|
|
|
|
|
+ std::span<const AcquisitionPresentationRow> presentationRows,
|
|
|
|
|
+ std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
+ std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
+ std::span<std::byte> response,
|
|
|
|
|
+ std::size_t& written,
|
|
|
|
|
+ SessionState& after) noexcept {
|
|
|
const std::size_t appendedIndex = before.family4ResidentCount;
|
|
const std::size_t appendedIndex = before.family4ResidentCount;
|
|
|
bool preservedManifest = acquisition.after.family4ResidentCount == appendedIndex + 1U;
|
|
bool preservedManifest = acquisition.after.family4ResidentCount == appendedIndex + 1U;
|
|
|
for (std::size_t index = 0; preservedManifest && index < appendedIndex; ++index) {
|
|
for (std::size_t index = 0; preservedManifest && index < appendedIndex; ++index) {
|
|
@@ -33,10 +91,10 @@ namespace {
|
|
|
== before.family4Residents[index].definitionId;
|
|
== before.family4Residents[index].definitionId;
|
|
|
}
|
|
}
|
|
|
if (!valid(acquisition.after) || !preservedManifest
|
|
if (!valid(acquisition.after) || !preservedManifest
|
|
|
- || acquisition.accountSoid != itemAcquisition.pending.accountSoid
|
|
|
|
|
- || acquisition.characterSoid != itemAcquisition.pending.characterSoid
|
|
|
|
|
- || acquisition.acquiredInstanceSoid != itemAcquisition.pending.acquiredInstanceSoid
|
|
|
|
|
- || acquisition.updatesAccount != itemAcquisition.pending.profileChanged
|
|
|
|
|
|
|
+ || acquisition.accountSoid != pending.accountSoid
|
|
|
|
|
+ || acquisition.characterSoid != pending.characterSoid
|
|
|
|
|
+ || acquisition.acquiredInstanceSoid != pending.acquiredInstanceSoid
|
|
|
|
|
+ || acquisition.updatesAccount != (pending.profileChanged || pendingSeasonReward.has_value())
|
|
|
|| acquisition.accountSoid != before.family4RootSoid
|
|
|| acquisition.accountSoid != before.family4RootSoid
|
|
|
|| acquisition.after.family4RootSoid != before.family4RootSoid
|
|
|| acquisition.after.family4RootSoid != before.family4RootSoid
|
|
|
|| before.family4ResidentCount >= before.family4Residents.size()
|
|
|| before.family4ResidentCount >= before.family4Residents.size()
|
|
@@ -46,8 +104,15 @@ namespace {
|
|
|
!= acquisition.acquiredInstanceSoid
|
|
!= acquisition.acquiredInstanceSoid
|
|
|
|| acquisition.after.family4Residents[appendedIndex].definitionId
|
|
|| acquisition.after.family4Residents[appendedIndex].definitionId
|
|
|
!= acquisition.itemInstanceDefinitionId
|
|
!= acquisition.itemInstanceDefinitionId
|
|
|
- || !push::append_item_acquisition_notification(
|
|
|
|
|
- scratch, acquisition, itemAcquisition.pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ || !push::append_item_acquisition_notification(scratch,
|
|
|
|
|
+ acquisition,
|
|
|
|
|
+ pending,
|
|
|
|
|
+ pendingSeasonReward,
|
|
|
|
|
+ presentationRows,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written)) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
@@ -55,21 +120,18 @@ namespace {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * Stages the account upsert (and possible manifest append) a profile-acquisition transaction
|
|
|
|
|
- * promised. Shared by the ordinary opcode-1820 transaction and a record-claim reward that
|
|
|
|
|
- * resolved to a profile-bucket grant.
|
|
|
|
|
- */
|
|
|
|
|
|
|
+/** Stages the account upsert and optional manifest append a profile acquisition promised. */
|
|
|
[[nodiscard]] bool
|
|
[[nodiscard]] bool
|
|
|
stage_profile_item_acquisition_push(Scratch& scratch,
|
|
stage_profile_item_acquisition_push(Scratch& scratch,
|
|
|
const SessionState& before,
|
|
const SessionState& before,
|
|
|
- const ProfileItemAcquisitionTransaction& profileAcquisition,
|
|
|
|
|
|
|
+ const ProfileItemAcquisition& acquisition,
|
|
|
|
|
+ const state::PendingProfileItemAcquisition& pending,
|
|
|
|
|
+ std::optional<std::uint16_t> pendingSeasonReward,
|
|
|
std::span<const std::byte, state::kAesKeySize> key,
|
|
std::span<const std::byte, state::kAesKeySize> key,
|
|
|
std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
std::span<std::byte> response,
|
|
std::span<std::byte> response,
|
|
|
std::size_t& written,
|
|
std::size_t& written,
|
|
|
SessionState& after) noexcept {
|
|
SessionState& after) noexcept {
|
|
|
- const ProfileItemAcquisition& acquisition = profileAcquisition.update;
|
|
|
|
|
const std::size_t priorResidentCount = before.family4ResidentCount;
|
|
const std::size_t priorResidentCount = before.family4ResidentCount;
|
|
|
const std::size_t expectedResidentCount =
|
|
const std::size_t expectedResidentCount =
|
|
|
priorResidentCount + static_cast<std::size_t>(acquisition.appendedResident);
|
|
priorResidentCount + static_cast<std::size_t>(acquisition.appendedResident);
|
|
@@ -105,18 +167,17 @@ stage_profile_item_acquisition_push(Scratch& scratch,
|
|
|
: acquisition.itemInstanceDefinitionId == 0 && !acquisition.appendedResident
|
|
: acquisition.itemInstanceDefinitionId == 0 && !acquisition.appendedResident
|
|
|
&& priorProfileResidentMatches == 0);
|
|
&& priorProfileResidentMatches == 0);
|
|
|
if (!valid(acquisition.after) || !validManifest || !sourceIdentityValid
|
|
if (!valid(acquisition.after) || !validManifest || !sourceIdentityValid
|
|
|
- || acquisition.accountSoid != profileAcquisition.pending.accountSoid
|
|
|
|
|
- || acquisition.acquiredInstanceSoid != profileAcquisition.pending.acquiredInstanceSoid
|
|
|
|
|
- || acquisition.actionSource != profileAcquisition.pending.actionSource
|
|
|
|
|
- || acquisition.appendedResident
|
|
|
|
|
- != (profileAcquisition.pending.appended && profileAcquisition.pending.actionSource)
|
|
|
|
|
|
|
+ || acquisition.accountSoid != pending.accountSoid
|
|
|
|
|
+ || acquisition.acquiredInstanceSoid != pending.acquiredInstanceSoid
|
|
|
|
|
+ || acquisition.actionSource != pending.actionSource
|
|
|
|
|
+ || acquisition.appendedResident != (pending.appended && pending.actionSource)
|
|
|
|| acquisition.accountSoid != before.family4RootSoid || before.family4ResidentCount == 0
|
|
|| acquisition.accountSoid != before.family4RootSoid || before.family4ResidentCount == 0
|
|
|
|| acquisition.accountDefinitionId != before.family4Residents.front().definitionId
|
|
|| acquisition.accountDefinitionId != before.family4Residents.front().definitionId
|
|
|
|| acquisition.after.family4RootSoid != before.family4RootSoid
|
|
|| acquisition.after.family4RootSoid != before.family4RootSoid
|
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|
|| acquisition.after.family4Version != before.family4Version + 1
|
|
|| acquisition.after.family4Version != before.family4Version + 1
|
|
|
|| !push::append_profile_item_acquisition_notification(
|
|
|| !push::append_profile_item_acquisition_notification(
|
|
|
- scratch, acquisition, profileAcquisition.pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, acquisition, pending, pendingSeasonReward, key, nonce, response, written)) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
@@ -130,6 +191,8 @@ stage_profile_item_acquisition_push(Scratch& scratch,
|
|
|
bool stage_service_outcome(Scratch& scratch,
|
|
bool stage_service_outcome(Scratch& scratch,
|
|
|
const SessionState& before,
|
|
const SessionState& before,
|
|
|
const ServiceOutcome& outcome,
|
|
const ServiceOutcome& outcome,
|
|
|
|
|
+ bool preserveAcquisitionPresentation,
|
|
|
|
|
+ std::span<const AcquisitionPresentationRow> acquisitionPresentationRows,
|
|
|
std::span<const std::byte, state::kAesKeySize> key,
|
|
std::span<const std::byte, state::kAesKeySize> key,
|
|
|
std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
std::span<std::byte> response,
|
|
std::span<std::byte> response,
|
|
@@ -141,6 +204,8 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
bool armsBannerRepush = false;
|
|
bool armsBannerRepush = false;
|
|
|
std::uint64_t bannerRoot = 0;
|
|
std::uint64_t bannerRoot = 0;
|
|
|
bool armsAbilityRefresh = false;
|
|
bool armsAbilityRefresh = false;
|
|
|
|
|
+ const auto* changeCharacter = transaction_if<ChangeCharacter>(outcome);
|
|
|
|
|
+ const auto* selectCharacter = transaction_if<SelectCharacter>(outcome);
|
|
|
const auto* equipment = transaction_if<EquipmentSwapTransaction>(outcome);
|
|
const auto* equipment = transaction_if<EquipmentSwapTransaction>(outcome);
|
|
|
const auto* subclassSelection = transaction_if<SubclassSelectionTransaction>(outcome);
|
|
const auto* subclassSelection = transaction_if<SubclassSelectionTransaction>(outcome);
|
|
|
const auto* itemState = transaction_if<ItemStateTransaction>(outcome);
|
|
const auto* itemState = transaction_if<ItemStateTransaction>(outcome);
|
|
@@ -149,6 +214,10 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
const auto* profileAcquisition = transaction_if<ProfileItemAcquisitionTransaction>(outcome);
|
|
const auto* profileAcquisition = transaction_if<ProfileItemAcquisitionTransaction>(outcome);
|
|
|
const auto* itemDismantle = transaction_if<ItemDismantleTransaction>(outcome);
|
|
const auto* itemDismantle = transaction_if<ItemDismantleTransaction>(outcome);
|
|
|
const auto* recordRewardGrant = transaction_if<RecordRewardGrantTransaction>(outcome);
|
|
const auto* recordRewardGrant = transaction_if<RecordRewardGrantTransaction>(outcome);
|
|
|
|
|
+ const auto* seasonPassReward = transaction_if<SeasonPassRewardTransaction>(outcome);
|
|
|
|
|
+ const auto presentationRows = preserveAcquisitionPresentation
|
|
|
|
|
+ ? acquisitionPresentationRows
|
|
|
|
|
+ : std::span<const AcquisitionPresentationRow>{};
|
|
|
if (outcome.hasSubscription) {
|
|
if (outcome.hasSubscription) {
|
|
|
push::append_queuez_notification(scratch,
|
|
push::append_queuez_notification(scratch,
|
|
|
before,
|
|
before,
|
|
@@ -166,26 +235,48 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
} else if (outcome.hasChangeCharacter) {
|
|
} else if (outcome.hasChangeCharacter) {
|
|
|
// The reply already carries the version this patch promises. A patch that cannot be built
|
|
// The reply already carries the version this patch promises. A patch that cannot be built
|
|
|
// leaves the ladder where it is, instead of holding back that reply.
|
|
// leaves the ladder where it is, instead of holding back that reply.
|
|
|
- if (!push::append_change_character_notification(
|
|
|
|
|
- scratch, outcome.changeCharacter, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ if (changeCharacter == nullptr
|
|
|
|
|
+ || !push::append_change_character_notification(
|
|
|
|
|
+ scratch, *changeCharacter, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=change result=fail");
|
|
"ev=queuez stage=change result=fail");
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
|
- after = outcome.changeCharacter.after;
|
|
|
|
|
|
|
+ after = changeCharacter->after;
|
|
|
} else if (equipment != nullptr) {
|
|
} else if (equipment != nullptr) {
|
|
|
// Body processing already staged this exact after-image so the correlated opcode-403
|
|
// Body processing already staged this exact after-image so the correlated opcode-403
|
|
|
// response could promise its version. Reuse it here; staging a second revision would make
|
|
// response could promise its version. Reuse it here; staging a second revision would make
|
|
|
// the response and pushed Family-4 ladder disagree.
|
|
// the response and pushed Family-4 ladder disagree.
|
|
|
|
|
+ if (equipment->pending == nullptr) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto& pending = *equipment->pending;
|
|
|
const EquipmentSwap& swap = equipment->update;
|
|
const EquipmentSwap& swap = equipment->update;
|
|
|
- if (!valid(swap.after) || swap.characterSoid != equipment->pending.characterSoid
|
|
|
|
|
|
|
+ std::array<AcquisitionPresentationRow, kAcquisitionPresentationRowCapacity>
|
|
|
|
|
+ mergedPresentationRows{};
|
|
|
|
|
+ std::uint8_t mergedPresentationRowCount = 0;
|
|
|
|
|
+ const bool hasPresentation =
|
|
|
|
|
+ preserveAcquisitionPresentation
|
|
|
|
|
+ && merge_equipment_presentation_rows(acquisitionPresentationRows,
|
|
|
|
|
+ pending,
|
|
|
|
|
+ mergedPresentationRows,
|
|
|
|
|
+ mergedPresentationRowCount);
|
|
|
|
|
+ if (preserveAcquisitionPresentation && !hasPresentation) {
|
|
|
|
|
+ core::log::write(core::log::Channel::server,
|
|
|
|
|
+ core::log::Level::warn,
|
|
|
|
|
+ "ev=queuez stage=equip_presentation result=fail");
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto presentationRows =
|
|
|
|
|
+ hasPresentation ? std::span(mergedPresentationRows).first(mergedPresentationRowCount)
|
|
|
|
|
+ : std::span<const AcquisitionPresentationRow>{};
|
|
|
|
|
+ if (!valid(swap.after) || swap.characterSoid != pending.characterSoid
|
|
|
|| swap.after.family4RootSoid != before.family4RootSoid
|
|
|| swap.after.family4RootSoid != before.family4RootSoid
|
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|
|| swap.after.family4Version != before.family4Version + 1
|
|
|| swap.after.family4Version != before.family4Version + 1
|
|
|
|| !push::append_equipment_swap_notification(
|
|
|| !push::append_equipment_swap_notification(
|
|
|
- scratch, swap, equipment->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, swap, pending, presentationRows, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=equip result=fail");
|
|
"ev=queuez stage=equip result=fail");
|
|
@@ -194,10 +285,17 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
}
|
|
}
|
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
|
after = swap.after;
|
|
after = swap.after;
|
|
|
|
|
+ if (preserveAcquisitionPresentation) {
|
|
|
|
|
+ if (hasPresentation) {
|
|
|
|
|
+ publication.acquisitionPresentationRows = mergedPresentationRows;
|
|
|
|
|
+ publication.acquisitionPresentationRowCount = mergedPresentationRowCount;
|
|
|
|
|
+ }
|
|
|
|
|
+ publication.updatesAcquisitionPresentationRows = true;
|
|
|
|
|
+ }
|
|
|
// Swapping the subclass slot invalidates the published ability buckets the same way an
|
|
// Swapping the subclass slot invalidates the published ability buckets the same way an
|
|
|
// opcode-801 pick does; the rebuild is likewise asynchronous, so this owes the same
|
|
// opcode-801 pick does; the rebuild is likewise asynchronous, so this owes the same
|
|
|
// delayed re-derivation rather than risking a race with whatever refresh runs below.
|
|
// delayed re-derivation rather than risking a race with whatever refresh runs below.
|
|
|
- if (equipment->pending.equipmentSlotIndex
|
|
|
|
|
|
|
+ if (pending.equipmentSlotIndex
|
|
|
== static_cast<std::size_t>(state::account::inventory::EquipmentSlot::subclass)) {
|
|
== static_cast<std::size_t>(state::account::inventory::EquipmentSlot::subclass)) {
|
|
|
armsAbilityRefresh = true;
|
|
armsAbilityRefresh = true;
|
|
|
}
|
|
}
|
|
@@ -206,10 +304,9 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
// in place: releasing and re-adding the same key tears down the ship/banner binding.
|
|
// in place: releasing and re-adding the same key tears down the ship/banner binding.
|
|
|
if (after.family0Active) {
|
|
if (after.family0Active) {
|
|
|
CharacterAppearanceRefresh refresh{};
|
|
CharacterAppearanceRefresh refresh{};
|
|
|
- if (!stage_character_appearance_refresh(
|
|
|
|
|
- after, equipment->pending.characterSoid, refresh)
|
|
|
|
|
|
|
+ if (!stage_character_appearance_refresh(after, pending.characterSoid, refresh)
|
|
|
|| !push::append_equipment_appearance_refresh_notification(
|
|
|| !push::append_equipment_appearance_refresh_notification(
|
|
|
- scratch, refresh, equipment->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, refresh, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=equip_appearance result=fail");
|
|
"ev=queuez stage=equip_appearance result=fail");
|
|
@@ -224,10 +321,9 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
// one exact +1 Family-3 revision. Failure keeps all three peer ladders and State private.
|
|
// one exact +1 Family-3 revision. Failure keeps all three peer ladders and State private.
|
|
|
if (after.family3Active) {
|
|
if (after.family3Active) {
|
|
|
RosterAppearanceRefresh refresh{};
|
|
RosterAppearanceRefresh refresh{};
|
|
|
- if (!stage_roster_appearance_refresh(
|
|
|
|
|
- after, equipment->pending.characterSoid, true, refresh)
|
|
|
|
|
|
|
+ if (!stage_roster_appearance_refresh(after, pending.characterSoid, true, refresh)
|
|
|
|| !push::append_equipment_roster_refresh_notification(
|
|
|| !push::append_equipment_roster_refresh_notification(
|
|
|
- scratch, refresh, equipment->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, refresh, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=equip_roster result=fail");
|
|
"ev=queuez stage=equip_roster result=fail");
|
|
@@ -238,6 +334,10 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
} else if (itemState != nullptr) {
|
|
} else if (itemState != nullptr) {
|
|
|
// Item-state bits live in the selected-character inventory row. Publish only that
|
|
// Item-state bits live in the selected-character inventory row. Publish only that
|
|
|
// resident character body; item-instance, appearance, roster and manifest are unchanged.
|
|
// resident character body; item-instance, appearance, roster and manifest are unchanged.
|
|
|
|
|
+ if (itemState->pending == nullptr) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto& pending = *itemState->pending;
|
|
|
const EquipmentSwap& update = itemState->update;
|
|
const EquipmentSwap& update = itemState->update;
|
|
|
bool preservedManifest = update.after.family4ResidentCount == before.family4ResidentCount;
|
|
bool preservedManifest = update.after.family4ResidentCount == before.family4ResidentCount;
|
|
|
for (std::size_t index = 0; preservedManifest && index < before.family4ResidentCount;
|
|
for (std::size_t index = 0; preservedManifest && index < before.family4ResidentCount;
|
|
@@ -248,12 +348,12 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
== before.family4Residents[index].definitionId;
|
|
== before.family4Residents[index].definitionId;
|
|
|
}
|
|
}
|
|
|
if (!valid(update.after) || !preservedManifest
|
|
if (!valid(update.after) || !preservedManifest
|
|
|
- || update.characterSoid != itemState->pending.characterSoid
|
|
|
|
|
|
|
+ || update.characterSoid != pending.characterSoid
|
|
|
|| update.after.family4RootSoid != before.family4RootSoid
|
|
|| update.after.family4RootSoid != before.family4RootSoid
|
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|
|| update.after.family4Version != before.family4Version + 1
|
|
|| update.after.family4Version != before.family4Version + 1
|
|
|
|| !push::append_item_state_notification(
|
|
|| !push::append_item_state_notification(
|
|
|
- scratch, update, itemState->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, update, pending, presentationRows, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=item_state result=fail");
|
|
"ev=queuez stage=item_state result=fail");
|
|
@@ -265,6 +365,10 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
// Body processing already staged the exact +1 revision opcode 801 promised. The instance
|
|
// Body processing already staged the exact +1 revision opcode 801 promised. The instance
|
|
|
// upsert goes first, then the appearance and roster refreshes, so gameplay reads the new
|
|
// upsert goes first, then the appearance and roster refreshes, so gameplay reads the new
|
|
|
// selection now rather than on the next unrelated poll.
|
|
// selection now rather than on the next unrelated poll.
|
|
|
|
|
+ if (subclassSelection->pending == nullptr) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto& pending = *subclassSelection->pending;
|
|
|
const SubclassSelection& selection = subclassSelection->update;
|
|
const SubclassSelection& selection = subclassSelection->update;
|
|
|
bool preservedManifest =
|
|
bool preservedManifest =
|
|
|
selection.after.family4ResidentCount == before.family4ResidentCount;
|
|
selection.after.family4ResidentCount == before.family4ResidentCount;
|
|
@@ -280,14 +384,14 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
&& resident.definitionId == selection.itemInstanceDefinitionId);
|
|
&& resident.definitionId == selection.itemInstanceDefinitionId);
|
|
|
}
|
|
}
|
|
|
if (!valid(selection.after) || !preservedManifest || targetMatches != 1
|
|
if (!valid(selection.after) || !preservedManifest || targetMatches != 1
|
|
|
- || selection.accountSoid != subclassSelection->pending.accountSoid
|
|
|
|
|
- || selection.characterSoid != subclassSelection->pending.characterSoid
|
|
|
|
|
- || selection.subclassInstanceSoid != subclassSelection->pending.subclassInstanceSoid
|
|
|
|
|
|
|
+ || selection.accountSoid != pending.accountSoid
|
|
|
|
|
+ || selection.characterSoid != pending.characterSoid
|
|
|
|
|
+ || selection.subclassInstanceSoid != pending.subclassInstanceSoid
|
|
|
|| selection.after.family4RootSoid != before.family4RootSoid
|
|
|| selection.after.family4RootSoid != before.family4RootSoid
|
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|
|| selection.after.family4Version != before.family4Version + 1
|
|
|| selection.after.family4Version != before.family4Version + 1
|
|
|
|| !push::append_subclass_selection_notification(
|
|
|| !push::append_subclass_selection_notification(
|
|
|
- scratch, selection, subclassSelection->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, selection, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=subclass_select result=fail");
|
|
"ev=queuez stage=subclass_select result=fail");
|
|
@@ -303,10 +407,9 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
// always owed a refresh once one is active.
|
|
// always owed a refresh once one is active.
|
|
|
if (after.family0Active) {
|
|
if (after.family0Active) {
|
|
|
CharacterAppearanceRefresh refresh{};
|
|
CharacterAppearanceRefresh refresh{};
|
|
|
- if (!stage_character_appearance_refresh(
|
|
|
|
|
- after, subclassSelection->pending.characterSoid, refresh)
|
|
|
|
|
|
|
+ if (!stage_character_appearance_refresh(after, pending.characterSoid, refresh)
|
|
|
|| !push::append_subclass_appearance_refresh_notification(
|
|
|| !push::append_subclass_appearance_refresh_notification(
|
|
|
- scratch, refresh, subclassSelection->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, refresh, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=subclass_appearance result=fail");
|
|
"ev=queuez stage=subclass_appearance result=fail");
|
|
@@ -316,10 +419,9 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
}
|
|
}
|
|
|
if (after.family3Active) {
|
|
if (after.family3Active) {
|
|
|
RosterAppearanceRefresh refresh{};
|
|
RosterAppearanceRefresh refresh{};
|
|
|
- if (!stage_roster_appearance_refresh(
|
|
|
|
|
- after, subclassSelection->pending.characterSoid, false, refresh)
|
|
|
|
|
|
|
+ if (!stage_roster_appearance_refresh(after, pending.characterSoid, false, refresh)
|
|
|
|| !push::append_subclass_roster_refresh_notification(
|
|
|| !push::append_subclass_roster_refresh_notification(
|
|
|
- scratch, refresh, subclassSelection->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, refresh, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=subclass_roster result=fail");
|
|
"ev=queuez stage=subclass_roster result=fail");
|
|
@@ -330,6 +432,10 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
} else if (socket != nullptr) {
|
|
} else if (socket != nullptr) {
|
|
|
// Body processing staged this exact +1 revision before encoding opcode 903's status pair.
|
|
// Body processing staged this exact +1 revision before encoding opcode 903's status pair.
|
|
|
// A socket selection changes only one already-resident item-instance body.
|
|
// A socket selection changes only one already-resident item-instance body.
|
|
|
|
|
+ if (socket->pending == nullptr) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto& pending = *socket->pending;
|
|
|
const SocketPlug& socketPlug = socket->update;
|
|
const SocketPlug& socketPlug = socket->update;
|
|
|
bool preservedManifest =
|
|
bool preservedManifest =
|
|
|
socketPlug.after.family4ResidentCount == before.family4ResidentCount;
|
|
socketPlug.after.family4ResidentCount == before.family4ResidentCount;
|
|
@@ -349,15 +455,15 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
== socketPlug.accountDefinitionId);
|
|
== socketPlug.accountDefinitionId);
|
|
|
}
|
|
}
|
|
|
if (!valid(socketPlug.after) || !preservedManifest || accountMatches != 1
|
|
if (!valid(socketPlug.after) || !preservedManifest || accountMatches != 1
|
|
|
- || targetMatches != 1 || socketPlug.accountSoid != socket->pending.accountSoid
|
|
|
|
|
- || socketPlug.characterSoid != socket->pending.characterSoid
|
|
|
|
|
- || socketPlug.targetInstanceSoid != socket->pending.targetInstanceSoid
|
|
|
|
|
- || socketPlug.updatesAccount != socket->pending.profileChanged
|
|
|
|
|
|
|
+ || targetMatches != 1 || socketPlug.accountSoid != pending.accountSoid
|
|
|
|
|
+ || socketPlug.characterSoid != pending.characterSoid
|
|
|
|
|
+ || socketPlug.targetInstanceSoid != pending.targetInstanceSoid
|
|
|
|
|
+ || socketPlug.updatesAccount != pending.profileChanged
|
|
|
|| socketPlug.after.family4RootSoid != before.family4RootSoid
|
|
|| socketPlug.after.family4RootSoid != before.family4RootSoid
|
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|
|| socketPlug.after.family4Version != before.family4Version + 1
|
|
|| socketPlug.after.family4Version != before.family4Version + 1
|
|
|
|| !push::append_socket_plug_notification(
|
|
|| !push::append_socket_plug_notification(
|
|
|
- scratch, socketPlug, socket->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, socketPlug, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=socket_plug result=fail");
|
|
"ev=queuez stage=socket_plug result=fail");
|
|
@@ -367,11 +473,11 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
after = socketPlug.after;
|
|
after = socketPlug.after;
|
|
|
// Equipped plugs feed Family zero's material, overflow-hash and sandbox-perk banks. An
|
|
// Equipped plugs feed Family zero's material, overflow-hash and sandbox-perk banks. An
|
|
|
// inventory-only socket change has no rendered character record to refresh.
|
|
// inventory-only socket change has no rendered character record to refresh.
|
|
|
- if (socket->pending.targetEquipped && after.family0Active) {
|
|
|
|
|
|
|
+ if (pending.targetEquipped && after.family0Active) {
|
|
|
CharacterAppearanceRefresh refresh{};
|
|
CharacterAppearanceRefresh refresh{};
|
|
|
- if (!stage_character_appearance_refresh(after, socket->pending.characterSoid, refresh)
|
|
|
|
|
|
|
+ if (!stage_character_appearance_refresh(after, pending.characterSoid, refresh)
|
|
|
|| !push::append_socket_appearance_refresh_notification(
|
|
|| !push::append_socket_appearance_refresh_notification(
|
|
|
- scratch, refresh, socket->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, refresh, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=socket_appearance result=fail");
|
|
"ev=queuez stage=socket_appearance result=fail");
|
|
@@ -381,12 +487,11 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
}
|
|
}
|
|
|
// A socket change can alter the rendered shader/perk banks in Family three, but it does not
|
|
// A socket change can alter the rendered shader/perk banks in Family three, but it does not
|
|
|
// change the roster's base-definition references. Only the character record is owed.
|
|
// change the roster's base-definition references. Only the character record is owed.
|
|
|
- if (socket->pending.targetEquipped && after.family3Active) {
|
|
|
|
|
|
|
+ if (pending.targetEquipped && after.family3Active) {
|
|
|
RosterAppearanceRefresh refresh{};
|
|
RosterAppearanceRefresh refresh{};
|
|
|
- if (!stage_roster_appearance_refresh(
|
|
|
|
|
- after, socket->pending.characterSoid, false, refresh)
|
|
|
|
|
|
|
+ if (!stage_roster_appearance_refresh(after, pending.characterSoid, false, refresh)
|
|
|
|| !push::append_socket_roster_refresh_notification(
|
|
|| !push::append_socket_roster_refresh_notification(
|
|
|
- scratch, refresh, socket->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, refresh, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=socket_roster result=fail");
|
|
"ev=queuez stage=socket_roster result=fail");
|
|
@@ -397,8 +502,18 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
} else if (itemAcquisition != nullptr) {
|
|
} else if (itemAcquisition != nullptr) {
|
|
|
// Body processing staged this exact manifest append before encoding the response version.
|
|
// Body processing staged this exact manifest append before encoding the response version.
|
|
|
// The character and new item objects must both fit or the State insertion is not committed.
|
|
// The character and new item objects must both fit or the State insertion is not committed.
|
|
|
- if (!stage_item_acquisition_push(
|
|
|
|
|
- scratch, before, *itemAcquisition, key, nonce, response, written, after)) {
|
|
|
|
|
|
|
+ if (itemAcquisition->pending == nullptr
|
|
|
|
|
+ || !stage_item_acquisition_push(scratch,
|
|
|
|
|
+ before,
|
|
|
|
|
+ itemAcquisition->update,
|
|
|
|
|
+ *itemAcquisition->pending,
|
|
|
|
|
+ std::nullopt,
|
|
|
|
|
+ presentationRows,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written,
|
|
|
|
|
+ after)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=acquire result=fail");
|
|
"ev=queuez stage=acquire result=fail");
|
|
@@ -407,50 +522,105 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
} else if (profileAcquisition != nullptr) {
|
|
} else if (profileAcquisition != nullptr) {
|
|
|
// A source-backed profile append creates one dependency before the account starts naming
|
|
// A source-backed profile append creates one dependency before the account starts naming
|
|
|
// it. Existing stacks and non-actionable currency rows preserve the complete manifest.
|
|
// it. Existing stacks and non-actionable currency rows preserve the complete manifest.
|
|
|
- if (!stage_profile_item_acquisition_push(
|
|
|
|
|
- scratch, before, *profileAcquisition, key, nonce, response, written, after)) {
|
|
|
|
|
|
|
+ if (profileAcquisition->pending == nullptr
|
|
|
|
|
+ || !stage_profile_item_acquisition_push(scratch,
|
|
|
|
|
+ before,
|
|
|
|
|
+ profileAcquisition->update,
|
|
|
|
|
+ *profileAcquisition->pending,
|
|
|
|
|
+ std::nullopt,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written,
|
|
|
|
|
+ after)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=profile_acquire result=fail");
|
|
"ev=queuez stage=profile_acquire result=fail");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
} else if (recordRewardGrant != nullptr) {
|
|
} else if (recordRewardGrant != nullptr) {
|
|
|
- // A record-claim reward names an item directly rather than a Collections row; whichever
|
|
|
|
|
- // acquisition kind its bucket resolved to stages exactly the same way its opcode-1820
|
|
|
|
|
- // counterpart does above, so this takes priority over the plain resync fallback below.
|
|
|
|
|
|
|
+ if (recordRewardGrant->pending == nullptr) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!push::append_record_reward_notification(scratch,
|
|
|
|
|
+ before,
|
|
|
|
|
+ recordRewardGrant->update,
|
|
|
|
|
+ *recordRewardGrant->pending,
|
|
|
|
|
+ presentationRows,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written)) {
|
|
|
|
|
+ core::log::write(core::log::Channel::server,
|
|
|
|
|
+ core::log::Level::warn,
|
|
|
|
|
+ "ev=queuez stage=record_reward result=fail");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ middleware::secure_channel::advance_nonce(nonce);
|
|
|
|
|
+ after = recordRewardGrant->update.after;
|
|
|
|
|
+ } else if (seasonPassReward != nullptr) {
|
|
|
|
|
+ if (seasonPassReward->pending == nullptr) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto& pending = *seasonPassReward->pending;
|
|
|
bool staged = false;
|
|
bool staged = false;
|
|
|
- if (const auto* itemUpdate =
|
|
|
|
|
- std::get_if<ItemAcquisition>(&recordRewardGrant->update)) {
|
|
|
|
|
- ItemAcquisitionTransaction wrapped{};
|
|
|
|
|
- if (const auto* itemPending = std::get_if<state::PendingItemAcquisition>(
|
|
|
|
|
- &recordRewardGrant->pending.grant)) {
|
|
|
|
|
- wrapped.pending = *itemPending;
|
|
|
|
|
|
|
+ if (const auto* itemUpdate = std::get_if<ItemAcquisition>(&seasonPassReward->update)) {
|
|
|
|
|
+ if (const auto* itemPending =
|
|
|
|
|
+ std::get_if<state::PendingItemAcquisition>(&pending.grant)) {
|
|
|
|
|
+ staged = stage_item_acquisition_push(scratch,
|
|
|
|
|
+ before,
|
|
|
|
|
+ *itemUpdate,
|
|
|
|
|
+ *itemPending,
|
|
|
|
|
+ pending.rewardIndex,
|
|
|
|
|
+ presentationRows,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written,
|
|
|
|
|
+ after);
|
|
|
}
|
|
}
|
|
|
- wrapped.update = *itemUpdate;
|
|
|
|
|
- staged = stage_item_acquisition_push(
|
|
|
|
|
- scratch, before, wrapped, key, nonce, response, written, after);
|
|
|
|
|
} else if (const auto* profileUpdate =
|
|
} else if (const auto* profileUpdate =
|
|
|
- std::get_if<ProfileItemAcquisition>(&recordRewardGrant->update)) {
|
|
|
|
|
- ProfileItemAcquisitionTransaction wrapped{};
|
|
|
|
|
- if (const auto* profilePending = std::get_if<state::PendingProfileItemAcquisition>(
|
|
|
|
|
- &recordRewardGrant->pending.grant)) {
|
|
|
|
|
- wrapped.pending = *profilePending;
|
|
|
|
|
|
|
+ std::get_if<ProfileItemAcquisition>(&seasonPassReward->update)) {
|
|
|
|
|
+ if (const auto* profilePending =
|
|
|
|
|
+ std::get_if<state::PendingProfileItemAcquisition>(&pending.grant)) {
|
|
|
|
|
+ staged = stage_profile_item_acquisition_push(scratch,
|
|
|
|
|
+ before,
|
|
|
|
|
+ *profileUpdate,
|
|
|
|
|
+ *profilePending,
|
|
|
|
|
+ pending.rewardIndex,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written,
|
|
|
|
|
+ after);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (const auto* bundle =
|
|
|
|
|
+ std::get_if<state::PendingDirectItemBundle>(&pending.grant)) {
|
|
|
|
|
+ staged = push::append_season_pass_package_notification(scratch,
|
|
|
|
|
+ before,
|
|
|
|
|
+ *bundle,
|
|
|
|
|
+ pending.rewardIndex,
|
|
|
|
|
+ presentationRows,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written,
|
|
|
|
|
+ after);
|
|
|
|
|
+ if (staged) {
|
|
|
|
|
+ middleware::secure_channel::advance_nonce(nonce);
|
|
|
}
|
|
}
|
|
|
- wrapped.update = *profileUpdate;
|
|
|
|
|
- staged = stage_profile_item_acquisition_push(
|
|
|
|
|
- scratch, before, wrapped, key, nonce, response, written, after);
|
|
|
|
|
}
|
|
}
|
|
|
if (!staged) {
|
|
if (!staged) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
- "ev=queuez stage=record_reward result=fail");
|
|
|
|
|
|
|
+ "ev=ws2400 stage=queuez_reward result=fail");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
} else if (outcome.hasRecordClaim) {
|
|
} else if (outcome.hasRecordClaim) {
|
|
|
// A claim rewrites one byte of the account flag bank and leaves the manifest alone, so a
|
|
// A claim rewrites one byte of the account flag bank and leaves the manifest alone, so a
|
|
|
// full account snapshot at the next version carries it with no other staging.
|
|
// full account snapshot at the next version carries it with no other staging.
|
|
|
if (!push::append_account_resync_notification(
|
|
if (!push::append_account_resync_notification(
|
|
|
- scratch, before, key, nonce, response, written, after)) {
|
|
|
|
|
|
|
+ scratch, before, presentationRows, key, nonce, response, written, after)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=ws1801 stage=queuez_resync result=fail");
|
|
"ev=ws1801 stage=queuez_resync result=fail");
|
|
@@ -460,6 +630,10 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
// A dismantle removes exactly one resident while preserving the relative order of every
|
|
// A dismantle removes exactly one resident while preserving the relative order of every
|
|
|
// survivor. The character after-image and empty release descriptor must fit together or
|
|
// survivor. The character after-image and empty release descriptor must fit together or
|
|
|
// the State removal is not committed.
|
|
// the State removal is not committed.
|
|
|
|
|
+ if (itemDismantle->pending == nullptr) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const auto& pending = *itemDismantle->pending;
|
|
|
const ItemDismantle& dismantle = itemDismantle->update;
|
|
const ItemDismantle& dismantle = itemDismantle->update;
|
|
|
bool compactedManifest =
|
|
bool compactedManifest =
|
|
|
before.family4ResidentCount != 0
|
|
before.family4ResidentCount != 0
|
|
@@ -487,17 +661,17 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
&& afterIndex == dismantle.after.family4ResidentCount;
|
|
&& afterIndex == dismantle.after.family4ResidentCount;
|
|
|
|
|
|
|
|
if (!valid(dismantle.after) || !compactedManifest
|
|
if (!valid(dismantle.after) || !compactedManifest
|
|
|
- || dismantle.accountSoid != itemDismantle->pending.accountSoid
|
|
|
|
|
- || dismantle.characterSoid != itemDismantle->pending.characterSoid
|
|
|
|
|
- || dismantle.dismantledInstanceSoid != itemDismantle->pending.dismantledInstanceSoid
|
|
|
|
|
- || dismantle.updatesAccount != itemDismantle->pending.profileChanged
|
|
|
|
|
|
|
+ || dismantle.accountSoid != pending.accountSoid
|
|
|
|
|
+ || dismantle.characterSoid != pending.characterSoid
|
|
|
|
|
+ || dismantle.dismantledInstanceSoid != pending.dismantledInstanceSoid
|
|
|
|
|
+ || dismantle.updatesAccount != pending.profileChanged
|
|
|
|| dismantle.accountSoid != before.family4RootSoid || before.family4ResidentCount == 0
|
|
|| dismantle.accountSoid != before.family4RootSoid || before.family4ResidentCount == 0
|
|
|
|| dismantle.accountDefinitionId != before.family4Residents.front().definitionId
|
|
|| dismantle.accountDefinitionId != before.family4Residents.front().definitionId
|
|
|
|| dismantle.after.family4RootSoid != before.family4RootSoid
|
|
|| dismantle.after.family4RootSoid != before.family4RootSoid
|
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|| before.family4Version == (std::numeric_limits<std::int32_t>::max)()
|
|
|
|| dismantle.after.family4Version != before.family4Version + 1
|
|
|| dismantle.after.family4Version != before.family4Version + 1
|
|
|
|| !push::append_item_dismantle_notification(
|
|
|| !push::append_item_dismantle_notification(
|
|
|
- scratch, dismantle, itemDismantle->pending, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ scratch, dismantle, pending, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=dismantle result=fail");
|
|
"ev=queuez stage=dismantle result=fail");
|
|
@@ -508,22 +682,23 @@ bool stage_service_outcome(Scratch& scratch,
|
|
|
} else if (outcome.hasSelectCharacter) {
|
|
} else if (outcome.hasSelectCharacter) {
|
|
|
// The reply is the Client's task completion and the move is a separate frame. A move that
|
|
// The reply is the Client's task completion and the move is a separate frame. A move that
|
|
|
// cannot be built leaves the selection where it is, instead of holding back that reply.
|
|
// cannot be built leaves the selection where it is, instead of holding back that reply.
|
|
|
- if (!push::append_select_character_notification(
|
|
|
|
|
- scratch, outcome.selectCharacter, key, nonce, response, written)) {
|
|
|
|
|
|
|
+ if (selectCharacter == nullptr
|
|
|
|
|
+ || !push::append_select_character_notification(
|
|
|
|
|
+ scratch, *selectCharacter, key, nonce, response, written)) {
|
|
|
core::log::write(core::log::Channel::server,
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
core::log::Level::warn,
|
|
|
"ev=queuez stage=select result=fail");
|
|
"ev=queuez stage=select result=fail");
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
middleware::secure_channel::advance_nonce(nonce);
|
|
|
- after = outcome.selectCharacter.after;
|
|
|
|
|
|
|
+ after = selectCharacter->after;
|
|
|
// The banner pair follows the family-four move, so it is sent against the state that move
|
|
// The banner pair follows the family-four move, so it is sent against the state that move
|
|
|
// made. A pair that cannot be built leaves the emblem where it is.
|
|
// made. A pair that cannot be built leaves the emblem where it is.
|
|
|
const SessionState& bannerBefore = after;
|
|
const SessionState& bannerBefore = after;
|
|
|
SessionState bannerAfter{};
|
|
SessionState bannerAfter{};
|
|
|
if (push::append_banner_move_notification(scratch,
|
|
if (push::append_banner_move_notification(scratch,
|
|
|
bannerBefore,
|
|
bannerBefore,
|
|
|
- outcome.selectCharacter.selectedCharacterSoid,
|
|
|
|
|
|
|
+ selectCharacter->selectedCharacterSoid,
|
|
|
key,
|
|
key,
|
|
|
nonce,
|
|
nonce,
|
|
|
response,
|
|
response,
|