load non-v4 level tables
This commit is contained in:
+3
-2
@@ -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
@@ -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());
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user