add level table JSON format
This commit is contained in:
@@ -27,7 +27,7 @@ void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
||||
phosg::fwrite_fmt(stream,
|
||||
"{:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {}",
|
||||
e.char_stats.atp, e.char_stats.mst, e.char_stats.evp, e.char_stats.hp, e.char_stats.dfp, e.char_stats.ata,
|
||||
e.char_stats.lck, e.esp, e.experience, e.meseta, names_str);
|
||||
e.char_stats.lck, e.esp, e.exp, e.meseta, names_str);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -540,7 +540,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
bb_player->disp.stats.char_stats.dfp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::DEF) * 2;
|
||||
bb_player->disp.stats.char_stats.lck += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::LUCK) * 2;
|
||||
bb_player->disp.stats.char_stats.hp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::HP) * 2;
|
||||
bb_player->disp.stats.experience = ch.character->disp.stats.experience;
|
||||
bb_player->disp.stats.exp = ch.character->disp.stats.exp;
|
||||
bb_player->disp.stats.meseta = ch.character->disp.stats.meseta;
|
||||
}
|
||||
bb_player->disp.technique_levels_v1 = ch.character->disp.technique_levels_v1;
|
||||
@@ -915,7 +915,7 @@ ChatCommandDefinition cc_edit(
|
||||
} else if (tokens.at(0) == "meseta" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.meseta = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "exp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.experience = stoul(tokens.at(1));
|
||||
p->disp.stats.exp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "level" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.level = stoul(tokens.at(1)) - 1;
|
||||
p->recompute_stats(s->level_table(a.c->version()), true);
|
||||
|
||||
+1
-1
@@ -703,7 +703,7 @@ void Client::create_challenge_overlay(
|
||||
overlay->disp.visual.char_class, overlay->disp.stats.level);
|
||||
overlay->disp.stats.esp = 40;
|
||||
overlay->disp.stats.attack_range = 10.0;
|
||||
overlay->disp.stats.experience = stats_delta.experience;
|
||||
overlay->disp.stats.exp = stats_delta.exp;
|
||||
overlay->disp.stats.meseta = 0;
|
||||
overlay->clear_all_material_usage();
|
||||
for (size_t z = 0; z < 0x13; z++) {
|
||||
|
||||
@@ -4269,7 +4269,9 @@ struct G_Attack_6x43_6x44_6x45 {
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed_ws__(G_Attack_6x43_6x44_6x45, 8);
|
||||
|
||||
// 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on GC NTE/V3/V4)
|
||||
// 6x46: Set attack strike targets (sent after each of 43, 44, and 45) (protected on GC NTE/V3/V4)
|
||||
// This command sets the targets of each strike of an attack (e.g. each pair of mechgun bullets, or each swing of a
|
||||
// pair of daggers). For multi-strike attacks, this is sent multiple times.
|
||||
// The number of targets is not bounds-checked during byteswapping on GC clients. The client only expects up to 10
|
||||
// entries here, so if the number of targets is too large, the client will byteswap the function's return address on
|
||||
// the stack, and it will crash.
|
||||
|
||||
@@ -197,3 +197,79 @@ size_t get_rel_array_count(const std::set<uint32_t>& offsets, size_t start_offse
|
||||
}
|
||||
return (*it - start_offset) / sizeof(T);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
class RELFileWriter {
|
||||
public:
|
||||
RELFileWriter() = default;
|
||||
~RELFileWriter() = default;
|
||||
|
||||
template <typename T>
|
||||
uint32_t put(const T& obj) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.put<T>(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write(const void* data, size_t size) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.write(data, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write(const std::string& data) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.write(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write_offset(uint32_t value) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->relocations.emplace(ret);
|
||||
this->w.put<U32T<BE>>(value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t write_ref(const ArrayRefT<BE>& ref) {
|
||||
uint32_t ret = this->w.size();
|
||||
this->w.put<ArrayRefT<BE>>(ref);
|
||||
this->relocations.emplace(ret + offsetof(ArrayRefT<BE>, offset));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void align(size_t alignment) {
|
||||
while (this->w.size() & (alignment - 1)) {
|
||||
this->w.put_u8(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string finalize(uint32_t root_offset) {
|
||||
RELFileFooterT<BE> footer;
|
||||
footer.root_offset = root_offset;
|
||||
|
||||
this->align(0x20);
|
||||
footer.relocations_offset = this->w.size();
|
||||
footer.num_relocations = this->relocations.size();
|
||||
footer.unused1[0] = 1;
|
||||
uint32_t last_offset = 0;
|
||||
for (uint32_t reloc_offset : this->relocations) {
|
||||
if (reloc_offset & 3) {
|
||||
throw std::logic_error("Relocation is not 4-byte aligned");
|
||||
}
|
||||
size_t reloc_value = (reloc_offset - last_offset) >> 2;
|
||||
if (reloc_value > 0xFFFF) {
|
||||
throw std::runtime_error("Relocation offset is too far away from previous");
|
||||
}
|
||||
this->w.put<U16T<BE>>(reloc_value);
|
||||
last_offset = reloc_offset;
|
||||
}
|
||||
|
||||
align(0x20);
|
||||
this->w.put<RELFileFooterT<BE>>(footer);
|
||||
|
||||
return std::move(this->w.str());
|
||||
}
|
||||
|
||||
phosg::StringWriter w;
|
||||
std::set<uint32_t> relocations;
|
||||
};
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
client_json.emplace("DFP", p->disp.stats.char_stats.dfp.load());
|
||||
client_json.emplace("ATA", p->disp.stats.char_stats.ata.load());
|
||||
client_json.emplace("LCK", p->disp.stats.char_stats.lck.load());
|
||||
client_json.emplace("EXP", p->disp.stats.experience.load());
|
||||
client_json.emplace("EXP", p->disp.stats.exp.load());
|
||||
client_json.emplace("Meseta", p->disp.stats.meseta.load());
|
||||
auto tech_levels_json = phosg::JSON::dict();
|
||||
for (size_t z = 0; z < 0x13; z++) {
|
||||
|
||||
+98
-148
@@ -2913,224 +2913,204 @@ public:
|
||||
}
|
||||
|
||||
static std::string serialize(const ItemParameterTable& pmt) {
|
||||
set<uint32_t> relocations;
|
||||
RELFileWriter<BE> rel;
|
||||
RootT root;
|
||||
phosg::StringWriter w;
|
||||
|
||||
if constexpr (!std::is_same_v<HeaderT, EmptyHeader>) {
|
||||
w.put<HeaderT>(HeaderT());
|
||||
rel.template put<HeaderT>(HeaderT());
|
||||
}
|
||||
|
||||
auto align = [&w](size_t alignment) -> void {
|
||||
while (w.size() & (alignment - 1)) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
};
|
||||
auto write_ref = [&w, &relocations](const ArrayRefT<BE>& ref) -> void {
|
||||
w.put<ArrayRefT<BE>>(ref);
|
||||
relocations.emplace(w.size() - 4);
|
||||
};
|
||||
|
||||
if constexpr (requires { root.entry_count; }) {
|
||||
root.entry_count = 0x13;
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> shields_ref{pmt.num_armors_or_shields_in_class(2) - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> shields_ref{pmt.num_armors_or_shields_in_class(2) - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (shields_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<ArmorOrShieldT>(pmt.get_armor_or_shield(2, data1_2));
|
||||
rel.template put<ArmorOrShieldT>(pmt.get_armor_or_shield(2, data1_2));
|
||||
}
|
||||
if constexpr (requires { root.shield_stat_boost_index_table; }) {
|
||||
root.shield_stat_boost_index_table = w.size();
|
||||
w.write(pmt.get_shield_stat_boost_index_table());
|
||||
root.shield_stat_boost_index_table = rel.write(pmt.get_shield_stat_boost_index_table());
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> armors_ref{pmt.num_armors_or_shields_in_class(1) - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> armors_ref{pmt.num_armors_or_shields_in_class(1) - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (armors_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<ArmorOrShieldT>(pmt.get_armor_or_shield(1, data1_2));
|
||||
rel.template put<ArmorOrShieldT>(pmt.get_armor_or_shield(1, data1_2));
|
||||
}
|
||||
if constexpr (requires { root.armor_stat_boost_index_table; }) {
|
||||
root.armor_stat_boost_index_table = w.size();
|
||||
w.write(pmt.get_armor_stat_boost_index_table());
|
||||
root.armor_stat_boost_index_table = rel.write(pmt.get_armor_stat_boost_index_table());
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> units_ref{pmt.num_units() - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> units_ref{pmt.num_units() - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (units_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<UnitT>(pmt.get_unit(data1_2));
|
||||
rel.template put<UnitT>(pmt.get_unit(data1_2));
|
||||
}
|
||||
|
||||
align(4);
|
||||
ArrayRefT<BE> mags_ref{pmt.num_mags() - HasImplicitPlaceholders, w.size()};
|
||||
rel.align(4);
|
||||
ArrayRefT<BE> mags_ref{pmt.num_mags() - HasImplicitPlaceholders, rel.w.size()};
|
||||
for (size_t data1_2 = 0; data1_2 < (mags_ref.count + HasImplicitPlaceholders); data1_2++) {
|
||||
w.put<MagT>(pmt.get_mag(data1_2));
|
||||
rel.template put<MagT>(pmt.get_mag(data1_2));
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
std::vector<ArrayRefT<BE>> tool_refs;
|
||||
for (size_t data1_1 = 0; data1_1 < pmt.num_tool_classes(); data1_1++) {
|
||||
auto& ref = tool_refs.emplace_back(ArrayRefT<BE>{pmt.num_tools_in_class(data1_1), w.size()});
|
||||
auto& ref = tool_refs.emplace_back(ArrayRefT<BE>{pmt.num_tools_in_class(data1_1), rel.w.size()});
|
||||
for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) {
|
||||
w.put<ToolT>(pmt.get_tool(data1_1, data1_2));
|
||||
rel.template put<ToolT>(pmt.get_tool(data1_1, data1_2));
|
||||
}
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
std::vector<ArrayRefT<BE>> weapon_refs;
|
||||
for (size_t data1_1 = 0; data1_1 < pmt.num_weapon_classes(); data1_1++) {
|
||||
auto& ref = weapon_refs.emplace_back(ArrayRefT<BE>{pmt.num_weapons_in_class(data1_1), w.size()});
|
||||
auto& ref = weapon_refs.emplace_back(ArrayRefT<BE>{pmt.num_weapons_in_class(data1_1), rel.w.size()});
|
||||
for (size_t data1_2 = 0; data1_2 < ref.count; data1_2++) {
|
||||
w.put<WeaponT>(pmt.get_weapon(data1_1, data1_2));
|
||||
rel.template put<WeaponT>(pmt.get_weapon(data1_1, data1_2));
|
||||
}
|
||||
}
|
||||
if constexpr (requires { root.weapon_stat_boost_index_table; }) {
|
||||
root.weapon_stat_boost_index_table = w.size();
|
||||
w.write(pmt.get_weapon_stat_boost_index_table());
|
||||
root.weapon_stat_boost_index_table = rel.write(pmt.get_weapon_stat_boost_index_table());
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.photon_color_table = w.size();
|
||||
rel.align(4);
|
||||
root.photon_color_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_photon_colors(); z++) {
|
||||
w.put<PhotonColorEntryT<BE>>(pmt.get_photon_color(z));
|
||||
rel.template put<PhotonColorEntryT<BE>>(pmt.get_photon_color(z));
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.weapon_range_table = w.size();
|
||||
rel.align(4);
|
||||
root.weapon_range_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_ranges(); z++) {
|
||||
w.put<WeaponRangeT<BE>>(pmt.get_weapon_range(z));
|
||||
rel.template put<WeaponRangeT<BE>>(pmt.get_weapon_range(z));
|
||||
}
|
||||
|
||||
root.weapon_kind_table = w.size();
|
||||
root.weapon_kind_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_classes(); z++) {
|
||||
w.put_u8(pmt.get_weapon_kind(z));
|
||||
rel.w.put_u8(pmt.get_weapon_kind(z));
|
||||
}
|
||||
|
||||
if constexpr (requires { root.weapon_integral_sale_divisor_table; }) {
|
||||
root.weapon_integral_sale_divisor_table = w.size();
|
||||
root.weapon_integral_sale_divisor_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_classes(); z++) {
|
||||
w.put_u8(pmt.get_sale_divisor(0, z));
|
||||
rel.w.put_u8(pmt.get_sale_divisor(0, z));
|
||||
}
|
||||
} else {
|
||||
align(4);
|
||||
root.weapon_sale_divisor_table = w.size();
|
||||
rel.align(4);
|
||||
root.weapon_sale_divisor_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_sale_divisors(); z++) {
|
||||
w.put<F32T<BE>>(pmt.get_sale_divisor(0, z));
|
||||
rel.template put<F32T<BE>>(pmt.get_sale_divisor(0, z));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (requires { root.non_weapon_integral_sale_divisor_table; }) {
|
||||
root.non_weapon_integral_sale_divisor_table = w.size();
|
||||
NonWeaponSaleDivisorsDCProtos sds;
|
||||
sds.armor_divisor = pmt.get_sale_divisor(1, 1);
|
||||
sds.shield_divisor = pmt.get_sale_divisor(1, 2);
|
||||
sds.unit_divisor = pmt.get_sale_divisor(1, 3);
|
||||
w.put<NonWeaponSaleDivisorsDCProtos>(sds);
|
||||
root.non_weapon_integral_sale_divisor_table = rel.template put<NonWeaponSaleDivisorsDCProtos>(sds);
|
||||
} else {
|
||||
align(4);
|
||||
root.non_weapon_sale_divisor_table = w.size();
|
||||
rel.align(4);
|
||||
NonWeaponSaleDivisorsT<BE> sds;
|
||||
sds.armor_divisor = pmt.get_sale_divisor(1, 1);
|
||||
sds.shield_divisor = pmt.get_sale_divisor(1, 2);
|
||||
sds.unit_divisor = pmt.get_sale_divisor(1, 3);
|
||||
sds.mag_divisor = pmt.get_sale_divisor(2, 0);
|
||||
w.put<NonWeaponSaleDivisorsT<BE>>(sds);
|
||||
root.non_weapon_sale_divisor_table = rel.template put<NonWeaponSaleDivisorsT<BE>>(sds);
|
||||
}
|
||||
|
||||
MagFeedResultsListOffsetsT<BE> mag_feed_result_offsets;
|
||||
for (size_t table_index = 0; table_index < 8; table_index++) {
|
||||
mag_feed_result_offsets[table_index] = w.size();
|
||||
mag_feed_result_offsets[table_index] = rel.w.size();
|
||||
for (size_t item_id = 0; item_id < 11; item_id++) {
|
||||
w.put<MagFeedResult>(pmt.get_mag_feed_result(table_index, item_id));
|
||||
rel.template put<MagFeedResult>(pmt.get_mag_feed_result(table_index, item_id));
|
||||
}
|
||||
}
|
||||
|
||||
root.star_value_table = w.size();
|
||||
w.write(pmt.get_star_value_table());
|
||||
root.star_value_table = rel.write(pmt.get_star_value_table());
|
||||
|
||||
if constexpr (requires { root.unknown_a1; }) {
|
||||
align(2);
|
||||
root.unknown_a1 = w.size();
|
||||
w.write(pmt.get_unknown_a1());
|
||||
rel.align(2);
|
||||
root.unknown_a1 = rel.write(pmt.get_unknown_a1());
|
||||
}
|
||||
|
||||
align(2);
|
||||
root.special_table = w.size();
|
||||
rel.align(2);
|
||||
root.special_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_specials(); z++) {
|
||||
w.put<SpecialT<BE>>(pmt.get_special(z));
|
||||
rel.template put<SpecialT<BE>>(pmt.get_special(z));
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.weapon_effect_table = w.size();
|
||||
rel.align(4);
|
||||
root.weapon_effect_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_weapon_effects(); z++) {
|
||||
w.put<WeaponEffectT<BE>>(pmt.get_weapon_effect(z));
|
||||
rel.template put<WeaponEffectT<BE>>(pmt.get_weapon_effect(z));
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
if constexpr (requires { root.shield_effect_table; }) {
|
||||
root.shield_effect_table = w.size();
|
||||
root.shield_effect_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_shield_effects(); z++) {
|
||||
w.put<ShieldEffectT<BE>>(pmt.get_shield_effect(z));
|
||||
rel.template put<ShieldEffectT<BE>>(pmt.get_shield_effect(z));
|
||||
}
|
||||
}
|
||||
|
||||
align(4);
|
||||
rel.align(4);
|
||||
if constexpr (requires { root.sound_remap_table; }) {
|
||||
std::vector<SoundRemapTableOffsetsT<BE>> remap_refs;
|
||||
const auto& remaps = pmt.get_all_sound_remaps();
|
||||
for (const auto& remap : remaps) {
|
||||
auto& remap_ref = remap_refs.emplace_back();
|
||||
remap_ref.sound_id = remap.sound_id;
|
||||
remap_ref.remaps_for_rt_index_table = w.size();
|
||||
remap_ref.remaps_for_rt_index_table = rel.w.size();
|
||||
for (uint32_t remap_sound_id : remap.by_rt_index) {
|
||||
w.put<U32T<BE>>(remap_sound_id);
|
||||
rel.template put<U32T<BE>>(remap_sound_id);
|
||||
}
|
||||
remap_ref.remaps_for_char_class_table = w.size();
|
||||
remap_ref.remaps_for_char_class_table = rel.w.size();
|
||||
for (uint32_t remap_sound_id : remap.by_char_class) {
|
||||
w.put<U32T<BE>>(remap_sound_id);
|
||||
rel.template put<U32T<BE>>(remap_sound_id);
|
||||
}
|
||||
}
|
||||
ArrayRefT<BE> remap_vec{remaps.size(), w.size()};
|
||||
ArrayRefT<BE> remap_vec{remaps.size(), rel.w.size()};
|
||||
for (const auto& remap_ref : remap_refs) {
|
||||
w.put<SoundRemapTableOffsetsT<BE>>(remap_ref);
|
||||
relocations.emplace(w.size() - 8);
|
||||
relocations.emplace(w.size() - 4);
|
||||
uint32_t offset = rel.template put<SoundRemapTableOffsetsT<BE>>(remap_ref);
|
||||
rel.relocations.emplace(offset + 4);
|
||||
rel.relocations.emplace(offset + 8);
|
||||
}
|
||||
root.sound_remap_table = w.size();
|
||||
write_ref(remap_vec);
|
||||
root.sound_remap_table = rel.write_ref(remap_vec);
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.stat_boost_table = w.size();
|
||||
rel.align(4);
|
||||
root.stat_boost_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_stat_boosts(); z++) {
|
||||
w.put<StatBoostT<BE>>(pmt.get_stat_boost(z));
|
||||
rel.template put<StatBoostT<BE>>(pmt.get_stat_boost(z));
|
||||
}
|
||||
|
||||
if constexpr (requires { root.max_tech_level_table; }) {
|
||||
root.max_tech_level_table = w.size();
|
||||
MaxTechniqueLevels max_tech_levels;
|
||||
for (size_t tech_num = 0; tech_num < 0x13; tech_num++) {
|
||||
for (size_t char_class = 0; char_class < 0x0C; char_class++) {
|
||||
max_tech_levels[tech_num][char_class] = pmt.get_max_tech_level(char_class, tech_num);
|
||||
}
|
||||
}
|
||||
w.put<MaxTechniqueLevels>(max_tech_levels);
|
||||
root.max_tech_level_table = rel.template put<MaxTechniqueLevels>(max_tech_levels);
|
||||
}
|
||||
|
||||
ArrayRefT<BE> combination_table_ref;
|
||||
if constexpr (requires { root.combination_table; }) {
|
||||
combination_table_ref.offset = w.size();
|
||||
combination_table_ref.offset = rel.w.size();
|
||||
combination_table_ref.count = pmt.num_item_combinations();
|
||||
for (size_t z = 0; z < combination_table_ref.count; z++) {
|
||||
w.put<ItemCombination>(pmt.get_item_combination(z));
|
||||
rel.template put<ItemCombination>(pmt.get_item_combination(z));
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (requires { root.tech_boost_table; }) {
|
||||
align(4);
|
||||
root.tech_boost_table = w.size();
|
||||
rel.align(4);
|
||||
root.tech_boost_table = rel.w.size();
|
||||
for (size_t z = 0; z < pmt.num_tech_boosts(); z++) {
|
||||
w.put<TechBoostT<BE>>(pmt.get_tech_boost(z));
|
||||
rel.template put<TechBoostT<BE>>(pmt.get_tech_boost(z));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3138,8 +3118,8 @@ public:
|
||||
if constexpr (requires { root.unwrap_table; }) {
|
||||
for (size_t event = 0; event < pmt.num_events(); event++) {
|
||||
auto [event_items, num_items] = pmt.get_event_items(event);
|
||||
unwrap_table_refs.emplace_back(ArrayRefT<BE>{num_items, w.size()});
|
||||
w.write(event_items, sizeof(EventItem) * num_items);
|
||||
unwrap_table_refs.emplace_back(ArrayRefT<BE>{num_items, rel.w.size()});
|
||||
rel.write(event_items, sizeof(EventItem) * num_items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3147,95 +3127,65 @@ public:
|
||||
if constexpr (requires { root.unsealable_table; }) {
|
||||
const auto& items = pmt.all_unsealable_items();
|
||||
unsealable_table_ref.count = items.size();
|
||||
unsealable_table_ref.offset = w.size();
|
||||
unsealable_table_ref.offset = rel.w.size();
|
||||
for (const auto& item : items) {
|
||||
UnsealableItem encoded;
|
||||
u32_to_item_code(encoded.item, item);
|
||||
w.put<UnsealableItem>(encoded);
|
||||
rel.template put<UnsealableItem>(encoded);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayRefT<BE> ranged_specials_ref;
|
||||
if constexpr (requires { root.ranged_special_table; }) {
|
||||
ranged_specials_ref.count = pmt.num_ranged_specials();
|
||||
ranged_specials_ref.offset = w.size();
|
||||
ranged_specials_ref.offset = rel.w.size();
|
||||
for (size_t z = 0; z < ranged_specials_ref.count; z++) {
|
||||
w.put<RangedSpecial>(pmt.get_ranged_special(z));
|
||||
rel.template put<RangedSpecial>(pmt.get_ranged_special(z));
|
||||
}
|
||||
}
|
||||
|
||||
align(4);
|
||||
root.armor_table = w.size();
|
||||
write_ref(armors_ref);
|
||||
write_ref(shields_ref);
|
||||
root.unit_table = w.size();
|
||||
write_ref(units_ref);
|
||||
root.mag_table = w.size();
|
||||
write_ref(mags_ref);
|
||||
root.tool_table = w.size();
|
||||
rel.align(4);
|
||||
root.armor_table = rel.write_ref(armors_ref);
|
||||
rel.write_ref(shields_ref);
|
||||
root.unit_table = rel.write_ref(units_ref);
|
||||
root.mag_table = rel.write_ref(mags_ref);
|
||||
root.tool_table = rel.w.size();
|
||||
for (const auto& ref : tool_refs) {
|
||||
write_ref(ref);
|
||||
rel.write_ref(ref);
|
||||
}
|
||||
root.weapon_table = w.size();
|
||||
root.weapon_table = rel.w.size();
|
||||
for (const auto& ref : weapon_refs) {
|
||||
write_ref(ref);
|
||||
rel.write_ref(ref);
|
||||
}
|
||||
if constexpr (requires { root.combination_table; }) {
|
||||
root.combination_table = w.size();
|
||||
write_ref(combination_table_ref);
|
||||
root.combination_table = rel.write_ref(combination_table_ref);
|
||||
}
|
||||
if constexpr (requires { root.unwrap_table; }) {
|
||||
ArrayRefT<BE> event_ref{unwrap_table_refs.size(), w.size()};
|
||||
ArrayRefT<BE> event_ref{unwrap_table_refs.size(), rel.w.size()};
|
||||
for (const auto& ref : unwrap_table_refs) {
|
||||
write_ref(ref);
|
||||
rel.write_ref(ref);
|
||||
}
|
||||
root.unwrap_table = w.size();
|
||||
write_ref(event_ref);
|
||||
root.unwrap_table = rel.write_ref(event_ref);
|
||||
}
|
||||
if constexpr (requires { root.unsealable_table; }) {
|
||||
root.unsealable_table = w.size();
|
||||
write_ref(unsealable_table_ref);
|
||||
root.unsealable_table = rel.write_ref(unsealable_table_ref);
|
||||
}
|
||||
if constexpr (requires { root.ranged_special_table; }) {
|
||||
root.ranged_special_table = w.size();
|
||||
write_ref(ranged_specials_ref);
|
||||
root.ranged_special_table = rel.write_ref(ranged_specials_ref);
|
||||
}
|
||||
|
||||
root.mag_feed_table = w.size();
|
||||
w.put<MagFeedResultsListOffsetsT<BE>>(mag_feed_result_offsets);
|
||||
root.mag_feed_table = rel.template put<MagFeedResultsListOffsetsT<BE>>(mag_feed_result_offsets);
|
||||
for (size_t z = 1; z <= 8; z++) {
|
||||
relocations.emplace(w.size() - (z * 4));
|
||||
rel.relocations.emplace(rel.w.size() - (z * 4));
|
||||
}
|
||||
|
||||
RELFileFooterT<BE> footer;
|
||||
footer.root_offset = w.size();
|
||||
w.put<RootT>(root);
|
||||
uint32_t root_offset = rel.template put<RootT>(root);
|
||||
constexpr size_t root_field_count = (sizeof(RootT) / 4) - ((requires { root.entry_count; }) ? 1 : 0);
|
||||
for (size_t z = 1; z <= root_field_count; z++) {
|
||||
relocations.emplace(w.size() - (z * 4));
|
||||
rel.relocations.emplace(rel.w.size() - (z * 4));
|
||||
}
|
||||
|
||||
align(0x20);
|
||||
footer.relocations_offset = w.size();
|
||||
footer.num_relocations = relocations.size();
|
||||
footer.unused1[0] = 1;
|
||||
uint32_t last_offset = 0;
|
||||
for (uint32_t reloc_offset : relocations) {
|
||||
if (reloc_offset & 3) {
|
||||
throw logic_error("Relocation is not 4-byte aligned");
|
||||
}
|
||||
size_t reloc_value = (reloc_offset - last_offset) >> 2;
|
||||
if (reloc_value > 0xFFFF) {
|
||||
throw runtime_error("Relocation offset is too far away from previous");
|
||||
}
|
||||
w.put<U16T<BE>>(reloc_value);
|
||||
last_offset = reloc_offset;
|
||||
}
|
||||
|
||||
align(0x20);
|
||||
w.put<RELFileFooterT<BE>>(footer);
|
||||
|
||||
return std::move(w.str());
|
||||
return rel.finalize(root_offset);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
+182
-92
@@ -5,14 +5,13 @@
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
|
||||
stats.level = 0;
|
||||
stats.experience = 0;
|
||||
stats.exp = 0;
|
||||
stats.char_stats = this->base_stats_for_class(char_class);
|
||||
}
|
||||
|
||||
@@ -28,43 +27,111 @@ void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t ch
|
||||
stats.char_stats.dfp += level_stats.dfp;
|
||||
stats.char_stats.ata += level_stats.ata;
|
||||
// Note: It is not a bug that lck is ignored here; the original code ignores it too.
|
||||
stats.experience = level_stats.experience;
|
||||
stats.exp = level_stats.exp;
|
||||
}
|
||||
}
|
||||
|
||||
LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
// TODO: The overall format of this file on V2 has much more data than we actually use. What's known of the
|
||||
// structure so far:
|
||||
le_uint32_t level_deltas; // (5468) -> u32[9] -> LevelStatsDelta[200]
|
||||
le_uint32_t unknown_a1; // (548C) -> float[6]
|
||||
le_uint32_t max_stats; // (54A4) -> PlayerStats[9]
|
||||
le_uint32_t level_100_stats; // (55E8) -> PlayerStats[9]
|
||||
le_uint32_t base_stats; // (57AC) -> u32[9] -> CharacterStats
|
||||
le_uint32_t unknown_a2; // (57D0) -> (0x120 zero bytes)
|
||||
le_uint32_t attack_data; // (58F0) -> AttackData[9]
|
||||
le_uint32_t unknown_a4; // (5AA0) -> parray<parray<float, 5>, 9>
|
||||
le_uint32_t unknown_a5; // (5B54) -> float[9]
|
||||
le_uint32_t unknown_a6; // (5B78) -> (0x30 bytes)
|
||||
le_uint32_t unknown_a7; // (5BA8) -> (0x2D bytes)
|
||||
le_uint32_t unknown_a8; // (5E00) -> u32[3] -> float[0x2D]
|
||||
le_uint32_t unknown_a9; // (5DF4) -> (0x90 bytes)
|
||||
le_uint32_t unknown_a10; // (60D0) -> u32[3] -> (0x10-byte struct)[0x0C]
|
||||
le_uint32_t unknown_a11; // (616C) -> u32[3] -> (0x30-bytes)
|
||||
le_uint32_t unknown_a12; // (64FC) -> u32[3] -> (0x14-byte struct)[0x0F]
|
||||
} __packed_ws__(Offsets, 0x40);
|
||||
|
||||
phosg::StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = phosg::StringReader(decompressed_data);
|
||||
} else {
|
||||
r = phosg::StringReader(data);
|
||||
phosg::JSON LevelTable::json() const {
|
||||
auto base_stats_json = phosg::JSON::list();
|
||||
auto max_stats_json = phosg::JSON::list();
|
||||
auto level_deltas_json = phosg::JSON::list();
|
||||
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
|
||||
base_stats_json.emplace_back(this->base_stats_for_class(char_class).json());
|
||||
max_stats_json.emplace_back(this->max_stats_for_class(char_class).json());
|
||||
auto this_class_level_deltas_json = phosg::JSON::list();
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
this_class_level_deltas_json.emplace_back(this->stats_delta_for_level(char_class, level).json());
|
||||
}
|
||||
level_deltas_json.emplace_back(std::move(this_class_level_deltas_json));
|
||||
}
|
||||
return phosg::JSON::dict({
|
||||
{"BaseStats", std::move(base_stats_json)},
|
||||
{"MaxStats", std::move(max_stats_json)},
|
||||
{"LevelDeltas", std::move(level_deltas_json)},
|
||||
});
|
||||
}
|
||||
|
||||
JSONLevelTable::JSONLevelTable(const phosg::JSON& json) {
|
||||
const auto& base_stats_json = json.at("BaseStats").as_list();
|
||||
const auto& max_stats_json = json.at("MaxStats").as_list();
|
||||
const auto& level_deltas_json = json.at("LevelDeltas").as_list();
|
||||
for (size_t char_class = 0; char_class < base_stats_json.size(); char_class++) {
|
||||
this->base_stats.emplace_back(CharacterStats::from_json(*base_stats_json.at(char_class)));
|
||||
this->max_stats.emplace_back(PlayerStats::from_json(*max_stats_json.at(char_class)));
|
||||
const auto& this_class_level_deltas_json = level_deltas_json.at(char_class)->as_list();
|
||||
auto& parsed_deltas = this->level_deltas.emplace_back();
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
parsed_deltas[level] = LevelStatsDelta::from_json(*this_class_level_deltas_json.at(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t JSONLevelTable::num_char_classes() const {
|
||||
return this->base_stats.size();
|
||||
}
|
||||
|
||||
const CharacterStats& JSONLevelTable::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const PlayerStats& JSONLevelTable::max_stats_for_class(uint8_t char_class) const {
|
||||
return this->max_stats.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& JSONLevelTable::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV2::LevelTableV2(const string& data) {
|
||||
struct Root {
|
||||
// The overall format of this file on V2 has much more data than we actually use. This table is sorted by the
|
||||
// offset in the PlayerTable.prs file; note that the offset fields in this structure do not match that order.
|
||||
// ## OFFS WHAT -> TARGET
|
||||
// 0008 level_deltas[0] -> LevelStatsDelta[200] (by level) (the rest follow immediately)
|
||||
// 00 5468 level_deltas -> u32[9] (by char_class)
|
||||
// 04 548C hp_tp_factors -> HPTPFactors[3] (by char_class_class; [0] = hunter, [1] = ranger, [2] = force)
|
||||
// 08 54A4 max_stats -> PlayerStats[9] (by char_class)
|
||||
// 0C 55E8 level_100_stats -> PlayerStats[9] (by char_class)
|
||||
// 572C base_stats[0] -> CharacterStats
|
||||
// 10 57AC base_stats -> u32[9] (by char_class)
|
||||
// 14 57D0 resist_data -> ResistData[9] (by char_class)
|
||||
// 18 58F0 attack_data -> AttackData[9] (by char_class)
|
||||
// 1C 5AA0 unknown_a4 -> float[15][3] (by [???][attack_number])
|
||||
// 20 5B54 unknown_a5 -> float[3][3] (by [strike_number][attack_number])
|
||||
// 24 5B78 unknown_a6 -> float[3][3] (by [strike_number][attack_number]) (may be [4][3] in original code; there are 0xC zero bytes after)
|
||||
// 28 5BA8 unknown_a7 -> uint8_t[15][3] (same indexes as unknown_a4)
|
||||
// 5BD8 unknown_a9[0] -> UnknownA9[15] (index unknown; appears animation-related)
|
||||
// 30 5DF4 unknown_a9 -> u32[3] (by char_class_class)
|
||||
// 2C 5E00 area_sound_configs -> AreaSoundConfig[0x12] (by area)
|
||||
// 5E90 unknown_a10[0] -> (0x10-byte struct)[0x0C] (the rest follow immediately)
|
||||
// 34 60D0 unknown_a10 -> u32[3] (by char_class_class)
|
||||
// 60DC unknown_a11[0] -> WeaponReference[12] (the rest follow immediately)
|
||||
// 38 616C unknown_a11 -> u32[3] (by char_class_class)
|
||||
// 6178 unknown_a12[0] -> UnknownA12[15] (the rest follow immediately)
|
||||
// 3C 64FC unknown_a12 -> u32[3] (by char_class_class)
|
||||
|
||||
/* 00 / 5468 * */ le_uint32_t level_deltas;
|
||||
/* 04 / 548C * */ le_uint32_t hp_tp_factors;
|
||||
/* 08 / 54A4 * */ le_uint32_t max_stats;
|
||||
/* 0C / 55E8 * */ le_uint32_t level_100_stats;
|
||||
/* 10 / 57AC * */ le_uint32_t base_stats;
|
||||
/* 14 / 57D0 * */ le_uint32_t resist_data;
|
||||
/* 18 / 58F0 * */ le_uint32_t attack_data;
|
||||
/* 1C / 5AA0 * */ le_uint32_t unknown_a4;
|
||||
/* 20 / 5B54 * */ le_uint32_t unknown_a5;
|
||||
/* 24 / 5B78 * */ le_uint32_t unknown_a6;
|
||||
/* 28 / 5BA8 * */ le_uint32_t unknown_a7;
|
||||
/* 2C / 5E00 * */ le_uint32_t area_sound_configs;
|
||||
/* 30 / 5DF4 * */ le_uint32_t unknown_a9;
|
||||
/* 34 / 60D0 * */ le_uint32_t unknown_a10;
|
||||
/* 38 / 616C * */ le_uint32_t unknown_a11;
|
||||
/* 3C / 64FC * */ le_uint32_t unknown_a12;
|
||||
} __packed_ws__(Root, 0x40);
|
||||
|
||||
phosg::StringReader r(data);
|
||||
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& offsets = r.pget<Root>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 9; char_class++) {
|
||||
@@ -73,17 +140,16 @@ LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
this->level_deltas[char_class][level] = src_level_deltas[level];
|
||||
}
|
||||
this->max_stats[char_class] = r.pget<PlayerStats>(offsets.max_stats + char_class * sizeof(PlayerStats));
|
||||
this->level_100_stats[char_class] = r.pget<PlayerStats>(offsets.level_100_stats + char_class * sizeof(PlayerStats));
|
||||
this->base_stats[char_class] = r.pget<CharacterStats>(base_stats_offsets[char_class]);
|
||||
}
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
size_t LevelTableV2::num_char_classes() const {
|
||||
return 9;
|
||||
}
|
||||
|
||||
const PlayerStats& LevelTableV2::level_100_stats_for_class(uint8_t char_class) const {
|
||||
return this->level_100_stats.at(char_class);
|
||||
const CharacterStats& LevelTableV2::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
const PlayerStats& LevelTableV2::max_stats_for_class(uint8_t char_class) const {
|
||||
@@ -94,46 +160,11 @@ const LevelStatsDelta& LevelTableV2::stats_delta_for_level(uint8_t char_class, u
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
|
||||
phosg::StringReader r;
|
||||
string decompressed_data;
|
||||
if (encrypted) {
|
||||
auto decrypted = decrypt_pr2_data<true>(data);
|
||||
decompressed_data = prs_decompress(decrypted.compressed_data);
|
||||
if (decompressed_data.size() != decrypted.decompressed_size) {
|
||||
throw runtime_error("decompressed data size does not match expected size");
|
||||
}
|
||||
r = phosg::StringReader(decompressed_data);
|
||||
} else {
|
||||
r = phosg::StringReader(data);
|
||||
}
|
||||
|
||||
// The GC format is very simple (but everything is big-endian):
|
||||
// root:
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(footer.root_offset));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
const auto& src_delta = src_deltas[level];
|
||||
auto& dest_delta = this->level_deltas[char_class][level];
|
||||
dest_delta.atp = src_delta.atp;
|
||||
dest_delta.mst = src_delta.mst;
|
||||
dest_delta.evp = src_delta.evp;
|
||||
dest_delta.hp = src_delta.hp;
|
||||
dest_delta.dfp = src_delta.dfp;
|
||||
dest_delta.ata = src_delta.ata;
|
||||
dest_delta.lck = src_delta.lck;
|
||||
dest_delta.tp = src_delta.tp;
|
||||
dest_delta.experience = src_delta.experience;
|
||||
}
|
||||
}
|
||||
size_t LevelTableV3::num_char_classes() const {
|
||||
return 12;
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV3BE::base_stats_for_class(uint8_t char_class) const {
|
||||
const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) const {
|
||||
static const array<CharacterStats, 12> data = {
|
||||
// ATP MST EVP HP DFP ATA LCK
|
||||
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
|
||||
@@ -168,31 +199,86 @@ static const array<PlayerStats, 12> max_stats_v3_v4 = {
|
||||
PlayerStats{{0x0474, 0x0407, 0x0384, 0x02CF, 0x0241, 0x06C2, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
||||
};
|
||||
|
||||
const PlayerStats& LevelTableV3BE::max_stats_for_class(uint8_t char_class) const {
|
||||
const PlayerStats& LevelTableV3::max_stats_for_class(uint8_t char_class) const {
|
||||
return max_stats_v3_v4.at(char_class);
|
||||
}
|
||||
|
||||
const LevelStatsDelta& LevelTableV3BE::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
const LevelStatsDelta& LevelTableV3::stats_delta_for_level(uint8_t char_class, uint8_t level) const {
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
struct Offsets {
|
||||
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
|
||||
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
|
||||
} __packed_ws__(Offsets, 8);
|
||||
template <bool BE>
|
||||
void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& deltas, const string& data) {
|
||||
// The V3 format is very simple:
|
||||
// root:
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
phosg::StringReader r(data);
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
const auto& offsets = r.pget<parray<U32T<BE>, 12>>(r.pget<U32T<BE>>(footer.root_offset));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaT<BE>, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
deltas[char_class][level] = src_deltas[level];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phosg::StringReader r;
|
||||
string decompressed_data;
|
||||
if (compressed) {
|
||||
decompressed_data = prs_decompress(data);
|
||||
r = phosg::StringReader(decompressed_data);
|
||||
} else {
|
||||
r = phosg::StringReader(data);
|
||||
LevelTableGC::LevelTableGC(const string& data) {
|
||||
parse_level_deltas_t<true>(this->level_deltas, data);
|
||||
}
|
||||
|
||||
LevelTableXB::LevelTableXB(const string& data) {
|
||||
parse_level_deltas_t<false>(this->level_deltas, data);
|
||||
}
|
||||
|
||||
struct RootV4 {
|
||||
le_uint32_t base_stats; // -> u32[12] -> CharacterStats
|
||||
le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200]
|
||||
} __packed_ws__(RootV4, 8);
|
||||
|
||||
std::string LevelTable::serialize_binary_v4() const {
|
||||
RELFileWriter<false> rel;
|
||||
RootV4 root;
|
||||
|
||||
{
|
||||
std::vector<uint32_t> offsets;
|
||||
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
|
||||
offsets.emplace_back(rel.put<CharacterStats>(this->base_stats_for_class(char_class)));
|
||||
}
|
||||
root.base_stats = rel.w.size();
|
||||
for (uint32_t offset : offsets) {
|
||||
rel.write_offset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<uint32_t> offsets;
|
||||
for (size_t char_class = 0; char_class < this->num_char_classes(); char_class++) {
|
||||
offsets.emplace_back(rel.w.size());
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
rel.put<LevelStatsDelta>(this->stats_delta_for_level(char_class, level));
|
||||
}
|
||||
}
|
||||
root.level_deltas = rel.w.size();
|
||||
for (uint32_t offset : offsets) {
|
||||
rel.write_offset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
size_t root_offset = rel.put<RootV4>(root);
|
||||
rel.relocations.emplace(root_offset);
|
||||
rel.relocations.emplace(root_offset + 4);
|
||||
|
||||
return rel.finalize(root_offset);
|
||||
}
|
||||
|
||||
LevelTableV4::LevelTableV4(const string& data) {
|
||||
|
||||
phosg::StringReader r(data);
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& offsets = r.pget<RootV4>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
@@ -204,6 +290,10 @@ LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t LevelTableV4::num_char_classes() const {
|
||||
return 12;
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV4::base_stats_for_class(uint8_t char_class) const {
|
||||
return this->base_stats.at(char_class);
|
||||
}
|
||||
|
||||
+168
-41
@@ -21,17 +21,34 @@ struct CharacterStatsT {
|
||||
/* 0A */ U16T<BE> ata = 0;
|
||||
/* 0C */ U16T<BE> lck = 0;
|
||||
/* 0E */
|
||||
|
||||
static CharacterStatsT<BE> from_json(const phosg::JSON& json) {
|
||||
return CharacterStatsT<BE>{
|
||||
json.at("ATP").as_int(),
|
||||
json.at("MST").as_int(),
|
||||
json.at("EVP").as_int(),
|
||||
json.at("HP").as_int(),
|
||||
json.at("DFP").as_int(),
|
||||
json.at("ATA").as_int(),
|
||||
json.at("LCK").as_int()};
|
||||
}
|
||||
phosg::JSON json() const {
|
||||
return phosg::JSON::dict({{"ATP", this->atp.load()},
|
||||
{"MST", this->mst.load()},
|
||||
{"EVP", this->evp.load()},
|
||||
{"HP", this->hp.load()},
|
||||
{"DFP", this->dfp.load()},
|
||||
{"ATA", this->ata.load()},
|
||||
{"LCK", this->lck.load()}});
|
||||
}
|
||||
operator CharacterStatsT<!BE>() const {
|
||||
CharacterStatsT<!BE> ret;
|
||||
ret.atp = this->atp;
|
||||
ret.mst = this->mst;
|
||||
ret.evp = this->evp;
|
||||
ret.hp = this->hp;
|
||||
ret.dfp = this->dfp;
|
||||
ret.ata = this->ata;
|
||||
ret.lck = this->lck;
|
||||
return ret;
|
||||
return CharacterStatsT<!BE>{
|
||||
this->atp.load(),
|
||||
this->mst.load(),
|
||||
this->evp.load(),
|
||||
this->hp.load(),
|
||||
this->dfp.load(),
|
||||
this->ata.load(),
|
||||
this->lck.load()};
|
||||
}
|
||||
} __packed_ws_be__(CharacterStatsT, 0x0E);
|
||||
using CharacterStats = CharacterStatsT<false>;
|
||||
@@ -44,37 +61,82 @@ struct PlayerStatsT {
|
||||
/* 10 */ F32T<BE> attack_range = 0.0;
|
||||
/* 14 */ F32T<BE> knockback_range = 0.0;
|
||||
/* 18 */ U32T<BE> level = 0; // Qedit specifies this as tech level when used for enemies
|
||||
/* 1C */ U32T<BE> experience = 0;
|
||||
/* 1C */ U32T<BE> exp = 0;
|
||||
/* 20 */ U32T<BE> meseta = 0; // Qedit specifies this as TP when used for enemies
|
||||
/* 24 */
|
||||
|
||||
operator PlayerStatsT<!BE>() const {
|
||||
PlayerStatsT<!BE> ret;
|
||||
ret.char_stats = this->char_stats;
|
||||
ret.esp = this->esp;
|
||||
ret.attack_range = this->attack_range;
|
||||
ret.knockback_range = this->knockback_range;
|
||||
ret.level = this->level;
|
||||
ret.experience = this->experience;
|
||||
ret.meseta = this->meseta;
|
||||
static PlayerStatsT<BE> from_json(const phosg::JSON& json) {
|
||||
return PlayerStatsT<BE>{
|
||||
CharacterStatsT<BE>::from_json(json),
|
||||
json.at("ESP").as_int(),
|
||||
json.at("AttackRange").as_float(),
|
||||
json.at("KnockbackRange").as_float(),
|
||||
json.at("Level").as_int(),
|
||||
json.at("EXP").as_int(),
|
||||
json.at("Meseta").as_int()};
|
||||
}
|
||||
phosg::JSON json() const {
|
||||
auto ret = this->char_stats.json();
|
||||
ret.emplace("ESP", this->esp.load());
|
||||
ret.emplace("AttackRange", this->attack_range.load());
|
||||
ret.emplace("KnockbackRange", this->knockback_range.load());
|
||||
ret.emplace("Level", this->level.load());
|
||||
ret.emplace("EXP", this->exp.load());
|
||||
ret.emplace("Meseta", this->meseta.load());
|
||||
return ret;
|
||||
}
|
||||
operator PlayerStatsT<!BE>() const {
|
||||
return PlayerStatsT<!BE>{
|
||||
this->char_stats,
|
||||
this->esp.load(),
|
||||
this->attack_range.load(),
|
||||
this->knockback_range.load(),
|
||||
this->level.load(),
|
||||
this->exp.load(),
|
||||
this->meseta.load()};
|
||||
}
|
||||
} __packed_ws_be__(PlayerStatsT, 0x24);
|
||||
using PlayerStats = PlayerStatsT<false>;
|
||||
using PlayerStatsBE = PlayerStatsT<true>;
|
||||
|
||||
template <bool BE>
|
||||
struct LevelStatsDeltaT {
|
||||
/* 00 */ uint8_t atp;
|
||||
/* 01 */ uint8_t mst;
|
||||
/* 02 */ uint8_t evp;
|
||||
/* 03 */ uint8_t hp;
|
||||
/* 04 */ uint8_t dfp;
|
||||
/* 05 */ uint8_t ata;
|
||||
/* 06 */ uint8_t lck;
|
||||
/* 07 */ uint8_t tp;
|
||||
/* 08 */ U32T<BE> experience;
|
||||
/* 00 */ uint8_t atp = 0;
|
||||
/* 01 */ uint8_t mst = 0;
|
||||
/* 02 */ uint8_t evp = 0;
|
||||
/* 03 */ uint8_t hp = 0;
|
||||
/* 04 */ uint8_t dfp = 0;
|
||||
/* 05 */ uint8_t ata = 0;
|
||||
/* 06 */ uint8_t lck = 0;
|
||||
/* 07 */ uint8_t tp = 0;
|
||||
/* 08 */ U32T<BE> exp = 0;
|
||||
/* 0C */
|
||||
static LevelStatsDeltaT<BE> from_json(const phosg::JSON& json) {
|
||||
return LevelStatsDeltaT<BE>{
|
||||
static_cast<uint8_t>(json.at("ATP").as_int()),
|
||||
static_cast<uint8_t>(json.at("MST").as_int()),
|
||||
static_cast<uint8_t>(json.at("EVP").as_int()),
|
||||
static_cast<uint8_t>(json.at("HP").as_int()),
|
||||
static_cast<uint8_t>(json.at("DFP").as_int()),
|
||||
static_cast<uint8_t>(json.at("ATA").as_int()),
|
||||
static_cast<uint8_t>(json.at("LCK").as_int()),
|
||||
static_cast<uint8_t>(json.at("TP").as_int()),
|
||||
static_cast<uint32_t>(json.at("EXP").as_int())};
|
||||
}
|
||||
phosg::JSON json() const {
|
||||
return phosg::JSON::dict({{"ATP", this->atp},
|
||||
{"MST", this->mst},
|
||||
{"EVP", this->evp},
|
||||
{"HP", this->hp},
|
||||
{"DFP", this->dfp},
|
||||
{"ATA", this->ata},
|
||||
{"LCK", this->lck},
|
||||
{"TP", this->tp},
|
||||
{"EXP", this->exp.load()}});
|
||||
}
|
||||
operator LevelStatsDeltaT<!BE>() const {
|
||||
return LevelStatsDeltaT<!BE>{
|
||||
this->atp, this->mst, this->evp, this->hp, this->dfp, this->ata, this->lck, this->tp, this->exp.load()};
|
||||
}
|
||||
|
||||
void apply(CharacterStats& ps) const {
|
||||
ps.ata += this->ata;
|
||||
@@ -95,6 +157,8 @@ class LevelTable {
|
||||
// Offsets structures inside the subclasses' constructor implementations for more details on the file formats.
|
||||
public:
|
||||
virtual ~LevelTable() = default;
|
||||
|
||||
virtual size_t num_char_classes() const = 0;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const = 0;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0;
|
||||
@@ -102,50 +166,113 @@ public:
|
||||
void reset_to_base(PlayerStats& stats, uint8_t char_class) const;
|
||||
void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const;
|
||||
|
||||
std::string serialize_binary_v4() const;
|
||||
phosg::JSON json() const;
|
||||
|
||||
protected:
|
||||
LevelTable() = default;
|
||||
};
|
||||
|
||||
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
|
||||
class JSONLevelTable : public LevelTable {
|
||||
public:
|
||||
LevelTableV2(const std::string& data, bool compressed);
|
||||
virtual ~LevelTableV2() = default;
|
||||
JSONLevelTable(const phosg::JSON& json);
|
||||
virtual ~JSONLevelTable() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
const PlayerStats& level_100_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
std::vector<CharacterStats> base_stats;
|
||||
std::vector<PlayerStats> max_stats;
|
||||
std::vector<std::array<LevelStatsDelta, 200>> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV2 : public LevelTable { // from PlayerTable.prs (PC)
|
||||
public:
|
||||
struct HPTPFactors {
|
||||
le_float hp_factor;
|
||||
le_float tp_factor;
|
||||
} __packed_ws__(HPTPFactors, 8);
|
||||
|
||||
struct UnknownA9 {
|
||||
le_float unknown_a1;
|
||||
le_float unknown_a2;
|
||||
le_float unknown_a3;
|
||||
} __packed_ws__(UnknownA9, 0x0C);
|
||||
|
||||
struct AreaSoundConfig {
|
||||
le_uint16_t step_sound;
|
||||
le_uint16_t grass_step_sound;
|
||||
le_uint16_t water_step_sound;
|
||||
parray<uint8_t, 2> unused;
|
||||
} __packed_ws__(AreaSoundConfig, 8);
|
||||
|
||||
struct WeaponReference {
|
||||
le_uint16_t data1_1;
|
||||
le_uint16_t data1_2;
|
||||
} __packed_ws__(WeaponReference, 4);
|
||||
|
||||
struct UnknownA12 {
|
||||
le_float unknown_a1;
|
||||
le_float unknown_a2;
|
||||
le_float unknown_a3;
|
||||
le_float unknown_a4;
|
||||
le_uint32_t unknown_a5;
|
||||
} __packed_ws__(UnknownA12, 0x14);
|
||||
|
||||
explicit LevelTableV2(const std::string& data);
|
||||
virtual ~LevelTableV2() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
protected:
|
||||
std::array<CharacterStats, 9> base_stats;
|
||||
std::array<PlayerStats, 9> level_100_stats;
|
||||
std::array<PlayerStats, 9> max_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 9> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableV3BE : public LevelTable { // from PlyLevelTbl.cpt (GC)
|
||||
class LevelTableV3 : public LevelTable { // from PlyLevelTbl.cpt (GC/XB)
|
||||
public:
|
||||
LevelTableV3BE(const std::string& data, bool encrypted);
|
||||
virtual ~LevelTableV3BE() = default;
|
||||
virtual ~LevelTableV3() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
LevelTableV3() = default;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
|
||||
class LevelTableGC : public LevelTableV3 {
|
||||
public:
|
||||
explicit LevelTableGC(const std::string& data);
|
||||
virtual ~LevelTableGC() = default;
|
||||
};
|
||||
|
||||
class LevelTableXB : public LevelTableV3 {
|
||||
public:
|
||||
explicit LevelTableXB(const std::string& data);
|
||||
virtual ~LevelTableXB() = default;
|
||||
};
|
||||
|
||||
class LevelTableV4 : public LevelTable { // from PlyLevelTbl.prs (BB)
|
||||
public:
|
||||
LevelTableV4(const std::string& data, bool compressed);
|
||||
explicit LevelTableV4(const std::string& data);
|
||||
virtual ~LevelTableV4() = default;
|
||||
|
||||
virtual size_t num_char_classes() const;
|
||||
virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const;
|
||||
virtual const PlayerStats& max_stats_for_class(uint8_t char_class) const;
|
||||
virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::array<CharacterStats, 12> base_stats;
|
||||
std::array<std::array<LevelStatsDelta, 200>, 12> level_deltas;
|
||||
};
|
||||
|
||||
+44
-3
@@ -2429,6 +2429,50 @@ Action a_encode_item_parameter_table(
|
||||
write_output_data(args, data.data(), data.size(), nullptr);
|
||||
});
|
||||
|
||||
Action a_decode_level_table(
|
||||
"decode-level-table", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
auto input_data = read_input_data(args);
|
||||
std::shared_ptr<LevelTable> table;
|
||||
bool decompressed = args.get<bool>("decompressed");
|
||||
switch (get_cli_version(args)) {
|
||||
case Version::PC_V2:
|
||||
table = std::make_shared<LevelTableV2>(decompressed ? input_data : prs_decompress(input_data));
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
table = std::make_shared<LevelTableGC>(
|
||||
decompressed ? input_data : decrypt_and_decompress_pr2_data<true>(input_data));
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
table = std::make_shared<LevelTableXB>(
|
||||
decompressed ? input_data : decrypt_and_decompress_pr2_data<false>(input_data));
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
table = std::make_shared<LevelTableV4>(decompressed ? input_data : prs_decompress(input_data));
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("This version does not have a level table");
|
||||
}
|
||||
auto json = table->json();
|
||||
uint32_t serialize_options = phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::SORT_DICT_KEYS;
|
||||
if (args.get<bool>("hex")) {
|
||||
serialize_options |= phosg::JSON::SerializeOption::HEX_INTEGERS;
|
||||
}
|
||||
string json_data = json.serialize(serialize_options);
|
||||
write_output_data(args, json_data.data(), json_data.size(), nullptr);
|
||||
});
|
||||
|
||||
Action a_encode_level_table(
|
||||
"encode-level-table-v4", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
JSONLevelTable table(phosg::JSON::parse(read_input_data(args)));
|
||||
string data = table.serialize_binary_v4();
|
||||
if (!args.get<bool>("decompressed")) {
|
||||
data = prs_compress_optimal(data);
|
||||
}
|
||||
write_output_data(args, data.data(), data.size(), nullptr);
|
||||
});
|
||||
|
||||
Action a_find_rel_sectionr(
|
||||
"find-rel-sections", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
@@ -2616,7 +2660,6 @@ Action a_print_level_stats(
|
||||
|
||||
vector<PlayerStats> level_1_v1_v2;
|
||||
vector<PlayerStats> level_100_v1_v2;
|
||||
vector<PlayerStats> level_100_limit_v1_v2;
|
||||
vector<PlayerStats> level_200_v1_v2;
|
||||
vector<PlayerStats> level_200_limit_v1_v2;
|
||||
vector<PlayerStats> level_1_v3;
|
||||
@@ -2628,7 +2671,6 @@ Action a_print_level_stats(
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (z < 9) {
|
||||
level_1_v1_v2.emplace_back().char_stats = s->level_table_v1_v2->base_stats_for_class(z);
|
||||
level_100_limit_v1_v2.emplace_back(s->level_table_v1_v2->level_100_stats_for_class(z));
|
||||
level_200_limit_v1_v2.emplace_back(s->level_table_v1_v2->max_stats_for_class(z));
|
||||
s->level_table_v1_v2->advance_to_level(level_100_v1_v2.emplace_back(level_1_v1_v2.back()), 99, z);
|
||||
s->level_table_v1_v2->advance_to_level(level_200_v1_v2.emplace_back(level_1_v1_v2.back()), 199, z);
|
||||
@@ -2682,7 +2724,6 @@ Action a_print_level_stats(
|
||||
|
||||
print_stats_set(level_1_v1_v2, "v1/v2 Lv.1 ");
|
||||
print_stats_set(level_100_v1_v2, "v1/v2 Lv.100");
|
||||
print_stats_set(level_100_limit_v1_v2, "v1 limit ");
|
||||
print_stats_set(level_200_v1_v2, "v2 Lv.200 ");
|
||||
print_stats_set(level_200_limit_v1_v2, "v2 limit ");
|
||||
print_stats_set(level_1_v3, "v3 Lv.1 ");
|
||||
|
||||
@@ -334,7 +334,7 @@ using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T<false>;
|
||||
using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T<true>;
|
||||
|
||||
struct PlayerDispDataBBPreview {
|
||||
/* 00 */ le_uint32_t experience = 0;
|
||||
/* 00 */ le_uint32_t exp = 0;
|
||||
/* 04 */ le_uint32_t level = 0;
|
||||
// The name field in this structure is used for the player's Guild Card number, apparently (possibly because it's a
|
||||
// char array and this is BB)
|
||||
|
||||
+1
-1
@@ -3070,7 +3070,7 @@ std::string disassemble_quest_script(
|
||||
l->lines.emplace_back(std::format(" {:04X} attack_range {:08X} /* {:g} */", l->offset + offsetof(PlayerStats, attack_range), stats.attack_range.load_raw(), stats.attack_range));
|
||||
l->lines.emplace_back(std::format(" {:04X} knockback_range {:08X} /* {:g} */", l->offset + offsetof(PlayerStats, knockback_range), stats.knockback_range.load_raw(), stats.knockback_range));
|
||||
l->lines.emplace_back(std::format(" {:04X} level {:08X} /* level {} */", l->offset + offsetof(PlayerStats, level), stats.level, stats.level + 1));
|
||||
l->lines.emplace_back(std::format(" {:04X} experience {:08X} /* {} */", l->offset + offsetof(PlayerStats, experience), stats.experience, stats.experience));
|
||||
l->lines.emplace_back(std::format(" {:04X} exp {:08X} /* {} */", l->offset + offsetof(PlayerStats, exp), stats.exp, stats.exp));
|
||||
l->lines.emplace_back(std::format(" {:04X} meseta {:08X} /* {} */", l->offset + offsetof(PlayerStats, meseta), stats.meseta, stats.meseta));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3947,7 +3947,7 @@ static void add_player_exp(shared_ptr<Client> c, uint32_t exp, uint16_t from_ene
|
||||
auto s = c->require_server_state();
|
||||
auto p = c->character_file();
|
||||
|
||||
p->disp.stats.experience += exp;
|
||||
p->disp.stats.exp += exp;
|
||||
if (c->version() == Version::BB_V4) {
|
||||
send_give_experience(c, exp, from_enemy_id);
|
||||
}
|
||||
@@ -3955,7 +3955,7 @@ static void add_player_exp(shared_ptr<Client> c, uint32_t exp, uint16_t from_ene
|
||||
bool leveled_up = false;
|
||||
do {
|
||||
const auto& level = s->level_table(c->version())->stats_delta_for_level(p->disp.visual.char_class, p->disp.stats.level + 1);
|
||||
if (p->disp.stats.experience >= level.experience) {
|
||||
if (p->disp.stats.exp >= level.exp) {
|
||||
leveled_up = true;
|
||||
level.apply(p->disp.stats.char_stats);
|
||||
p->disp.stats.level++;
|
||||
@@ -4017,7 +4017,7 @@ static uint32_t base_exp_for_enemy_type(
|
||||
const auto& bp_table = bp_index->get_table(is_solo, episode);
|
||||
const auto& bp_stats_indexes = type_definition_for_enemy(enemy_type).bp_stats_indexes;
|
||||
if (!bp_stats_indexes.empty()) {
|
||||
return bp_table.stats_for_index(difficulty, bp_stats_indexes.back()).experience;
|
||||
return bp_table.stats_for_index(difficulty, bp_stats_indexes.back()).exp;
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
@@ -4700,8 +4700,8 @@ static asio::awaitable<void> on_battle_level_up_bb(shared_ptr<Client> c, Subcomm
|
||||
auto s = c->require_server_state();
|
||||
auto lp = lc->character_file();
|
||||
uint32_t target_level = min<uint32_t>(lp->disp.stats.level + cmd.num_levels, 199);
|
||||
uint32_t before_exp = lp->disp.stats.experience;
|
||||
int32_t exp_delta = lp->disp.stats.experience - before_exp;
|
||||
uint32_t before_exp = lp->disp.stats.exp;
|
||||
int32_t exp_delta = lp->disp.stats.exp - before_exp;
|
||||
if (exp_delta > 0) {
|
||||
s->level_table(lc->version())->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class);
|
||||
if (lc->version() == Version::BB_V4) {
|
||||
|
||||
@@ -370,7 +370,7 @@ PSOBBBaseSystemFile::PSOBBBaseSystemFile() {
|
||||
PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const {
|
||||
PlayerDispDataBBPreview pre;
|
||||
pre.level = this->disp.stats.level;
|
||||
pre.experience = this->disp.stats.experience;
|
||||
pre.exp = this->disp.stats.exp;
|
||||
pre.visual = this->disp.visual;
|
||||
pre.name = this->disp.name;
|
||||
pre.play_time_seconds = this->play_time_seconds;
|
||||
@@ -1429,11 +1429,11 @@ void PSOBBCharacterFile::import_tethealla_material_usage(std::shared_ptr<const L
|
||||
|
||||
void PSOBBCharacterFile::recompute_stats(std::shared_ptr<const LevelTable> level_table, bool reset_exp) {
|
||||
uint32_t level = this->disp.stats.level;
|
||||
uint32_t exp = this->disp.stats.experience;
|
||||
uint32_t exp = this->disp.stats.exp;
|
||||
level_table->reset_to_base(this->disp.stats, this->disp.visual.char_class);
|
||||
level_table->advance_to_level(this->disp.stats, level, this->disp.visual.char_class);
|
||||
if (!reset_exp) {
|
||||
this->disp.stats.experience = exp;
|
||||
this->disp.stats.exp = exp;
|
||||
}
|
||||
|
||||
this->disp.stats.char_stats.atp += (this->get_material_usage(MaterialType::POWER) * 2);
|
||||
|
||||
+4
-4
@@ -1888,9 +1888,9 @@ void ServerState::load_battle_params() {
|
||||
|
||||
void ServerState::load_level_tables() {
|
||||
config_log.info_f("Loading level tables");
|
||||
this->level_table_v1_v2 = make_shared<LevelTableV2>(phosg::load_file("system/level-tables/PlayerTable-pc-v2.prs"), true);
|
||||
this->level_table_v3 = make_shared<LevelTableV3BE>(phosg::load_file("system/level-tables/PlyLevelTbl-gc-v3.cpt"), true);
|
||||
this->level_table_v4 = make_shared<LevelTableV4>(*this->load_bb_file("PlyLevelTbl.prs"), true);
|
||||
this->level_table_v1_v2 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file("system/level-tables/level-table-v1-v2.json")));
|
||||
this->level_table_v3 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file("system/level-tables/level-table-v3.json")));
|
||||
this->level_table_v4 = make_shared<JSONLevelTable>(phosg::JSON::parse(phosg::load_file("system/level-tables/level-table-v4.json")));
|
||||
}
|
||||
|
||||
void ServerState::load_text_index() {
|
||||
@@ -2248,7 +2248,7 @@ void ServerState::generate_bb_stream_file() {
|
||||
add_file("BattleParamEntry_lab_on.dat");
|
||||
add_file("BattleParamEntry_ep4.dat");
|
||||
add_file("BattleParamEntry_ep4_on.dat");
|
||||
add_file("PlyLevelTbl.prs");
|
||||
add_file("PlyLevelTbl.prs", prs_compress_optimal(this->level_table_v4->serialize_binary_v4()));
|
||||
add_file("ItemMagEdit.prs");
|
||||
auto pmt = this->item_parameter_table(Version::BB_V4);
|
||||
add_file("ItemPMT.prs", prs_compress_optimal(pmt->serialize_binary(Version::BB_V4)));
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
|
||||
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
|
||||
std::shared_ptr<const QuestIndex> quest_index;
|
||||
std::shared_ptr<const LevelTableV2> level_table_v1_v2;
|
||||
std::shared_ptr<const LevelTable> level_table_v1_v2;
|
||||
std::shared_ptr<const LevelTable> level_table_v3;
|
||||
std::shared_ptr<const LevelTable> level_table_v4;
|
||||
std::shared_ptr<const BattleParamsIndex> battle_params;
|
||||
|
||||
+12
-12
@@ -293,18 +293,18 @@ uint8_t npc_for_name(const string& name, Version version) {
|
||||
|
||||
const char* name_for_char_class(uint8_t cls) {
|
||||
static const array<const char*, 12> names = {
|
||||
/* 00 */ "HUmar", // 0
|
||||
/* 01 */ "HUnewearl", // 0
|
||||
/* 02 */ "HUcast", // 1
|
||||
/* 03 */ "RAmar", // 0
|
||||
/* 04 */ "RAcast", // 2
|
||||
/* 05 */ "RAcaseal", // 1
|
||||
/* 06 */ "FOmarl", // 0
|
||||
/* 07 */ "FOnewm", // 0
|
||||
/* 08 */ "FOnewearl", // 0
|
||||
/* 09 */ "HUcaseal", // 1
|
||||
/* 0A */ "FOmar", // 0
|
||||
/* 0B */ "RAmarl"}; // 0
|
||||
/* 00 */ "HUmar",
|
||||
/* 01 */ "HUnewearl",
|
||||
/* 02 */ "HUcast",
|
||||
/* 03 */ "RAmar",
|
||||
/* 04 */ "RAcast",
|
||||
/* 05 */ "RAcaseal",
|
||||
/* 06 */ "FOmarl",
|
||||
/* 07 */ "FOnewm",
|
||||
/* 08 */ "FOnewearl",
|
||||
/* 09 */ "HUcaseal",
|
||||
/* 0A */ "FOmar",
|
||||
/* 0B */ "RAmarl"};
|
||||
try {
|
||||
return names.at(cls);
|
||||
} catch (const out_of_range&) {
|
||||
|
||||
Reference in New Issue
Block a user