convert TekkerAdjustmentSet to JSON
This commit is contained in:
@@ -128,6 +128,7 @@ set(SOURCES
|
||||
src/SignalWatcher.cc
|
||||
src/StaticGameData.cc
|
||||
src/TeamIndex.cc
|
||||
src/TekkerAdjustmentSet.cc
|
||||
src/Text.cc
|
||||
src/TextIndex.cc
|
||||
src/Version.cc
|
||||
|
||||
@@ -1041,80 +1041,3 @@ const WeaponRandomSet::RangeTableEntry*
|
||||
WeaponRandomSet::get_favored_grind_range(size_t index) const {
|
||||
return &this->r.pget<RangeTableEntry>(this->offsets->favored_grind_range_table + sizeof(RangeTableEntry) * index);
|
||||
}
|
||||
|
||||
TekkerAdjustmentSet::TekkerAdjustmentSet(std::shared_ptr<const std::string> data) : data(data), r(*data) {
|
||||
this->offsets = &this->r.pget<Offsets>(this->r.pget_u32b(this->r.size() - 0x10));
|
||||
}
|
||||
|
||||
const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_table(
|
||||
std::array<ProbabilityTable<uint8_t, 100>, 10>& tables_default,
|
||||
std::array<ProbabilityTable<uint8_t, 100>, 10>& tables_favored,
|
||||
uint32_t offset_and_count_offset,
|
||||
bool favored,
|
||||
uint8_t section_id) const {
|
||||
if (section_id >= 10) {
|
||||
throw std::runtime_error("invalid section ID");
|
||||
}
|
||||
ProbabilityTable<uint8_t, 100>& table = favored ? tables_favored[section_id] : tables_default[section_id];
|
||||
if (table.count == 0) {
|
||||
uint32_t offset = r.pget_u32b(offset_and_count_offset);
|
||||
uint32_t count_per_section_id = r.pget_u32b(offset_and_count_offset + 4);
|
||||
auto* entries = &r.pget<DeltaProbabilityEntry>(offset, sizeof(DeltaProbabilityEntry) * count_per_section_id * 10);
|
||||
for (size_t z = count_per_section_id * section_id; z < count_per_section_id * (section_id + 1); z++) {
|
||||
size_t count = favored ? entries[z].count_favored : entries[z].count_default;
|
||||
for (size_t w = 0; w < count; w++) {
|
||||
table.push(entries[z].delta_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_special_upgrade_prob_table(uint8_t section_id, bool favored) const {
|
||||
return this->get_table(
|
||||
this->special_upgrade_prob_tables_default,
|
||||
this->special_upgrade_prob_tables_favored,
|
||||
this->offsets->special_upgrade_prob_table_offset,
|
||||
favored, section_id);
|
||||
}
|
||||
|
||||
const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_grind_delta_prob_table(uint8_t section_id, bool favored) const {
|
||||
return this->get_table(
|
||||
this->grind_delta_prob_tables_default,
|
||||
this->grind_delta_prob_tables_favored,
|
||||
this->offsets->grind_delta_prob_table_offset,
|
||||
favored, section_id);
|
||||
}
|
||||
|
||||
const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_bonus_delta_prob_table(uint8_t section_id, bool favored) const {
|
||||
return this->get_table(
|
||||
this->bonus_delta_prob_tables_default,
|
||||
this->bonus_delta_prob_tables_favored,
|
||||
this->offsets->bonus_delta_prob_table_offset,
|
||||
favored, section_id);
|
||||
}
|
||||
|
||||
int8_t TekkerAdjustmentSet::get_luck(uint32_t start_offset, uint8_t delta_index) const {
|
||||
phosg::StringReader sub_r = r.sub(start_offset);
|
||||
while (!sub_r.eof()) {
|
||||
const auto& entry = sub_r.get<LuckTableEntry>();
|
||||
if (entry.delta_index == 0xFF) {
|
||||
return 0;
|
||||
} else if (entry.delta_index == delta_index) {
|
||||
return entry.luck;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t TekkerAdjustmentSet::get_luck_for_special_upgrade(uint8_t delta_index) const {
|
||||
return this->get_luck(this->offsets->special_upgrade_luck_table_offset, delta_index);
|
||||
}
|
||||
|
||||
int8_t TekkerAdjustmentSet::get_luck_for_grind_delta(uint8_t delta_index) const {
|
||||
return this->get_luck(this->offsets->grind_delta_luck_table_offset, delta_index);
|
||||
}
|
||||
|
||||
int8_t TekkerAdjustmentSet::get_luck_for_bonus_delta(uint8_t delta_index) const {
|
||||
return this->get_luck(this->offsets->bonus_delta_luck_offset, delta_index);
|
||||
}
|
||||
|
||||
+6
-187
@@ -285,55 +285,18 @@ public:
|
||||
explicit JSONCommonItemSet(const phosg::JSON& json);
|
||||
};
|
||||
|
||||
// Note: There are clearly better ways of doing this, but this implementation closely follows what the original code in
|
||||
// the client does.
|
||||
template <typename ItemT, size_t MaxCount>
|
||||
struct ProbabilityTable {
|
||||
ItemT items[MaxCount];
|
||||
size_t count;
|
||||
|
||||
ProbabilityTable() : count(0) {}
|
||||
|
||||
void push(ItemT item) {
|
||||
if (this->count == MaxCount) {
|
||||
throw std::runtime_error("push to full probability table");
|
||||
}
|
||||
this->items[this->count++] = item;
|
||||
}
|
||||
|
||||
ItemT pop() {
|
||||
if (this->count == 0) {
|
||||
throw std::runtime_error("pop from empty probability table");
|
||||
}
|
||||
return this->items[--this->count];
|
||||
}
|
||||
|
||||
void shuffle(std::shared_ptr<RandomGenerator> rand_crypt) {
|
||||
for (size_t z = 1; z < this->count; z++) {
|
||||
size_t other_z = rand_crypt->next() % (z + 1);
|
||||
ItemT t = this->items[z];
|
||||
this->items[z] = this->items[other_z];
|
||||
this->items[other_z] = t;
|
||||
}
|
||||
}
|
||||
|
||||
ItemT sample(std::shared_ptr<RandomGenerator> rand_crypt) const {
|
||||
if (this->count == 0) {
|
||||
throw std::runtime_error("sample from empty probability table");
|
||||
} else if (this->count == 1) {
|
||||
return this->items[0];
|
||||
} else {
|
||||
return this->items[rand_crypt->next() % this->count];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RELFileSet {
|
||||
public:
|
||||
template <typename ValueT, typename WeightT = ValueT>
|
||||
struct WeightTableEntry {
|
||||
ValueT value;
|
||||
WeightT weight;
|
||||
phosg::JSON json() const {
|
||||
return phosg::JSON::dict({{"Weight", this->weight}, {"Value", this->value}});
|
||||
}
|
||||
static WeightTableEntry<ValueT, WeightT> from_json(const phosg::JSON& json) {
|
||||
return WeightTableEntry<ValueT, WeightT>{json.get_int("Weight"), json.get_int("Value")};
|
||||
}
|
||||
} __attribute__((packed));
|
||||
using WeightTableEntry8 = WeightTableEntry<uint8_t>;
|
||||
using WeightTableEntry32 = WeightTableEntry<be_uint32_t>;
|
||||
@@ -432,147 +395,3 @@ private:
|
||||
|
||||
const Offsets* offsets;
|
||||
};
|
||||
|
||||
class TekkerAdjustmentSet {
|
||||
public:
|
||||
// This class parses and accesses data from JudgeItem.rel
|
||||
TekkerAdjustmentSet(std::shared_ptr<const std::string> data);
|
||||
|
||||
const ProbabilityTable<uint8_t, 100>& get_special_upgrade_prob_table(uint8_t section_id, bool favored) const;
|
||||
const ProbabilityTable<uint8_t, 100>& get_grind_delta_prob_table(uint8_t section_id, bool favored) const;
|
||||
const ProbabilityTable<uint8_t, 100>& get_bonus_delta_prob_table(uint8_t section_id, bool favored) const;
|
||||
int8_t get_luck_for_special_upgrade(uint8_t delta_index) const;
|
||||
int8_t get_luck_for_grind_delta(uint8_t delta_index) const;
|
||||
int8_t get_luck_for_bonus_delta(uint8_t delta_index) const;
|
||||
|
||||
private:
|
||||
const ProbabilityTable<uint8_t, 100>& get_table(
|
||||
std::array<ProbabilityTable<uint8_t, 100>, 10>& tables_default,
|
||||
std::array<ProbabilityTable<uint8_t, 100>, 10>& tables_favored,
|
||||
uint32_t offset_and_count_offset,
|
||||
bool favored,
|
||||
uint8_t section_id) const;
|
||||
int8_t get_luck(uint32_t start_offset, uint8_t delta_index) const;
|
||||
|
||||
std::shared_ptr<const std::string> data;
|
||||
phosg::StringReader r;
|
||||
|
||||
struct DeltaProbabilityEntry {
|
||||
uint8_t delta_index;
|
||||
uint8_t count_default;
|
||||
uint8_t count_favored;
|
||||
} __packed_ws__(DeltaProbabilityEntry, 3);
|
||||
struct LuckTableEntry {
|
||||
uint8_t delta_index;
|
||||
int8_t luck;
|
||||
} __packed_ws__(LuckTableEntry, 2);
|
||||
|
||||
struct Offsets {
|
||||
// Each section ID's favored weapon class has different probabilities than those used for all other weapons. The
|
||||
// tables are labeled with (D) for the default values and (F) for the favored-class values.
|
||||
|
||||
// Note that the favored bonuses for Redria are all zero; these values are unused because Redria does not have a
|
||||
// favored weapon type. Curiously, Yellowboze also does not have a favored weapon type, but the values for
|
||||
// Yellowboze are not all zero.
|
||||
|
||||
// This table specifies how likely a special is to be upgraded or downgraded by one level.
|
||||
// In PSO V3, the special upgrade table is:
|
||||
// Viridia => (D) +1=10%, 0=60%, -1=30%
|
||||
// Viridia => (F) +1=25%, 0=50%, -1=25%
|
||||
// Greennill => (D) +1=25%, 0=65%, -1=10%
|
||||
// Greennill => (F) +1=40%, 0=55%, -1=5%
|
||||
// Skyly => (D) +1=15%, 0=70%, -1=15%
|
||||
// Skyly => (F) +1=30%, 0=60%, -1=10%
|
||||
// Bluefull => (D) +1=10%, 0=60%, -1=30%
|
||||
// Bluefull => (F) +1=25%, 0=50%, -1=25%
|
||||
// Purplenum => (D) +1=25%, 0=65%, -1=10%
|
||||
// Purplenum => (F) +1=40%, 0=55%, -1=5%
|
||||
// Pinkal => (D) +1=15%, 0=70%, -1=15%
|
||||
// Pinkal => (F) +1=30%, 0=60%, -1=10%
|
||||
// Redria => (D) +1=20%, 0=60%, -1=20%
|
||||
// Redria => (F) +1=0%, 0=0%, -1=0%
|
||||
// Oran => (D) +1=15%, 0=70%, -1=15%
|
||||
// Oran => (F) +1=30%, 0=60%, -1=10%
|
||||
// Yellowboze => (D) +1=25%, 0=65%, -1=10%
|
||||
// Yellowboze => (F) +1=40%, 0=55%, -1=5%
|
||||
// Whitill => (D) +1=10%, 0=60%, -1=30%
|
||||
// Whitill => (F) +1=25%, 0=50%, -1=25%
|
||||
be_uint32_t special_upgrade_prob_table_offset; // [{c, o -> (DeltaProbabilityEntry)[10][c]})
|
||||
|
||||
// This table specifies how likely a weapon's grind is to be upgraded or downgraded, and by how much. The final
|
||||
// grind value is clamped to the range between 0 and the weapon's maximum grind from ItemPMT, inclusive.
|
||||
// In PSO V3, the grind delta table is:
|
||||
// Viridia => (D) +3=3%, +2=7%, +1=13%, 0=60%, -1=10%, -2=7%, -3=0%
|
||||
// Viridia => (F) +3=5%, +2=13%, +1=25%, 0=50%, -1=7%, -2=0%, -3=0%
|
||||
// Greennill => (D) +3=0%, +2=5%, +1=10%, 0=70%, -1=10%, -2=5%, -3=0%
|
||||
// Greennill => (F) +3=3%, +2=7%, +1=20%, 0=60%, -1=10%, -2=0%, -3=0%
|
||||
// Skyly => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Skyly => (F) +3=3%, +2=12%, +1=20%, 0=50%, -1=10%, -2=5%, -3=0%
|
||||
// Bluefull => (D) +3=3%, +2=7%, +1=13%, 0=60%, -1=10%, -2=7%, -3=0%
|
||||
// Bluefull => (F) +3=5%, +2=13%, +1=25%, 0=50%, -1=7%, -2=0%, -3=0%
|
||||
// Purplenum => (D) +3=0%, +2=5%, +1=10%, 0=70%, -1=10%, -2=5%, -3=0%
|
||||
// Purplenum => (F) +3=3%, +2=7%, +1=20%, 0=60%, -1=10%, -2=0%, -3=0%
|
||||
// Pinkal => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Pinkal => (F) +3=3%, +2=12%, +1=20%, 0=50%, -1=10%, -2=5%, -3=0%
|
||||
// Redria => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Redria => (F) +3=0%, +2=0%, +1=0%, 0=0%, -1=0%, -2=0%, -3=0%
|
||||
// Oran => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Oran => (F) +3=3%, +2=12%, +1=20%, 0=50%, -1=10%, -2=5%, -3=0%
|
||||
// Yellowboze => (D) +3=0%, +2=5%, +1=10%, 0=70%, -1=10%, -2=5%, -3=0%
|
||||
// Yellowboze => (F) +3=3%, +2=7%, +1=20%, 0=60%, -1=10%, -2=0%, -3=0%
|
||||
// Whitill => (D) +3=3%, +2=7%, +1=13%, 0=60%, -1=10%, -2=7%, -3=0%
|
||||
// Whitill => (F) +3=5%, +2=13%, +1=25%, 0=50%, -1=7%, -2=0%, -3=0%
|
||||
be_uint32_t grind_delta_prob_table_offset; // [{c, o -> (DeltaProbabilityEntry)[10][c]})
|
||||
|
||||
// This table specifies how likely a weapon's bonuses are to be upgraded or downgraded, and by how much. The final
|
||||
// bonuses are capped above at 100, but there is no lower limit (so negative results are possible).
|
||||
// In PSO V3, the bonus delta table is:
|
||||
// Viridia => (D) +10=5%, +5=15%, 0=60%, -5=15%, -10=5%
|
||||
// Viridia => (F) +10=8%, +5=20%, 0=60%, -5=10%, -10=2%
|
||||
// Greennill => (D) +10=5%, +5=10%, 0=50%, -5=25%, -10=10%
|
||||
// Greennill => (F) +10=8%, +5=15%, 0=50%, -5=20%, -10=7%
|
||||
// Skyly => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Skyly => (F) +10=13%, +5=30%, 0=50%, -5=5%, -10=2%
|
||||
// Bluefull => (D) +10=5%, +5=15%, 0=60%, -5=15%, -10=5%
|
||||
// Bluefull => (F) +10=8%, +5=20%, 0=60%, -5=10%, -10=2%
|
||||
// Purplenum => (D) +10=5%, +5=10%, 0=50%, -5=25%, -10=10%
|
||||
// Purplenum => (F) +10=8%, +5=15%, 0=50%, -5=20%, -10=7%
|
||||
// Pinkal => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Pinkal => (F) +10=13%, +5=30%, 0=50%, -5=5%, -10=2%
|
||||
// Redria => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Redria => (F) +10=0%, +5=0%, 0=0%, -5=0%, -10=0%
|
||||
// Oran => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Oran => (F) +10=13%, +5=30%, 0=50%, -5=5%, -10=2%
|
||||
// Yellowboze => (D) +10=5%, +5=10%, 0=50%, -5=25%, -10=10%
|
||||
// Yellowboze => (F) +10=8%, +5=15%, 0=50%, -5=20%, -10=7%
|
||||
// Whitill => (D) +10=5%, +5=15%, 0=60%, -5=15%, -10=5%
|
||||
// Whitill => (F) +10=8%, +5=20%, 0=60%, -5=10%, -10=2%
|
||||
be_uint32_t bonus_delta_prob_table_offset; // [{c, o -> (DeltaProbabilityEntry)[10][c]})
|
||||
|
||||
// There is a secondary computation done during weapon adjustment that appears to determine how "good" the
|
||||
// resulting weapon is compared to its original state. If the result of this computation is positive, the game
|
||||
// plays a jingle when the tekker result is accepted. These tables describe how much each delta affects this value,
|
||||
// which we call luck.
|
||||
|
||||
// In PSO V3, the special upgrade luck table is:
|
||||
// +1 => +20, 0 => 0, -1 => -20
|
||||
be_uint32_t special_upgrade_luck_table_offset; // LuckTableEntry[...]; ending with FF FF
|
||||
|
||||
// In PSO V3, the grind delta luck table is:
|
||||
// +3 => +10, +2 => +5, +1 => +3, 0 => 0, -1 => -3, -2 => -5, -3 => -10
|
||||
be_uint32_t grind_delta_luck_table_offset; // LuckTableEntry[...]; ending with FF FF
|
||||
|
||||
// In PSO V3, the bonus delta luck table is:
|
||||
// +10 => +15, +5 => +8, 0 => 0, -5 => -8, -10 => -15
|
||||
be_uint32_t bonus_delta_luck_offset; // LuckTableEntry[...]; ending with FF FF
|
||||
} __packed_ws__(Offsets, 0x18);
|
||||
|
||||
const Offsets* offsets;
|
||||
|
||||
mutable std::array<ProbabilityTable<uint8_t, 100>, 10> special_upgrade_prob_tables_default;
|
||||
mutable std::array<ProbabilityTable<uint8_t, 100>, 10> special_upgrade_prob_tables_favored;
|
||||
mutable std::array<ProbabilityTable<uint8_t, 100>, 10> grind_delta_prob_tables_default;
|
||||
mutable std::array<ProbabilityTable<uint8_t, 100>, 10> grind_delta_prob_tables_favored;
|
||||
mutable std::array<ProbabilityTable<uint8_t, 100>, 10> bonus_delta_prob_tables_default;
|
||||
mutable std::array<ProbabilityTable<uint8_t, 100>, 10> bonus_delta_prob_tables_favored;
|
||||
};
|
||||
|
||||
+82
-49
@@ -6,19 +6,48 @@
|
||||
#include "EnemyType.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
// The favored weapon type table is hardcoded in the game client. The table is:
|
||||
// Viridia shots
|
||||
// Greennill rifles
|
||||
// Skyly swords
|
||||
// Bluefull partisans
|
||||
// Purplenum mechguns
|
||||
// Pinkal canes
|
||||
// Redria (none)
|
||||
// Oran daggers
|
||||
// Yellowboze (none)
|
||||
// Whitill slicers
|
||||
static const std::array<uint8_t, 10> favored_weapon_by_section_id = {
|
||||
0x09, 0x07, 0x02, 0x04, 0x08, 0x0A, 0xFF, 0x03, 0xFF, 0x05};
|
||||
// Note: There are clearly better ways of doing this, but this implementation closely follows what the original code in
|
||||
// the client does.
|
||||
template <typename ItemT, size_t MaxCount>
|
||||
struct ProbabilityTable {
|
||||
ItemT items[MaxCount];
|
||||
size_t count;
|
||||
|
||||
ProbabilityTable() : count(0) {}
|
||||
|
||||
void push(ItemT item) {
|
||||
if (this->count == MaxCount) {
|
||||
throw std::runtime_error("push to full probability table");
|
||||
}
|
||||
this->items[this->count++] = item;
|
||||
}
|
||||
|
||||
ItemT pop() {
|
||||
if (this->count == 0) {
|
||||
throw std::runtime_error("pop from empty probability table");
|
||||
}
|
||||
return this->items[--this->count];
|
||||
}
|
||||
|
||||
void shuffle(std::shared_ptr<RandomGenerator> rand_crypt) {
|
||||
for (size_t z = 1; z < this->count; z++) {
|
||||
size_t other_z = rand_crypt->next() % (z + 1);
|
||||
ItemT t = this->items[z];
|
||||
this->items[z] = this->items[other_z];
|
||||
this->items[other_z] = t;
|
||||
}
|
||||
}
|
||||
|
||||
ItemT sample(std::shared_ptr<RandomGenerator> rand_crypt) const {
|
||||
if (this->count == 0) {
|
||||
throw std::runtime_error("sample from empty probability table");
|
||||
} else if (this->count == 1) {
|
||||
return this->items[0];
|
||||
} else {
|
||||
return this->items[rand_crypt->next() % this->count];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ItemCreator::ItemCreator(
|
||||
std::shared_ptr<const CommonItemSet> common_item_set,
|
||||
@@ -1511,7 +1540,7 @@ void ItemCreator::generate_weapon_shop_item_grind(ItemData& item, size_t player_
|
||||
table_index = 5;
|
||||
}
|
||||
|
||||
uint8_t favored_weapon = favored_weapon_by_section_id.at(this->section_id);
|
||||
uint8_t favored_weapon = TekkerAdjustmentSet::favored_weapon_type_for_section_id(this->section_id);
|
||||
bool is_favored = (favored_weapon != 0xFF) && (item.data1[1] == favored_weapon);
|
||||
const auto* range = is_favored
|
||||
? this->weapon_random_set->get_favored_grind_range(table_index)
|
||||
@@ -1716,56 +1745,61 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
throw std::runtime_error("tekker deltas can only be applied to weapons");
|
||||
}
|
||||
|
||||
static const std::array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
|
||||
|
||||
bool favored = item.data1[1] == favored_weapon_by_section_id[section_id];
|
||||
bool favored = (item.data1[1] == TekkerAdjustmentSet::favored_weapon_type_for_section_id(section_id));
|
||||
ssize_t luck = 0;
|
||||
|
||||
this->log.info_f("Applying tekker deltas for {} weapon", favored ? "favored" : "non-favored");
|
||||
|
||||
auto sample_prob_table = [this](const TekkerAdjustmentSet::Table& table) -> int8_t {
|
||||
size_t sample = this->rand_crypt->next() % table.total;
|
||||
for (const auto& [k, v] : table.probs) {
|
||||
if (sample < v) {
|
||||
return k;
|
||||
}
|
||||
sample -= v;
|
||||
}
|
||||
throw std::logic_error("Table total is incorrect");
|
||||
};
|
||||
|
||||
// Adjust the weapon's special
|
||||
{
|
||||
const auto& prob_table = this->tekker_adjustment_set->get_special_upgrade_prob_table(section_id, favored);
|
||||
uint8_t delta_index = prob_table.sample(this->rand_crypt);
|
||||
int8_t delta = delta_table.at(delta_index);
|
||||
this->log.info_f("(Special) Delta index {}, delta {}", delta_index, delta);
|
||||
// Note: The original code checks specifically for -1 and +1 here, but the data files only include delta_indexes 4,
|
||||
// 5, and 6 (which correspond to -1, 0, and 1) anyway, so we just check for positive and negative numbers instead.
|
||||
// When using the original JudgeItem.rel file, the behavior should be the same, but this feels more correct.
|
||||
try {
|
||||
uint8_t new_special;
|
||||
if (delta < 0) {
|
||||
new_special = item.data1[4] - 1;
|
||||
} else if (delta > 0) {
|
||||
new_special = item.data1[4] + 1;
|
||||
} else {
|
||||
new_special = item.data1[4];
|
||||
}
|
||||
if (new_special != item.data1[4]) {
|
||||
int8_t delta = sample_prob_table(favored
|
||||
? this->tekker_adjustment_set->favored_special_delta_table[section_id]
|
||||
: this->tekker_adjustment_set->default_special_delta_table[section_id]);
|
||||
this->log.info_f("(Special) Delta {} chosen", delta);
|
||||
for (; delta != 0; delta += (delta < 0) - (0 < delta)) {
|
||||
try {
|
||||
// Note: The original code checks specifically for -1 and +1 here and only increments or decrements the special
|
||||
// by 1, and the data files only include delta_indexes 4, 5, and 6 (which correspond to -1, 0, and 1). But we
|
||||
// want to support other levels of delta indexes, so we simply add delta instead. When using the original
|
||||
// JudgeItem.rel file, the behavior should be the same, but this logic feels more correct.
|
||||
uint8_t new_special = item.data1[4] + delta;
|
||||
if (this->item_parameter_table->get_special(item.data1[4]).type ==
|
||||
this->item_parameter_table->get_special(new_special).type) {
|
||||
item.data1[4] = new_special;
|
||||
this->log.info_f("(Special) Delta {} applied", delta);
|
||||
break;
|
||||
} else {
|
||||
this->log.info_f("(Special) Delta canceled because it would change special category");
|
||||
this->log.info_f("(Special) Delta {} canceled because it would change special category", delta);
|
||||
}
|
||||
} catch (const std::out_of_range&) {
|
||||
// Invalid special number passed to get_special; treat it as if delta == 0
|
||||
}
|
||||
} catch (const std::out_of_range&) {
|
||||
// Invalid special number passed to get_special; just ignore it
|
||||
}
|
||||
luck += this->tekker_adjustment_set->get_luck_for_special_upgrade(delta_index);
|
||||
luck += this->tekker_adjustment_set->special_luck_table.at(delta);
|
||||
this->log.info_f("(Special) Luck is now {}", luck);
|
||||
}
|
||||
|
||||
// Adjust the weapon's grind if it's not rare
|
||||
if (!this->item_parameter_table->is_item_rare(item)) {
|
||||
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
|
||||
const auto& prob_table = this->tekker_adjustment_set->get_grind_delta_prob_table(section_id, favored);
|
||||
uint8_t delta_index = prob_table.sample(this->rand_crypt);
|
||||
int8_t delta = delta_table.at(delta_index);
|
||||
this->log.info_f("(Grind) Delta index {}, delta {}", delta_index, delta);
|
||||
int8_t delta = sample_prob_table(favored
|
||||
? this->tekker_adjustment_set->favored_grind_delta_table[section_id]
|
||||
: this->tekker_adjustment_set->default_grind_delta_table[section_id]);
|
||||
this->log.info_f("(Grind) Delta {} chosen", delta);
|
||||
int16_t new_grind = static_cast<int16_t>(item.data1[3]) + static_cast<int16_t>(delta);
|
||||
item.data1[3] = std::clamp<int16_t>(new_grind, 0, weapon_def.max_grind);
|
||||
luck += this->tekker_adjustment_set->get_luck_for_grind_delta(delta_index);
|
||||
luck += this->tekker_adjustment_set->grind_luck_table.at(delta);
|
||||
this->log.info_f("(Grind) Luck is now {}", luck);
|
||||
} else {
|
||||
this->log.info_f("(Grind) Item is rare; skipping grind adjustment");
|
||||
@@ -1773,11 +1807,10 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
|
||||
// Adjust the weapon's bonuses
|
||||
{
|
||||
const auto& prob_table = this->tekker_adjustment_set->get_bonus_delta_prob_table(section_id, favored);
|
||||
// Note: The original code really does use the same delta for all three bonuses.
|
||||
uint8_t delta_index = prob_table.sample(this->rand_crypt);
|
||||
int8_t delta = delta_table.at(delta_index);
|
||||
this->log.info_f("(Bonuses) Delta index {}, delta {}", delta_index, delta);
|
||||
int8_t delta = sample_prob_table(favored
|
||||
? this->tekker_adjustment_set->favored_bonus_delta_table[section_id]
|
||||
: this->tekker_adjustment_set->default_bonus_delta_table[section_id]);
|
||||
this->log.info_f("(Bonuses) Delta {} chosen", delta);
|
||||
// Note: The original code doesn't check if there's actually a bonus in each slot before incrementing the values.
|
||||
// Presumably there's a check later that will clear any invalid bonuses, but we don't have such a check, so we need
|
||||
// to check here if each bonus is actually present.
|
||||
@@ -1786,7 +1819,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
item.data1[z + 1] = std::min<int8_t>(item.data1[z + 1] + delta, 100);
|
||||
}
|
||||
}
|
||||
luck += this->tekker_adjustment_set->get_luck_for_bonus_delta(delta_index);
|
||||
luck += this->tekker_adjustment_set->bonus_luck_table.at(delta);
|
||||
this->log.info_f("(Bonuses) Luck is now {}", luck);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "RareItemSet.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "TekkerAdjustmentSet.hh"
|
||||
|
||||
// This file and ItemCreator.cc are essentially a direct reverse-engineering of the item creation algorithm in PSO GC.
|
||||
// Only minor changes have been made to support BB (as described in the comments in the implementation) and to support
|
||||
|
||||
+23
@@ -2431,6 +2431,29 @@ Action a_encode_mag_metadata_table(
|
||||
write_output_data(args, data, nullptr);
|
||||
});
|
||||
|
||||
Action a_decode_tekker_adjustment_set(
|
||||
"decode-tekker-adjustment-set", "\
|
||||
decode-tekker-adjustment-set [INPUT-FILENAME [OUTPUT-FILENAME]] [OPTIONS...]\n\
|
||||
Converts a JudgeItem.rel file into a JSON tekker adjustment set. Use\n\
|
||||
--big-endian if the .rel file is from PSO GC.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto input_data = read_input_data(args);
|
||||
TekkerAdjustmentSet table(input_data, args.get<bool>("big-endian"));
|
||||
auto json = table.json();
|
||||
auto serialized = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::SORT_DICT_KEYS);
|
||||
write_output_data(args, serialized, nullptr);
|
||||
});
|
||||
|
||||
Action a_encode_tekker_adjustment_set(
|
||||
"encode-tekker-adjustment-set", "\
|
||||
encode-tekker-adjustment-set [INPUT-FILENAME [OUTPUT-FILENAME]] [OPTIONS...]\n\
|
||||
Converts a JSON tekker adjustment set into a JudgeItem.rel file compatible\n\
|
||||
with the game client. Use --big-endian if the .rel file is for PSO GC.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
TekkerAdjustmentSet table(phosg::JSON::parse(read_input_data(args)));
|
||||
write_output_data(args, table.serialize_binary(args.get<bool>("big-endian")), nullptr);
|
||||
});
|
||||
|
||||
Action a_decode_level_table(
|
||||
"decode-level-table", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
|
||||
+2
-2
@@ -2128,8 +2128,8 @@ void ServerState::load_drop_tables() {
|
||||
new_weapon_random_sets[z] = std::make_shared<WeaponRandomSet>(weapon_data);
|
||||
}
|
||||
|
||||
config_log.info_f("Loading tekker adjustment table");
|
||||
auto tekker_data = std::make_shared<std::string>(phosg::load_file("system/tables/JudgeItem-gc-v3.rel"));
|
||||
config_log.info_f("Loading tekker adjustment set");
|
||||
auto tekker_data = phosg::JSON::parse(phosg::load_file("system/tables/tekker-adjustment-set.json"));
|
||||
auto new_tekker_adjustment_set = std::make_shared<TekkerAdjustmentSet>(tekker_data);
|
||||
|
||||
this->rare_item_sets = std::move(new_rare_item_sets);
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
#include "TeamIndex.hh"
|
||||
#include "TekkerAdjustmentSet.hh"
|
||||
#include "WordSelectTable.hh"
|
||||
|
||||
// Forward declarations due to reference cycles
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
#include "TekkerAdjustmentSet.hh"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
static const std::array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
|
||||
static const std::unordered_map<int8_t, size_t> reverse_delta_table = {
|
||||
{-10, 0}, {-5, 1}, {-3, 2}, {-2, 3}, {-1, 4}, {0, 5}, {1, 6}, {2, 7}, {3, 8}, {5, 9}, {10, 10}};
|
||||
|
||||
struct DeltaProbabilityEntry {
|
||||
uint8_t delta_index;
|
||||
uint8_t count_default;
|
||||
uint8_t count_favored;
|
||||
} __packed_ws__(DeltaProbabilityEntry, 3);
|
||||
|
||||
struct LuckTableEntry {
|
||||
uint8_t delta_index;
|
||||
int8_t luck;
|
||||
} __packed_ws__(LuckTableEntry, 2);
|
||||
|
||||
template <bool BE>
|
||||
struct ProbTableRefT {
|
||||
U32T<BE> offset;
|
||||
U32T<BE> count;
|
||||
} __packed_ws_be__(ProbTableRefT, 8);
|
||||
|
||||
template <bool BE>
|
||||
struct RootT {
|
||||
// Each section ID's favored weapon class has different probabilities than those used for all other weapons. The
|
||||
// tables are labeled with (D) for the default values and (F) for the favored-class values.
|
||||
|
||||
// Note that the favored bonuses for Redria are all zero; these values are unused because Redria does not have a
|
||||
// favored weapon type. Curiously, Yellowboze also does not have a favored weapon type, but the values for Yellowboze
|
||||
// are not all zero.
|
||||
|
||||
// This table specifies how likely a special is to be upgraded or downgraded by one level.
|
||||
// In PSO V3, the special upgrade table is:
|
||||
// Viridia => (D) +1=10%, 0=60%, -1=30%
|
||||
// Viridia => (F) +1=25%, 0=50%, -1=25%
|
||||
// Greennill => (D) +1=25%, 0=65%, -1=10%
|
||||
// Greennill => (F) +1=40%, 0=55%, -1=5%
|
||||
// Skyly => (D) +1=15%, 0=70%, -1=15%
|
||||
// Skyly => (F) +1=30%, 0=60%, -1=10%
|
||||
// Bluefull => (D) +1=10%, 0=60%, -1=30%
|
||||
// Bluefull => (F) +1=25%, 0=50%, -1=25%
|
||||
// Purplenum => (D) +1=25%, 0=65%, -1=10%
|
||||
// Purplenum => (F) +1=40%, 0=55%, -1=5%
|
||||
// Pinkal => (D) +1=15%, 0=70%, -1=15%
|
||||
// Pinkal => (F) +1=30%, 0=60%, -1=10%
|
||||
// Redria => (D) +1=20%, 0=60%, -1=20%
|
||||
// Redria => (F) +1=0%, 0=0%, -1=0%
|
||||
// Oran => (D) +1=15%, 0=70%, -1=15%
|
||||
// Oran => (F) +1=30%, 0=60%, -1=10%
|
||||
// Yellowboze => (D) +1=25%, 0=65%, -1=10%
|
||||
// Yellowboze => (F) +1=40%, 0=55%, -1=5%
|
||||
// Whitill => (D) +1=10%, 0=60%, -1=30%
|
||||
// Whitill => (F) +1=25%, 0=50%, -1=25%
|
||||
U32T<BE> special_delta_table_offset; // [{c, o -> (DeltaProbabilityEntry)[10][c]})
|
||||
|
||||
// This table specifies how likely a weapon's grind is to be upgraded or downgraded, and by how much. The final grind
|
||||
// value is clamped to the range between 0 and the weapon's maximum grind from ItemPMT, inclusive.
|
||||
// In PSO V3, the grind delta table is:
|
||||
// Viridia => (D) +3=3%, +2=7%, +1=13%, 0=60%, -1=10%, -2=7%, -3=0%
|
||||
// Viridia => (F) +3=5%, +2=13%, +1=25%, 0=50%, -1=7%, -2=0%, -3=0%
|
||||
// Greennill => (D) +3=0%, +2=5%, +1=10%, 0=70%, -1=10%, -2=5%, -3=0%
|
||||
// Greennill => (F) +3=3%, +2=7%, +1=20%, 0=60%, -1=10%, -2=0%, -3=0%
|
||||
// Skyly => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Skyly => (F) +3=3%, +2=12%, +1=20%, 0=50%, -1=10%, -2=5%, -3=0%
|
||||
// Bluefull => (D) +3=3%, +2=7%, +1=13%, 0=60%, -1=10%, -2=7%, -3=0%
|
||||
// Bluefull => (F) +3=5%, +2=13%, +1=25%, 0=50%, -1=7%, -2=0%, -3=0%
|
||||
// Purplenum => (D) +3=0%, +2=5%, +1=10%, 0=70%, -1=10%, -2=5%, -3=0%
|
||||
// Purplenum => (F) +3=3%, +2=7%, +1=20%, 0=60%, -1=10%, -2=0%, -3=0%
|
||||
// Pinkal => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Pinkal => (F) +3=3%, +2=12%, +1=20%, 0=50%, -1=10%, -2=5%, -3=0%
|
||||
// Redria => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Redria => (F) +3=0%, +2=0%, +1=0%, 0=0%, -1=0%, -2=0%, -3=0%
|
||||
// Oran => (D) +3=0%, +2=7%, +1=10%, 0=60%, -1=13%, -2=7%, -3=3%
|
||||
// Oran => (F) +3=3%, +2=12%, +1=20%, 0=50%, -1=10%, -2=5%, -3=0%
|
||||
// Yellowboze => (D) +3=0%, +2=5%, +1=10%, 0=70%, -1=10%, -2=5%, -3=0%
|
||||
// Yellowboze => (F) +3=3%, +2=7%, +1=20%, 0=60%, -1=10%, -2=0%, -3=0%
|
||||
// Whitill => (D) +3=3%, +2=7%, +1=13%, 0=60%, -1=10%, -2=7%, -3=0%
|
||||
// Whitill => (F) +3=5%, +2=13%, +1=25%, 0=50%, -1=7%, -2=0%, -3=0%
|
||||
U32T<BE> grind_delta_table_offset; // [{c, o -> (DeltaProbabilityEntry)[10][c]})
|
||||
|
||||
// This table specifies how likely a weapon's bonuses are to be upgraded or downgraded, and by how much. The final
|
||||
// bonuses are capped above at 100, but there is no lower limit (so negative results are possible).
|
||||
// In PSO V3, the bonus delta table is:
|
||||
// Viridia => (D) +10=5%, +5=15%, 0=60%, -5=15%, -10=5%
|
||||
// Viridia => (F) +10=8%, +5=20%, 0=60%, -5=10%, -10=2%
|
||||
// Greennill => (D) +10=5%, +5=10%, 0=50%, -5=25%, -10=10%
|
||||
// Greennill => (F) +10=8%, +5=15%, 0=50%, -5=20%, -10=7%
|
||||
// Skyly => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Skyly => (F) +10=13%, +5=30%, 0=50%, -5=5%, -10=2%
|
||||
// Bluefull => (D) +10=5%, +5=15%, 0=60%, -5=15%, -10=5%
|
||||
// Bluefull => (F) +10=8%, +5=20%, 0=60%, -5=10%, -10=2%
|
||||
// Purplenum => (D) +10=5%, +5=10%, 0=50%, -5=25%, -10=10%
|
||||
// Purplenum => (F) +10=8%, +5=15%, 0=50%, -5=20%, -10=7%
|
||||
// Pinkal => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Pinkal => (F) +10=13%, +5=30%, 0=50%, -5=5%, -10=2%
|
||||
// Redria => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Redria => (F) +10=0%, +5=0%, 0=0%, -5=0%, -10=0%
|
||||
// Oran => (D) +10=10%, +5=25%, 0=50%, -5=10%, -10=5%
|
||||
// Oran => (F) +10=13%, +5=30%, 0=50%, -5=5%, -10=2%
|
||||
// Yellowboze => (D) +10=5%, +5=10%, 0=50%, -5=25%, -10=10%
|
||||
// Yellowboze => (F) +10=8%, +5=15%, 0=50%, -5=20%, -10=7%
|
||||
// Whitill => (D) +10=5%, +5=15%, 0=60%, -5=15%, -10=5%
|
||||
// Whitill => (F) +10=8%, +5=20%, 0=60%, -5=10%, -10=2%
|
||||
U32T<BE> bonus_delta_table_offset; // [{c, o -> (DeltaProbabilityEntry)[10][c]})
|
||||
|
||||
// There is a secondary computation done during weapon adjustment that appears to determine how "good" the resulting
|
||||
// weapon is compared to its original state. If the result of this computation is positive, the game plays a jingle
|
||||
// when the tekker result is accepted. These tables describe how much each delta affects this value, which we call
|
||||
// luck.
|
||||
|
||||
// In PSO V3, the special upgrade luck table is:
|
||||
// +1 => +20, 0 => 0, -1 => -20
|
||||
U32T<BE> special_luck_table_offset; // LuckTableEntry[...]; ending with FF FF
|
||||
|
||||
// In PSO V3, the grind delta luck table is:
|
||||
// +3 => +10, +2 => +5, +1 => +3, 0 => 0, -1 => -3, -2 => -5, -3 => -10
|
||||
U32T<BE> grind_luck_table_offset; // LuckTableEntry[...]; ending with FF FF
|
||||
|
||||
// In PSO V3, the bonus delta luck table is:
|
||||
// +10 => +15, +5 => +8, 0 => 0, -5 => -8, -10 => -15
|
||||
U32T<BE> bonus_luck_table_offset; // LuckTableEntry[...]; ending with FF FF
|
||||
} __packed_ws_be__(RootT, 0x18);
|
||||
|
||||
uint8_t TekkerAdjustmentSet::favored_weapon_type_for_section_id(uint8_t section_id) {
|
||||
// The favored weapon type table is hardcoded in the game client. The table is:
|
||||
// Viridia shots
|
||||
// Greennill rifles
|
||||
// Skyly swords
|
||||
// Bluefull partisans
|
||||
// Purplenum mechguns
|
||||
// Pinkal canes
|
||||
// Redria (none)
|
||||
// Oran daggers
|
||||
// Yellowboze (none)
|
||||
// Whitill slicers
|
||||
static const std::array<uint8_t, 10> data{0x09, 0x07, 0x02, 0x04, 0x08, 0x0A, 0xFF, 0x03, 0xFF, 0x05};
|
||||
return data.at(section_id);
|
||||
}
|
||||
|
||||
TekkerAdjustmentSet::TekkerAdjustmentSet(const void* data, size_t size, bool big_endian) {
|
||||
if (big_endian) {
|
||||
this->parse_t<true>(data, size);
|
||||
} else {
|
||||
this->parse_t<false>(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
TekkerAdjustmentSet::TekkerAdjustmentSet(const std::string& data, bool big_endian)
|
||||
: TekkerAdjustmentSet(data.data(), data.size(), big_endian) {}
|
||||
|
||||
TekkerAdjustmentSet::TekkerAdjustmentSet(const phosg::JSON& json) {
|
||||
auto parse_delta_table = [](const phosg::JSON& json) -> std::array<Table, 10> {
|
||||
if (!json.is_dict() || json.size() != 10) {
|
||||
throw std::runtime_error("Invalid structure for TekkerAdjustmentSet JSON delta table");
|
||||
}
|
||||
std::array<Table, 10> ret;
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
auto& table = ret[section_id];
|
||||
for (const auto& [k, v] : json.at(name_for_section_id(section_id)).as_dict()) {
|
||||
auto prob = v->as_int();
|
||||
table.probs.emplace(stoll(k), prob);
|
||||
table.total += prob;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
this->favored_special_delta_table = parse_delta_table(json.at("FavoredSpecialDeltaTable"));
|
||||
this->default_special_delta_table = parse_delta_table(json.at("DefaultSpecialDeltaTable"));
|
||||
this->favored_grind_delta_table = parse_delta_table(json.at("FavoredGrindDeltaTable"));
|
||||
this->default_grind_delta_table = parse_delta_table(json.at("DefaultGrindDeltaTable"));
|
||||
this->favored_bonus_delta_table = parse_delta_table(json.at("FavoredBonusDeltaTable"));
|
||||
this->default_bonus_delta_table = parse_delta_table(json.at("DefaultBonusDeltaTable"));
|
||||
|
||||
auto parse_luck_table = [](const phosg::JSON& json) -> std::unordered_map<int8_t, int8_t> {
|
||||
std::unordered_map<int8_t, int8_t> ret;
|
||||
for (const auto& [k, v] : json.as_dict()) {
|
||||
ret.emplace(stoll(k), v->as_int());
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
this->special_luck_table = parse_luck_table(json.at("SpecialLuckTable"));
|
||||
this->grind_luck_table = parse_luck_table(json.at("GrindLuckTable"));
|
||||
this->bonus_luck_table = parse_luck_table(json.at("BonusLuckTable"));
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
void TekkerAdjustmentSet::parse_t(const void* data, size_t size) {
|
||||
phosg::StringReader r(data, size);
|
||||
const auto& root = r.pget<RootT<BE>>(r.pget<U32T<BE>>(size - 0x10));
|
||||
|
||||
auto parse_delta_table = [&r](std::array<Table, 10>& favored_tables, std::array<Table, 10>& default_tables, uint32_t ref_offset) -> void {
|
||||
const auto& ref = r.pget<ProbTableRefT<BE>>(ref_offset);
|
||||
auto* entries = &r.pget<DeltaProbabilityEntry>(ref.offset, sizeof(DeltaProbabilityEntry) * ref.count * 10);
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
auto& favored_table = favored_tables[section_id];
|
||||
auto& default_table = default_tables[section_id];
|
||||
for (size_t z = 0; z < ref.count; z++) {
|
||||
const auto& entry = entries[section_id * ref.count + z];
|
||||
int8_t delta = delta_table.at(entry.delta_index);
|
||||
favored_table.probs.emplace(delta, entry.count_favored);
|
||||
favored_table.total += entry.count_favored;
|
||||
default_table.probs.emplace(delta, entry.count_default);
|
||||
default_table.total += entry.count_default;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
parse_delta_table(this->favored_special_delta_table, this->default_special_delta_table, root.special_delta_table_offset);
|
||||
parse_delta_table(this->favored_grind_delta_table, this->default_grind_delta_table, root.grind_delta_table_offset);
|
||||
parse_delta_table(this->favored_bonus_delta_table, this->default_bonus_delta_table, root.bonus_delta_table_offset);
|
||||
|
||||
auto parse_luck_table = [&r](uint32_t offset) -> std::unordered_map<int8_t, int8_t> {
|
||||
auto sub_r = r.sub(offset);
|
||||
std::unordered_map<int8_t, int8_t> ret;
|
||||
for (;;) {
|
||||
const auto& entry = sub_r.get<LuckTableEntry>();
|
||||
if (entry.delta_index == 0xFF) {
|
||||
break;
|
||||
}
|
||||
ret.emplace(delta_table.at(entry.delta_index), entry.luck);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
this->special_luck_table = parse_luck_table(root.special_luck_table_offset);
|
||||
this->grind_luck_table = parse_luck_table(root.grind_luck_table_offset);
|
||||
this->bonus_luck_table = parse_luck_table(root.bonus_luck_table_offset);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
std::string TekkerAdjustmentSet::serialize_binary_t() const {
|
||||
RELFileWriter<BE> rel;
|
||||
|
||||
auto serialize_delta_tables = [&rel](const std::array<Table, 10>& favored_tables, const std::array<Table, 10>& default_tables) -> ProbTableRefT<BE> {
|
||||
std::set<int8_t> all_deltas;
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
for (const auto& [delta, _] : favored_tables[section_id].probs) {
|
||||
all_deltas.emplace(delta);
|
||||
}
|
||||
for (const auto& [delta, _] : default_tables[section_id].probs) {
|
||||
all_deltas.emplace(delta);
|
||||
}
|
||||
}
|
||||
|
||||
ProbTableRefT<BE> ret{rel.w.size(), all_deltas.size()};
|
||||
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
for (auto delta_it = all_deltas.rbegin(); delta_it != all_deltas.rend(); delta_it++) {
|
||||
DeltaProbabilityEntry entry;
|
||||
entry.delta_index = reverse_delta_table.at(*delta_it);
|
||||
try {
|
||||
entry.count_favored = favored_tables[section_id].probs.at(*delta_it);
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
entry.count_default = default_tables[section_id].probs.at(*delta_it);
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
rel.template put<DeltaProbabilityEntry>(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto special_delta_ref = serialize_delta_tables(this->favored_special_delta_table, this->default_special_delta_table);
|
||||
auto grind_delta_ref = serialize_delta_tables(this->favored_grind_delta_table, this->default_grind_delta_table);
|
||||
auto bonus_delta_ref = serialize_delta_tables(this->favored_bonus_delta_table, this->default_bonus_delta_table);
|
||||
|
||||
auto serialize_luck_table = [&rel](const std::unordered_map<int8_t, int8_t>& table) -> uint32_t {
|
||||
uint32_t ret = rel.w.size();
|
||||
|
||||
std::vector<std::pair<uint8_t, int8_t>> entries;
|
||||
for (const auto& [delta, luck] : table) {
|
||||
entries.emplace_back(std::make_pair(reverse_delta_table.at(delta), luck));
|
||||
}
|
||||
std::sort(entries.begin(), entries.end(), std::greater<std::pair<uint8_t, int8_t>>());
|
||||
|
||||
for (const auto& [delta_index, luck] : entries) {
|
||||
rel.w.put_u8(delta_index);
|
||||
rel.w.put_s8(luck);
|
||||
}
|
||||
rel.w.put_u16(0xFFFF);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
RootT<BE> root;
|
||||
root.special_luck_table_offset = serialize_luck_table(this->special_luck_table);
|
||||
root.grind_luck_table_offset = serialize_luck_table(this->grind_luck_table);
|
||||
root.bonus_luck_table_offset = serialize_luck_table(this->bonus_luck_table);
|
||||
|
||||
rel.align(4);
|
||||
rel.relocations.emplace(rel.w.size());
|
||||
root.special_delta_table_offset = rel.template put<ProbTableRefT<BE>>(special_delta_ref);
|
||||
rel.relocations.emplace(rel.w.size());
|
||||
root.grind_delta_table_offset = rel.template put<ProbTableRefT<BE>>(grind_delta_ref);
|
||||
rel.relocations.emplace(rel.w.size());
|
||||
root.bonus_delta_table_offset = rel.template put<ProbTableRefT<BE>>(bonus_delta_ref);
|
||||
|
||||
uint32_t root_offset = rel.template put<RootT<BE>>(root);
|
||||
for (size_t z = 1; z <= sizeof(RootT<BE>) / 4; z++) {
|
||||
rel.relocations.emplace(rel.w.size() - (z * 4));
|
||||
}
|
||||
|
||||
return rel.finalize(root_offset);
|
||||
}
|
||||
|
||||
std::string TekkerAdjustmentSet::serialize_binary(bool big_endian) const {
|
||||
return big_endian ? this->serialize_binary_t<true>() : this->serialize_binary_t<false>();
|
||||
}
|
||||
|
||||
phosg::JSON TekkerAdjustmentSet::json() const {
|
||||
auto ret = phosg::JSON::dict();
|
||||
|
||||
auto serialize_delta_table = [](const std::array<Table, 10>& table) -> phosg::JSON {
|
||||
auto ret = phosg::JSON::dict();
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
auto secid_ret = phosg::JSON::dict();
|
||||
for (const auto& [k, v] : table[section_id].probs) {
|
||||
secid_ret.emplace(std::format("{}", k), v);
|
||||
}
|
||||
ret.emplace(name_for_section_id(section_id), std::move(secid_ret));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
ret.emplace("FavoredSpecialDeltaTable", serialize_delta_table(this->favored_special_delta_table));
|
||||
ret.emplace("DefaultSpecialDeltaTable", serialize_delta_table(this->default_special_delta_table));
|
||||
ret.emplace("FavoredGrindDeltaTable", serialize_delta_table(this->favored_grind_delta_table));
|
||||
ret.emplace("DefaultGrindDeltaTable", serialize_delta_table(this->default_grind_delta_table));
|
||||
ret.emplace("FavoredBonusDeltaTable", serialize_delta_table(this->favored_bonus_delta_table));
|
||||
ret.emplace("DefaultBonusDeltaTable", serialize_delta_table(this->default_bonus_delta_table));
|
||||
|
||||
auto serialize_luck_table = [](const std::unordered_map<int8_t, int8_t>& table) -> phosg::JSON {
|
||||
auto ret = phosg::JSON::dict();
|
||||
for (const auto& [k, v] : table) {
|
||||
ret.emplace(std::format("{}", k), v);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
ret.emplace("SpecialLuckTable", serialize_luck_table(this->special_luck_table));
|
||||
ret.emplace("GrindLuckTable", serialize_luck_table(this->grind_luck_table));
|
||||
ret.emplace("BonusLuckTable", serialize_luck_table(this->bonus_luck_table));
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/JSON.hh>
|
||||
|
||||
#include "EnemyType.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
struct TekkerAdjustmentSet {
|
||||
// This struct parses and accesses data from JudgeItem.rel
|
||||
|
||||
TekkerAdjustmentSet(const void* data, size_t size, bool big_endian);
|
||||
TekkerAdjustmentSet(const std::string& data, bool big_endian);
|
||||
explicit TekkerAdjustmentSet(const phosg::JSON& json);
|
||||
|
||||
template <bool BE>
|
||||
void parse_t(const void* data, size_t size);
|
||||
template <bool BE>
|
||||
std::string serialize_binary_t() const;
|
||||
|
||||
std::string serialize_binary(bool big_endian) const;
|
||||
phosg::JSON json() const;
|
||||
|
||||
static uint8_t favored_weapon_type_for_section_id(uint8_t section_id);
|
||||
|
||||
struct Table {
|
||||
std::unordered_map<int8_t, size_t> probs;
|
||||
size_t total;
|
||||
};
|
||||
|
||||
std::array<Table, 10> favored_special_delta_table;
|
||||
std::array<Table, 10> default_special_delta_table;
|
||||
std::array<Table, 10> favored_grind_delta_table;
|
||||
std::array<Table, 10> default_grind_delta_table;
|
||||
std::array<Table, 10> favored_bonus_delta_table;
|
||||
std::array<Table, 10> default_bonus_delta_table;
|
||||
std::unordered_map<int8_t, int8_t> special_luck_table;
|
||||
std::unordered_map<int8_t, int8_t> grind_luck_table;
|
||||
std::unordered_map<int8_t, int8_t> bonus_luck_table;
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"BonusLuckTable": {"-10": -15, "-5": -8, "0": 0, "10": 15, "5": 8},
|
||||
"DefaultBonusDeltaTable": {
|
||||
"Bluefull": {"-10": 5, "-5": 15, "0": 60, "10": 5, "5": 15},
|
||||
"Greennill": {"-10": 10, "-5": 25, "0": 50, "10": 5, "5": 10},
|
||||
"Oran": {"-10": 5, "-5": 10, "0": 50, "10": 10, "5": 25},
|
||||
"Pinkal": {"-10": 5, "-5": 10, "0": 50, "10": 10, "5": 25},
|
||||
"Purplenum": {"-10": 10, "-5": 25, "0": 50, "10": 5, "5": 10},
|
||||
"Redria": {"-10": 5, "-5": 10, "0": 50, "10": 10, "5": 25},
|
||||
"Skyly": {"-10": 5, "-5": 10, "0": 50, "10": 10, "5": 25},
|
||||
"Viridia": {"-10": 5, "-5": 15, "0": 60, "10": 5, "5": 15},
|
||||
"Whitill": {"-10": 5, "-5": 15, "0": 60, "10": 5, "5": 15},
|
||||
"Yellowboze": {"-10": 10, "-5": 25, "0": 50, "10": 5, "5": 10}
|
||||
},
|
||||
"DefaultGrindDeltaTable": {
|
||||
"Bluefull": {"-1": 10, "-2": 7, "-3": 0, "0": 60, "1": 13, "2": 7, "3": 3},
|
||||
"Greennill": {"-1": 10, "-2": 5, "-3": 0, "0": 70, "1": 10, "2": 5, "3": 0},
|
||||
"Oran": {"-1": 13, "-2": 7, "-3": 3, "0": 60, "1": 10, "2": 7, "3": 0},
|
||||
"Pinkal": {"-1": 13, "-2": 7, "-3": 3, "0": 60, "1": 10, "2": 7, "3": 0},
|
||||
"Purplenum": {"-1": 10, "-2": 5, "-3": 0, "0": 70, "1": 10, "2": 5, "3": 0},
|
||||
"Redria": {"-1": 13, "-2": 7, "-3": 3, "0": 60, "1": 10, "2": 7, "3": 0},
|
||||
"Skyly": {"-1": 13, "-2": 7, "-3": 3, "0": 60, "1": 10, "2": 7, "3": 0},
|
||||
"Viridia": {"-1": 10, "-2": 7, "-3": 0, "0": 60, "1": 13, "2": 7, "3": 3},
|
||||
"Whitill": {"-1": 10, "-2": 7, "-3": 0, "0": 60, "1": 13, "2": 7, "3": 3},
|
||||
"Yellowboze": {"-1": 10, "-2": 5, "-3": 0, "0": 70, "1": 10, "2": 5, "3": 0}
|
||||
},
|
||||
"DefaultSpecialDeltaTable": {
|
||||
"Bluefull": {"-1": 30, "0": 60, "1": 10},
|
||||
"Greennill": {"-1": 10, "0": 65, "1": 25},
|
||||
"Oran": {"-1": 15, "0": 70, "1": 15},
|
||||
"Pinkal": {"-1": 15, "0": 70, "1": 15},
|
||||
"Purplenum": {"-1": 10, "0": 65, "1": 25},
|
||||
"Redria": {"-1": 20, "0": 60, "1": 20},
|
||||
"Skyly": {"-1": 15, "0": 70, "1": 15},
|
||||
"Viridia": {"-1": 30, "0": 60, "1": 10},
|
||||
"Whitill": {"-1": 30, "0": 60, "1": 10},
|
||||
"Yellowboze": {"-1": 10, "0": 65, "1": 25}
|
||||
},
|
||||
"FavoredBonusDeltaTable": {
|
||||
"Bluefull": {"-10": 2, "-5": 10, "0": 60, "10": 8, "5": 20},
|
||||
"Greennill": {"-10": 7, "-5": 20, "0": 50, "10": 8, "5": 15},
|
||||
"Oran": {"-10": 2, "-5": 5, "0": 50, "10": 13, "5": 30},
|
||||
"Pinkal": {"-10": 2, "-5": 5, "0": 50, "10": 13, "5": 30},
|
||||
"Purplenum": {"-10": 7, "-5": 20, "0": 50, "10": 8, "5": 15},
|
||||
"Redria": {"-10": 0, "-5": 0, "0": 0, "10": 0, "5": 0},
|
||||
"Skyly": {"-10": 2, "-5": 5, "0": 50, "10": 13, "5": 30},
|
||||
"Viridia": {"-10": 2, "-5": 10, "0": 60, "10": 8, "5": 20},
|
||||
"Whitill": {"-10": 2, "-5": 10, "0": 60, "10": 8, "5": 20},
|
||||
"Yellowboze": {"-10": 7, "-5": 20, "0": 50, "10": 8, "5": 15}
|
||||
},
|
||||
"FavoredGrindDeltaTable": {
|
||||
"Bluefull": {"-1": 7, "-2": 0, "-3": 0, "0": 50, "1": 25, "2": 13, "3": 5},
|
||||
"Greennill": {"-1": 10, "-2": 0, "-3": 0, "0": 60, "1": 20, "2": 7, "3": 3},
|
||||
"Oran": {"-1": 10, "-2": 5, "-3": 0, "0": 50, "1": 20, "2": 12, "3": 3},
|
||||
"Pinkal": {"-1": 10, "-2": 5, "-3": 0, "0": 50, "1": 20, "2": 12, "3": 3},
|
||||
"Purplenum": {"-1": 10, "-2": 0, "-3": 0, "0": 60, "1": 20, "2": 7, "3": 3},
|
||||
"Redria": {"-1": 0, "-2": 0, "-3": 0, "0": 0, "1": 0, "2": 0, "3": 0},
|
||||
"Skyly": {"-1": 10, "-2": 5, "-3": 0, "0": 50, "1": 20, "2": 12, "3": 3},
|
||||
"Viridia": {"-1": 7, "-2": 0, "-3": 0, "0": 50, "1": 25, "2": 13, "3": 5},
|
||||
"Whitill": {"-1": 7, "-2": 0, "-3": 0, "0": 50, "1": 25, "2": 13, "3": 5},
|
||||
"Yellowboze": {"-1": 10, "-2": 0, "-3": 0, "0": 60, "1": 20, "2": 7, "3": 3}
|
||||
},
|
||||
"FavoredSpecialDeltaTable": {
|
||||
"Bluefull": {"-1": 25, "0": 50, "1": 25},
|
||||
"Greennill": {"-1": 5, "0": 55, "1": 40},
|
||||
"Oran": {"-1": 10, "0": 60, "1": 30},
|
||||
"Pinkal": {"-1": 10, "0": 60, "1": 30},
|
||||
"Purplenum": {"-1": 5, "0": 55, "1": 40},
|
||||
"Redria": {"-1": 0, "0": 0, "1": 0},
|
||||
"Skyly": {"-1": 10, "0": 60, "1": 30},
|
||||
"Viridia": {"-1": 25, "0": 50, "1": 25},
|
||||
"Whitill": {"-1": 25, "0": 50, "1": 25},
|
||||
"Yellowboze": {"-1": 5, "0": 55, "1": 40}
|
||||
},
|
||||
"GrindLuckTable": {"-1": -3, "-2": -5, "-3": -10, "0": 0, "1": 3, "2": 5, "3": 10},
|
||||
"SpecialLuckTable": {"-1": -20, "0": 0, "1": 20}
|
||||
}
|
||||
@@ -21,6 +21,11 @@ bindiff tests/game-tables/battle-params-ep1-off.dat tests/game-tables/battle-par
|
||||
bindiff tests/game-tables/battle-params-ep2-off.dat tests/game-tables/battle-params-encoded_lab.dat
|
||||
bindiff tests/game-tables/battle-params-ep4-off.dat tests/game-tables/battle-params-encoded_ep4.dat
|
||||
|
||||
echo "... (tekker-adjustment-set)"
|
||||
$EXECUTABLE decode-tekker-adjustment-set --big-endian $DIR/tekker-adjustment-set.expected.bin $DIR/tekker-adjustment-set.json
|
||||
$EXECUTABLE encode-tekker-adjustment-set --big-endian $DIR/tekker-adjustment-set.json $DIR/tekker-adjustment-set.encoded.bin
|
||||
bindiff $DIR/tekker-adjustment-set.expected.bin $DIR/tekker-adjustment-set.encoded.bin
|
||||
|
||||
echo "... (level-table) BB"
|
||||
$EXECUTABLE decode-level-table --bb-v4 $DIR/level-table-bb-v4.expected.bin --decompressed $DIR/level-table-bb-v4.json --hex
|
||||
$EXECUTABLE encode-level-table-v4 $DIR/level-table-bb-v4.json $DIR/level-table-bb-v4.encoded.bin --decompressed
|
||||
|
||||
Reference in New Issue
Block a user