loadout_catalyst_regression_tests.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. #include <array>
  2. #include <cstdint>
  3. #include <cstdio>
  4. #include <optional>
  5. #include <span>
  6. #include "middleware/datagen/character_record/appearance/internal.h"
  7. #include "middleware/datagen/family4/loadout/loadout_item_resolver.h"
  8. #include "state/build_data/inventory/buckets/inventory_bucket_catalog.h"
  9. #include "state/build_data/items/catalysts/exotic_catalyst_catalog.h"
  10. #include "state/build_data/items/details/item_detail_catalog.h"
  11. #include "state/build_data/items/item_catalog.h"
  12. #include "state/build_data/runtime.h"
  13. #include "state/build_data/socket_entry_lists/socket_entry_list_catalog.h"
  14. extern int failures;
  15. namespace {
  16. bool forceCompletionFailure{};
  17. } // namespace
  18. namespace sunrise::state::build_data {
  19. bool find_item_definition_hash(std::uint32_t definitionHash,
  20. items::Definition& definition) noexcept {
  21. return items::find_hash(definitionHash, definition);
  22. }
  23. bool find_configured_item_detail(std::uint16_t definitionIndex,
  24. items::details::Definition& definition) noexcept {
  25. return items::details::find(definitionIndex, definition);
  26. }
  27. bool find_inventory_bucket_descriptor(std::uint8_t bucketId,
  28. inventory::buckets::Descriptor& descriptor) noexcept {
  29. return inventory::buckets::find(bucketId, descriptor);
  30. }
  31. bool find_socket_entry_list(std::uint16_t definitionIndex,
  32. socket_entry_lists::Definition& definition) noexcept {
  33. return socket_entry_lists::find(definitionIndex, definition);
  34. }
  35. bool find_socket_entry_table(std::uint16_t definitionIndex,
  36. socket_entry_lists::EntryTable& table) noexcept {
  37. return socket_entry_lists::find_entry_table(definitionIndex, table);
  38. }
  39. items::catalysts::ApplyResult
  40. complete_exotic_catalyst(std::uint16_t itemDefinitionIndex,
  41. std::uint32_t& flags,
  42. std::span<std::optional<std::uint16_t>> plugs) noexcept {
  43. if (forceCompletionFailure) {
  44. flags = 7;
  45. if (!plugs.empty()) {
  46. plugs.front() = std::uint16_t{4};
  47. plugs.back() = std::uint16_t{4};
  48. }
  49. return items::catalysts::ApplyResult::failed;
  50. }
  51. return items::catalysts::apply_completed(itemDefinitionIndex, flags, plugs);
  52. }
  53. bool find_investment_constants(constants::InvestmentConstants& value) noexcept {
  54. value = {.extracted = true, .lightStatRow = 1, .characterStatRows = {2, 3, 4, 5, 6, 7}};
  55. return true;
  56. }
  57. std::uint16_t resolve_exotic_catalyst_effect(std::uint16_t itemDefinitionIndex,
  58. std::uint8_t socketLane,
  59. std::uint16_t plugDefinitionIndex) noexcept {
  60. return items::catalysts::resolve_effect(itemDefinitionIndex, socketLane, plugDefinitionIndex);
  61. }
  62. } // namespace sunrise::state::build_data
  63. namespace {
  64. namespace appearance = sunrise::middleware::datagen::character_record::appearance;
  65. namespace buckets = sunrise::state::build_data::inventory::buckets;
  66. namespace catalysts = sunrise::state::build_data::items::catalysts;
  67. namespace character_layout = sunrise::middleware::datagen::character_record::layout;
  68. namespace details = sunrise::state::build_data::items::details;
  69. namespace items = sunrise::state::build_data::items;
  70. namespace loadout = sunrise::middleware::datagen::family4::loadout;
  71. namespace socket_lists = sunrise::state::build_data::socket_entry_lists;
  72. constexpr std::uint32_t kWeaponHash = 0x12345678U;
  73. constexpr std::uint32_t kDefaultPlugHash = 0x23456789U;
  74. constexpr std::uint32_t kProgressPlugHash = 0x3456789AU;
  75. constexpr std::uint32_t kCompletedPlugHash = 0x456789ABU;
  76. constexpr std::uint32_t kEffectHash = 0x56789ABCU;
  77. constexpr std::uint16_t kCatalystPerk = 77;
  78. constexpr std::uint8_t kCatalystStatRow = 24;
  79. constexpr std::int32_t kCatalystStatValue = 7;
  80. void expect(bool value, const char* label) noexcept {
  81. if (!value) {
  82. std::fprintf(stderr, "FAIL %s\n", label);
  83. ++failures;
  84. }
  85. }
  86. items::Definition
  87. item(std::uint16_t index, std::uint32_t hash, items::Tier tier = items::Tier::none) noexcept {
  88. items::Definition value{};
  89. value.definitionHash = hash;
  90. value.definitionIndex = index;
  91. value.tier = static_cast<std::uint8_t>(tier);
  92. return value;
  93. }
  94. void clear_catalogs() noexcept {
  95. forceCompletionFailure = false;
  96. catalysts::clear();
  97. socket_lists::clear();
  98. buckets::clear();
  99. details::clear();
  100. items::clear();
  101. }
  102. } // namespace
  103. void test_resolved_catalyst_output() noexcept {
  104. clear_catalogs();
  105. std::array<items::Definition, 5> itemRows{
  106. item(0, kWeaponHash, items::Tier::exotic),
  107. item(1, kDefaultPlugHash),
  108. item(2, kProgressPlugHash),
  109. item(3, kCompletedPlugHash),
  110. item(4, kEffectHash, items::Tier::exotic),
  111. };
  112. itemRows[0].bucketId = 0;
  113. details::Definition detail{};
  114. detail.definitionIndex = 0;
  115. detail.definitionHash = kWeaponHash;
  116. detail.bucketId = 0;
  117. detail.maxStackSize = 1;
  118. detail.instancedDefinitionState = details::InstancedDefinitionState::instanced;
  119. detail.equipmentSlot = std::int8_t{7};
  120. detail.ordinarySocketState = details::OrdinarySocketState::present;
  121. detail.ordinarySocketCount = 8;
  122. detail.initialPlugIndices[7] = 1;
  123. detail.socketEntryListIndex = 0;
  124. details::Definition effectDetail{};
  125. effectDetail.definitionIndex = 4;
  126. effectDetail.definitionHash = kEffectHash;
  127. effectDetail.bucketId = 0;
  128. effectDetail.maxStackSize = 1;
  129. effectDetail.instancedDefinitionState = details::InstancedDefinitionState::stackable;
  130. effectDetail.sandboxPerkCount = 1;
  131. effectDetail.sandboxPerks[0] = kCatalystPerk;
  132. effectDetail.statCount = 1;
  133. effectDetail.stats[0] = {kCatalystStatRow, kCatalystStatValue};
  134. const std::array detailRows{detail, effectDetail};
  135. const std::array bucketRows{
  136. buckets::Descriptor{0, buckets::ArraySelector::character, 0, 10, 7, 0},
  137. };
  138. const std::array socketListRows{
  139. socket_lists::Definition{0x12345678U, 0, 0, 0},
  140. };
  141. const std::array catalystRows{
  142. catalysts::Definition{kWeaponHash, 0, 3, 4, 7, catalysts::Availability::released},
  143. };
  144. const bool ready = items::replace(itemRows) && details::replace(detailRows)
  145. && buckets::replace(bucketRows) && socket_lists::replace(socketListRows)
  146. && catalysts::replace(catalystRows);
  147. expect(ready, "resolved-output fixture publishes");
  148. if (!ready) {
  149. clear_catalogs();
  150. return;
  151. }
  152. sunrise::state::account::inventory::Item authored{};
  153. authored.instanceSoid = 1;
  154. authored.definitionHash = kWeaponHash;
  155. authored.level = 50;
  156. authored.quantity = 1;
  157. authored.flags = 3;
  158. sunrise::state::CharacterState character{};
  159. loadout::Candidate output{};
  160. expect(
  161. loadout::resolve_item(authored, character, itemRows.size(), socketListRows.size(), output),
  162. "resolved client item accepts catalyst completion");
  163. expect(output.item.flags == 7, "resolved client item carries all item-state bits");
  164. expect(output.item.instance.ordinarySockets.plugs[7] == 3,
  165. "resolved client item carries the completed display plug");
  166. loadout::ResolvedInstances instances{};
  167. instances.itemCount = 1;
  168. instances.items[0].equipmentSlot = 7;
  169. instances.items[0].instance = output.item.instance;
  170. character_layout::Appearance appearanceRecord{};
  171. appearanceRecord.smallBankA.fill(character_layout::kEmptyDefinitionIndex);
  172. appearanceRecord.overflowHashes.fill(character_layout::kNoHash);
  173. appearance::apply_perk_banks(instances, appearanceRecord);
  174. expect(appearanceRecord.smallBankA[0] == kCatalystPerk,
  175. "display plug resolves the catalyst perk from its effect row");
  176. appearance::apply_overflow_hashes(instances, appearanceRecord);
  177. expect(appearanceRecord.overflowHashes[0] == kEffectHash,
  178. "display plug resolves the catalyst hash from its effect row");
  179. expect(appearance::apply_stats(instances, 50, appearanceRecord),
  180. "display plug builds catalyst stats");
  181. expect(appearanceRecord.weaponStats[0][0].key == static_cast<std::int8_t>(kCatalystStatRow)
  182. && appearanceRecord.weaponStats[0][0].value == kCatalystStatValue,
  183. "display plug resolves the catalyst stat from its effect row");
  184. forceCompletionFailure = true;
  185. loadout::Candidate failedOutput{};
  186. failedOutput.item.flags = 0xA5A5U;
  187. expect(loadout::resolve_item(
  188. authored, character, itemRows.size(), socketListRows.size(), failedOutput),
  189. "catalyst apply failure preserves the resolved base item");
  190. expect(failedOutput.item.flags == authored.flags,
  191. "catalyst apply failure does not leak changed flags");
  192. expect(failedOutput.item.instance.ordinarySockets.plugs[7] == 1
  193. && !failedOutput.item.instance.ordinarySockets.plugs[0].has_value(),
  194. "catalyst apply failure does not leak changed plugs");
  195. clear_catalogs();
  196. }