account_state.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #include "account_state.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <cmath>
  5. namespace sunrise::state::account {
  6. namespace {
  7. /** Enough fixed storage for every account, character, profile-stack, and character-item key. */
  8. inline constexpr std::size_t kIdentityCapacity =
  9. 1 + inventory::kProfileItemCapacity
  10. + kCharacterCapacity * (1 + inventory::kEquipmentSlotCount + inventory::kCharacterItemCapacity);
  11. /** @return True when a profile row carries no authored or runtime-owned value. */
  12. [[nodiscard]] bool empty_profile_item(const inventory::ProfileItem& item) noexcept {
  13. return item.instanceSoid == 0 && item.definitionHash == 0 && item.quantity == 0
  14. && item.mutationSerial == 0;
  15. }
  16. /** Tier bits 1-5 are the native rarity ladder; bit 0 (no tier) is never a payout target. */
  17. constexpr std::uint8_t kDismantleTierMaskBits = 0b0011'1110U;
  18. constexpr std::uint8_t kDismantleClassMaskBits =
  19. static_cast<std::uint8_t>(DismantleGearClass::weapon)
  20. | static_cast<std::uint8_t>(DismantleGearClass::armor);
  21. /** @return True when one unused dismantle policy row is canonical zero. */
  22. [[nodiscard]] bool empty_dismantle_reward(const DismantleRewardPolicy& reward) noexcept {
  23. return reward.definitionHash == 0 && reward.quantity == 0 && reward.tierMask == 0
  24. && reward.classMask == 0 && reward.masterwork == DismantleMasterworkFilter::any;
  25. }
  26. /** Checks filled policy rows, uniqueness, and the zero tail. */
  27. [[nodiscard]] bool valid_dismantle_rewards(const AccountState& state) noexcept {
  28. if (state.dismantleRewardCount > state.dismantleRewards.size()) {
  29. return false;
  30. }
  31. for (std::size_t index = 0; index < state.dismantleRewards.size(); ++index) {
  32. const DismantleRewardPolicy& reward = state.dismantleRewards[index];
  33. if (index >= state.dismantleRewardCount) {
  34. if (!empty_dismantle_reward(reward)) {
  35. return false;
  36. }
  37. continue;
  38. }
  39. if (reward.definitionHash == inventory::kNoDefinitionHash || reward.quantity <= 0
  40. || (reward.tierMask & ~kDismantleTierMaskBits) != 0
  41. || (reward.classMask & ~kDismantleClassMaskBits) != 0
  42. || reward.masterwork > DismantleMasterworkFilter::notMasterworked) {
  43. return false;
  44. }
  45. for (std::size_t prior = 0; prior < index; ++prior) {
  46. if (same_dismantle_policy_key(state.dismantleRewards[prior], reward)) {
  47. return false;
  48. }
  49. }
  50. }
  51. return true;
  52. }
  53. /** Adds one nonzero globally unique key to a bounded identity set. */
  54. [[nodiscard]] bool append_identity(std::array<std::uint64_t, kIdentityCapacity>& identities,
  55. std::size_t& count,
  56. std::uint64_t soid) noexcept {
  57. if (soid == 0 || count >= identities.size()) {
  58. return false;
  59. }
  60. const auto end = identities.cbegin() + static_cast<std::ptrdiff_t>(count);
  61. if (std::find(identities.cbegin(), end, soid) != end) {
  62. return false;
  63. }
  64. identities[count++] = soid;
  65. return true;
  66. }
  67. /** Checks the complete authored/runtime structure without consulting installed build data. */
  68. [[nodiscard]] bool valid_impl(const AccountState& state) noexcept {
  69. if (state.profileItemCount > state.profileItems.size()
  70. || state.characterCount > state.characters.size()) {
  71. return false;
  72. }
  73. if (state.primarySoid == 0) {
  74. if (state.profileItemCount != 0 || state.characterCount != 0
  75. || state.dismantleRewardCount != 0 || state.settings.configured
  76. || state.settings.keyBindings.configured) {
  77. return false;
  78. }
  79. return std::all_of(
  80. state.profileItems.cbegin(), state.profileItems.cend(), empty_profile_item)
  81. && std::all_of(state.dismantleRewards.cbegin(),
  82. state.dismantleRewards.cend(),
  83. empty_dismantle_reward);
  84. }
  85. if (!settings::valid(state.settings) || !valid_dismantle_rewards(state)) {
  86. return false;
  87. }
  88. std::array<std::uint64_t, kIdentityCapacity> identities{};
  89. std::size_t identityCount = 0;
  90. if (!append_identity(identities, identityCount, state.primarySoid)) {
  91. return false;
  92. }
  93. for (std::size_t index = 0; index < state.profileItems.size(); ++index) {
  94. const inventory::ProfileItem& item = state.profileItems[index];
  95. if (index >= state.profileItemCount) {
  96. if (!empty_profile_item(item)) {
  97. return false;
  98. }
  99. continue;
  100. }
  101. if (item.definitionHash == inventory::kNoDefinitionHash || item.quantity <= 0
  102. || item.mutationSerial < 0
  103. || (item.instanceSoid != 0
  104. && !append_identity(identities, identityCount, item.instanceSoid))) {
  105. return false;
  106. }
  107. }
  108. bool selected = false;
  109. for (std::size_t index = 0; index < state.characterCount; ++index) {
  110. const CharacterState& character = state.characters[index];
  111. if (!append_identity(identities, identityCount, character.soid)
  112. || (character.selected && selected) || character.race > CharacterRace::exo
  113. || character.gender > CharacterGender::female
  114. || character.characterClass > CharacterClass::warlock
  115. || !std::isfinite(character.appearanceValue) || !inventory::valid(character.equipment)
  116. || !inventory::valid(character.inventory)) {
  117. return false;
  118. }
  119. selected = selected || character.selected;
  120. for (const std::optional<inventory::Item>& item : character.equipment.slots) {
  121. if (item.has_value()
  122. && !append_identity(identities, identityCount, item->instanceSoid)) {
  123. return false;
  124. }
  125. }
  126. for (std::size_t itemIndex = 0; itemIndex < character.inventory.count; ++itemIndex) {
  127. if (!append_identity(identities,
  128. identityCount,
  129. character.inventory.values[itemIndex].instanceSoid)) {
  130. return false;
  131. }
  132. }
  133. }
  134. return true;
  135. }
  136. } // namespace
  137. /**
  138. * Checks bounded ids and whole settings for every nonzero account.
  139. * @param state Account State to check.
  140. * @return True for empty State, or a whole account with unique ids.
  141. */
  142. bool valid(const AccountState& state) noexcept {
  143. return valid_impl(state);
  144. }
  145. /** Checks settings-authored State before runtime-only profile stack identities are seeded. */
  146. bool valid_authored(const AccountState& state) noexcept {
  147. return valid_impl(state);
  148. }
  149. /**
  150. * Finds the selected character without storing a second account-level key.
  151. * @param state Account snapshot read under the lock.
  152. * @return The selected character's nonzero SOID, or zero when none is selected.
  153. */
  154. std::uint64_t selected_character_soid(const AccountState& state) noexcept {
  155. const std::size_t count = (std::min)(state.characterCount, state.characters.size());
  156. for (std::size_t index = 0; index < count; ++index) {
  157. if (state.characters[index].selected) {
  158. return state.characters[index].soid;
  159. }
  160. }
  161. return 0;
  162. }
  163. /**
  164. * Finds the character the family-zero banner pair names.
  165. * The pair goes out before any pick, so it falls back to the first character.
  166. * @param state Account snapshot read under the lock.
  167. * @return The character's nonzero SOID, or zero when the account owns none.
  168. */
  169. std::uint64_t banner_character_soid(const AccountState& state) noexcept {
  170. const std::uint64_t selected = selected_character_soid(state);
  171. if (selected != 0) {
  172. return selected;
  173. }
  174. return state.characterCount == 0 ? 0 : state.characters[0].soid;
  175. }
  176. } // namespace sunrise::state::account