configured_equipment_identity.cpp 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #include "configured_equipment_identity.h"
  2. #include <limits>
  3. #include <optional>
  4. namespace sunrise::state::runtime::equipment {
  5. namespace {
  6. /** FNV-1a's 64-bit offset basis gives the equipment fingerprint a stable nonzero start. */
  7. constexpr std::uint64_t kEquipmentHashOffsetBasis = 14695981039346656037ULL;
  8. /** FNV-1a's 64-bit prime mixes each ordered equipment byte without keeping source data. */
  9. constexpr std::uint64_t kEquipmentHashPrime = 1099511628211ULL;
  10. /** Marker 0 marks an empty semantic equipment slot. */
  11. constexpr std::uint8_t kAbsentItemMarker = 0;
  12. /** Marker 1 marks a present item, even when its authored definition hash is 0. */
  13. constexpr std::uint8_t kPresentItemMarker = 1;
  14. /**
  15. * Mixes one ordered byte into the settings-sensitive equipment fingerprint.
  16. * @param hash Mutable 64-bit FNV-1a accumulator.
  17. * @param value Next canonical byte.
  18. */
  19. void mix_byte(std::uint64_t& hash, std::uint8_t value) noexcept {
  20. hash ^= value;
  21. hash *= kEquipmentHashPrime;
  22. }
  23. /**
  24. * Mixes one 16-bit policy value in explicit least-significant-byte order.
  25. * @param hash Mutable 64-bit FNV-1a accumulator.
  26. * @param value Configured score selector.
  27. */
  28. void mix_value(std::uint64_t& hash, std::uint16_t value) noexcept {
  29. for (std::size_t byteIndex = 0; byteIndex < sizeof value; ++byteIndex) {
  30. const std::size_t shift = byteIndex * (std::numeric_limits<std::uint8_t>::digits);
  31. mix_byte(hash, static_cast<std::uint8_t>(value >> shift));
  32. }
  33. }
  34. /**
  35. * Mixes one 32-bit authored or policy value in explicit least-significant-byte order.
  36. * @param hash Mutable 64-bit FNV-1a accumulator.
  37. * @param value Authored base hash, authored level, or score-policy revision.
  38. */
  39. void mix_value(std::uint64_t& hash, std::uint32_t value) noexcept {
  40. for (std::size_t byteIndex = 0; byteIndex < sizeof value; ++byteIndex) {
  41. const std::size_t shift = byteIndex * (std::numeric_limits<std::uint8_t>::digits);
  42. mix_byte(hash, static_cast<std::uint8_t>(value >> shift));
  43. }
  44. }
  45. /**
  46. * Mixes one item's socket policy and every authored plug lane.
  47. * The extraction pass reads a detail row for each authored plug, so a changed plug must rebuild.
  48. * @param hash Mutable 64-bit FNV-1a accumulator.
  49. * @param sockets Authored socket policy and lanes.
  50. */
  51. void mix_sockets(std::uint64_t& hash, const account::inventory::Sockets& sockets) noexcept {
  52. mix_byte(hash, static_cast<std::uint8_t>(sockets.policy));
  53. mix_byte(hash, static_cast<std::uint8_t>(sockets.plugCount));
  54. for (std::size_t lane = 0; lane < sockets.plugCount && lane < sockets.plugs.size(); ++lane) {
  55. if (!sockets.plugs[lane].has_value()) {
  56. mix_byte(hash, kAbsentItemMarker);
  57. continue;
  58. }
  59. mix_byte(hash, kPresentItemMarker);
  60. mix_value(hash, *sockets.plugs[lane]);
  61. }
  62. }
  63. /** Mixes the installed-detail inputs shared by equipped and unequipped authored items. */
  64. void mix_item(std::uint64_t& hash, const account::inventory::Item& item) noexcept {
  65. // SOIDs, quantity, gates and secrets stay outside build identity.
  66. mix_value(hash, item.definitionHash);
  67. mix_value(hash, static_cast<std::uint32_t>(item.level));
  68. mix_sockets(hash, item.sockets);
  69. // Meaningful only for a subclass, but mixed for every item: the ability bucket rows are keyed
  70. // by these, so a changed pick must rebuild, and they live on the item now (each owned
  71. // subclass remembers its own picks independently rather than sharing one set).
  72. mix_byte(hash, item.movementAbilityEntry);
  73. mix_byte(hash, item.grenadeAbilityEntry);
  74. mix_byte(hash, item.superAbilityEntry);
  75. mix_byte(hash, item.meleeAbilityEntry);
  76. mix_byte(hash, item.classAbilityEntry);
  77. }
  78. } // namespace
  79. /** Builds a nonsecret cache identity from ordered authored equipment. */
  80. std::uint64_t configured_hash(const AccountState& accountState) noexcept {
  81. std::uint64_t hash = kEquipmentHashOffsetBasis;
  82. mix_byte(hash, static_cast<std::uint8_t>(accountState.characterCount));
  83. for (std::size_t characterIndex = 0; characterIndex < accountState.characterCount;
  84. ++characterIndex) {
  85. const CharacterState& character = accountState.characters[characterIndex];
  86. for (const std::optional<account::inventory::Item>& item : character.equipment.slots) {
  87. if (!item.has_value()) {
  88. mix_byte(hash, kAbsentItemMarker);
  89. continue;
  90. }
  91. mix_byte(hash, kPresentItemMarker);
  92. mix_item(hash, *item);
  93. }
  94. static_assert(account::inventory::kCharacterItemCapacity
  95. <= (std::numeric_limits<std::uint8_t>::max)());
  96. mix_byte(hash, static_cast<std::uint8_t>(character.inventory.count));
  97. for (std::size_t itemIndex = 0; itemIndex < character.inventory.count; ++itemIndex) {
  98. mix_item(hash, character.inventory.values[itemIndex]);
  99. }
  100. }
  101. return hash;
  102. }
  103. } // namespace sunrise::state::runtime::equipment