load non-v4 level tables

This commit is contained in:
Martin Michelsen
2024-05-17 20:32:52 -07:00
parent f71980382a
commit d8230eb37a
11 changed files with 110 additions and 35 deletions
+3 -2
View File
@@ -1299,8 +1299,9 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
p->disp.stats.experience = stoul(tokens.at(1));
} else if (tokens.at(0) == "level" && cheats_allowed) {
uint32_t level = stoul(tokens.at(1)) - 1;
s->level_table->reset_to_base(p->disp.stats, p->disp.visual.char_class);
s->level_table->advance_to_level(p->disp.stats, level, p->disp.visual.char_class);
auto level_table = s->level_table(c->version());
level_table->reset_to_base(p->disp.stats, p->disp.visual.char_class);
level_table->advance_to_level(p->disp.stats, level, p->disp.visual.char_class);
} else if (tokens.at(0) == "namecolor") {
uint32_t new_color;
sscanf(tokens.at(1).c_str(), "%8X", &new_color);
+14 -6
View File
@@ -775,12 +775,8 @@ void Client::load_all_files() {
player_data_log.info("Loaded system data from %s", char_filename.c_str());
}
uint8_t lang = this->language();
player_data_log.info("Overriding language fields in save files with %02hhX (%c)",
lang, char_for_language_code(lang));
this->character_data->inventory.language = lang;
this->character_data->guild_card.language = lang;
this->system_data->base.language = lang;
this->update_character_data_after_load(this->character_data);
this->system_data->base.language = this->language();
} else {
player_data_log.info("Character file is missing: %s", char_filename.c_str());
@@ -873,6 +869,7 @@ void Client::load_all_files() {
} else {
player_data_log.info("Loaded legacy player data from %s", nsc_filename.c_str());
}
this->update_character_data_after_load(this->character_data);
}
}
@@ -892,6 +889,15 @@ void Client::load_all_files() {
}
}
void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> charfile) {
charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version()));
uint8_t lang = this->language();
player_data_log.info("Overriding language fields in save files with %02hhX (%c)", lang, char_for_language_code(lang));
charfile->inventory.language = lang;
charfile->guild_card.language = lang;
}
void Client::save_all() {
if (this->system_data) {
this->save_system_file();
@@ -991,6 +997,7 @@ void Client::load_backup_character(uint32_t account_id, size_t index) {
throw runtime_error("incorrect flag in character file header");
}
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
this->update_character_data_after_load(this->character_data);
this->v1_v2_last_reported_disp.reset();
}
@@ -1075,6 +1082,7 @@ void Client::use_character_bank(int8_t index) {
throw runtime_error("incorrect flag in character file header");
}
this->external_bank_character = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
this->update_character_data_after_load(this->external_bank_character);
this->external_bank_character_index = index;
files_manager->set_character(filename, this->external_bank_character);
player_data_log.info("Loaded character data from %s for external bank", filename.c_str());
+1
View File
@@ -406,4 +406,5 @@ private:
void save_and_clear_external_bank();
void load_all_files();
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data);
};
+2 -4
View File
@@ -1567,10 +1567,8 @@ void ItemCreator::generate_weapon_shop_item_grind(ItemData& item, size_t player_
? this->weapon_random_set->get_favored_grind_range(table_index)
: this->weapon_random_set->get_standard_grind_range(table_index);
const auto& weapon_def = this->item_parameter_table->get_weapon(
item.data1[1], item.data1[2]);
item.data1[3] = clamp<uint8_t>(
this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
item.data1[3] = clamp<uint8_t>(this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
}
void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t player_level) {
+8 -6
View File
@@ -1989,11 +1989,11 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
lc->delete_overlay();
if (l->quest->battle_rules) {
lc->use_default_bank();
lc->create_battle_overlay(l->quest->battle_rules, s->level_table);
lc->create_battle_overlay(l->quest->battle_rules, s->level_table(lc->version()));
lc->log.info("Created battle overlay");
} else if (l->quest->challenge_template_index >= 0) {
lc->use_default_bank();
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
lc->log.info("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
}
@@ -3216,17 +3216,19 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
c->language(),
player->disp.visual,
player->disp.name.decode(c->language()),
s->level_table);
s->level_table(c->version()));
bb_player->disp.visual.version = 4;
bb_player->disp.visual.name_color_checksum = 0x00000000;
bb_player->inventory = player->inventory;
// Before V3, player stats can't be correctly computed from other fields
// because material usage isn't stored anywhere. For these versions, we
// have to trust the stats field from the player's data.
auto level_table = s->level_table(c->version());
if (is_v1_or_v2(c->version())) {
bb_player->disp.stats = player->disp.stats;
bb_player->import_tethealla_material_usage(level_table);
} else {
s->level_table->advance_to_level(bb_player->disp.stats, player->disp.stats.level, bb_player->disp.visual.char_class);
level_table->advance_to_level(bb_player->disp.stats, player->disp.stats.level, bb_player->disp.visual.char_class);
bb_player->disp.stats.char_stats.atp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::POWER) * 2;
bb_player->disp.stats.char_stats.mst += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::MIND) * 2;
bb_player->disp.stats.char_stats.evp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::EVADE) * 2;
@@ -3641,7 +3643,7 @@ static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} else {
try {
auto s = c->require_server_state();
c->create_character_file(c->login->account->account_id, c->language(), cmd.preview, s->level_table);
c->create_character_file(c->login->account->account_id, c->language(), cmd.preview, s->level_table(c->version()));
} catch (const exception& e) {
send_message_box(c, string_printf("$C6New character could not be created:\n%s", e.what()));
return;
@@ -3761,7 +3763,7 @@ static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
for (auto lc : l->clients) {
if (lc) {
lc->use_default_bank();
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
lc->log.info("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
}
+6 -6
View File
@@ -3232,7 +3232,8 @@ static void on_level_up(shared_ptr<Client> c, uint8_t command, uint8_t flag, voi
if (is_pre_v1(c->version())) {
check_size_t<G_LevelUp_DCNTE_6x30>(data, size);
auto s = c->require_server_state();
const auto& level_incrs = s->level_table->stats_delta_for_level(p->disp.visual.char_class, p->disp.stats.level + 1);
auto level_table = s->level_table(c->version());
const auto& level_incrs = level_table->stats_delta_for_level(p->disp.visual.char_class, p->disp.stats.level + 1);
p->disp.stats.char_stats.atp += level_incrs.atp;
p->disp.stats.char_stats.mst += level_incrs.mst;
p->disp.stats.char_stats.evp += level_incrs.evp;
@@ -3265,8 +3266,7 @@ static void add_player_exp(shared_ptr<Client> c, uint32_t exp) {
bool leveled_up = false;
do {
const auto& level = s->level_table->stats_delta_for_level(
p->disp.visual.char_class, p->disp.stats.level + 1);
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) {
leveled_up = true;
level.apply(p->disp.stats.char_stats);
@@ -3775,7 +3775,7 @@ static void on_battle_restart_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* d
if (lc) {
lc->delete_overlay();
lc->use_default_bank();
lc->create_battle_overlay(new_rules, s->level_table);
lc->create_battle_overlay(new_rules, s->level_table(c->version()));
}
}
l->load_maps();
@@ -3795,7 +3795,7 @@ static void on_battle_level_up_bb(shared_ptr<Client> c, uint8_t, uint8_t, void*
auto lp = lc->character();
uint32_t target_level = lp->disp.stats.level + cmd.num_levels;
uint32_t before_exp = lp->disp.stats.experience;
s->level_table->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class);
s->level_table(lc->version())->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class);
send_give_experience(lc, lp->disp.stats.experience - before_exp);
send_level_up(lc);
}
@@ -3839,7 +3839,7 @@ static void on_challenge_mode_retry_or_quit(shared_ptr<Client> c, uint8_t comman
for (auto lc : l->clients) {
if (lc) {
lc->use_default_bank();
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(c->version()));
lc->log.info("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
}
+35
View File
@@ -4,6 +4,7 @@
#include <stdexcept>
#include <string>
#include "LevelTable.hh"
#include "PSOProtocol.hh"
using namespace std;
@@ -919,6 +920,40 @@ void PSOBBCharacterFile::clear_all_material_usage() {
}
}
void PSOBBCharacterFile::import_tethealla_material_usage(std::shared_ptr<const LevelTable> level_table) {
// Tethealla (Ephinea) doesn't store material counts anywhere in the file,
// so if the material counts in the inventory extension data are all zero,
// check the current stats against the expected stats for the character's
// current level and set the material counts if they make sense.
if (this->get_material_usage(PSOBBCharacterFile::MaterialType::POWER) |
this->get_material_usage(PSOBBCharacterFile::MaterialType::MIND) |
this->get_material_usage(PSOBBCharacterFile::MaterialType::EVADE) |
this->get_material_usage(PSOBBCharacterFile::MaterialType::DEF) |
this->get_material_usage(PSOBBCharacterFile::MaterialType::LUCK)) {
return;
}
PlayerStats level_base_stats = this->disp.stats;
level_table->reset_to_base(level_base_stats, this->disp.visual.char_class);
level_table->advance_to_level(level_base_stats, this->disp.stats.level, this->disp.visual.char_class);
uint64_t pow = (this->disp.stats.char_stats.atp - level_base_stats.char_stats.atp) / 2;
uint64_t mind = (this->disp.stats.char_stats.mst - level_base_stats.char_stats.mst) / 2;
uint64_t evade = (this->disp.stats.char_stats.evp - level_base_stats.char_stats.evp) / 2;
uint64_t def = (this->disp.stats.char_stats.dfp - level_base_stats.char_stats.dfp) / 2;
uint64_t luck = (this->disp.stats.char_stats.lck - level_base_stats.char_stats.lck) / 2;
// We intentionally do not check any limits here. This is because on pre-v3,
// there are no limits, and we don't want to reject legitimate characters
// that have used more than 250 materials.
this->set_material_usage(PSOBBCharacterFile::MaterialType::POWER, pow);
this->set_material_usage(PSOBBCharacterFile::MaterialType::MIND, mind);
this->set_material_usage(PSOBBCharacterFile::MaterialType::EVADE, evade);
this->set_material_usage(PSOBBCharacterFile::MaterialType::DEF, def);
this->set_material_usage(PSOBBCharacterFile::MaterialType::LUCK, luck);
}
static uint16_t crc16(const void* data, size_t size) {
static const uint16_t table[0x100] = {
// clang-format off
+1
View File
@@ -674,6 +674,7 @@ struct PSOBBCharacterFile {
uint8_t get_material_usage(MaterialType which) const;
void set_material_usage(MaterialType which, uint8_t usage);
void clear_all_material_usage();
void import_tethealla_material_usage(std::shared_ptr<const LevelTable> level_table);
} __packed_ws__(PSOBBCharacterFile, 0x2EA4);
////////////////////////////////////////////////////////////////////////////////
+3 -3
View File
@@ -209,7 +209,7 @@ CommandDefinition c_reload(
functions - recompile all client-side patches and functions\n\
item-definitions - reload item definitions files\n\
item-name-index - regenerate item name list\n\
level-table - reload the level-up tables\n\
level-tables - reload the player stats tables\n\
patch-files - reindex the PC and BB patch directories\n\
quests - reindex all quests (including Episode3 download quests)\n\
set-tables - reload set data tables\n\
@@ -246,8 +246,8 @@ CommandDefinition c_reload(
args.s->load_set_data_tables(true);
} else if (type == "battle-params") {
args.s->load_battle_params(true);
} else if (type == "level-table") {
args.s->load_level_table(true);
} else if (type == "level-tables") {
args.s->load_level_tables(true);
} else if (type == "text-index") {
args.s->load_text_index(true);
} else if (type == "word-select") {
+32 -6
View File
@@ -372,6 +372,28 @@ shared_ptr<const SetDataTableBase> ServerState::set_data_table(
return ret;
}
shared_ptr<const LevelTable> ServerState::level_table(Version version) const {
switch (version) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
case Version::GC_NTE: // TODO: Does NTE use the v2 table, the v3 table, or neither?
return this->level_table_v1_v2;
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3:
return this->level_table_v3;
case Version::BB_V4:
return this->level_table_v4;
default:
throw logic_error("level table not available for version");
}
}
shared_ptr<const ItemParameterTable> ServerState::item_parameter_table(Version version) const {
auto ret = this->item_parameter_tables.at(static_cast<size_t>(version));
if (ret == nullptr) {
@@ -1407,12 +1429,16 @@ void ServerState::load_battle_params(bool from_non_event_thread) {
this->forward_or_call(from_non_event_thread, std::move(set));
}
void ServerState::load_level_table(bool from_non_event_thread) {
config_log.info("Loading level table");
auto new_table = make_shared<LevelTableV4>(*this->load_bb_file("PlyLevelTbl.prs"), true);
void ServerState::load_level_tables(bool from_non_event_thread) {
config_log.info("Loading level tables");
auto new_table_v1_v2 = make_shared<LevelTableV2>(load_file("system/level-tables/PlayerTable-pc-v2.prs"), true);
auto new_table_v3 = make_shared<LevelTableV3BE>(load_file("system/level-tables/PlyLevelTbl-gc-v3.cpt"), true);
auto new_table_v4 = make_shared<LevelTableV4>(*this->load_bb_file("PlyLevelTbl.prs"), true);
auto set = [s = this->shared_from_this(), new_table = std::move(new_table)]() {
s->level_table = std::move(new_table);
auto set = [s = this->shared_from_this(), new_table_v1_v2 = std::move(new_table_v1_v2), new_table_v3 = std::move(new_table_v3), new_table_v4 = std::move(new_table_v4)]() {
s->level_table_v1_v2 = std::move(new_table_v1_v2);
s->level_table_v3 = std::move(new_table_v3);
s->level_table_v4 = std::move(new_table_v4);
};
this->forward_or_call(from_non_event_thread, std::move(set));
}
@@ -1834,7 +1860,7 @@ void ServerState::load_all() {
this->create_default_lobbies();
this->load_set_data_tables(false);
this->load_battle_params(false);
this->load_level_table(false);
this->load_level_tables(false);
this->load_text_index(false);
this->load_word_select_table(false);
this->load_item_definitions(false);
+5 -2
View File
@@ -155,7 +155,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> default_quest_index;
std::shared_ptr<const QuestIndex> ep3_download_quest_index;
std::shared_ptr<const LevelTable> level_table;
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;
std::shared_ptr<const GSLArchive> bb_data_gsl;
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
@@ -302,6 +304,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, uint8_t difficulty) const;
std::shared_ptr<const LevelTable> level_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
@@ -364,7 +367,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
void load_patch_indexes(bool from_non_event_thread);
void clear_map_file_caches();
void load_battle_params(bool from_non_event_thread);
void load_level_table(bool from_non_event_thread);
void load_level_tables(bool from_non_event_thread);
void load_text_index(bool from_non_event_thread);
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
std::shared_ptr<const ItemParameterTable> pmt,