package_subclass_build.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. #include <array>
  2. #include "../../../../state/account/account_state.h"
  3. #include "../../../../state/build_data/runtime.h"
  4. #include "../../../../state/runtime/runtime.h"
  5. #include "internal.h"
  6. namespace sunrise::client::content::items::packages {
  7. namespace {
  8. namespace domain = state::build_data::abilities;
  9. /** The authored equipment slot that holds the subclass. */
  10. constexpr std::size_t kSubclassSlot =
  11. static_cast<std::size_t>(state::account::inventory::EquipmentSlot::subclass);
  12. /**
  13. * Finds the socket entry list that carries one character's subclass abilities.
  14. * @param character Authored character.
  15. * @param socketEntryListIndex Receives the subclass's socket-entry-list index.
  16. * @return True when the character equips a subclass whose detail is published.
  17. */
  18. [[nodiscard]] bool subclass_list(const state::CharacterState& character,
  19. std::uint16_t& socketEntryListIndex,
  20. const char*& reason) noexcept {
  21. const auto& slot = character.equipment.slots[kSubclassSlot];
  22. state::build_data::items::Definition item{};
  23. state::build_data::items::details::Definition detail{};
  24. reason = "subclass_slot";
  25. if (!slot.has_value()) {
  26. return false;
  27. }
  28. reason = "subclass_item";
  29. if (!state::build_data::find_item_definition_hash(slot->definitionHash, item)) {
  30. return false;
  31. }
  32. reason = "subclass_detail";
  33. if (!state::build_data::find_configured_item_detail(item.definitionIndex, detail)) {
  34. return false;
  35. }
  36. socketEntryListIndex = detail.socketEntryListIndex;
  37. return true;
  38. }
  39. /** @param rows Rows built so far. @return True when the candidate's key is already held. */
  40. [[nodiscard]] bool held(std::span<const domain::Definition> rows,
  41. const domain::Definition& row) noexcept {
  42. for (const domain::Definition& existing : rows) {
  43. if (existing.socketEntryListIndex == row.socketEntryListIndex
  44. && existing.selection == row.selection) {
  45. return true;
  46. }
  47. }
  48. return false;
  49. }
  50. /** @param character Authored character. @return Its 5 selected socket entries. */
  51. [[nodiscard]] domain::Selection selection_of(const state::CharacterState& character) noexcept {
  52. return {character.movementAbilityEntry,
  53. character.grenadeAbilityEntry,
  54. character.superAbilityEntry,
  55. character.meleeAbilityEntry,
  56. character.classAbilityEntry};
  57. }
  58. } // namespace
  59. /** Builds one ability bucket row per distinct subclass and ability selection in use. */
  60. bool build_character_abilities(const reader::Source& source,
  61. reader::Scratch& scratch,
  62. std::span<const std::byte> root,
  63. std::vector<std::byte>& table,
  64. std::vector<std::byte>& definition,
  65. std::vector<std::byte>& blob,
  66. std::span<state::build_data::abilities::Definition> output,
  67. std::size_t& count) noexcept {
  68. count = 0;
  69. std::uint32_t tableTag = 0;
  70. tables::Array rows{};
  71. if (!tables::slot_tag(root, tables::kSocketEntryListTableSlot, tableTag) || tableTag == 0) {
  72. report_ability_failure("table_slot", 0, root.size(), tableTag);
  73. return false;
  74. }
  75. if (!reader::read_tag(source, scratch, tableTag, table)) {
  76. report_ability_failure("table_read", 0, tableTag, 0);
  77. return false;
  78. }
  79. if (!tables::find_array_at(
  80. std::span<const std::byte>{table}, tables::kTableArrayDescriptor, rows)) {
  81. report_ability_failure("table_array", 0, table.size(), 0);
  82. return false;
  83. }
  84. const state::AccountState account = state::account_snapshot();
  85. for (std::size_t character = 0; character < account.characterCount && count < output.size();
  86. ++character) {
  87. domain::Definition row{};
  88. const char* subclassReason = "subclass";
  89. if (!subclass_list(
  90. account.characters[character], row.socketEntryListIndex, subclassReason)) {
  91. const auto& subclass = account.characters[character].equipment.slots[kSubclassSlot];
  92. report_ability_failure(
  93. subclassReason, character, subclass.has_value() ? subclass->definitionHash : 0, 0);
  94. continue;
  95. }
  96. // The selection is held in a local because the row it also keys is the build's output.
  97. const domain::Selection selection = selection_of(account.characters[character]);
  98. row.selection = selection;
  99. if (held(output.first(count), row)) {
  100. continue;
  101. }
  102. tables::IndexRow indexRow{};
  103. if (!tables::index_row(
  104. std::span<const std::byte>{table}, rows, row.socketEntryListIndex, indexRow)
  105. || indexRow.targetTag == 0) {
  106. report_ability_failure("index_row", character, row.socketEntryListIndex, rows.count);
  107. continue;
  108. }
  109. if (!reader::read_tag(source, scratch, indexRow.targetTag, definition)) {
  110. report_ability_failure(
  111. "definition_read", character, row.socketEntryListIndex, indexRow.targetTag);
  112. continue;
  113. }
  114. if (!build_ability_buckets(
  115. source, scratch, std::span<const std::byte>{definition}, blob, selection, row)) {
  116. const std::size_t packedSelection =
  117. selection.movementEntry | (selection.grenadeEntry << 8U)
  118. | (selection.superEntry << 16U) | (selection.meleeEntry << 24U);
  119. report_ability_failure(
  120. "bucket_build", character, row.socketEntryListIndex, packedSelection);
  121. continue;
  122. }
  123. output[count++] = row;
  124. }
  125. if (count == 0) {
  126. report_ability_failure("empty", account.characterCount, rows.count, output.size());
  127. }
  128. return true;
  129. }
  130. } // namespace sunrise::client::content::items::packages