|
|
@@ -3,6 +3,7 @@
|
|
|
|
|
|
#include "../../../../core/logging/log.h"
|
|
|
#include "../../../../state/build_data/runtime.h"
|
|
|
+#include "../../../../state/equipment/light/definition.h"
|
|
|
#include "internal.h"
|
|
|
|
|
|
namespace sunrise::middleware::datagen::character_record::appearance {
|
|
|
@@ -54,10 +55,11 @@ namespace constants = state::build_data::constants;
|
|
|
* Collects every stat row one equipped item or its plugs declare.
|
|
|
* @param equipped Effective plug lanes.
|
|
|
* @param count Occupied entries, advanced per distinct row.
|
|
|
+ * @return False for invalid stat rows
|
|
|
+ * or insufficient storage.
|
|
|
*/
|
|
|
-void collect_rows(const Equipped& equipped,
|
|
|
- std::span<std::uint8_t> rows,
|
|
|
- std::size_t& count) noexcept {
|
|
|
+[[nodiscard]] bool
|
|
|
+collect_rows(const Equipped& equipped, std::span<std::uint8_t> rows, std::size_t& count) noexcept {
|
|
|
const std::size_t lanes = equipped.laneCount + 1;
|
|
|
for (std::size_t source = 0; source < lanes; ++source) {
|
|
|
const std::uint16_t definitionIndex =
|
|
|
@@ -69,15 +71,24 @@ void collect_rows(const Equipped& equipped,
|
|
|
}
|
|
|
const std::size_t stats =
|
|
|
detail.statCount < detail.stats.size() ? detail.statCount : detail.stats.size();
|
|
|
- for (std::size_t entry = 0; entry < stats && count < rows.size(); ++entry) {
|
|
|
+ for (std::size_t entry = 0; entry < stats; ++entry) {
|
|
|
const std::uint8_t row = detail.stats[entry].row;
|
|
|
- if (row != details::kEmptyStatRow
|
|
|
- && std::find(rows.begin(), rows.begin() + static_cast<std::ptrdiff_t>(count), row)
|
|
|
- == rows.begin() + static_cast<std::ptrdiff_t>(count)) {
|
|
|
+ if (row == details::kEmptyStatRow) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (row >= constants::kStatRowCount) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (std::find(rows.begin(), rows.begin() + static_cast<std::ptrdiff_t>(count), row)
|
|
|
+ == rows.begin() + static_cast<std::ptrdiff_t>(count)) {
|
|
|
+ if (count >= rows.size()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
rows[count++] = row;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -100,34 +111,43 @@ void append(std::uint8_t row,
|
|
|
++count;
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * Fills one per-weapon table with every row that weapon and its plugs declare, ascending.
|
|
|
- * @param equipped Effective plug lanes of one weapon.
|
|
|
- * @param table Per-weapon stat table.
|
|
|
- */
|
|
|
-void apply_weapon_table(const Equipped& equipped,
|
|
|
- std::array<layout::StatRow, layout::kStatRowCapacity>& table) noexcept {
|
|
|
- std::array<std::uint8_t, layout::kStatRowCapacity> rows{};
|
|
|
- std::size_t rowCount = 0;
|
|
|
- collect_rows(equipped, rows, rowCount);
|
|
|
+/** Writes item Power and definition stats, rejecting invalid rows or insufficient space. */
|
|
|
+[[nodiscard]] bool
|
|
|
+apply_weapon_table(const Equipped& equipped,
|
|
|
+ std::uint8_t powerRow,
|
|
|
+ std::int32_t power,
|
|
|
+ std::array<layout::StatRow, layout::kStatRowCapacity>& table) noexcept {
|
|
|
+ std::array<std::uint8_t, constants::kStatRowCount> rows{powerRow};
|
|
|
+ std::size_t rowCount = 1;
|
|
|
+ if (!collect_rows(equipped, rows, rowCount)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
std::sort(rows.begin(), rows.begin() + static_cast<std::ptrdiff_t>(rowCount));
|
|
|
+ std::array<layout::StatRow, layout::kStatRowCapacity> staged{};
|
|
|
std::size_t written = 0;
|
|
|
for (std::size_t entry = 0; entry < rowCount; ++entry) {
|
|
|
- append(rows[entry], item_total(equipped, rows[entry]), table, written);
|
|
|
+ const std::uint8_t row = rows[entry];
|
|
|
+ const std::int32_t value = row == powerRow ? power : item_total(equipped, row);
|
|
|
+ if (value > 0 && written >= staged.size()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ append(row, value, staged, written);
|
|
|
}
|
|
|
+ table = staged;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
/** Diagnostic latch: the constants are a boot-time domain, so one line settles their absence. */
|
|
|
std::atomic<bool> g_reportedMissingConstants{};
|
|
|
|
|
|
-/** Reports once that the installed investment constants are not published. */
|
|
|
+/** Reports once that the installed investment constants are unavailable or invalid. */
|
|
|
void report_missing_constants() noexcept {
|
|
|
if (g_reportedMissingConstants.exchange(true, std::memory_order_relaxed)) {
|
|
|
return;
|
|
|
}
|
|
|
core::log::write(core::log::Channel::server,
|
|
|
core::log::Level::warn,
|
|
|
- "ev=char_stats stage=constants result=absent");
|
|
|
+ "ev=char_stats stage=constants result=unavailable_or_invalid");
|
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
@@ -137,9 +157,8 @@ bool apply_stats(const family4::loadout::ResolvedInstances& instances,
|
|
|
std::int32_t light,
|
|
|
layout::Appearance& appearance) noexcept {
|
|
|
constants::InvestmentConstants named{};
|
|
|
- if (!state::build_data::find_investment_constants(named)) {
|
|
|
- // Without the named rows there is no light row either, so the record cannot be built at
|
|
|
- // all. Report it once: the alternative is a silently empty stat table.
|
|
|
+ if (!state::build_data::find_investment_constants(named) || !constants::valid(named)) {
|
|
|
+ // Publishing an absent or unusable weapon Power row silently selects the damage floor.
|
|
|
report_missing_constants();
|
|
|
return false;
|
|
|
}
|
|
|
@@ -168,8 +187,12 @@ bool apply_stats(const family4::loadout::ResolvedInstances& instances,
|
|
|
}
|
|
|
details::Definition detail{};
|
|
|
Equipped equipped{};
|
|
|
- if (resolve_equipped(instances.items[index], detail, equipped)) {
|
|
|
- apply_weapon_table(equipped, appearance.weaponStats[weapon]);
|
|
|
+ std::int32_t power = 0;
|
|
|
+ if (!resolve_equipped(instances.items[index], detail, equipped)
|
|
|
+ || !state::equipment::light::item_power(instances.items[index].instance.level, power)
|
|
|
+ || !apply_weapon_table(
|
|
|
+ equipped, named.weaponPowerStatRow, power, appearance.weaponStats[weapon])) {
|
|
|
+ return false;
|
|
|
}
|
|
|
}
|
|
|
return true;
|