334 lines
14 KiB
C++
334 lines
14 KiB
C++
#include "BattleParamsIndex.hh"
|
|
|
|
#include <phosg/Filesystem.hh>
|
|
#include <phosg/Strings.hh>
|
|
|
|
#include "Loggers.hh"
|
|
#include "PSOEncryption.hh"
|
|
#include "StaticGameData.hh"
|
|
|
|
BattleParamsIndex::AttackData BattleParamsIndex::AttackData::from_json(const phosg::JSON& json) {
|
|
return AttackData{
|
|
json.get_int("MinATP"),
|
|
json.get_int("MaxATP"),
|
|
json.get_int("MinATA"),
|
|
json.get_int("MaxATA"),
|
|
json.get_float("DistanceX"),
|
|
json.get_int("Angle"),
|
|
json.get_float("DistanceY"),
|
|
json.get_int("UnknownA8"),
|
|
json.get_int("UnknownA9"),
|
|
json.get_int("UnknownA10"),
|
|
json.get_int("UnknownA11"),
|
|
json.get_int("UnknownA12"),
|
|
json.get_int("UnknownA13"),
|
|
json.get_int("UnknownA14"),
|
|
json.get_int("UnknownA15"),
|
|
json.get_int("UnknownA16"),
|
|
};
|
|
}
|
|
phosg::JSON BattleParamsIndex::AttackData::json() const {
|
|
return phosg::JSON::dict({
|
|
{"MinATP", this->min_atp.load()},
|
|
{"MaxATP", this->max_atp.load()},
|
|
{"MinATA", this->min_ata.load()},
|
|
{"MaxATA", this->max_ata.load()},
|
|
{"DistanceX", this->distance_x.load()},
|
|
{"Angle", this->angle.load()},
|
|
{"DistanceY", this->distance_y.load()},
|
|
{"UnknownA8", this->unknown_a8.load()},
|
|
{"UnknownA9", this->unknown_a9.load()},
|
|
{"UnknownA10", this->unknown_a10.load()},
|
|
{"UnknownA11", this->unknown_a11.load()},
|
|
{"UnknownA12", this->unknown_a12.load()},
|
|
{"UnknownA13", this->unknown_a13.load()},
|
|
{"UnknownA14", this->unknown_a14.load()},
|
|
{"UnknownA15", this->unknown_a15.load()},
|
|
{"UnknownA16", this->unknown_a16.load()},
|
|
});
|
|
}
|
|
|
|
BattleParamsIndex::ResistData BattleParamsIndex::ResistData::from_json(const phosg::JSON& json) {
|
|
return BattleParamsIndex::ResistData{
|
|
json.get_int("EVPBonus"),
|
|
json.get_int("EFR"),
|
|
json.get_int("EIC"),
|
|
json.get_int("ETH"),
|
|
json.get_int("ELT"),
|
|
json.get_int("EDK"),
|
|
json.get_int("UnknownA6"),
|
|
json.get_int("UnknownA7"),
|
|
json.get_int("UnknownA8"),
|
|
json.get_int("UnknownA9"),
|
|
json.get_int("DFPBonus"),
|
|
};
|
|
}
|
|
phosg::JSON BattleParamsIndex::ResistData::json() const {
|
|
return phosg::JSON::dict({
|
|
{"EVPBonus", this->evp_bonus.load()},
|
|
{"EFR", this->efr.load()},
|
|
{"EIC", this->eic.load()},
|
|
{"ETH", this->eth.load()},
|
|
{"ELT", this->elt.load()},
|
|
{"EDK", this->edk.load()},
|
|
{"UnknownA6", this->unknown_a6.load()},
|
|
{"UnknownA7", this->unknown_a7.load()},
|
|
{"UnknownA8", this->unknown_a8.load()},
|
|
{"UnknownA9", this->unknown_a9.load()},
|
|
{"DFPBonus", this->dfp_bonus.load()},
|
|
});
|
|
}
|
|
|
|
BattleParamsIndex::MovementData BattleParamsIndex::MovementData::from_json(const phosg::JSON& json) {
|
|
const auto& fparams_json = json.at("FParams").as_list();
|
|
const auto& iparams_json = json.at("IParams").as_list();
|
|
return BattleParamsIndex::MovementData{
|
|
fparams_json.at(0)->as_float(),
|
|
fparams_json.at(1)->as_float(),
|
|
fparams_json.at(2)->as_float(),
|
|
fparams_json.at(3)->as_float(),
|
|
fparams_json.at(4)->as_float(),
|
|
fparams_json.at(5)->as_float(),
|
|
iparams_json.at(0)->as_float(),
|
|
iparams_json.at(1)->as_float(),
|
|
iparams_json.at(2)->as_float(),
|
|
iparams_json.at(3)->as_float(),
|
|
iparams_json.at(4)->as_float(),
|
|
iparams_json.at(5)->as_float(),
|
|
};
|
|
}
|
|
phosg::JSON BattleParamsIndex::MovementData::json() const {
|
|
auto fparams_list = phosg::JSON::list({
|
|
this->fparam1.load(),
|
|
this->fparam2.load(),
|
|
this->fparam3.load(),
|
|
this->fparam4.load(),
|
|
this->fparam5.load(),
|
|
this->fparam6.load(),
|
|
});
|
|
auto iparams_list = phosg::JSON::list({
|
|
this->iparam1.load(),
|
|
this->iparam2.load(),
|
|
this->iparam3.load(),
|
|
this->iparam4.load(),
|
|
this->iparam5.load(),
|
|
this->iparam6.load(),
|
|
});
|
|
return phosg::JSON::dict({{"FParams", std::move(fparams_list)}, {"IParams", std::move(iparams_list)}});
|
|
}
|
|
|
|
BattleParamsIndex::Table BattleParamsIndex::Table::from_json(const phosg::JSON& json) {
|
|
BattleParamsIndex::Table ret;
|
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
|
const auto& diff_json = json.at(name_for_difficulty(difficulty));
|
|
for (size_t z = 0; z < 0x60; z++) {
|
|
const auto& entry_json = diff_json.at(z);
|
|
ret.stats[static_cast<size_t>(difficulty)][z] = PlayerStats::from_json(entry_json.at("Stats"));
|
|
ret.attack_data[static_cast<size_t>(difficulty)][z] = AttackData::from_json(entry_json.at("AttackData"));
|
|
ret.resist_data[static_cast<size_t>(difficulty)][z] = ResistData::from_json(entry_json.at("ResistData"));
|
|
ret.movement_data[static_cast<size_t>(difficulty)][z] = MovementData::from_json(entry_json.at("MovementData"));
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
phosg::JSON BattleParamsIndex::Table::json() const {
|
|
auto ret = phosg::JSON::dict();
|
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
|
auto diff_ret = phosg::JSON::list();
|
|
for (size_t z = 0; z < 0x60; z++) {
|
|
auto stats_json = this->stats_for_index(difficulty, z).json();
|
|
auto attack_data_json = this->attack_data_for_index(difficulty, z).json();
|
|
auto resist_data_json = this->resist_data_for_index(difficulty, z).json();
|
|
auto movement_data_json = this->movement_data_for_index(difficulty, z).json();
|
|
std::set<EnemyType> stats_names;
|
|
std::set<EnemyType> attack_data_names;
|
|
std::set<EnemyType> resist_data_names;
|
|
std::set<EnemyType> movement_data_names;
|
|
for (Episode episode : ALL_EPISODES_V4) {
|
|
for (const auto& enemy_type : enemy_types_for_battle_param_stats_index(episode, z)) {
|
|
stats_names.emplace(enemy_type);
|
|
}
|
|
for (const auto& enemy_type : enemy_types_for_battle_param_attack_data_index(episode, z)) {
|
|
attack_data_names.emplace(enemy_type);
|
|
}
|
|
for (const auto& enemy_type : enemy_types_for_battle_param_resist_data_index(episode, z)) {
|
|
resist_data_names.emplace(enemy_type);
|
|
}
|
|
for (const auto& enemy_type : enemy_types_for_battle_param_movement_data_index(episode, z)) {
|
|
movement_data_names.emplace(enemy_type);
|
|
}
|
|
}
|
|
auto stats_names_json = phosg::JSON::list();
|
|
for (EnemyType enemy_type : stats_names) {
|
|
stats_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
|
}
|
|
auto attack_data_names_json = phosg::JSON::list();
|
|
for (EnemyType enemy_type : attack_data_names) {
|
|
attack_data_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
|
}
|
|
auto resist_data_names_json = phosg::JSON::list();
|
|
for (EnemyType enemy_type : resist_data_names) {
|
|
resist_data_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
|
}
|
|
auto movement_data_names_json = phosg::JSON::list();
|
|
for (EnemyType enemy_type : movement_data_names) {
|
|
movement_data_names_json.emplace_back(phosg::name_for_enum(enemy_type));
|
|
}
|
|
stats_json.emplace("Enemies", std::move(stats_names_json));
|
|
attack_data_json.emplace("Enemies", std::move(attack_data_names_json));
|
|
resist_data_json.emplace("Enemies", std::move(resist_data_names_json));
|
|
movement_data_json.emplace("Enemies", std::move(movement_data_names_json));
|
|
diff_ret.emplace_back(phosg::JSON::dict({
|
|
{"BPIndex", z},
|
|
{"Stats", std::move(stats_json)},
|
|
{"AttackData", std::move(attack_data_json)},
|
|
{"ResistData", std::move(resist_data_json)},
|
|
{"MovementData", std::move(movement_data_json)},
|
|
}));
|
|
}
|
|
ret.emplace(name_for_difficulty(difficulty), std::move(diff_ret));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
|
phosg::fwrite_fmt(stream, "========== STATS\n");
|
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
|
phosg::fwrite_fmt(stream, "{} ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF NAMES\n",
|
|
abbreviation_for_difficulty(difficulty));
|
|
for (size_t z = 0; z < 0x60; z++) {
|
|
const auto& e = this->stats[static_cast<size_t>(difficulty)][z];
|
|
phosg::fwrite_fmt(stream, " {:02X} ", z);
|
|
std::string names_str;
|
|
for (auto type : enemy_types_for_battle_param_stats_index(episode, z)) {
|
|
if (!names_str.empty()) {
|
|
names_str += ", ";
|
|
}
|
|
names_str += phosg::name_for_enum(type);
|
|
}
|
|
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.exp, e.meseta, names_str);
|
|
fputc('\n', stream);
|
|
}
|
|
}
|
|
|
|
phosg::fwrite_fmt(stream, "========== ATTACK DATA\n");
|
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
|
phosg::fwrite_fmt(stream, "{} ZZ ATP- ATP+ ATA- ATA+ -DIST-X- -ANGLE-- -DIST-Y- -A8- -A9- A10- A11- --A12--- --A13--- --A14--- --A15--- --A16---\n",
|
|
abbreviation_for_difficulty(difficulty));
|
|
for (size_t z = 0; z < 0x60; z++) {
|
|
const auto& e = this->attack_data[static_cast<size_t>(difficulty)][z];
|
|
phosg::fwrite_fmt(stream,
|
|
" {:02X} {:04X} {:04X} {:04X} {:04X} {:8.3f} {:08X} {:8.3f} {:04X} {:04X} {:04X} {:04X} {:08X} {:08X} {:08X} {:08X} {:08X}",
|
|
z, e.min_atp, e.max_atp, e.min_ata, e.max_ata, e.distance_x, e.angle, e.distance_y, e.unknown_a8,
|
|
e.unknown_a9, e.unknown_a10, e.unknown_a11, e.unknown_a12, e.unknown_a13, e.unknown_a14, e.unknown_a15,
|
|
e.unknown_a16);
|
|
fputc('\n', stream);
|
|
}
|
|
}
|
|
|
|
phosg::fwrite_fmt(stream, "========== RESIST DATA\n");
|
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
|
phosg::fwrite_fmt(stream, "{} ZZ EVP- EFR- EIC- ETH- ELT- EDK- ---A6--- ---A7--- ---A8--- ---A9--- --DFP---\n",
|
|
abbreviation_for_difficulty(difficulty));
|
|
for (size_t z = 0; z < 0x60; z++) {
|
|
const auto& e = this->resist_data[static_cast<size_t>(difficulty)][z];
|
|
phosg::fwrite_fmt(stream,
|
|
" {:02X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:08X} {:08X} {:08X} {:08X} {:08X}",
|
|
z, e.evp_bonus, e.efr, e.eic, e.eth, e.elt, e.edk, e.unknown_a6, e.unknown_a7, e.unknown_a8, e.unknown_a9,
|
|
e.dfp_bonus);
|
|
fputc('\n', stream);
|
|
}
|
|
}
|
|
|
|
phosg::fwrite_fmt(stream, "========== MOVEMENT DATA\n");
|
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
|
phosg::fwrite_fmt(stream, "{} ZZ FPARAM-1 FPARAM-2 FPARAM-3 FPARAM-4 FPARAM-5 FPARAM-6 IPARAM-1 IPARAM-2 IPARAM-3 IPARAM-4 IPARAM-5 IPARAM-6\n",
|
|
abbreviation_for_difficulty(difficulty));
|
|
for (size_t z = 0; z < 0x60; z++) {
|
|
const auto& e = this->movement_data[static_cast<size_t>(difficulty)][z];
|
|
phosg::fwrite_fmt(stream,
|
|
" {:02X} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X}",
|
|
z, e.fparam1, e.fparam2, e.fparam3, e.fparam4, e.fparam5, e.fparam6,
|
|
e.iparam1, e.iparam2, e.iparam3, e.iparam4, e.iparam5, e.iparam6);
|
|
fputc('\n', stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
phosg::JSON BattleParamsIndex::json() const {
|
|
return phosg::JSON::dict({
|
|
{"Episode1-Online", this->get_table(false, Episode::EP1).json()},
|
|
{"Episode2-Online", this->get_table(false, Episode::EP2).json()},
|
|
{"Episode4-Online", this->get_table(false, Episode::EP4).json()},
|
|
{"Episode1-Solo", this->get_table(true, Episode::EP1).json()},
|
|
{"Episode2-Solo", this->get_table(true, Episode::EP2).json()},
|
|
{"Episode4-Solo", this->get_table(true, Episode::EP4).json()},
|
|
});
|
|
}
|
|
|
|
JSONBattleParamsIndex::JSONBattleParamsIndex(const phosg::JSON& json) {
|
|
this->tables[0][0] = Table::from_json(json.at("Episode1-Online"));
|
|
this->tables[0][1] = Table::from_json(json.at("Episode2-Online"));
|
|
this->tables[0][2] = Table::from_json(json.at("Episode4-Online"));
|
|
this->tables[1][0] = Table::from_json(json.at("Episode1-Solo"));
|
|
this->tables[1][1] = Table::from_json(json.at("Episode2-Solo"));
|
|
this->tables[1][2] = Table::from_json(json.at("Episode4-Solo"));
|
|
}
|
|
|
|
const BattleParamsIndex::Table& JSONBattleParamsIndex::get_table(bool solo, Episode episode) const {
|
|
switch (episode) {
|
|
case Episode::EP1:
|
|
return this->tables[!!solo][0];
|
|
case Episode::EP2:
|
|
return this->tables[!!solo][1];
|
|
case Episode::EP4:
|
|
return this->tables[!!solo][2];
|
|
default:
|
|
throw std::invalid_argument("invalid episode");
|
|
}
|
|
}
|
|
|
|
BinaryBattleParamsIndex::BinaryBattleParamsIndex(
|
|
std::shared_ptr<const std::string> data_on_ep1,
|
|
std::shared_ptr<const std::string> data_on_ep2,
|
|
std::shared_ptr<const std::string> data_on_ep4,
|
|
std::shared_ptr<const std::string> data_off_ep1,
|
|
std::shared_ptr<const std::string> data_off_ep2,
|
|
std::shared_ptr<const std::string> data_off_ep4) {
|
|
this->files[0][0].data = data_on_ep1;
|
|
this->files[0][1].data = data_on_ep2;
|
|
this->files[0][2].data = data_on_ep4;
|
|
this->files[1][0].data = data_off_ep1;
|
|
this->files[1][1].data = data_off_ep2;
|
|
this->files[1][2].data = data_off_ep4;
|
|
|
|
for (uint8_t is_solo = 0; is_solo < 2; is_solo++) {
|
|
for (uint8_t episode = 0; episode < 3; episode++) {
|
|
auto& file = this->files[is_solo][episode];
|
|
if (file.data->size() < sizeof(Table)) {
|
|
throw std::runtime_error(std::format(
|
|
"battle params table size is incorrect (expected {:X} bytes, have {:X} bytes; is_solo={}, episode={})",
|
|
sizeof(Table), file.data->size(), is_solo, episode));
|
|
}
|
|
file.table = reinterpret_cast<const Table*>(file.data->data());
|
|
}
|
|
}
|
|
}
|
|
|
|
const BattleParamsIndex::Table& BinaryBattleParamsIndex::get_table(bool solo, Episode episode) const {
|
|
switch (episode) {
|
|
case Episode::EP1:
|
|
return *this->files[!!solo][0].table;
|
|
case Episode::EP2:
|
|
return *this->files[!!solo][1].table;
|
|
case Episode::EP4:
|
|
return *this->files[!!solo][2].table;
|
|
default:
|
|
throw std::invalid_argument("invalid episode");
|
|
}
|
|
}
|