exotic_catalyst_builder.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. #include "exotic_catalyst_builder.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <functional>
  5. #include <optional>
  6. namespace sunrise::state::build_data::items::catalysts {
  7. namespace {
  8. /** Native equipment slots 7, 8, and 9 are kinetic, energy, and heavy weapons. */
  9. constexpr std::int8_t kFirstWeaponSlot = 7;
  10. constexpr std::int8_t kLastWeaponSlot = 9;
  11. struct LaneResult {
  12. bool catalyst{};
  13. Error error{Error::none};
  14. std::uint16_t completedPlugDefinitionIndex{details::kUnavailableItemIndex};
  15. std::uint16_t effectDefinitionIndex{details::kUnavailableItemIndex};
  16. std::uint16_t acquisitionDefinitionIndex{kUnavailableAcquisitionIndex};
  17. std::uint16_t completionValueIndex{kUnavailableCompletionValueIndex};
  18. std::int32_t completionValue{};
  19. };
  20. /**
  21. * @param hashes Sorted hashes to validate.
  22. * @return True when the table is strict and contains no zero hash.
  23. */
  24. [[nodiscard]] bool valid_hashes(std::span<const std::uint32_t> hashes) noexcept {
  25. return std::none_of(
  26. hashes.begin(), hashes.end(), [](std::uint32_t hash) { return hash == 0; })
  27. && std::adjacent_find(hashes.begin(), hashes.end(), std::greater_equal{})
  28. == hashes.end();
  29. }
  30. /**
  31. * Finds a source item without requiring a dense test fixture.
  32. * @param definitions Source item definitions in native index order.
  33. * @param index Native item index to find.
  34. * @return The matching item, or null when no item has the index.
  35. */
  36. [[nodiscard]] const items::Definition*
  37. find_item(std::span<const items::Definition> definitions, std::uint16_t index) noexcept {
  38. if (static_cast<std::size_t>(index) < definitions.size()
  39. && definitions[index].definitionIndex == index) {
  40. return &definitions[index];
  41. }
  42. const auto found = std::lower_bound(
  43. definitions.begin(),
  44. definitions.end(),
  45. index,
  46. [](const items::Definition& value, auto key) { return value.definitionIndex < key; });
  47. return found != definitions.end() && found->definitionIndex == index ? &*found : nullptr;
  48. }
  49. /**
  50. * Finds a source detail without requiring a dense test fixture.
  51. * @param definitions Source item details in native index order.
  52. * @param index Native item index to find.
  53. * @return The matching detail, or null when no detail has the index.
  54. */
  55. [[nodiscard]] const details::Definition*
  56. find_detail(std::span<const details::Definition> definitions, std::uint16_t index) noexcept {
  57. if (static_cast<std::size_t>(index) < definitions.size()
  58. && definitions[index].definitionIndex == index) {
  59. return &definitions[index];
  60. }
  61. const auto found = std::lower_bound(
  62. definitions.begin(),
  63. definitions.end(),
  64. index,
  65. [](const details::Definition& value, auto key) { return value.definitionIndex < key; });
  66. return found != definitions.end() && found->definitionIndex == index ? &*found : nullptr;
  67. }
  68. /** Finds one dense or sorted completion-condition row. */
  69. [[nodiscard]] const CompletionCondition*
  70. find_completion_condition(std::span<const CompletionCondition> definitions,
  71. std::uint16_t index) noexcept {
  72. if (static_cast<std::size_t>(index) < definitions.size()
  73. && definitions[index].itemDefinitionIndex == index) {
  74. return &definitions[index];
  75. }
  76. const auto found = std::lower_bound(
  77. definitions.begin(),
  78. definitions.end(),
  79. index,
  80. [](const CompletionCondition& value, auto key) {
  81. return value.itemDefinitionIndex < key;
  82. });
  83. return found != definitions.end() && found->itemDefinitionIndex == index ? &*found : nullptr;
  84. }
  85. /** Finds one dense or sorted socket-type acquisition row. */
  86. [[nodiscard]] const AcquisitionGate*
  87. find_acquisition_gate(std::span<const AcquisitionGate> definitions,
  88. std::uint16_t socketType) noexcept {
  89. if (static_cast<std::size_t>(socketType) < definitions.size()
  90. && definitions[socketType].socketType == socketType) {
  91. return &definitions[socketType];
  92. }
  93. const auto found = std::lower_bound(
  94. definitions.begin(),
  95. definitions.end(),
  96. socketType,
  97. [](const AcquisitionGate& value, auto key) { return value.socketType < key; });
  98. return found != definitions.end() && found->socketType == socketType ? &*found : nullptr;
  99. }
  100. /**
  101. * Resolves the exotic item row that owns one completed plug's native perks and stat changes.
  102. * Later catalysts use that item as their socket plug. Legacy sockets use a display-only plug in
  103. * the same category, so the unique exotic stackable item in that category supplies the effect.
  104. * @param source Parsed target-build tables.
  105. * @param completedPlugDefinitionIndex Completed display or active plug from the socket pool.
  106. * @param socketType Native socket type that owns the acquired-state gate.
  107. * @return Completed lane relation, or an effect mapping error.
  108. */
  109. [[nodiscard]] LaneResult complete_lane(const Source& source,
  110. std::uint16_t completedPlugDefinitionIndex,
  111. std::uint16_t socketType) noexcept {
  112. const items::Definition* completed = find_item(source.items, completedPlugDefinitionIndex);
  113. if (completed == nullptr || completed->plugCategoryHash == 0) {
  114. return {.catalyst = true,
  115. .error = Error::invalidEffect,
  116. .completedPlugDefinitionIndex = completedPlugDefinitionIndex};
  117. }
  118. const AcquisitionGate* acquisition =
  119. find_acquisition_gate(source.acquisitionGates, socketType);
  120. if (acquisition == nullptr || acquisition->state != AcquisitionState::present
  121. || acquisition->definitionIndex == kUnavailableAcquisitionIndex) {
  122. return {.catalyst = true,
  123. .error = Error::invalidAcquisition,
  124. .completedPlugDefinitionIndex = completedPlugDefinitionIndex};
  125. }
  126. std::optional<std::uint16_t> effect;
  127. for (const items::Definition& candidate : source.items) {
  128. if (candidate.plugCategoryHash != completed->plugCategoryHash
  129. || candidate.tier != static_cast<std::uint8_t>(items::Tier::exotic)) {
  130. continue;
  131. }
  132. const details::Definition* detail = find_detail(source.details, candidate.definitionIndex);
  133. if (detail == nullptr || detail->definitionHash != candidate.definitionHash
  134. || detail->instancedDefinitionState
  135. != details::InstancedDefinitionState::stackable
  136. || (detail->sandboxPerkCount == 0 && detail->statCount == 0)) {
  137. continue;
  138. }
  139. if (effect.has_value()) {
  140. return {.catalyst = true,
  141. .error = Error::invalidEffect,
  142. .completedPlugDefinitionIndex = completedPlugDefinitionIndex};
  143. }
  144. effect = candidate.definitionIndex;
  145. }
  146. if (!effect.has_value()) {
  147. return {.catalyst = true,
  148. .error = Error::invalidEffect,
  149. .completedPlugDefinitionIndex = completedPlugDefinitionIndex};
  150. }
  151. std::uint16_t completionValueIndex = kUnavailableCompletionValueIndex;
  152. std::int32_t completionValue = 0;
  153. if (*effect != completedPlugDefinitionIndex) {
  154. const CompletionCondition* condition =
  155. find_completion_condition(source.completionConditions, *effect);
  156. if (condition == nullptr || condition->state != CompletionConditionState::present
  157. || condition->valueIndex == kUnavailableCompletionValueIndex
  158. || condition->value <= 0) {
  159. return {.catalyst = true,
  160. .error = Error::invalidCompletion,
  161. .completedPlugDefinitionIndex = completedPlugDefinitionIndex,
  162. .effectDefinitionIndex = *effect,
  163. .acquisitionDefinitionIndex = acquisition->definitionIndex};
  164. }
  165. completionValueIndex = condition->valueIndex;
  166. completionValue = condition->value;
  167. }
  168. return {.catalyst = true,
  169. .error = Error::none,
  170. .completedPlugDefinitionIndex = completedPlugDefinitionIndex,
  171. .effectDefinitionIndex = *effect,
  172. .acquisitionDefinitionIndex = acquisition->definitionIndex,
  173. .completionValueIndex = completionValueIndex,
  174. .completionValue = completionValue};
  175. }
  176. /**
  177. * Finds one exact item and lane rule in its canonical order.
  178. * @param rules Socket rules in item and lane order.
  179. * @param itemDefinitionIndex Native item index to find.
  180. * @param lane Native socket lane to find.
  181. * @return The matching rule, or null when no rule matches.
  182. */
  183. [[nodiscard]] const socket_plugs::Rule*
  184. find_rule(std::span<const socket_plugs::Rule> rules,
  185. std::uint16_t itemDefinitionIndex,
  186. std::uint8_t lane) noexcept {
  187. const auto found = std::lower_bound(
  188. rules.begin(),
  189. rules.end(),
  190. std::pair{itemDefinitionIndex, lane},
  191. [](const socket_plugs::Rule& value, const auto& key) {
  192. return value.itemDefinitionIndex < key.first
  193. || (value.itemDefinitionIndex == key.first && value.lane < key.second);
  194. });
  195. return found != rules.end() && found->itemDefinitionIndex == itemDefinitionIndex
  196. && found->lane == lane
  197. ? &*found
  198. : nullptr;
  199. }
  200. /**
  201. * @param released Sorted released weapon hashes.
  202. * @param hash Weapon hash to find.
  203. * @return Index of a released hash, or no value for a placeholder.
  204. */
  205. [[nodiscard]] std::optional<std::size_t>
  206. released_index(std::span<const std::uint32_t> released, std::uint32_t hash) noexcept {
  207. const auto found = std::lower_bound(released.begin(), released.end(), hash);
  208. if (found == released.end() || *found != hash) {
  209. return std::nullopt;
  210. }
  211. return static_cast<std::size_t>(found - released.begin());
  212. }
  213. /**
  214. * Classifies one lane when its pool has a target-build catalyst marker.
  215. * @param source Parsed target-build tables.
  216. * @param facts Pinned facts for the target build.
  217. * @param detail Item detail row that owns the lane.
  218. * @param lane Native socket lane to classify.
  219. * @return The catalyst state and any safe-failure reason.
  220. */
  221. [[nodiscard]] LaneResult classify_lane(const Source& source,
  222. const Facts& facts,
  223. const details::Definition& detail,
  224. std::uint8_t lane) noexcept {
  225. const socket_plugs::Rule* rule =
  226. find_rule(source.socketPlugRules, detail.definitionIndex, lane);
  227. if (rule == nullptr) {
  228. return {};
  229. }
  230. if (rule->reserved != 0 || rule->poolIndex >= source.socketPlugPools.size()) {
  231. return {.catalyst = true, .error = Error::invalidSocket};
  232. }
  233. const socket_plugs::Pool& pool = source.socketPlugPools[rule->poolIndex];
  234. if (pool.memberOffset > source.socketPlugMembers.size()
  235. || pool.memberCount > source.socketPlugMembers.size() - pool.memberOffset) {
  236. return {.catalyst = true, .error = Error::invalidSocket};
  237. }
  238. const auto members = source.socketPlugMembers.subspan(pool.memberOffset, pool.memberCount);
  239. bool hasEmpty = false;
  240. std::size_t legacyCount = 0;
  241. std::uint16_t legacyCompletionIndex = 0;
  242. bool invalidMember = false;
  243. for (const std::uint16_t member : members) {
  244. const items::Definition* plug = find_item(source.items, member);
  245. if (plug == nullptr) {
  246. invalidMember = true;
  247. continue;
  248. }
  249. if (plug->definitionHash == facts.emptyCatalystPlugHash) {
  250. hasEmpty = true;
  251. }
  252. if (std::binary_search(facts.legacyCompletionPlugHashes.begin(),
  253. facts.legacyCompletionPlugHashes.end(),
  254. plug->definitionHash)) {
  255. ++legacyCount;
  256. legacyCompletionIndex = member;
  257. }
  258. }
  259. if (!hasEmpty && legacyCount == 0) {
  260. return {};
  261. }
  262. if (invalidMember) {
  263. return {.catalyst = true, .error = Error::invalidPlug};
  264. }
  265. const std::uint16_t defaultIndex = detail.initialPlugIndices[lane];
  266. const items::Definition* defaultPlug = find_item(source.items, defaultIndex);
  267. if (defaultIndex == details::kUnavailableItemIndex || defaultPlug == nullptr
  268. || std::find(members.begin(), members.end(), defaultIndex) == members.end()) {
  269. return {.catalyst = true, .error = Error::invalidPlug};
  270. }
  271. if (members.size() == 2 && hasEmpty && legacyCount == 0
  272. && defaultPlug->definitionHash == facts.emptyCatalystPlugHash) {
  273. const auto active =
  274. std::find_if(members.begin(), members.end(), [defaultIndex](auto member) {
  275. return member != defaultIndex;
  276. });
  277. return active != members.end()
  278. ? complete_lane(source, *active, detail.socketTypes[lane])
  279. : LaneResult{.catalyst = true, .error = Error::ambiguousLifecycle};
  280. }
  281. if (members.size() == 3 && !hasEmpty && legacyCount == 1
  282. && legacyCompletionIndex != defaultIndex) {
  283. return complete_lane(source, legacyCompletionIndex, detail.socketTypes[lane]);
  284. }
  285. return {.catalyst = true, .error = Error::ambiguousLifecycle};
  286. }
  287. /**
  288. * Clears staged output and records one safe failure.
  289. * @param output Staged catalog rows to clear.
  290. * @param count Staged row count to reset.
  291. * @param report Build report to update.
  292. * @param error Failure reason.
  293. * @param itemDefinitionHash Item hash that caused the failure, if known.
  294. * @param lane Socket lane that caused the failure, if known.
  295. * @return Always false.
  296. */
  297. [[nodiscard]] bool fail(std::span<Definition> output,
  298. std::size_t& count,
  299. Report& report,
  300. Error error,
  301. std::uint32_t itemDefinitionHash = 0,
  302. std::uint8_t lane = 0) noexcept {
  303. std::fill(output.begin(), output.end(), Definition{});
  304. count = 0;
  305. ++report.unsupported;
  306. report.error = error;
  307. report.itemDefinitionHash = itemDefinitionHash;
  308. report.socketLane = lane;
  309. return false;
  310. }
  311. /**
  312. * @param item Source item row.
  313. * @param detail Source item detail row.
  314. * @return True for an exotic in one of the three weapon equipment slots.
  315. */
  316. [[nodiscard]] bool exotic_weapon(const items::Definition& item,
  317. const details::Definition& detail) noexcept {
  318. return item.tier == static_cast<std::uint8_t>(items::Tier::exotic)
  319. && detail.instancedDefinitionState == details::InstancedDefinitionState::instanced
  320. && detail.equipmentSlot.has_value() && *detail.equipmentSlot >= kFirstWeaponSlot
  321. && *detail.equipmentSlot <= kLastWeaponSlot
  322. && detail.ordinarySocketState == details::OrdinarySocketState::present
  323. && detail.ordinarySocketCount <= details::kInitialPlugCapacity;
  324. }
  325. } // namespace
  326. bool derive(const Source& source,
  327. const Facts& facts,
  328. std::span<Definition> output,
  329. std::size_t& count,
  330. Report& report) noexcept {
  331. count = 0;
  332. report = {};
  333. std::fill(output.begin(), output.end(), Definition{});
  334. if (facts.imageTimestamp == 0 || facts.imageSize == 0 || facts.emptyCatalystPlugHash == 0
  335. || facts.legacyCompletionPlugHashes.size() > kDefinitionCapacity
  336. || facts.releasedWeaponHashes.size() > kDefinitionCapacity
  337. || !valid_hashes(facts.legacyCompletionPlugHashes)
  338. || !valid_hashes(facts.releasedWeaponHashes)) {
  339. return fail(output, count, report, Error::unsupportedBuild);
  340. }
  341. if (source.build.imageTimestamp != facts.imageTimestamp
  342. || source.build.imageSize != facts.imageSize) {
  343. return fail(output, count, report, Error::unsupportedBuild);
  344. }
  345. std::array<bool, kDefinitionCapacity> releasedFound{};
  346. for (const details::Definition& detail : source.details) {
  347. const items::Definition* item = find_item(source.items, detail.definitionIndex);
  348. if (item == nullptr || item->definitionHash != detail.definitionHash
  349. || !exotic_weapon(*item, detail)) {
  350. continue;
  351. }
  352. std::optional<std::size_t> release =
  353. released_index(facts.releasedWeaponHashes, item->definitionHash);
  354. std::optional<CompletedCatalyst> completed;
  355. std::optional<Error> unclear;
  356. std::uint8_t detectedLane = 0;
  357. for (std::size_t laneIndex = 0; laneIndex < detail.ordinarySocketCount; ++laneIndex) {
  358. const auto lane = static_cast<std::uint8_t>(laneIndex);
  359. const LaneResult result = classify_lane(source, facts, detail, lane);
  360. if (!result.catalyst) {
  361. continue;
  362. }
  363. detectedLane = lane;
  364. if (result.error != Error::none) {
  365. unclear = result.error;
  366. continue;
  367. }
  368. if (completed.has_value()) {
  369. unclear = Error::ambiguousLifecycle;
  370. continue;
  371. }
  372. completed = CompletedCatalyst{lane,
  373. result.completedPlugDefinitionIndex,
  374. result.effectDefinitionIndex,
  375. result.acquisitionDefinitionIndex,
  376. result.completionValueIndex,
  377. result.completionValue};
  378. }
  379. if (!completed.has_value() && !unclear.has_value()) {
  380. continue;
  381. }
  382. if (unclear.has_value()) {
  383. if (release.has_value()) {
  384. return fail(output,
  385. count,
  386. report,
  387. *unclear,
  388. item->definitionHash,
  389. detectedLane);
  390. }
  391. if (count >= output.size() || count >= kDefinitionCapacity) {
  392. return fail(output,
  393. count,
  394. report,
  395. Error::invalidSocket,
  396. item->definitionHash,
  397. detectedLane);
  398. }
  399. output[count++] = Definition{item->definitionHash,
  400. item->definitionIndex,
  401. details::kUnavailableItemIndex,
  402. details::kUnavailableItemIndex,
  403. kUnavailableAcquisitionIndex,
  404. kUnavailableCompletionValueIndex,
  405. detectedLane,
  406. Availability::unsupported,
  407. 0};
  408. ++report.unsupported;
  409. continue;
  410. }
  411. if (count >= output.size() || count >= kDefinitionCapacity) {
  412. return fail(output,
  413. count,
  414. report,
  415. Error::invalidSocket,
  416. item->definitionHash,
  417. completed->socketLane);
  418. }
  419. if (release.has_value()) {
  420. if (releasedFound[*release]) {
  421. return fail(output,
  422. count,
  423. report,
  424. Error::ambiguousLifecycle,
  425. item->definitionHash,
  426. completed->socketLane);
  427. }
  428. releasedFound[*release] = true;
  429. ++report.released;
  430. } else {
  431. ++report.placeholder;
  432. }
  433. output[count++] = Definition{item->definitionHash,
  434. item->definitionIndex,
  435. completed->completedPlugDefinitionIndex,
  436. completed->effectDefinitionIndex,
  437. completed->acquisitionDefinitionIndex,
  438. completed->completionValueIndex,
  439. completed->socketLane,
  440. release.has_value() ? Availability::released
  441. : Availability::placeholder,
  442. completed->completionValue};
  443. }
  444. for (std::size_t index = 0; index < facts.releasedWeaponHashes.size(); ++index) {
  445. if (!releasedFound[index]) {
  446. return fail(output,
  447. count,
  448. report,
  449. Error::missingReleased,
  450. facts.releasedWeaponHashes[index]);
  451. }
  452. }
  453. std::sort(output.begin(), output.begin() + count, [](const Definition& left,
  454. const Definition& right) {
  455. return left.itemDefinitionIndex < right.itemDefinitionIndex;
  456. });
  457. return true;
  458. }
  459. bool matches_derived(const Source& source,
  460. const Facts& facts,
  461. std::span<const Definition> definitions) noexcept {
  462. std::array<Definition, kDefinitionCapacity> expected{};
  463. std::size_t expectedCount = 0;
  464. Report report{};
  465. if (!derive(source, facts, expected, expectedCount, report)
  466. || expectedCount != definitions.size()) {
  467. return false;
  468. }
  469. return std::equal(
  470. expected.begin(),
  471. expected.begin() + expectedCount,
  472. definitions.begin(),
  473. [](const Definition& left, const Definition& right) {
  474. return left.itemDefinitionHash == right.itemDefinitionHash
  475. && left.itemDefinitionIndex == right.itemDefinitionIndex
  476. && left.completedPlugDefinitionIndex == right.completedPlugDefinitionIndex
  477. && left.effectDefinitionIndex == right.effectDefinitionIndex
  478. && left.acquisitionDefinitionIndex == right.acquisitionDefinitionIndex
  479. && left.completionValueIndex == right.completionValueIndex
  480. && left.socketLane == right.socketLane
  481. && left.availability == right.availability
  482. && left.completionValue == right.completionValue;
  483. });
  484. }
  485. bool matches_cached(const Source& source,
  486. const Facts& facts,
  487. std::span<const Definition> definitions) noexcept {
  488. std::array<CompletionCondition, kDefinitionCapacity> completionConditions{};
  489. std::array<AcquisitionGate, kDefinitionCapacity> acquisitionGates{};
  490. std::size_t completionCount = 0;
  491. std::size_t acquisitionCount = 0;
  492. for (const Definition& definition : definitions) {
  493. if (definition.availability == Availability::unsupported) {
  494. continue;
  495. }
  496. const details::Definition* detail = find_detail(source.details,
  497. definition.itemDefinitionIndex);
  498. if (detail == nullptr || definition.socketLane >= detail->ordinarySocketCount
  499. || find_item(source.items, definition.acquisitionDefinitionIndex) == nullptr) {
  500. return false;
  501. }
  502. const std::uint16_t socketType = detail->socketTypes[definition.socketLane];
  503. const auto priorGate = std::find_if(
  504. acquisitionGates.begin(),
  505. acquisitionGates.begin() + acquisitionCount,
  506. [socketType](const AcquisitionGate& gate) { return gate.socketType == socketType; });
  507. if (priorGate != acquisitionGates.begin() + acquisitionCount) {
  508. if (priorGate->definitionIndex != definition.acquisitionDefinitionIndex) {
  509. return false;
  510. }
  511. } else if (acquisitionCount >= acquisitionGates.size()) {
  512. return false;
  513. } else {
  514. acquisitionGates[acquisitionCount++] = {
  515. socketType, definition.acquisitionDefinitionIndex, AcquisitionState::present};
  516. }
  517. if (definition.completionValueIndex == kUnavailableCompletionValueIndex) {
  518. continue;
  519. }
  520. const auto priorCondition = std::find_if(
  521. completionConditions.begin(),
  522. completionConditions.begin() + completionCount,
  523. [&definition](const CompletionCondition& condition) {
  524. return condition.itemDefinitionIndex == definition.effectDefinitionIndex;
  525. });
  526. if (priorCondition != completionConditions.begin() + completionCount) {
  527. if (priorCondition->valueIndex != definition.completionValueIndex
  528. || priorCondition->value != definition.completionValue) {
  529. return false;
  530. }
  531. } else if (completionCount >= completionConditions.size()) {
  532. return false;
  533. } else {
  534. completionConditions[completionCount++] = {
  535. definition.effectDefinitionIndex,
  536. definition.completionValueIndex,
  537. definition.completionValue,
  538. CompletionConditionState::present};
  539. }
  540. }
  541. std::sort(completionConditions.begin(),
  542. completionConditions.begin() + completionCount,
  543. [](const CompletionCondition& left, const CompletionCondition& right) {
  544. return left.itemDefinitionIndex < right.itemDefinitionIndex;
  545. });
  546. std::sort(acquisitionGates.begin(),
  547. acquisitionGates.begin() + acquisitionCount,
  548. [](const AcquisitionGate& left, const AcquisitionGate& right) {
  549. return left.socketType < right.socketType;
  550. });
  551. Source rebuilt = source;
  552. rebuilt.completionConditions = std::span(completionConditions).first(completionCount);
  553. rebuilt.acquisitionGates = std::span(acquisitionGates).first(acquisitionCount);
  554. return matches_derived(rebuilt, facts, definitions);
  555. }
  556. } // namespace sunrise::state::build_data::items::catalysts