| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- #include <algorithm>
- #include <array>
- #include <cstring>
- #include <optional>
- #include <utility>
- #include "../../../../state/account/account_state.h"
- #include "../../../../state/runtime/runtime.h"
- #include "internal.h"
- namespace sunrise::client::content::items::packages {
- namespace {
- namespace domain = state::build_data::items::details;
- /**
- * Fallback equipment slot for old definitions whose optional equipment block is absent.
- * The
- * installed definition's own slot wins when present; unlike a bucket, it can distinguish
- *
- * multiple native equipment positions that share one inventory bucket.
- */
- constexpr std::array<std::pair<std::uint8_t, std::int8_t>, 16> kEquipmentSlotOfBucket{{
- {16, 0},
- {3, 1},
- {4, 2},
- {5, 4},
- {6, 5},
- {7, 6},
- {0, 7},
- {1, 8},
- {2, 9},
- {10, 10},
- {9, 11},
- {8, 12},
- {27, 13},
- {41, 14},
- {17, 15},
- {47, 17},
- }};
- /** @param bucketId Inventory bucket. @return Its equipment slot, or none when not equippable. */
- [[nodiscard]] std::optional<std::int8_t> equipment_slot(std::uint8_t bucketId) noexcept {
- for (const auto& entry : kEquipmentSlotOfBucket) {
- if (entry.first == bucketId) {
- return entry.second;
- }
- }
- return std::nullopt;
- }
- /** @param row Package row. @return Its cached detail form. */
- [[nodiscard]] domain::Definition to_detail(const tables::items::Row& row) noexcept {
- domain::Definition detail{};
- detail.definitionIndex = row.definitionIndex;
- detail.definitionHash = row.definitionHash;
- detail.bucketId = row.bucketId;
- detail.maxStackSize = row.maxStackSize;
- detail.instancedDefinitionState = row.instanced ? domain::InstancedDefinitionState::instanced
- : domain::InstancedDefinitionState::stackable;
- detail.equipmentSlot =
- row.equipmentSlot.has_value() ? row.equipmentSlot : equipment_slot(row.bucketId);
- detail.ordinarySocketState =
- row.hasSockets ? domain::OrdinarySocketState::present : domain::OrdinarySocketState::absent;
- detail.ordinarySocketCount = row.socketCount;
- for (std::size_t lane = 0; lane < detail.initialPlugIndices.size(); ++lane) {
- detail.initialPlugIndices[lane] = row.initialPlugs[lane];
- detail.socketTypes[lane] = row.socketTypes[lane];
- }
- detail.socketEntryListIndex = row.socketEntryListIndex;
- // Every field below comes from the package blob only; the loaded definition is never read.
- const std::size_t stats =
- row.statCount < detail.stats.size() ? row.statCount : detail.stats.size();
- for (std::size_t entry = 0; entry < stats; ++entry) {
- detail.stats[entry] = {row.statRows[entry], row.statValues[entry]};
- }
- detail.statCount = static_cast<std::uint8_t>(stats);
- detail.gearArtIndex = row.gearArtIndex;
- detail.artArrangementIndex = row.artArrangementIndex;
- const std::size_t perks = row.sandboxPerkCount < detail.sandboxPerks.size()
- ? row.sandboxPerkCount
- : detail.sandboxPerks.size();
- for (std::size_t entry = 0; entry < perks; ++entry) {
- detail.sandboxPerks[entry] = row.sandboxPerks[entry];
- }
- detail.sandboxPerkCount = static_cast<std::uint8_t>(perks);
- const std::size_t overrides = row.renderOverrideCount < detail.renderOverrides.size()
- ? row.renderOverrideCount
- : detail.renderOverrides.size();
- for (std::size_t entry = 0; entry < overrides; ++entry) {
- detail.renderOverrides[entry] = {row.renderOverrides[entry].stage,
- row.renderOverrides[entry].key,
- row.renderOverrides[entry].value};
- }
- detail.renderOverrideCount = static_cast<std::uint8_t>(overrides);
- return detail;
- }
- /** The constants blob's own 8-byte prefix comes before every offset the client quotes. */
- constexpr std::size_t kConstantsPrefix = 8;
- /** Client offset of the stat row the banner's power number is searched by. */
- constexpr std::size_t kLightStatRowOffset = 592;
- /**
- * Client offsets of the 6 character stat rows, in the two runs the blob stores them in.
- * The client reads these as 6 separate scalars, not as one array, so each is named here.
- */
- constexpr std::size_t kCharacterStatRowOffsets[]{593, 594, 595, 622, 623, 624};
- } // namespace
- /** Collects the authored equipment and plug hashes every configured character names. */
- bool collect_authored_hashes(AuthoredHashes& output) noexcept {
- output = {};
- const state::AccountState account = state::account_snapshot();
- if (!state::account::valid(account)) {
- return false;
- }
- const auto append = [&output](const state::account::inventory::Item& item) noexcept {
- if (output.count >= output.values.size()) {
- return false;
- }
- output.values[output.count++] = item.definitionHash;
- for (std::size_t lane = 0; lane < item.sockets.plugCount; ++lane) {
- if (!item.sockets.plugs[lane].has_value()) {
- continue;
- }
- if (output.count >= output.values.size()) {
- return false;
- }
- output.values[output.count++] = *item.sockets.plugs[lane];
- }
- return true;
- };
- for (std::size_t character = 0; character < account.characterCount; ++character) {
- for (const auto& item : account.characters[character].equipment.slots) {
- if (!item.has_value()) {
- continue;
- }
- if (!append(*item)) {
- return false;
- }
- }
- const state::account::inventory::CharacterItems& inventory =
- account.characters[character].inventory;
- for (std::size_t item = 0; item < inventory.count; ++item) {
- if (!append(inventory.values[item])) {
- return false;
- }
- }
- }
- const auto end = output.values.begin() + static_cast<std::ptrdiff_t>(output.count);
- std::sort(output.values.begin(), end);
- output.count =
- static_cast<std::size_t>(std::unique(output.values.begin(), end) - output.values.begin());
- return output.count != 0;
- }
- /** @param hashes Sorted authored hashes. @param hash Row hash. @return True when authored. */
- bool authored(const AuthoredHashes& hashes, std::uint32_t hash) noexcept {
- const auto begin = hashes.values.begin();
- const auto end = begin + static_cast<std::ptrdiff_t>(hashes.count);
- return std::binary_search(begin, end, hash);
- }
- /** @return True when the row's bucket maps to a supported equipment slot. */
- bool equippable(const tables::items::Row& row) noexcept {
- return equipment_slot(row.bucketId).has_value();
- }
- /** Adds one definition index to the deduplicated requested set. */
- void request(std::uint16_t definitionIndex, DetailRequests& requested) noexcept {
- if (static_cast<std::size_t>(definitionIndex) < requested.size()) {
- requested.set(definitionIndex);
- }
- }
- /** Adds every socket lane's initial plug to the requested set. */
- void append_initial_plugs(const tables::items::Row& row,
- std::uint64_t itemDefinitionCount,
- DetailRequests& requested) noexcept {
- for (std::size_t lane = 0; lane < row.socketCount; ++lane) {
- if (row.initialPlugs[lane] == tables::items::kUnavailablePlug
- || row.initialPlugs[lane] >= itemDefinitionCount) {
- continue;
- }
- request(row.initialPlugs[lane], requested);
- }
- }
- /** Materializes requested native indices in ascending order. */
- bool materialize_requests(const DetailRequests& requested,
- std::span<std::uint16_t> output,
- std::size_t& count) noexcept {
- count = 0;
- for (std::size_t index = 0; index < requested.size(); ++index) {
- if (!requested.test(index)) {
- continue;
- }
- if (count >= output.size()) {
- count = 0;
- return false;
- }
- output[count++] = static_cast<std::uint16_t>(index);
- }
- return true;
- }
- /** Reads one requested definition and turns it into its cached detail form. */
- bool build_detail(const DetailSource& source,
- std::uint16_t definitionIndex,
- domain::Definition& detail,
- tables::items::Row& item) noexcept {
- tables::IndexRow indexRow{};
- item = {};
- item.definitionIndex = definitionIndex;
- if (!tables::index_row(source.table, source.array, definitionIndex, indexRow)
- || !reader::read_tag(
- *source.source, *source.scratch, indexRow.targetTag, *source.definition)
- || !tables::items::read_definition(std::span<const std::byte>{*source.definition}, item)) {
- return false;
- }
- item.definitionHash = indexRow.definitionHash;
- detail = to_detail(item);
- return true;
- }
- /** Reads the stat rows the installed investment constants blob names. */
- bool read_investment_constants(const reader::Source& source,
- reader::Scratch& scratch,
- std::span<const std::byte> root,
- std::vector<std::byte>& blob,
- state::build_data::constants::InvestmentConstants& output) noexcept {
- output = {};
- std::uint32_t tag = 0;
- if (!tables::slot_tag(root, tables::kInvestmentConstantsSlot, tag) || tag == 0
- || !reader::read_tag(source, scratch, tag, blob)) {
- return false;
- }
- const std::size_t last =
- kConstantsPrefix + kCharacterStatRowOffsets[std::size(kCharacterStatRowOffsets) - 1];
- if (blob.size() <= last) {
- return false;
- }
- output.lightStatRow =
- std::to_integer<std::uint8_t>(blob[kConstantsPrefix + kLightStatRowOffset]);
- for (std::size_t row = 0; row < std::size(kCharacterStatRowOffsets); ++row) {
- output.characterStatRows[row] =
- std::to_integer<std::uint8_t>(blob[kConstantsPrefix + kCharacterStatRowOffsets[row]]);
- }
- output.extracted = true;
- return true;
- }
- } // namespace sunrise::client::content::items::packages
|