configured_equipment_light_resolver.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #include "configured_equipment_light_resolver.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <cstddef>
  5. #include <cstdint>
  6. #include <optional>
  7. #include <span>
  8. #include "../../../build_data/runtime.h"
  9. #include "../calculation/equipment_light_calculation.h"
  10. namespace sunrise::state::equipment::light::resolution {
  11. namespace {
  12. namespace authored = account::inventory;
  13. namespace build_details = build_data::items::details;
  14. namespace build_items = build_data::items;
  15. /** At most 2 non-selected rows remain in a 3-character account. */
  16. constexpr std::size_t kOtherCharacterCapacity = kCharacterCapacity - 1U;
  17. /** Native equipment-slot identity learned for every used authored semantic slot. */
  18. using SemanticSlotMap = std::array<std::optional<std::uint8_t>, authored::kEquipmentSlotCount>;
  19. /** Authored semantic-slot owner learned for every used native equipment slot. */
  20. using NativeSlotMap = std::array<std::optional<std::size_t>, build_details::kEquipmentSlotCount>;
  21. /**
  22. * Limits the used character count to the fixed row storage.
  23. * @param account Account configuration, read under the lock.
  24. * @return Rows that can be indexed without leaving the fixed array.
  25. */
  26. [[nodiscard]] std::size_t occupied_rows(const AccountState& account) noexcept {
  27. return (std::min)(account.characterCount, account.characters.size());
  28. }
  29. /**
  30. * Finds one authored item's native slot and computes its score.
  31. * @param item Already-checked authored equipment item.
  32. * @param nativeSlot Receives the installed native equipment slot.
  33. * @param itemScore Receives the installed definition index and computed light score.
  34. * @return True when the dense item and its configured detail agree on one native slot.
  35. */
  36. [[nodiscard]] bool
  37. resolve_item(const authored::Item& item, std::size_t& nativeSlot, ItemScore& itemScore) noexcept {
  38. build_items::Definition definition{};
  39. build_details::Definition detail{};
  40. if (!build_data::find_item_definition_hash(item.definitionHash, definition)
  41. || !build_data::find_configured_item_detail(definition.definitionIndex, detail)
  42. || detail.definitionIndex != definition.definitionIndex || !detail.equipmentSlot.has_value()
  43. || *detail.equipmentSlot < 0
  44. || static_cast<std::size_t>(*detail.equipmentSlot) >= build_details::kEquipmentSlotCount) {
  45. return false;
  46. }
  47. nativeSlot = static_cast<std::size_t>(*detail.equipmentSlot);
  48. std::int32_t power = 0;
  49. if (!item_power(item.level, power)) {
  50. return false;
  51. }
  52. itemScore = ItemScore{definition.definitionIndex, power};
  53. return true;
  54. }
  55. /**
  56. * Builds one character's native slot maxima while learning semantic-slot consistency.
  57. * @param character Already-checked authored character.
  58. * @param semanticSlots Cross-character semantic-to-native map, learned in place.
  59. * @param nativeSlots Cross-character native-to-semantic owner, learned in place.
  60. * @param output Receives one complete native slot array only on success.
  61. * @return True when every item is found and no semantic or native slot collides.
  62. */
  63. [[nodiscard]] bool resolve_character(const CharacterState& character,
  64. SemanticSlotMap& semanticSlots,
  65. NativeSlotMap& nativeSlots,
  66. SlotScores& output) noexcept {
  67. SlotScores staged{};
  68. for (std::size_t semanticSlot = 0; semanticSlot < character.equipment.slots.size();
  69. ++semanticSlot) {
  70. const std::optional<authored::Item>& item = character.equipment.slots[semanticSlot];
  71. if (!item.has_value()) {
  72. continue;
  73. }
  74. std::size_t nativeSlot = 0;
  75. ItemScore score{};
  76. if (!resolve_item(*item, nativeSlot, score) || staged[nativeSlot].has_value()) {
  77. return false;
  78. }
  79. if (semanticSlots[semanticSlot].has_value() && *semanticSlots[semanticSlot] != nativeSlot) {
  80. return false;
  81. }
  82. if (nativeSlots[nativeSlot].has_value() && *nativeSlots[nativeSlot] != semanticSlot) {
  83. return false;
  84. }
  85. // Both directions must commit together so later characters cannot alias a native slot.
  86. semanticSlots[semanticSlot] = static_cast<std::uint8_t>(nativeSlot);
  87. nativeSlots[nativeSlot] = semanticSlot;
  88. staged[nativeSlot] = score;
  89. }
  90. output = staged;
  91. return true;
  92. }
  93. } // namespace
  94. /**
  95. * Finds authored equipment in the installed item and detail maps, then computes light from the
  96. * authored item levels.
  97. */
  98. bool resolve(const AccountState& account,
  99. std::size_t selectedCharacterIndex,
  100. Evaluation& output) noexcept {
  101. const std::size_t count = occupied_rows(account);
  102. if (selectedCharacterIndex >= count || !account.characters[selectedCharacterIndex].selected) {
  103. return false;
  104. }
  105. std::array<SlotScores, kCharacterCapacity> characterScores{};
  106. SemanticSlotMap semanticSlots{};
  107. NativeSlotMap nativeSlots{};
  108. for (std::size_t characterIndex = 0; characterIndex < count; ++characterIndex) {
  109. if (!resolve_character(account.characters[characterIndex],
  110. semanticSlots,
  111. nativeSlots,
  112. characterScores[characterIndex])) {
  113. return false;
  114. }
  115. }
  116. std::array<SlotScores, kOtherCharacterCapacity> otherCharacters{};
  117. std::size_t otherCharacterCount = 0;
  118. for (std::size_t characterIndex = 0; characterIndex < count; ++characterIndex) {
  119. if (characterIndex != selectedCharacterIndex
  120. && otherCharacterCount < otherCharacters.size()) {
  121. otherCharacters[otherCharacterCount++] = characterScores[characterIndex];
  122. }
  123. }
  124. // The profile summary array reports the same equipped items as the character array. The
  125. // generator fills both from one loadout.
  126. const SlotScores& profileSlotMaxima = characterScores[selectedCharacterIndex];
  127. Evaluation staged{};
  128. if (!calculation::evaluate(characterScores[selectedCharacterIndex],
  129. profileSlotMaxima,
  130. std::span(otherCharacters).first(otherCharacterCount),
  131. staged)) {
  132. return false;
  133. }
  134. output = staged;
  135. return true;
  136. }
  137. /** Computes the equipment light one character displays, selected or not. */
  138. bool character_light(const AccountState& account,
  139. std::size_t characterIndex,
  140. std::int32_t& light) noexcept {
  141. light = 0;
  142. if (characterIndex >= occupied_rows(account)) {
  143. return false;
  144. }
  145. SlotScores scores{};
  146. SemanticSlotMap semanticSlots{};
  147. NativeSlotMap nativeSlots{};
  148. if (!resolve_character(
  149. account.characters[characterIndex], semanticSlots, nativeSlots, scores)) {
  150. return false;
  151. }
  152. Evaluation evaluation{};
  153. if (!calculation::evaluate(scores, SlotScores{}, std::span<const SlotScores>{}, evaluation)) {
  154. return false;
  155. }
  156. light = evaluation.average;
  157. return true;
  158. }
  159. } // namespace sunrise::state::equipment::light::resolution