Просмотр исходного кода

Preserve queued grants on presentation failure and correct pool selection

Zach 1 месяц назад
Родитель
Сommit
37785c819b

+ 10 - 6
Sunrise/src/server/bap/bap_world_reward_queue.cpp

@@ -18,10 +18,6 @@ bool commit_world_reward(const WorldRewardRequest& request) noexcept {
     }
     bool committed = false;
     if (request.kind == WorldRewardKind::item) {
-        state::build_data::items::Definition item{};
-        if (!state::build_data::find_item_definition_index(request.itemDefinitionIndex, item)) {
-            return false;
-        }
         if (state::item_grant_route(request.itemDefinitionIndex) == state::ItemGrantRoute::quest) {
             state::PendingItemAcquisition acquisition;
             committed = request.quantity == 1
@@ -31,11 +27,19 @@ bool commit_world_reward(const WorldRewardRequest& request) noexcept {
         } else {
             const std::unique_ptr<state::PendingRecordRewardGrant> grant(
                 new (std::nothrow) state::PendingRecordRewardGrant);
+            const char* reason = "reward_allocation";
             committed = grant && request.quantity > 0
                         && state::prepare_item_reward(request.itemDefinitionIndex,
                                                       static_cast<std::uint32_t>(request.quantity),
-                                                      *grant)
-                        && state::commit_record_reward(*grant);
+                                                      *grant,
+                                                      &reason);
+            if (committed) {
+                reason = "inventory_commit";
+                committed = state::commit_record_reward(*grant);
+            }
+            if (!committed) {
+                report_reward_refusal("world_settle", request.itemDefinitionIndex, reason);
+            }
         }
     } else {
         state::PendingProfileItemAcquisition acquisition;

+ 73 - 79
Sunrise/src/server/bap/encrypted/queuez/queuez_deferred_push.cpp

@@ -78,32 +78,34 @@ bool consume_world_record_reward(const WorldRewardRequest& request,
         return fail(reason);
     }
     queuez::RecordRewardGrant update{};
-    if (!queuez::stage_record_reward_grant(session.queuez, *pending, update)) {
-        return fail("inventory_stage");
-    }
     touchesScratch = true;
     std::size_t framedSize = 0;
-    if (!push::append_record_reward_notification(scratch,
-                                                 session.queuez,
-                                                 update,
-                                                 *pending,
-                                                 active_acquisition_presentation_rows(session),
-                                                 session.sessionKey,
-                                                 session.sendNonce,
-                                                 scratch.framed,
-                                                 framedSize)
-        || framedSize == 0) {
-        return fail("inventory_encode");
-    }
-    if (framedSize > response.size()) {
-        return false;
-    }
+    const bool staged = queuez::stage_record_reward_grant(session.queuez, *pending, update);
+    const bool encoded =
+        staged
+        && push::append_record_reward_notification(scratch,
+                                                   session.queuez,
+                                                   update,
+                                                   *pending,
+                                                   active_acquisition_presentation_rows(session),
+                                                   session.sessionKey,
+                                                   session.sendNonce,
+                                                   scratch.framed,
+                                                   framedSize)
+        && framedSize != 0;
     if (!state::commit_record_reward(*pending)) {
         return fail("inventory_commit");
     }
     if (!bap::complete_world_reward(request.id) || !transaction.commit()) {
         return fail("queue_commit");
     }
+    // A failed presentation still commits the prepared draw and retires its queue row.
+    if (!encoded || framedSize > response.size()) {
+        bap::arm_account_resync_everywhere();
+        return fail(!staged    ? "inventory_stage"
+                    : !encoded ? "inventory_encode"
+                               : "response_capacity");
+    }
     std::copy_n(scratch.framed.begin(), framedSize, response.begin());
     written = framedSize;
     middleware::secure_channel::advance_nonce(session.sendNonce);
@@ -120,10 +122,6 @@ bool consume_world_record_reward(const WorldRewardRequest& request,
                                                   std::span<std::byte> response,
                                                   std::size_t& written,
                                                   bool& touchesScratch) noexcept {
-    state::build_data::items::Definition item{};
-    if (!state::build_data::find_item_definition_index(request.itemDefinitionIndex, item)) {
-        return false;
-    }
     if (state::item_grant_route(request.itemDefinitionIndex) != state::ItemGrantRoute::quest) {
         return consume_world_record_reward(
             request, session, scratch, response, written, touchesScratch);
@@ -138,46 +136,43 @@ bool consume_world_record_reward(const WorldRewardRequest& request,
         core::log::write(core::log::Channel::server,
                          core::log::Level::warn,
                          "ev=queuez stage=world_acquisition result=fail reason=prepare");
-        bap::settle_world_reward();
         return false;
     }
     touchesScratch = true;
     queuez::ItemAcquisition acquisition{};
-    if (!queuez::stage_item_acquisition(session.queuez,
-                                        pending.accountSoid,
-                                        pending.characterSoid,
-                                        pending.acquiredInstanceSoid,
-                                        pending.updates_account(),
-                                        acquisition)) {
-        core::log::write(core::log::Channel::server,
-                         core::log::Level::warn,
-                         "ev=queuez stage=world_acquisition result=fail reason=stage");
-        bap::settle_world_reward();
-        return false;
-    }
+    const bool staged = queuez::stage_item_acquisition(session.queuez,
+                                                       pending.accountSoid,
+                                                       pending.characterSoid,
+                                                       pending.acquiredInstanceSoid,
+                                                       pending.updates_account(),
+                                                       acquisition);
     auto nextSendNonce = session.sendNonce;
     std::size_t framedSize = 0;
-    if (!push::append_item_acquisition_notification(scratch,
-                                                    acquisition,
-                                                    pending,
-                                                    active_acquisition_presentation_rows(session),
-                                                    session.sessionKey,
-                                                    nextSendNonce,
-                                                    scratch.framed,
-                                                    framedSize)
-        || framedSize == 0 || framedSize > response.size()) {
-        core::log::write(core::log::Channel::server,
-                         core::log::Level::warn,
-                         "ev=queuez stage=world_acquisition result=fail reason=encode");
-        bap::settle_world_reward();
-        return false;
-    }
+    const bool encoded =
+        staged
+        && push::append_item_acquisition_notification(scratch,
+                                                      acquisition,
+                                                      pending,
+                                                      active_acquisition_presentation_rows(session),
+                                                      session.sessionKey,
+                                                      nextSendNonce,
+                                                      scratch.framed,
+                                                      framedSize)
+        && framedSize != 0;
     if (!state::commit_item_acquisition(pending) || !bap::complete_world_reward(request.id)
         || !transaction.commit()) {
         core::log::write(core::log::Channel::server,
                          core::log::Level::warn,
                          "ev=queuez stage=world_acquisition result=fail reason=commit");
-        bap::settle_world_reward();
+        return false;
+    }
+    if (!encoded || framedSize > response.size()) {
+        bap::arm_account_resync_everywhere();
+        report_reward_refusal("world_acquisition",
+                              request.itemDefinitionIndex,
+                              !staged    ? "inventory_stage"
+                              : !encoded ? "inventory_encode"
+                                         : "response_capacity");
         return false;
     }
     std::copy_n(scratch.framed.begin(), framedSize, response.begin());
@@ -208,45 +203,41 @@ bool consume_world_record_reward(const WorldRewardRequest& request,
         core::log::write(core::log::Channel::server,
                          core::log::Level::warn,
                          "ev=queuez stage=world_profile_acquisition result=fail reason=prepare");
-        bap::settle_world_reward();
         return false;
     }
     touchesScratch = true;
     queuez::ProfileItemAcquisition acquisition{};
-    if (!queuez::stage_profile_item_acquisition(session.queuez,
-                                                pending.accountSoid,
-                                                pending.acquiredInstanceSoid,
-                                                pending.actionSource,
-                                                pending.appended,
-                                                acquisition)) {
-        core::log::write(core::log::Channel::server,
-                         core::log::Level::warn,
-                         "ev=queuez stage=world_profile_acquisition result=fail reason=stage");
-        bap::settle_world_reward();
-        return false;
-    }
+    const bool staged = queuez::stage_profile_item_acquisition(session.queuez,
+                                                               pending.accountSoid,
+                                                               pending.acquiredInstanceSoid,
+                                                               pending.actionSource,
+                                                               pending.appended,
+                                                               acquisition);
     auto nextSendNonce = session.sendNonce;
     std::size_t framedSize = 0;
-    if (!push::append_profile_item_acquisition_notification(scratch,
-                                                            acquisition,
-                                                            pending,
-                                                            session.sessionKey,
-                                                            nextSendNonce,
-                                                            scratch.framed,
-                                                            framedSize)
-        || framedSize == 0 || framedSize > response.size()) {
-        core::log::write(core::log::Channel::server,
-                         core::log::Level::warn,
-                         "ev=queuez stage=world_profile_acquisition result=fail reason=encode");
-        bap::settle_world_reward();
-        return false;
-    }
+    const bool encoded = staged
+                         && push::append_profile_item_acquisition_notification(scratch,
+                                                                               acquisition,
+                                                                               pending,
+                                                                               session.sessionKey,
+                                                                               nextSendNonce,
+                                                                               scratch.framed,
+                                                                               framedSize)
+                         && framedSize != 0;
     if (!state::commit_profile_item_acquisition(pending) || !bap::complete_world_reward(request.id)
         || !transaction.commit()) {
         core::log::write(core::log::Channel::server,
                          core::log::Level::warn,
                          "ev=queuez stage=world_profile_acquisition result=fail reason=commit");
-        bap::settle_world_reward();
+        return false;
+    }
+    if (!encoded || framedSize > response.size()) {
+        bap::arm_account_resync_everywhere();
+        report_reward_refusal("world_profile_acquisition",
+                              request.itemDefinitionIndex,
+                              !staged    ? "inventory_stage"
+                              : !encoded ? "inventory_encode"
+                                         : "response_capacity");
         return false;
     }
     std::copy_n(scratch.framed.begin(), framedSize, response.begin());
@@ -759,6 +750,9 @@ bool consume_deferred(Session& session,
         if (published) {
             return true;
         }
+        if (session.accountResyncArmed) {
+            return false;
+        }
     }
     if (consume_seasonal_experience_presentation(
             session, scratch, response, written, touchesScratch)) {

+ 27 - 31
Sunrise/src/state/rewards/reward_resolver.cpp

@@ -5,13 +5,13 @@
 
 #include "../build_data/rewards/reward_catalog.h"
 #include "../build_data/runtime.h"
-#include "middleware/content/packages/tables/definition_index_table.h"
+#include "middleware/content/packages/tables/unlock_opcode.h"
 
 namespace sunrise::state::rewards {
 namespace {
 
 namespace definitions = build_data::rewards;
-/** The empty bucket tag inherits the enclosing pool's bucket constraint. */
+/** The empty category tag matches every wrapper selection. */
 constexpr std::uint32_t kEmptyTag = 0x811C9DC5U;
 /** Native postfix expressions use at most 256 signed 32-bit stack values. */
 constexpr std::size_t kExpressionCapacity = 256;
@@ -158,6 +158,8 @@ struct Resolver {
     Result& result;
     std::uint64_t random;
     Selection selection;
+    std::array<const definitions::Entry*, definitions::kGrantCapacity> selectedEntries{};
+    std::array<std::uint32_t, definitions::kGrantCapacity> selectedCategories{};
 
     double fraction() noexcept {
         // SplitMix64 makes a prepared seed replayable without shared random state.
@@ -171,15 +173,18 @@ struct Resolver {
 
     bool weight(const definitions::Entry& entry,
                 std::uint32_t category,
-                std::uint32_t bucket,
                 std::size_t depth,
                 double& output) noexcept {
         output = 0;
-        if (entry.categoryHash != category
-            || (bucket != kEmptyTag && entry.bucketHash != kEmptyTag
-                && entry.bucketHash != bucket)) {
+        if (entry.categoryHash != kEmptyTag && entry.categoryHash != category) {
             return true;
         }
+        // Draws exclude the selected pool row within its category, not every copy of its item.
+        for (std::size_t i = 0; i < result.count; ++i) {
+            if (selectedEntries[i] == &entry && selectedCategories[i] == category) {
+                return true;
+            }
+        }
         if (selection == Selection::equipment && entry.poolIndex == definitions::kAbsent) {
             build_data::items::details::Definition item{};
             if (!build_data::find_configured_item_detail(entry.itemIndex, item)
@@ -207,7 +212,11 @@ struct Resolver {
                 if (modifier.valueIndex != definitions::kAbsent) {
                     return refuse(context, "indexed_weight");
                 }
-                value = modifier.value;
+                // The first matching modifier replaces the weight; a negative value keeps it.
+                if (modifier.value >= 0) {
+                    value = modifier.value;
+                }
+                break;
             }
         }
         if (!std::isfinite(value) || value < 0) {
@@ -218,11 +227,7 @@ struct Resolver {
         }
         if (entry.poolIndex != definitions::kAbsent) {
             double total = 0;
-            if (!pool_weight(entry.poolIndex,
-                             category,
-                             entry.bucketHash == kEmptyTag ? bucket : entry.bucketHash,
-                             depth + 1,
-                             total)) {
+            if (!pool_weight(entry.poolIndex, category, depth + 1, total)) {
                 return false;
             }
             if (total == 0) {
@@ -232,11 +237,6 @@ struct Resolver {
             if (entry.itemIndex >= data.items.size()) {
                 return false;
             }
-            for (std::size_t i = 0; i < result.count; ++i) {
-                if (result.grants[i].itemIndex == entry.itemIndex) {
-                    return true;
-                }
-            }
         } else {
             return refuse(context, "reward_mapping");
         }
@@ -246,7 +246,6 @@ struct Resolver {
 
     bool pool_weight(std::uint16_t index,
                      std::uint32_t category,
-                     std::uint32_t bucket,
                      std::size_t depth,
                      double& total) noexcept {
         total = 0;
@@ -259,7 +258,7 @@ struct Resolver {
         }
         for (const auto& entry : data.entries.subspan(range.first, range.count)) {
             double value = 0;
-            if (!weight(entry, category, bucket, depth, value)) {
+            if (!weight(entry, category, depth, value)) {
                 return false;
             }
             total += value;
@@ -269,7 +268,6 @@ struct Resolver {
 
     bool draw(std::uint16_t index,
               std::uint32_t category,
-              std::uint32_t bucket,
               std::size_t depth,
               double total) noexcept {
         const auto range = data.pools[index].entries;
@@ -277,7 +275,7 @@ struct Resolver {
         const definitions::Entry* chosen = nullptr;
         for (const auto& entry : data.entries.subspan(range.first, range.count)) {
             double value = 0;
-            if (!weight(entry, category, bucket, depth, value)) {
+            if (!weight(entry, category, depth, value)) {
                 return false;
             }
             if (value == 0) {
@@ -289,22 +287,21 @@ struct Resolver {
                 break;
             }
         }
-        if (chosen == nullptr
-            || (chosen->quantity != 1 && chosen->poolIndex != definitions::kAbsent)) {
-            return refuse(context, "nested_quantity");
+        if (chosen == nullptr) {
+            return refuse(context, "empty_pool");
         }
         if (chosen->poolIndex != definitions::kAbsent) {
-            const auto childBucket = chosen->bucketHash == kEmptyTag ? bucket : chosen->bucketHash;
             double childTotal = 0;
-            return pool_weight(chosen->poolIndex, category, childBucket, depth + 1, childTotal)
-                   && childTotal > 0
-                   && draw(chosen->poolIndex, category, childBucket, depth + 1, childTotal);
+            return pool_weight(chosen->poolIndex, category, depth + 1, childTotal) && childTotal > 0
+                   && draw(chosen->poolIndex, category, depth + 1, childTotal);
         }
         if (chosen->quantity == 0 || chosen->quantity > INT32_MAX
             || result.count == result.grants.size()
             || !definitions::fits(chosen->sockets, data.sockets)) {
             return false;
         }
+        selectedEntries[result.count] = chosen;
+        selectedCategories[result.count] = category;
         auto& grant = result.grants[result.count++];
         grant.itemIndex = chosen->itemIndex;
         grant.quantity = static_cast<std::int32_t>(chosen->quantity);
@@ -355,15 +352,14 @@ bool resolve_item(definitions::View data,
             }
             for (std::size_t j = 0; j < declared.count; ++j) {
                 double total = 0;
-                if (!resolver.pool_weight(
-                        item.poolIndex, declared.categoryHash, kEmptyTag, 0, total)) {
+                if (!resolver.pool_weight(item.poolIndex, declared.categoryHash, 0, total)) {
                     return false;
                 }
                 // A fixed bundle can have fewer eligible members after an acquisition unlock.
                 if (total == 0) {
                     break;
                 }
-                if (!resolver.draw(item.poolIndex, declared.categoryHash, kEmptyTag, 0, total)) {
+                if (!resolver.draw(item.poolIndex, declared.categoryHash, 0, total)) {
                     return false;
                 }
             }