From 6d16f8095a3065e6e55721ddb73dd39a6a9b5e31 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 7 Dec 2023 12:23:08 -0800 Subject: [PATCH] factor ClientGameData into Client to reduce data duplication --- CMakeLists.txt | 2 +- src/ChatCommands.cc | 28 +- src/ChoiceSearch.cc | 14 +- src/ChoiceSearch.hh | 2 +- src/Client.cc | 620 +++++++++++++++++++++++++++++- src/Client.hh | 102 ++++- src/CommandFormats.hh | 3 +- src/Episode3/BattleRecord.hh | 2 +- src/Episode3/Tournament.cc | 2 +- src/Episode3/Tournament.hh | 4 +- src/Items.cc | 6 +- src/License.cc | 2 + src/License.hh | 1 + src/Lobby.cc | 8 +- src/Lobby.hh | 1 - src/Player.cc | 707 ----------------------------------- src/Player.hh | 162 -------- src/PlayerFilesManager.cc | 119 ++++++ src/PlayerFilesManager.hh | 47 +++ src/PlayerSubordinates.hh | 2 +- src/ReceiveCommands.cc | 227 ++++++----- src/ReceiveSubcommands.cc | 137 ++++--- src/SendCommands.cc | 72 ++-- src/Server.cc | 2 +- src/ServerState.hh | 1 + 25 files changed, 1123 insertions(+), 1150 deletions(-) delete mode 100644 src/Player.cc delete mode 100644 src/Player.hh create mode 100644 src/PlayerFilesManager.cc create mode 100644 src/PlayerFilesManager.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a0a55d9..0c627bbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,7 +86,7 @@ set(SOURCES src/Menu.cc src/NetworkAddresses.cc src/PatchFileIndex.cc - src/Player.cc + src/PlayerFilesManager.cc src/PlayerSubordinates.cc src/ProxyCommands.cc src/ProxyServer.cc diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 5747da78..197b833d 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -269,7 +269,7 @@ static void server_command_qcheck(shared_ptr c, const std::string& args) send_text_message_printf(c, "$C7Quest flag 0x%hX (%hu)\nis %s on %s", flag_num, flag_num, - c->game_data.character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set", + c->character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set", name_for_difficulty(l->difficulty)); } @@ -287,9 +287,9 @@ static void server_command_qset_qclear(shared_ptr c, const std::string& uint16_t flag_num = stoul(args, nullptr, 0); if (should_set) { - c->game_data.character()->quest_flags.set(l->difficulty, flag_num); + c->character()->quest_flags.set(l->difficulty, flag_num); } else { - c->game_data.character()->quest_flags.clear(l->difficulty, flag_num); + c->character()->quest_flags.clear(l->difficulty, flag_num); } if (is_v1_or_v2(c->version())) { @@ -364,7 +364,7 @@ static void proxy_command_qcall(shared_ptr ses, cons } static void server_command_show_material_counts(shared_ptr c, const std::string&) { - auto p = c->game_data.character(); + auto p = c->character(); if (is_v1_or_v2(c->version())) { send_text_message_printf(c, "%hhu HP, %hhu TP", p->get_material_usage(PSOBBCharacterFile::MaterialType::HP), @@ -903,7 +903,7 @@ static void server_command_edit(shared_ptr c, const std::string& args) { vector tokens = split(encoded_args, ' '); try { - auto p = c->game_data.character(); + auto p = c->character(); if (tokens.at(0) == "atp") { p->disp.stats.char_stats.atp = stoul(tokens.at(1)); } else if (tokens.at(0) == "mst") { @@ -997,24 +997,24 @@ static void server_command_change_bank(shared_ptr c, const std::string& throw runtime_error("cannot change banks while at the bank counter"); } - ssize_t new_char_index = args.empty() ? (c->game_data.bb_character_index + 1) : stol(args, nullptr, 0); + ssize_t new_char_index = args.empty() ? (c->bb_character_index + 1) : stol(args, nullptr, 0); if (new_char_index == 0) { - if (c->game_data.use_shared_bank()) { + if (c->use_shared_bank()) { send_text_message_printf(c, "$C6Using shared bank (0)"); } else { send_text_message_printf(c, "$C6Created shared bank (0)"); } } else if (new_char_index <= 4) { - c->game_data.use_character_bank(new_char_index - 1); - auto bp = c->game_data.current_bank_character(); + c->use_character_bank(new_char_index - 1); + auto bp = c->current_bank_character(); auto name = bp->disp.name.decode(c->language()); send_text_message_printf(c, "$C6Using %s\'s bank (%zu)", name.c_str(), new_char_index); } else { throw runtime_error("invalid bank number"); } - const auto& bank = c->game_data.current_bank(); + const auto& bank = c->current_bank(); send_text_message_printf(c, "%" PRIu32 " items\n%" PRIu32 " Meseta", bank.num_items.load(), bank.meseta.load()); } @@ -1079,7 +1079,7 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg check_is_game(l, false); size_t index = stoull(args, nullptr, 0); - c->game_data.load_backup_character(c->license->serial_number, index); + c->load_backup_character(c->license->serial_number, index); auto s = c->require_server_state(); send_player_leave_notification(l, c->lobby_client_id); @@ -1089,7 +1089,7 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg static void server_command_save(shared_ptr c, const std::string&) { check_version(c, Version::BB_V4); try { - c->game_data.save_all(); + c->save_all(); send_text_message(c, "All data saved"); } catch (const exception& e) { send_text_message_printf(c, "Can\'t save data:\n%s", e.what()); @@ -1101,7 +1101,7 @@ static void server_command_save(shared_ptr c, const std::string&) { // Administration commands static string name_for_client(shared_ptr c) { - auto player = c->game_data.character(false); + auto player = c->character(false); if (player.get()) { return player->disp.name.decode(player->inventory.language); } @@ -1624,7 +1624,7 @@ static void server_command_surrender(shared_ptr c, const std::string&) { send_text_message(c, "$C6Battle has not\nyet started"); return; } - const string& name = c->game_data.character()->disp.name.decode(c->language()); + const string& name = c->character()->disp.name.decode(c->language()); send_text_message_printf(l, "$C6%s has\nsurrendered", name.c_str()); for (const auto& watcher_l : l->watcher_lobbies) { send_text_message_printf(watcher_l, "$C6%s has\nsurrendered", name.c_str()); diff --git a/src/ChoiceSearch.cc b/src/ChoiceSearch.cc index c69728c1..6acdda1c 100644 --- a/src/ChoiceSearch.cc +++ b/src/ChoiceSearch.cc @@ -28,10 +28,10 @@ const vector CHOICE_SEARCH_CATEGORIES({ if (choice_id == 0x0000) { return true; } - uint32_t target_level = target_c->game_data.character()->disp.stats.level + 1; + uint32_t target_level = target_c->character()->disp.stats.level + 1; switch (choice_id) { case 0x0001: - return (labs(static_cast(target_level - searcher_c->game_data.character()->disp.stats.level)) <= 5); + return (labs(static_cast(target_level - searcher_c->character()->disp.stats.level)) <= 5); case 0x0002: return (target_level <= 10); case 0x0003: @@ -80,13 +80,13 @@ const vector CHOICE_SEARCH_CATEGORIES({ case 0x0000: return true; case 0x0010: - return target_c->game_data.character()->disp.visual.class_flags & 0x20; + return target_c->character()->disp.visual.class_flags & 0x20; case 0x0011: - return target_c->game_data.character()->disp.visual.class_flags & 0x40; + return target_c->character()->disp.visual.class_flags & 0x40; case 0x0012: - return target_c->game_data.character()->disp.visual.class_flags & 0x80; + return target_c->character()->disp.visual.class_flags & 0x80; default: - return ((choice_id - 1) == target_c->game_data.character()->disp.visual.char_class); + return ((choice_id - 1) == target_c->character()->disp.visual.char_class); } }, }, @@ -142,7 +142,7 @@ const vector CHOICE_SEARCH_CATEGORIES({ {0x0006, "Challenge"}, }, .client_matches = +[](shared_ptr, shared_ptr target_c, uint16_t choice_id) -> bool { - uint16_t target_choice_id = target_c->game_data.character()->choice_search_config.get_setting(0x0204); + uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204); return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id); }, }, diff --git a/src/ChoiceSearch.hh b/src/ChoiceSearch.hh index e6a390b8..5c32be4f 100644 --- a/src/ChoiceSearch.hh +++ b/src/ChoiceSearch.hh @@ -8,7 +8,7 @@ #include "Text.hh" -struct Client; +class Client; struct ChoiceSearchConfig { le_uint32_t disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3 diff --git a/src/Client.cc b/src/Client.cc index d42423cf..1d95b43f 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -153,7 +153,6 @@ Client::Client( lobby_client_id(0), lobby_arrow_color(0), preferred_lobby_id(-1), - game_data(server->get_state()->player_files_manager), save_game_data_event( event_new( bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST, @@ -172,10 +171,13 @@ Client::Client( card_battle_table_number(-1), card_battle_table_seat_number(0), card_battle_table_seat_state(0), + should_update_play_time(false), + bb_character_index(-1), next_exp_value(0), can_chat(true), - dol_base_addr(0) { - + dol_base_addr(0), + external_bank_character_index(-1), + last_play_time_update(0) { this->config.set_flags_for_version(version, -1); this->config.specific_version = default_specific_version_for_version(version, -1); @@ -204,6 +206,9 @@ Client::~Client() { } } + if ((this->version() == Version::BB_V4) && (this->character_data.get())) { + this->save_all(); + } this->log.info("Deleted"); } @@ -222,11 +227,15 @@ void Client::reschedule_ping_and_timeout_events() { } void Client::set_license(shared_ptr l) { - this->license = l; - this->game_data.guild_card_number = this->license->serial_number; if (this->version() == Version::BB_V4) { - this->game_data.set_bb_username(this->license->bb_username); + // Make sure bb_username is filename-safe + for (char ch : l->bb_username) { + if (!isalnum(ch) && (ch != '-') && (ch != '_')) { + throw runtime_error("invalid characters in username"); + } + } } + this->license = l; } shared_ptr Client::require_server_state() const { @@ -254,7 +263,7 @@ shared_ptr Client::team() const { return nullptr; } - auto p = this->game_data.character(false); + auto p = this->character(false); auto s = this->require_server_state(); auto team = s->team_index->get_by_id(this->license->bb_team_id); if (!team) { @@ -291,7 +300,7 @@ bool Client::can_access_quest(shared_ptr q, uint8_t difficulty) con return true; } if ((q->require_flag >= 0) && - !this->game_data.character()->quest_flags.get(difficulty, q->require_flag)) { + !this->character()->quest_flags.get(difficulty, q->require_flag)) { return false; } if (!q->require_team_reward_key.empty()) { @@ -311,8 +320,8 @@ void Client::save_game_data() { if (this->version() != Version::BB_V4) { throw logic_error("save_game_data called for non-BB client"); } - if (this->game_data.character(false)) { - this->game_data.save_all(); + if (this->character(false)) { + this->save_all(); } } @@ -353,3 +362,594 @@ void Client::suspend_timeouts() { event_del(this->idle_timeout_event.get()); this->log.info("Timeouts suspended"); } + +void Client::create_battle_overlay(shared_ptr rules, shared_ptr level_table) { + this->overlay_character_data = make_shared(*this->character(true, false)); + + if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) { + this->overlay_character_data->inventory.remove_all_items_of_type(0); + this->overlay_character_data->inventory.remove_all_items_of_type(1); + } + if (rules->mag_mode == BattleRules::MagMode::FORBID_ALL) { + this->overlay_character_data->inventory.remove_all_items_of_type(2); + } + if (rules->tool_mode != BattleRules::ToolMode::ALLOW) { + this->overlay_character_data->inventory.remove_all_items_of_type(3); + } + if (rules->replace_char) { + // TODO: Shouldn't we clear other material usage here? It looks like the + // original code doesn't, but that seems wrong. + this->overlay_character_data->inventory.hp_from_materials = 0; + this->overlay_character_data->inventory.tp_from_materials = 0; + + uint32_t target_level = clamp(rules->char_level, 0, 199); + uint8_t char_class = this->overlay_character_data->disp.visual.char_class; + auto& stats = this->overlay_character_data->disp.stats; + + stats.reset_to_base(char_class, level_table); + stats.advance_to_level(char_class, target_level, level_table); + + stats.unknown_a1 = 40; + stats.meseta = 300; + } + if (rules->tech_disk_mode == BattleRules::TechDiskMode::LIMIT_LEVEL) { + // TODO: Verify this is what the game actually does. + for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) { + uint8_t existing_level = this->overlay_character_data->get_technique_level(tech_num); + if ((existing_level != 0xFF) && (existing_level > rules->max_tech_level)) { + this->overlay_character_data->set_technique_level(tech_num, rules->max_tech_level); + } + } + } else if (rules->tech_disk_mode == BattleRules::TechDiskMode::FORBID_ALL) { + for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) { + this->overlay_character_data->set_technique_level(tech_num, 0xFF); + } + } + if (rules->meseta_mode != BattleRules::MesetaMode::ALLOW) { + this->overlay_character_data->disp.stats.meseta = 0; + } + if (rules->forbid_scape_dolls) { + this->overlay_character_data->inventory.remove_all_items_of_type(3, 9); + } +} + +void Client::create_challenge_overlay(Version version, size_t template_index, shared_ptr level_table) { + auto p = this->character(true, false); + const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index); + + this->overlay_character_data = make_shared(*p); + auto overlay = this->overlay_character_data; + + for (size_t z = 0; z < overlay->inventory.items.size(); z++) { + auto& i = overlay->inventory.items[z]; + i.present = 0; + i.unknown_a1 = 0; + i.extension_data1 = 0; + i.extension_data2 = 0; + i.flags = 0; + i.data = ItemData(); + } + + overlay->inventory.items[13].extension_data2 = 1; + + overlay->disp.stats.reset_to_base(overlay->disp.visual.char_class, level_table); + overlay->disp.stats.advance_to_level(overlay->disp.visual.char_class, tpl.level, level_table); + + overlay->disp.stats.unknown_a1 = 40; + overlay->disp.stats.unknown_a3 = 10.0; + overlay->disp.stats.experience = level_table->stats_delta_for_level(overlay->disp.visual.char_class, overlay->disp.stats.level).experience; + overlay->disp.stats.meseta = 0; + overlay->clear_all_material_usage(); + for (size_t z = 0; z < 0x13; z++) { + overlay->set_technique_level(z, 0xFF); + } + + for (size_t z = 0; z < tpl.items.size(); z++) { + auto& inv_item = overlay->inventory.items[z]; + inv_item.present = tpl.items[z].present; + inv_item.unknown_a1 = tpl.items[z].unknown_a1; + inv_item.flags = tpl.items[z].flags; + inv_item.data = tpl.items[z].data; + } + overlay->inventory.num_items = tpl.items.size(); + + for (const auto& tech_level : tpl.tech_levels) { + overlay->set_technique_level(tech_level.tech_num, tech_level.level); + } +} + +void Client::import_blocked_senders(const parray& blocked_senders) { + this->blocked_senders.clear(); + for (size_t z = 0; z < blocked_senders.size(); z++) { + if (blocked_senders[z]) { + this->blocked_senders.emplace(blocked_senders[z]); + } + } +} + +shared_ptr Client::system_file(bool allow_load) { + if (!this->system_data && allow_load) { + this->load_all_files(); + } + return this->system_data; +} + +shared_ptr Client::system_file(bool allow_load) const { + if (!this->system_data.get() && allow_load) { + throw runtime_error("system data is not loaded"); + } + return this->system_data; +} + +shared_ptr Client::character(bool allow_load, bool allow_overlay) { + if (this->overlay_character_data && allow_overlay) { + return this->overlay_character_data; + } + if (!this->character_data && allow_load) { + if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) { + throw runtime_error("character index not specified"); + } + this->load_all_files(); + } + return this->character_data; +} + +shared_ptr Client::character(bool allow_load, bool allow_overlay) const { + if (allow_overlay && this->overlay_character_data) { + return this->overlay_character_data; + } + if (!this->character_data && allow_load) { + throw runtime_error("character data is not loaded"); + } + return this->character_data; +} + +shared_ptr Client::guild_card_file(bool allow_load) { + if (!this->guild_card_data && allow_load) { + this->load_all_files(); + } + return this->guild_card_data; +} + +shared_ptr Client::guild_card_file(bool allow_load) const { + if (!this->guild_card_data && allow_load) { + throw runtime_error("account data is not loaded"); + } + return this->guild_card_data; +} + +string Client::system_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have system data"); + } + if (!this->license) { + throw logic_error("client is not logged in"); + } + return string_printf("system/players/system_%s.psosys", this->license->bb_username.c_str()); +} + +string Client::character_filename(const std::string& bb_username, int8_t index) { + if (bb_username.empty()) { + throw logic_error("non-BB players do not have character data"); + } + if (index < 0) { + throw logic_error("character index is not set"); + } + return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index); +} + +string Client::backup_character_filename(uint32_t serial_number, size_t index) { + return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", serial_number, index); +} + +string Client::character_filename(int8_t index) const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have character data"); + } + if (!this->license) { + throw logic_error("client is not logged in"); + } + return this->character_filename(this->license->bb_username, (index < 0) ? this->bb_character_index : index); +} + +string Client::guild_card_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have character data"); + } + if (!this->license) { + throw logic_error("client is not logged in"); + } + return string_printf("system/players/guild_cards_%s.psocard", this->license->bb_username.c_str()); +} + +string Client::shared_bank_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have character data"); + } + if (!this->license) { + throw logic_error("client is not logged in"); + } + return string_printf("system/players/shared_bank_%s.psobank", this->license->bb_username.c_str()); +} + +string Client::legacy_account_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have character data"); + } + if (!this->license) { + throw logic_error("client is not logged in"); + } + return string_printf("system/players/account_%s.nsa", this->license->bb_username.c_str()); +} + +string Client::legacy_player_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have character data"); + } + if (!this->license) { + throw logic_error("client is not logged in"); + } + if (this->bb_character_index < 0) { + throw logic_error("character index is not set"); + } + return string_printf( + "system/players/player_%s_%hhd.nsc", + this->license->bb_username.c_str(), + static_cast(this->bb_character_index + 1)); +} + +void Client::create_character_file( + uint32_t guild_card_number, + uint8_t language, + const PlayerDispDataBBPreview& preview, + shared_ptr level_table) { + this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table); + this->save_character_file(); +} + +void Client::load_all_files() { + if (this->version() != Version::BB_V4) { + this->system_data = make_shared(); + this->character_data = make_shared(); + this->guild_card_data = make_shared(); + return; + } + if (!this->license) { + throw logic_error("cannot load BB player data until client is logged in"); + } + + this->system_data.reset(); + this->character_data.reset(); + this->guild_card_data.reset(); + + auto files_manager = this->require_server_state()->player_files_manager; + + string sys_filename = this->system_filename(); + this->system_data = files_manager->get_system(sys_filename); + if (this->system_data) { + player_data_log.info("Using loaded system file %s", sys_filename.c_str()); + } else if (isfile(sys_filename)) { + this->system_data = make_shared(load_object_file(sys_filename, true)); + files_manager->set_system(sys_filename, this->system_data); + player_data_log.info("Loaded system data from %s", sys_filename.c_str()); + } else { + player_data_log.info("System file is missing: %s", sys_filename.c_str()); + } + + if (this->bb_character_index >= 0) { + string char_filename = this->character_filename(); + this->character_data = files_manager->get_character(char_filename); + if (this->character_data) { + player_data_log.info("Using loaded character file %s", char_filename.c_str()); + } else if (isfile(char_filename)) { + auto f = fopen_unique(char_filename, "rb"); + auto header = freadx(f.get()); + if (header.size != 0x399C) { + throw runtime_error("incorrect size in character file header"); + } + if (header.command != 0x00E7) { + throw runtime_error("incorrect command in character file header"); + } + if (header.flag != 0x00000000) { + throw runtime_error("incorrect flag in character file header"); + } + this->character_data = make_shared(freadx(f.get())); + files_manager->set_character(char_filename, this->character_data); + player_data_log.info("Loaded character data from %s", char_filename.c_str()); + + // If there was no .psosys file, load the system file from the .psochar + // file instead + if (!this->system_data) { + this->system_data = make_shared(freadx(f.get())); + files_manager->set_system(sys_filename, this->system_data); + player_data_log.info("Loaded system data from %s", char_filename.c_str()); + } + } else { + player_data_log.info("Character file is missing: %s", char_filename.c_str()); + } + } + + string card_filename = this->guild_card_filename(); + this->guild_card_data = files_manager->get_guild_card(card_filename); + if (this->guild_card_data) { + player_data_log.info("Using loaded Guild Card file %s", card_filename.c_str()); + } else if (isfile(card_filename)) { + this->guild_card_data = make_shared(load_object_file(card_filename)); + files_manager->set_guild_card(card_filename, this->guild_card_data); + player_data_log.info("Loaded Guild Card data from %s", card_filename.c_str()); + } else { + player_data_log.info("Guild Card file is missing: %s", card_filename.c_str()); + } + + // If any of the above files were missing, try to load from .nsa/.nsc files instead + if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) { + string nsa_filename = this->legacy_account_filename(); + shared_ptr nsa_data; + if (isfile(nsa_filename)) { + nsa_data = make_shared(load_object_file(nsa_filename)); + if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) { + throw runtime_error("account data header is incorrect"); + } + if (!this->system_data) { + this->system_data = make_shared(nsa_data->system_file.base); + files_manager->set_system(sys_filename, this->system_data); + player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str()); + } + if (!this->guild_card_data) { + this->guild_card_data = make_shared(nsa_data->guild_card_file); + files_manager->set_guild_card(card_filename, this->guild_card_data); + player_data_log.info("Loaded legacy Guild Card data from %s", nsa_filename.c_str()); + } + } + + if (!this->system_data) { + this->system_data = make_shared(); + files_manager->set_system(sys_filename, this->system_data); + player_data_log.info("Created new system data"); + } + if (!this->guild_card_data) { + this->guild_card_data = make_shared(); + files_manager->set_guild_card(card_filename, this->guild_card_data); + player_data_log.info("Created new Guild Card data"); + } + + if (!this->character_data && (this->bb_character_index >= 0)) { + string nsc_filename = this->legacy_player_filename(); + auto nsc_data = load_object_file(nsc_filename); + if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) { + nsc_data.signature = LegacySavedPlayerDataBB::SIGNATURE_V0; + nsc_data.unused.clear(); + nsc_data.battle_records.place_counts.clear(0); + nsc_data.battle_records.disconnect_count = 0; + nsc_data.battle_records.unknown_a1.clear(0); + } else if (nsc_data.signature != LegacySavedPlayerDataBB::SIGNATURE_V1) { + throw runtime_error("legacy player data has incorrect signature"); + } + + this->character_data = make_shared(); + files_manager->set_character(this->character_filename(), this->character_data); + this->character_data->inventory = nsc_data.inventory; + this->character_data->disp = nsc_data.disp; + this->character_data->play_time_seconds = nsc_data.disp.play_time; + this->character_data->unknown_a2 = nsc_data.unknown_a2; + this->character_data->quest_flags = nsc_data.quest_flags; + this->character_data->death_count = nsc_data.death_count; + this->character_data->bank = nsc_data.bank; + this->character_data->guild_card.guild_card_number = this->license->serial_number; + this->character_data->guild_card.name = nsc_data.disp.name; + this->character_data->guild_card.description = nsc_data.guild_card_description; + this->character_data->guild_card.present = 1; + this->character_data->guild_card.language = nsc_data.inventory.language; + this->character_data->guild_card.section_id = nsc_data.disp.visual.section_id; + this->character_data->guild_card.char_class = nsc_data.disp.visual.char_class; + this->character_data->auto_reply = nsc_data.auto_reply; + this->character_data->info_board = nsc_data.info_board; + this->character_data->battle_records = nsc_data.battle_records; + this->character_data->challenge_records = nsc_data.challenge_records; + this->character_data->tech_menu_config = nsc_data.tech_menu_config; + this->character_data->quest_global_flags = nsc_data.quest_global_flags; + if (nsa_data) { + this->character_data->option_flags = nsa_data->option_flags; + this->character_data->symbol_chats = nsa_data->symbol_chats; + this->character_data->shortcuts = nsa_data->shortcuts; + player_data_log.info("Loaded legacy player data from %s and %s", nsa_filename.c_str(), nsc_filename.c_str()); + } else { + player_data_log.info("Loaded legacy player data from %s", nsc_filename.c_str()); + } + } + } + + this->blocked_senders.clear(); + for (size_t z = 0; z < this->guild_card_data->blocked.size(); z++) { + if (this->guild_card_data->blocked[z].present) { + this->blocked_senders.emplace(this->guild_card_data->blocked[z].guild_card_number); + } + } + + if (this->character_data) { + this->last_play_time_update = now(); + } +} + +void Client::save_all() { + if (this->system_data) { + this->save_system_file(); + } + if (this->character_data) { + this->save_character_file(); + } + if (this->guild_card_data) { + this->save_guild_card_file(); + } + if (this->external_bank) { + string filename = this->shared_bank_filename(); + save_object_file(filename, *this->external_bank); + player_data_log.info("Saved shared bank file %s", filename.c_str()); + } + if (this->external_bank_character) { + this->save_character_file( + this->character_filename(this->external_bank_character_index), + this->system_data, + this->external_bank_character); + } +} + +void Client::save_system_file() const { + if (!this->system_data) { + throw logic_error("no system file loaded"); + } + string filename = this->system_filename(); + save_object_file(filename, *this->system_data); + player_data_log.info("Saved system file %s", filename.c_str()); +} + +void Client::save_character_file( + const string& filename, + shared_ptr system, + shared_ptr character) { + auto f = fopen_unique(filename, "wb"); + PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000}; + fwritex(f.get(), header); + fwritex(f.get(), *character); + fwritex(f.get(), *system); + // TODO: Technically, we should write the actual team membership struct to the + // file here, but that would cause Client to depend on License, which + // it currently does not. This data doesn't matter at all for correctness + // within newserv, since it ignores this data entirely and instead generates + // the membership struct from the team ID in the License and the team's state. + // So, writing correct data here would mostly be for compatibility with other + // PSO servers. But if the other server is newserv, then this data would be + // used anyway, and if it's not, then it would presumably have a different set + // of teams with a different set of team IDs anyway, so the membership struct + // here would be useless either way. + static const PSOBBTeamMembership empty_membership; + fwritex(f.get(), empty_membership); + player_data_log.info("Saved character file %s", filename.c_str()); +} + +void Client::save_character_file() { + if (!this->system_data.get()) { + throw logic_error("no system file loaded"); + } + if (!this->character_data.get()) { + throw logic_error("no character file loaded"); + } + if (this->should_update_play_time) { + // This is slightly inaccurate, since fractions of a second are truncated + // off each time we save. I'm lazy, so insert shrug emoji here. + uint64_t t = now(); + uint64_t seconds = (t - this->last_play_time_update) / 1000000; + this->character_data->disp.play_time += seconds; + this->character_data->play_time_seconds = this->character_data->disp.play_time; + player_data_log.info("Added %" PRIu64 " seconds to play time", seconds); + this->last_play_time_update = t; + } + + this->save_character_file(this->character_filename(), this->system_data, this->character_data); +} + +void Client::save_guild_card_file() const { + if (!this->guild_card_data.get()) { + throw logic_error("no Guild Card file loaded"); + } + string filename = this->guild_card_filename(); + save_object_file(filename, *this->guild_card_data); + player_data_log.info("Saved Guild Card file %s", filename.c_str()); +} + +void Client::load_backup_character(uint32_t serial_number, size_t index) { + string filename = this->backup_character_filename(serial_number, index); + auto f = fopen_unique(filename, "rb"); + auto header = freadx(f.get()); + if (header.size != 0x399C) { + throw runtime_error("incorrect size in character file header"); + } + if (header.command != 0x00E7) { + throw runtime_error("incorrect command in character file header"); + } + if (header.flag != 0x00000000) { + throw runtime_error("incorrect flag in character file header"); + } + this->character_data = make_shared(freadx(f.get())); + this->v1_v2_last_reported_disp.reset(); +} + +void Client::unload_character() { + this->save_character_file(); + this->character_data.reset(); + this->log.info("Unloaded character"); +} + +PlayerBank& Client::current_bank() { + if (this->external_bank) { + return *this->external_bank; + } else if (this->external_bank_character) { + return this->external_bank_character->bank; + } + return this->character()->bank; +} + +std::shared_ptr Client::current_bank_character() { + return this->external_bank_character ? this->external_bank_character : this->character(); +} + +void Client::use_default_bank() { + if (this->external_bank) { + string filename = this->shared_bank_filename(); + save_object_file(filename, *this->external_bank); + this->external_bank.reset(); + player_data_log.info("Detached shared bank %s", filename.c_str()); + } + if (this->external_bank_character) { + string filename = this->character_filename(this->external_bank_character_index); + this->save_character_file(filename, this->system_data, this->external_bank_character); + this->external_bank_character.reset(); + player_data_log.info("Detached character %s from bank", filename.c_str()); + } +} + +bool Client::use_shared_bank() { + this->use_default_bank(); + string filename = this->shared_bank_filename(); + if (isfile(filename)) { + this->external_bank = make_shared(load_object_file(filename)); + player_data_log.info("Loaded shared bank %s", filename.c_str()); + return true; + } else { + this->external_bank = make_shared(); + player_data_log.info("Created shared bank for %s", filename.c_str()); + return false; + } +} + +void Client::use_character_bank(int8_t index) { + this->use_default_bank(); + if (index != this->bb_character_index) { + auto files_manager = this->require_server_state()->player_files_manager; + + string filename = this->character_filename(index); + this->external_bank_character = files_manager->get_character(filename); + if (this->external_bank_character) { + this->external_bank_character_index = index; + player_data_log.info("Using loaded character file %s for external bank", filename.c_str()); + } else if (isfile(filename)) { + auto f = fopen_unique(filename, "rb"); + auto header = freadx(f.get()); + if (header.size != 0x399C) { + throw runtime_error("incorrect size in character file header"); + } + if (header.command != 0x00E7) { + throw runtime_error("incorrect command in character file header"); + } + if (header.flag != 0x00000000) { + throw runtime_error("incorrect flag in character file header"); + } + this->external_bank_character = make_shared(freadx(f.get())); + 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()); + } else { + throw runtime_error("character does not exist"); + } + } +} diff --git a/src/Client.hh b/src/Client.hh index cbf77932..61b10392 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -15,7 +15,6 @@ #include "PSOEncryption.hh" #include "PSOProtocol.hh" #include "PatchFileIndex.hh" -#include "Player.hh" #include "Quest.hh" #include "QuestScript.hh" #include "TeamIndex.hh" @@ -26,7 +25,8 @@ extern const uint64_t CLIENT_CONFIG_MAGIC; class Server; struct Lobby; -struct Client : public std::enable_shared_from_this { +class Client : public std::enable_shared_from_this { +public: enum class Flag : uint64_t { // clang-format off @@ -148,7 +148,6 @@ struct Client : public std::enable_shared_from_this { }; std::weak_ptr server; - std::weak_ptr server_state; uint64_t id; PrefixedLogger log; @@ -181,7 +180,7 @@ struct Client : public std::enable_shared_from_this { uint8_t lobby_client_id; uint8_t lobby_arrow_color; int64_t preferred_lobby_id; // <0 = no preference - ClientGameData game_data; + std::unique_ptr save_game_data_event; std::unique_ptr send_ping_event; std::unique_ptr idle_timeout_event; @@ -198,6 +197,28 @@ struct Client : public std::enable_shared_from_this { }; std::unique_ptr> game_join_command_queue; + // Character / game data + struct PendingItemTrade { + uint8_t other_client_id; + bool confirmed; // true if client has sent a D2 command + std::vector items; + }; + struct PendingCardTrade { + uint8_t other_client_id; + bool confirmed; // true if client has sent an EE D2 command + std::vector> card_to_count; + }; + bool should_update_play_time; + std::unordered_set blocked_senders; + std::unique_ptr v1_v2_last_reported_disp; + // These are null unless the client is within the trade sequence (D0-D4 or EE commands) + std::unique_ptr pending_item_trade; + std::unique_ptr pending_card_trade; + std::shared_ptr ep3_config; // Null for non-Ep3 + int8_t bb_character_index; + ItemData bb_identify_result; + std::array, 3> bb_shop_contents; + // Miscellaneous (used by chat commands) uint32_t next_exp_value; // next EXP value to give G_SwitchStateChanged_6x05 last_switch_enabled_command; @@ -249,4 +270,77 @@ struct Client : public std::enable_shared_from_this { void idle_timeout(); void suspend_timeouts(); + + const std::string& get_bb_username() const; + void set_bb_username(const std::string& bb_username); + + void create_battle_overlay(std::shared_ptr rules, std::shared_ptr level_table); + void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr level_table); + inline void delete_overlay() { + this->overlay_character_data.reset(); + } + inline bool has_overlay() const { + return this->overlay_character_data.get() != nullptr; + } + + void import_blocked_senders(const parray& blocked_senders); + + std::shared_ptr system_file(bool allow_load = true); + std::shared_ptr character(bool allow_load = true, bool allow_overlay = true); + std::shared_ptr guild_card_file(bool allow_load = true); + std::shared_ptr system_file(bool allow_load = true) const; + std::shared_ptr character(bool allow_load = true, bool allow_overlay = true) const; + std::shared_ptr guild_card_file(bool allow_load = true) const; + + void create_character_file( + uint32_t guild_card_number, + uint8_t language, + const PlayerDispDataBBPreview& preview, + std::shared_ptr level_table); + + std::string system_filename() const; + static std::string character_filename(const std::string& bb_username, int8_t index); + static std::string backup_character_filename(uint32_t serial_number, size_t index); + std::string character_filename(int8_t index = -1) const; + std::string guild_card_filename() const; + std::string shared_bank_filename() const; + + std::string legacy_player_filename() const; + std::string legacy_account_filename() const; + + void save_all(); + void save_system_file() const; + static void save_character_file( + const std::string& filename, + std::shared_ptr sys, + std::shared_ptr character); + // Note: This function is not const because it updates the player's play time. + void save_character_file(); + void save_guild_card_file() const; + + void load_backup_character(uint32_t serial_number, size_t index); + void unload_character(); + + PlayerBank& current_bank(); + std::shared_ptr current_bank_character(); + bool use_shared_bank(); // Returns true if the bank exists; false if it was created + void use_character_bank(int8_t bb_character_index); + void use_default_bank(); + +private: + // The overlay character data is used in battle and challenge modes, when + // character data is temporarily replaced in-game. In other play modes and in + // lobbies, overlay_character_data is null. + std::shared_ptr system_data; + std::shared_ptr overlay_character_data; + std::shared_ptr character_data; + std::shared_ptr guild_card_data; + std::shared_ptr external_bank; + std::shared_ptr external_bank_character; + int8_t external_bank_character_index; + uint64_t last_play_time_update; + + void save_and_clear_external_bank(); + + void load_all_files(); }; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index e2ff081b..343d6d05 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -10,7 +10,7 @@ #include "Episode3/MapState.hh" #include "Episode3/PlayerStateSubordinates.hh" #include "PSOProtocol.hh" -#include "Player.hh" +#include "PlayerSubordinates.hh" #include "SaveFileFormats.hh" #include "Text.hh" @@ -1053,7 +1053,6 @@ struct C_OpenFileConfirmation_44_A6 { // 61 (C->S): Player data // Internal name: SndCharaDataV2 (SndCharaData in DCv1) -// See the PSOPlayerData structs in Player.hh for this command's format. // header.flag specifies the format version, which is related to (but not // identical to) the game's major version. For example, the format version is 01 // on DC v1, 02 on PSO PC, 03 on PSO GC, XB, and BB, and 04 on Episode 3. diff --git a/src/Episode3/BattleRecord.hh b/src/Episode3/BattleRecord.hh index fb1dfc04..65770448 100644 --- a/src/Episode3/BattleRecord.hh +++ b/src/Episode3/BattleRecord.hh @@ -9,7 +9,7 @@ #include #include -#include "../Player.hh" +#include "../PlayerSubordinates.hh" struct Lobby; diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index b573eb1f..2a3a03d3 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -16,7 +16,7 @@ Tournament::PlayerEntry::PlayerEntry(uint32_t serial_number, const string& playe Tournament::PlayerEntry::PlayerEntry(shared_ptr c) : serial_number(c->license->serial_number), client(c), - player_name(c->game_data.character()->disp.name.decode(c->language())) {} + player_name(c->character()->disp.name.decode(c->language())) {} Tournament::PlayerEntry::PlayerEntry( shared_ptr com_deck) diff --git a/src/Episode3/Tournament.hh b/src/Episode3/Tournament.hh index f463ed23..239a3354 100644 --- a/src/Episode3/Tournament.hh +++ b/src/Episode3/Tournament.hh @@ -10,10 +10,10 @@ #include #include -#include "../Player.hh" +#include "DataIndexes.hh" struct Lobby; -struct Client; +class Client; struct ServerState; namespace Episode3 { diff --git a/src/Items.cc b/src/Items.cc index 4fc6132b..7b63fa39 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -17,7 +17,7 @@ void player_use_item(shared_ptr c, size_t item_index) { bool is_v3_or_later = is_v3(c->version()) || is_v4(c->version()); bool should_delete_item = is_v3_or_later; - auto player = c->game_data.character(); + auto player = c->character(); auto& item = player->inventory.items[item_index]; uint32_t item_identifier = item.data.primary_identifier(); @@ -54,7 +54,7 @@ void player_use_item(shared_ptr c, size_t item_index) { weapon.data.data1[3] += (item.data.data1[2] + 1); } else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material - auto p = c->game_data.character(); + auto p = c->character(); using Type = PSOBBCharacterFile::MaterialType; Type type; @@ -272,7 +272,7 @@ void player_feed_mag(std::shared_ptr c, size_t mag_item_index, size_t fe }); auto s = c->require_server_state(); - auto player = c->game_data.character(); + auto player = c->character(); auto& fed_item = player->inventory.items[fed_item_index]; auto& mag_item = player->inventory.items[mag_item_index]; diff --git a/src/License.cc b/src/License.cc index db26baba..11908e58 100644 --- a/src/License.cc +++ b/src/License.cc @@ -26,6 +26,7 @@ License::License(const JSON& json) this->bb_password = json.get_string("BBPassword", ""); this->flags = json.get_int("Flags", 0); this->ban_end_time = json.get_int("BanEndTime", 0); + this->auto_reply_message = json.get_string("AutoReplyMessage", ""); this->ep3_current_meseta = json.get_int("Ep3CurrentMeseta", 0); this->ep3_total_meseta_earned = json.get_int("Ep3TotalMesetaEarned", 0); this->bb_team_id = json.get_int("BBTeamID", 0); @@ -43,6 +44,7 @@ JSON License::json() const { {"BBPassword", this->bb_password}, {"Flags", this->flags}, {"BanEndTime", this->ban_end_time}, + {"AutoReplyMessage", this->auto_reply_message}, {"Ep3CurrentMeseta", this->ep3_current_meseta}, {"Ep3TotalMesetaEarned", this->ep3_total_meseta_earned}, {"BBTeamID", this->bb_team_id}, diff --git a/src/License.hh b/src/License.hh index 015a3644..a23ff3e3 100644 --- a/src/License.hh +++ b/src/License.hh @@ -43,6 +43,7 @@ struct License { uint32_t flags = 0; uint64_t ban_end_time = 0; // 0 = not banned + std::string auto_reply_message; uint32_t ep3_current_meseta = 0; uint32_t ep3_total_meseta_earned = 0; diff --git a/src/Lobby.cc b/src/Lobby.cc index 93482500..df6fe404 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -219,7 +219,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { // If the lobby is recording a battle record, add the player join event if (this->battle_record) { - auto p = c->game_data.character(); + auto p = c->character(); PlayerLobbyDataDCGC lobby_data; lobby_data.player_tag = 0x00010000; lobby_data.guild_card_number = c->license->serial_number; @@ -228,7 +228,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { lobby_data, p->inventory, p->disp.to_dcpcv3(c->language(), c->language()), - c->game_data.ep3_config ? (c->game_data.ep3_config->online_clv_exp / 100) : 0); + c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0); } // Send spectator count notifications if needed @@ -318,7 +318,7 @@ shared_ptr Lobby::find_client(const string* identifier, uint64_t serial_ (lc->license->serial_number == serial_number)) { return lc; } - if (identifier && (lc->game_data.character()->disp.name.eq(*identifier, lc->language()))) { + if (identifier && (lc->character()->disp.name.eq(*identifier, lc->language()))) { return lc; } } @@ -384,7 +384,7 @@ void Lobby::on_item_id_generated_externally(uint32_t item_id) { } void Lobby::assign_inventory_and_bank_item_ids(shared_ptr c) { - auto p = c->game_data.character(); + auto p = c->character(); for (size_t z = 0; z < p->inventory.num_items; z++) { p->inventory.items[z].data.id = this->generate_item_id(c->lobby_client_id); } diff --git a/src/Lobby.hh b/src/Lobby.hh index d056e435..c6c9ddb8 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -16,7 +16,6 @@ #include "Episode3/Server.hh" #include "ItemCreator.hh" #include "Map.hh" -#include "Player.hh" #include "Quest.hh" #include "StaticGameData.hh" #include "Text.hh" diff --git a/src/Player.cc b/src/Player.cc deleted file mode 100644 index 8aa0a307..00000000 --- a/src/Player.cc +++ /dev/null @@ -1,707 +0,0 @@ -#include "Player.hh" - -#include -#include -#include - -#include -#include -#include - -#include "FileContentsCache.hh" -#include "ItemData.hh" -#include "Loggers.hh" -#include "PSOEncryption.hh" -#include "PSOProtocol.hh" -#include "StaticGameData.hh" -#include "Text.hh" -#include "Version.hh" - -using namespace std; - -PlayerFilesManager::PlayerFilesManager(std::shared_ptr base) - : base(base), - clear_expired_files_event( - event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this), - event_free) { - auto tv = usecs_to_timeval(30 * 1000 * 1000); - event_add(this->clear_expired_files_event.get(), &tv); -} - -template -size_t erase_unused(std::unordered_map>& m) { - size_t ret = 0; - for (auto it = m.begin(); it != m.end();) { - if (it->second.use_count() <= 1) { - it = m.erase(it); - ret++; - } else { - it++; - } - } - return ret; -} - -std::shared_ptr PlayerFilesManager::get_system(const std::string& filename) { - try { - return this->loaded_system_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -std::shared_ptr PlayerFilesManager::get_character(const std::string& filename) { - try { - return this->loaded_character_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -std::shared_ptr PlayerFilesManager::get_guild_card(const std::string& filename) { - try { - return this->loaded_guild_card_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -std::shared_ptr PlayerFilesManager::get_bank(const std::string& filename) { - try { - return this->loaded_bank_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_system_files.emplace(filename, file).second) { - throw runtime_error("Guild Card file already loaded: " + filename); - } -} - -void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_character_files.emplace(filename, file).second) { - throw runtime_error("character file already loaded: " + filename); - } -} - -void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_guild_card_files.emplace(filename, file).second) { - throw runtime_error("Guild Card file already loaded: " + filename); - } -} - -void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_bank_files.emplace(filename, file).second) { - throw runtime_error("bank file already loaded: " + filename); - } -} - -void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) { - auto* self = reinterpret_cast(ctx); - size_t num_deleted = erase_unused(self->loaded_system_files); - if (num_deleted) { - player_data_log.info("Cleared %zu expired system file(s)", num_deleted); - } - num_deleted = erase_unused(self->loaded_character_files); - if (num_deleted) { - player_data_log.info("Cleared %zu expired character file(s)", num_deleted); - } - num_deleted = erase_unused(self->loaded_guild_card_files); - if (num_deleted) { - player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted); - } - num_deleted = erase_unused(self->loaded_bank_files); - if (num_deleted) { - player_data_log.info("Cleared %zu expired bank file(s)", num_deleted); - } -} - -ClientGameData::ClientGameData(std::shared_ptr files_manager) - : guild_card_number(0), - should_update_play_time(false), - bb_character_index(-1), - files_manager(files_manager), - last_play_time_update(0) { - for (size_t z = 0; z < this->blocked_senders.size(); z++) { - this->blocked_senders[z] = 0; - } -} - -ClientGameData::~ClientGameData() { - if (!this->bb_username.empty() && this->character_data.get()) { - this->save_all(); - } -} - -const string& ClientGameData::get_bb_username() const { - return this->bb_username; -} - -void ClientGameData::set_bb_username(const string& bb_username) { - // Make sure bb_username is filename-safe - for (char ch : bb_username) { - if (!isalnum(ch) && (ch != '-') && (ch != '_')) { - throw runtime_error("invalid characters in username"); - } - } - this->bb_username = bb_username; -} - -void ClientGameData::create_battle_overlay(shared_ptr rules, shared_ptr level_table) { - this->overlay_character_data = make_shared(*this->character(true, false)); - - if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) { - this->overlay_character_data->inventory.remove_all_items_of_type(0); - this->overlay_character_data->inventory.remove_all_items_of_type(1); - } - if (rules->mag_mode == BattleRules::MagMode::FORBID_ALL) { - this->overlay_character_data->inventory.remove_all_items_of_type(2); - } - if (rules->tool_mode != BattleRules::ToolMode::ALLOW) { - this->overlay_character_data->inventory.remove_all_items_of_type(3); - } - if (rules->replace_char) { - // TODO: Shouldn't we clear other material usage here? It looks like the - // original code doesn't, but that seems wrong. - this->overlay_character_data->inventory.hp_from_materials = 0; - this->overlay_character_data->inventory.tp_from_materials = 0; - - uint32_t target_level = clamp(rules->char_level, 0, 199); - uint8_t char_class = this->overlay_character_data->disp.visual.char_class; - auto& stats = this->overlay_character_data->disp.stats; - - stats.reset_to_base(char_class, level_table); - stats.advance_to_level(char_class, target_level, level_table); - - stats.unknown_a1 = 40; - stats.meseta = 300; - } - if (rules->tech_disk_mode == BattleRules::TechDiskMode::LIMIT_LEVEL) { - // TODO: Verify this is what the game actually does. - for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) { - uint8_t existing_level = this->overlay_character_data->get_technique_level(tech_num); - if ((existing_level != 0xFF) && (existing_level > rules->max_tech_level)) { - this->overlay_character_data->set_technique_level(tech_num, rules->max_tech_level); - } - } - } else if (rules->tech_disk_mode == BattleRules::TechDiskMode::FORBID_ALL) { - for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) { - this->overlay_character_data->set_technique_level(tech_num, 0xFF); - } - } - if (rules->meseta_mode != BattleRules::MesetaMode::ALLOW) { - this->overlay_character_data->disp.stats.meseta = 0; - } - if (rules->forbid_scape_dolls) { - this->overlay_character_data->inventory.remove_all_items_of_type(3, 9); - } -} - -void ClientGameData::create_challenge_overlay(Version version, size_t template_index, shared_ptr level_table) { - auto p = this->character(true, false); - const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index); - - this->overlay_character_data = make_shared(*p); - auto overlay = this->overlay_character_data; - - for (size_t z = 0; z < overlay->inventory.items.size(); z++) { - auto& i = overlay->inventory.items[z]; - i.present = 0; - i.unknown_a1 = 0; - i.extension_data1 = 0; - i.extension_data2 = 0; - i.flags = 0; - i.data = ItemData(); - } - - overlay->inventory.items[13].extension_data2 = 1; - - overlay->disp.stats.reset_to_base(overlay->disp.visual.char_class, level_table); - overlay->disp.stats.advance_to_level(overlay->disp.visual.char_class, tpl.level, level_table); - - overlay->disp.stats.unknown_a1 = 40; - overlay->disp.stats.unknown_a3 = 10.0; - overlay->disp.stats.experience = level_table->stats_delta_for_level(overlay->disp.visual.char_class, overlay->disp.stats.level).experience; - overlay->disp.stats.meseta = 0; - overlay->clear_all_material_usage(); - for (size_t z = 0; z < 0x13; z++) { - overlay->set_technique_level(z, 0xFF); - } - - for (size_t z = 0; z < tpl.items.size(); z++) { - auto& inv_item = overlay->inventory.items[z]; - inv_item.present = tpl.items[z].present; - inv_item.unknown_a1 = tpl.items[z].unknown_a1; - inv_item.flags = tpl.items[z].flags; - inv_item.data = tpl.items[z].data; - } - overlay->inventory.num_items = tpl.items.size(); - - for (const auto& tech_level : tpl.tech_levels) { - overlay->set_technique_level(tech_level.tech_num, tech_level.level); - } -} - -shared_ptr ClientGameData::system(bool allow_load) { - if (!this->system_data && allow_load) { - this->load_all_files(); - } - return this->system_data; -} - -shared_ptr ClientGameData::system(bool allow_load) const { - if (!this->system_data.get() && allow_load) { - throw runtime_error("system data is not loaded"); - } - return this->system_data; -} - -shared_ptr ClientGameData::character(bool allow_load, bool allow_overlay) { - if (this->overlay_character_data && allow_overlay) { - return this->overlay_character_data; - } - if (!this->character_data && allow_load) { - if (!this->bb_username.empty() && (this->bb_character_index < 0)) { - throw runtime_error("character index not specified"); - } - this->load_all_files(); - } - return this->character_data; -} - -shared_ptr ClientGameData::character(bool allow_load, bool allow_overlay) const { - if (allow_overlay && this->overlay_character_data) { - return this->overlay_character_data; - } - if (!this->character_data && allow_load) { - throw runtime_error("character data is not loaded"); - } - return this->character_data; -} - -shared_ptr ClientGameData::guild_cards(bool allow_load) { - if (!this->guild_card_data && allow_load) { - this->load_all_files(); - } - return this->guild_card_data; -} - -shared_ptr ClientGameData::guild_cards(bool allow_load) const { - if (!this->guild_card_data && allow_load) { - throw runtime_error("account data is not loaded"); - } - return this->guild_card_data; -} - -string ClientGameData::system_filename() const { - if (this->bb_username.empty()) { - throw logic_error("non-BB players do not have system data"); - } - return string_printf("system/players/system_%s.psosys", this->bb_username.c_str()); -} - -string ClientGameData::character_filename(const std::string& bb_username, int8_t index) { - if (bb_username.empty()) { - throw logic_error("non-BB players do not have character data"); - } - if (index < 0) { - throw logic_error("character index is not set"); - } - return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index); -} - -string ClientGameData::backup_character_filename(uint32_t serial_number, size_t index) { - return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", serial_number, index); -} - -string ClientGameData::character_filename(int8_t index) const { - if (this->bb_username.empty()) { - throw logic_error("non-BB players do not have character data"); - } - if (index < 0) { - index = this->bb_character_index; - } - if (index < 0) { - throw logic_error("character index is not set"); - } - return this->character_filename(this->bb_username, index); -} - -string ClientGameData::guild_card_filename() const { - if (this->bb_username.empty()) { - throw logic_error("non-BB players do not have Guild Card files"); - } - return string_printf("system/players/guild_cards_%s.psocard", this->bb_username.c_str()); -} - -string ClientGameData::shared_bank_filename() const { - if (this->bb_username.empty()) { - throw logic_error("non-BB players do not have shared bank files"); - } - return string_printf("system/players/shared_bank_%s.psobank", this->bb_username.c_str()); -} - -string ClientGameData::legacy_account_filename() const { - if (this->bb_username.empty()) { - throw logic_error("non-BB players do not have legacy account data"); - } - return string_printf("system/players/account_%s.nsa", this->bb_username.c_str()); -} - -string ClientGameData::legacy_player_filename() const { - if (this->bb_username.empty()) { - throw logic_error("non-BB players do not have legacy player data"); - } - if (this->bb_character_index < 0) { - throw logic_error("character index is not set"); - } - return string_printf( - "system/players/player_%s_%hhd.nsc", - this->bb_username.c_str(), - static_cast(this->bb_character_index + 1)); -} - -void ClientGameData::create_character_file( - uint32_t guild_card_number, - uint8_t language, - const PlayerDispDataBBPreview& preview, - shared_ptr level_table) { - this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table); - this->save_character_file(); -} - -void ClientGameData::load_all_files() { - if (this->bb_username.empty()) { - this->system_data = make_shared(); - this->character_data = make_shared(); - this->guild_card_data = make_shared(); - return; - } - - this->system_data.reset(); - this->character_data.reset(); - this->guild_card_data.reset(); - - string sys_filename = this->system_filename(); - this->system_data = this->files_manager->get_system(sys_filename); - if (this->system_data) { - player_data_log.info("Using loaded system file %s", sys_filename.c_str()); - } else if (isfile(sys_filename)) { - this->system_data = make_shared(load_object_file(sys_filename, true)); - this->files_manager->set_system(sys_filename, this->system_data); - player_data_log.info("Loaded system data from %s", sys_filename.c_str()); - } else { - player_data_log.info("System file is missing: %s", sys_filename.c_str()); - } - - if (this->bb_character_index >= 0) { - string char_filename = this->character_filename(); - this->character_data = this->files_manager->get_character(char_filename); - if (this->character_data) { - player_data_log.info("Using loaded character file %s", char_filename.c_str()); - } else if (isfile(char_filename)) { - auto f = fopen_unique(char_filename, "rb"); - auto header = freadx(f.get()); - if (header.size != 0x399C) { - throw runtime_error("incorrect size in character file header"); - } - if (header.command != 0x00E7) { - throw runtime_error("incorrect command in character file header"); - } - if (header.flag != 0x00000000) { - throw runtime_error("incorrect flag in character file header"); - } - this->character_data = make_shared(freadx(f.get())); - this->files_manager->set_character(char_filename, this->character_data); - player_data_log.info("Loaded character data from %s", char_filename.c_str()); - - // If there was no .psosys file, load the system file from the .psochar - // file instead - if (!this->system_data) { - this->system_data = make_shared(freadx(f.get())); - this->files_manager->set_system(sys_filename, this->system_data); - player_data_log.info("Loaded system data from %s", char_filename.c_str()); - } - } else { - player_data_log.info("Character file is missing: %s", char_filename.c_str()); - } - } - - string card_filename = this->guild_card_filename(); - this->guild_card_data = this->files_manager->get_guild_card(card_filename); - if (this->guild_card_data) { - player_data_log.info("Using loaded Guild Card file %s", card_filename.c_str()); - } else if (isfile(card_filename)) { - this->guild_card_data = make_shared(load_object_file(card_filename)); - this->files_manager->set_guild_card(card_filename, this->guild_card_data); - player_data_log.info("Loaded Guild Card data from %s", card_filename.c_str()); - } else { - player_data_log.info("Guild Card file is missing: %s", card_filename.c_str()); - } - - // If any of the above files were missing, try to load from .nsa/.nsc files instead - if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) { - string nsa_filename = this->legacy_account_filename(); - shared_ptr nsa_data; - if (isfile(nsa_filename)) { - nsa_data = make_shared(load_object_file(nsa_filename)); - if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) { - throw runtime_error("account data header is incorrect"); - } - if (!this->system_data) { - this->system_data = make_shared(nsa_data->system_file.base); - this->files_manager->set_system(sys_filename, this->system_data); - player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str()); - } - if (!this->guild_card_data) { - this->guild_card_data = make_shared(nsa_data->guild_card_file); - this->files_manager->set_guild_card(card_filename, this->guild_card_data); - player_data_log.info("Loaded legacy Guild Card data from %s", nsa_filename.c_str()); - } - } - - if (!this->system_data) { - this->system_data = make_shared(); - this->files_manager->set_system(sys_filename, this->system_data); - player_data_log.info("Created new system data"); - } - if (!this->guild_card_data) { - this->guild_card_data = make_shared(); - this->files_manager->set_guild_card(card_filename, this->guild_card_data); - player_data_log.info("Created new Guild Card data"); - } - - if (!this->character_data && (this->bb_character_index >= 0)) { - string nsc_filename = this->legacy_player_filename(); - auto nsc_data = load_object_file(nsc_filename); - if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) { - nsc_data.signature = LegacySavedPlayerDataBB::SIGNATURE_V0; - nsc_data.unused.clear(); - nsc_data.battle_records.place_counts.clear(0); - nsc_data.battle_records.disconnect_count = 0; - nsc_data.battle_records.unknown_a1.clear(0); - } else if (nsc_data.signature != LegacySavedPlayerDataBB::SIGNATURE_V1) { - throw runtime_error("legacy player data has incorrect signature"); - } - - this->character_data = make_shared(); - this->files_manager->set_character(this->character_filename(), this->character_data); - this->character_data->inventory = nsc_data.inventory; - this->character_data->disp = nsc_data.disp; - this->character_data->play_time_seconds = nsc_data.disp.play_time; - this->character_data->unknown_a2 = nsc_data.unknown_a2; - this->character_data->quest_flags = nsc_data.quest_flags; - this->character_data->death_count = nsc_data.death_count; - this->character_data->bank = nsc_data.bank; - this->character_data->guild_card.guild_card_number = this->guild_card_number; - this->character_data->guild_card.name = nsc_data.disp.name; - this->character_data->guild_card.description = nsc_data.guild_card_description; - this->character_data->guild_card.present = 1; - this->character_data->guild_card.language = nsc_data.inventory.language; - this->character_data->guild_card.section_id = nsc_data.disp.visual.section_id; - this->character_data->guild_card.char_class = nsc_data.disp.visual.char_class; - this->character_data->auto_reply = nsc_data.auto_reply; - this->character_data->info_board = nsc_data.info_board; - this->character_data->battle_records = nsc_data.battle_records; - this->character_data->challenge_records = nsc_data.challenge_records; - this->character_data->tech_menu_config = nsc_data.tech_menu_config; - this->character_data->quest_global_flags = nsc_data.quest_global_flags; - if (nsa_data) { - this->character_data->option_flags = nsa_data->option_flags; - this->character_data->symbol_chats = nsa_data->symbol_chats; - this->character_data->shortcuts = nsa_data->shortcuts; - player_data_log.info("Loaded legacy player data from %s and %s", nsa_filename.c_str(), nsc_filename.c_str()); - } else { - player_data_log.info("Loaded legacy player data from %s", nsc_filename.c_str()); - } - } - } - - this->blocked_senders.fill(0); - for (size_t z = 0; z < this->guild_card_data->blocked.size(); z++) { - if (this->guild_card_data->blocked[z].present) { - this->blocked_senders[z] = this->guild_card_data->blocked[z].guild_card_number; - } - } - - if (this->character_data) { - this->last_play_time_update = now(); - } -} - -void ClientGameData::save_all() { - if (this->system_data) { - this->save_system_file(); - } - if (this->character_data) { - this->save_character_file(); - } - if (this->guild_card_data) { - this->save_guild_card_file(); - } - if (this->external_bank) { - string filename = this->shared_bank_filename(); - save_object_file(filename, *this->external_bank); - player_data_log.info("Saved shared bank file %s", filename.c_str()); - } - if (this->external_bank_character) { - this->save_character_file( - this->character_filename(this->external_bank_character_index), - this->system_data, - this->external_bank_character); - } -} - -void ClientGameData::save_system_file() const { - if (!this->system_data) { - throw logic_error("no system file loaded"); - } - string filename = this->system_filename(); - save_object_file(filename, *this->system_data); - player_data_log.info("Saved system file %s", filename.c_str()); -} - -void ClientGameData::save_character_file( - const string& filename, - shared_ptr system, - shared_ptr character) { - auto f = fopen_unique(filename, "wb"); - PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000}; - fwritex(f.get(), header); - fwritex(f.get(), *character); - fwritex(f.get(), *system); - // TODO: Technically, we should write the actual team membership struct to the - // file here, but that would cause ClientGameData to depend on License, which - // it currently does not. This data doesn't matter at all for correctness - // within newserv, since it ignores this data entirely and instead generates - // the membership struct from the team ID in the License and the team's state. - // So, writing correct data here would mostly be for compatibility with other - // PSO servers. But if the other server is newserv, then this data would be - // used anyway, and if it's not, then it would presumably have a different set - // of teams with a different set of team IDs anyway, so the membership struct - // here would be useless either way. - static const PSOBBTeamMembership empty_membership; - fwritex(f.get(), empty_membership); - player_data_log.info("Saved character file %s", filename.c_str()); -} - -void ClientGameData::save_character_file() { - if (!this->system_data.get()) { - throw logic_error("no system file loaded"); - } - if (!this->character_data.get()) { - throw logic_error("no character file loaded"); - } - if (this->should_update_play_time) { - // This is slightly inaccurate, since fractions of a second are truncated - // off each time we save. I'm lazy, so insert shrug emoji here. - uint64_t t = now(); - uint64_t seconds = (t - this->last_play_time_update) / 1000000; - this->character_data->disp.play_time += seconds; - this->character_data->play_time_seconds = this->character_data->disp.play_time; - player_data_log.info("Added %" PRIu64 " seconds to play time", seconds); - this->last_play_time_update = t; - } - - this->save_character_file(this->character_filename(), this->system_data, this->character_data); -} - -void ClientGameData::save_guild_card_file() const { - if (!this->guild_card_data.get()) { - throw logic_error("no Guild Card file loaded"); - } - string filename = this->guild_card_filename(); - save_object_file(filename, *this->guild_card_data); - player_data_log.info("Saved Guild Card file %s", filename.c_str()); -} - -void ClientGameData::load_backup_character(uint32_t serial_number, size_t index) { - string filename = this->backup_character_filename(serial_number, index); - auto f = fopen_unique(filename, "rb"); - auto header = freadx(f.get()); - if (header.size != 0x399C) { - throw runtime_error("incorrect size in character file header"); - } - if (header.command != 0x00E7) { - throw runtime_error("incorrect command in character file header"); - } - if (header.flag != 0x00000000) { - throw runtime_error("incorrect flag in character file header"); - } - this->character_data = make_shared(freadx(f.get())); - this->last_reported_disp_v1_v2.reset(); -} - -PlayerBank& ClientGameData::current_bank() { - if (this->external_bank) { - return *this->external_bank; - } else if (this->external_bank_character) { - return this->external_bank_character->bank; - } - return this->character()->bank; -} - -std::shared_ptr ClientGameData::current_bank_character() { - return this->external_bank_character ? this->external_bank_character : this->character(); -} - -void ClientGameData::use_default_bank() { - if (this->external_bank) { - string filename = this->shared_bank_filename(); - save_object_file(filename, *this->external_bank); - this->external_bank.reset(); - player_data_log.info("Detached shared bank %s", filename.c_str()); - } - if (this->external_bank_character) { - string filename = this->character_filename(this->external_bank_character_index); - this->save_character_file(filename, this->system_data, this->external_bank_character); - this->external_bank_character.reset(); - player_data_log.info("Detached character %s from bank", filename.c_str()); - } -} - -bool ClientGameData::use_shared_bank() { - this->use_default_bank(); - string filename = this->shared_bank_filename(); - if (isfile(filename)) { - this->external_bank = make_shared(load_object_file(filename)); - player_data_log.info("Loaded shared bank %s", filename.c_str()); - return true; - } else { - this->external_bank = make_shared(); - player_data_log.info("Created shared bank for %s", filename.c_str()); - return false; - } -} - -void ClientGameData::use_character_bank(int8_t index) { - this->use_default_bank(); - if (index != this->bb_character_index) { - string filename = this->character_filename(index); - this->external_bank_character = this->files_manager->get_character(filename); - if (this->external_bank_character) { - this->external_bank_character_index = index; - player_data_log.info("Using loaded character file %s for external bank", filename.c_str()); - } else if (isfile(filename)) { - auto f = fopen_unique(filename, "rb"); - auto header = freadx(f.get()); - if (header.size != 0x399C) { - throw runtime_error("incorrect size in character file header"); - } - if (header.command != 0x00E7) { - throw runtime_error("incorrect command in character file header"); - } - if (header.flag != 0x00000000) { - throw runtime_error("incorrect flag in character file header"); - } - this->external_bank_character = make_shared(freadx(f.get())); - this->external_bank_character_index = index; - this->files_manager->set_character(filename, this->external_bank_character); - player_data_log.info("Loaded character data from %s for external bank", filename.c_str()); - } else { - throw runtime_error("character does not exist"); - } - } -} diff --git a/src/Player.hh b/src/Player.hh deleted file mode 100644 index 438280d1..00000000 --- a/src/Player.hh +++ /dev/null @@ -1,162 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "Episode3/DataIndexes.hh" -#include "ItemCreator.hh" -#include "ItemNameIndex.hh" -#include "LevelTable.hh" -#include "PlayerSubordinates.hh" -#include "SaveFileFormats.hh" -#include "Text.hh" -#include "Version.hh" - -struct PendingItemTrade { - uint8_t other_client_id; - bool confirmed; // true if client has sent a D2 command - std::vector items; -}; - -struct PendingCardTrade { - uint8_t other_client_id; - bool confirmed; // true if client has sent an EE D2 command - std::vector> card_to_count; -}; - -class PlayerFilesManager { -public: - explicit PlayerFilesManager(std::shared_ptr base); - ~PlayerFilesManager() = default; - - std::shared_ptr get_system(const std::string& filename); - std::shared_ptr get_character(const std::string& filename); - std::shared_ptr get_guild_card(const std::string& filename); - std::shared_ptr get_bank(const std::string& filename); - - void set_system(const std::string& filename, std::shared_ptr file); - void set_character(const std::string& filename, std::shared_ptr file); - void set_guild_card(const std::string& filename, std::shared_ptr file); - void set_bank(const std::string& filename, std::shared_ptr file); - -private: - std::shared_ptr base; - std::unique_ptr clear_expired_files_event; - - std::unordered_map> loaded_system_files; - std::unordered_map> loaded_character_files; - std::unordered_map> loaded_guild_card_files; - std::unordered_map> loaded_bank_files; - - static void clear_expired_files(evutil_socket_t fd, short events, void* ctx); -}; - -class ClientGameData { -public: - uint32_t guild_card_number; - bool should_update_play_time; - - // The following fields are not saved, and are only used in certain situations - - std::array blocked_senders; - - // This is only used if the client is v1 or v2 - std::unique_ptr last_reported_disp_v1_v2; - - // Null unless the client is within the trade sequence (D0-D4 or EE commands) - std::unique_ptr pending_item_trade; - std::unique_ptr pending_card_trade; - - // Null unless the client is Episode 3 and has sent its config already - std::shared_ptr ep3_config; - - // These are only used if the client is BB - int8_t bb_character_index; - ItemData identify_result; - std::array, 3> shop_contents; - - explicit ClientGameData(std::shared_ptr files_manager); - ~ClientGameData(); - - const std::string& get_bb_username() const; - void set_bb_username(const std::string& bb_username); - - void create_battle_overlay(std::shared_ptr rules, std::shared_ptr level_table); - void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr level_table); - inline void delete_overlay() { - this->overlay_character_data.reset(); - } - inline bool has_overlay() const { - return this->overlay_character_data.get() != nullptr; - } - - std::shared_ptr system(bool allow_load = true); - std::shared_ptr system(bool allow_load = true) const; - - std::shared_ptr character(bool allow_load = true, bool allow_overlay = true); - std::shared_ptr character(bool allow_load = true, bool allow_overlay = true) const; - - std::shared_ptr guild_cards(bool allow_load = true); - std::shared_ptr guild_cards(bool allow_load = true) const; - - void create_character_file( - uint32_t guild_card_number, - uint8_t language, - const PlayerDispDataBBPreview& preview, - std::shared_ptr level_table); - - std::string system_filename() const; - static std::string character_filename(const std::string& bb_username, int8_t index); - static std::string backup_character_filename(uint32_t serial_number, size_t index); - std::string character_filename(int8_t index = -1) const; - std::string guild_card_filename() const; - std::string shared_bank_filename() const; - - std::string legacy_player_filename() const; - std::string legacy_account_filename() const; - - void save_all(); - void save_system_file() const; - static void save_character_file( - const std::string& filename, - std::shared_ptr sys, - std::shared_ptr character); - // Note: This function is not const because it updates the player's play time. - void save_character_file(); - void save_guild_card_file() const; - - void load_backup_character(uint32_t serial_number, size_t index); - - PlayerBank& current_bank(); - std::shared_ptr current_bank_character(); - bool use_shared_bank(); // Returns true if the bank exists; false if it was created - void use_character_bank(int8_t bb_character_index); - void use_default_bank(); - -private: - std::string bb_username; - std::shared_ptr files_manager; - - // The overlay character data is used in battle and challenge modes, when - // character data is temporarily replaced in-game. In other play modes and in - // lobbies, overlay_character_data is null. - std::shared_ptr system_data; - std::shared_ptr overlay_character_data; - std::shared_ptr character_data; - std::shared_ptr guild_card_data; - std::shared_ptr external_bank; - std::shared_ptr external_bank_character; - int8_t external_bank_character_index; - uint64_t last_play_time_update; - - void save_and_clear_external_bank(); - - void load_all_files(); -}; diff --git a/src/PlayerFilesManager.cc b/src/PlayerFilesManager.cc new file mode 100644 index 00000000..6b1a6581 --- /dev/null +++ b/src/PlayerFilesManager.cc @@ -0,0 +1,119 @@ +#include "PlayerFilesManager.hh" + +#include +#include +#include + +#include +#include +#include + +#include "FileContentsCache.hh" +#include "ItemData.hh" +#include "Loggers.hh" +#include "PSOEncryption.hh" +#include "PSOProtocol.hh" +#include "StaticGameData.hh" +#include "Text.hh" +#include "Version.hh" + +using namespace std; + +PlayerFilesManager::PlayerFilesManager(std::shared_ptr base) + : base(base), + clear_expired_files_event( + event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this), + event_free) { + auto tv = usecs_to_timeval(30 * 1000 * 1000); + event_add(this->clear_expired_files_event.get(), &tv); +} + +template +size_t erase_unused(std::unordered_map>& m) { + size_t ret = 0; + for (auto it = m.begin(); it != m.end();) { + if (it->second.use_count() <= 1) { + it = m.erase(it); + ret++; + } else { + it++; + } + } + return ret; +} + +std::shared_ptr PlayerFilesManager::get_system(const std::string& filename) { + try { + return this->loaded_system_files.at(filename); + } catch (const out_of_range&) { + return nullptr; + } +} + +std::shared_ptr PlayerFilesManager::get_character(const std::string& filename) { + try { + return this->loaded_character_files.at(filename); + } catch (const out_of_range&) { + return nullptr; + } +} + +std::shared_ptr PlayerFilesManager::get_guild_card(const std::string& filename) { + try { + return this->loaded_guild_card_files.at(filename); + } catch (const out_of_range&) { + return nullptr; + } +} + +std::shared_ptr PlayerFilesManager::get_bank(const std::string& filename) { + try { + return this->loaded_bank_files.at(filename); + } catch (const out_of_range&) { + return nullptr; + } +} + +void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr file) { + if (!this->loaded_system_files.emplace(filename, file).second) { + throw runtime_error("Guild Card file already loaded: " + filename); + } +} + +void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr file) { + if (!this->loaded_character_files.emplace(filename, file).second) { + throw runtime_error("character file already loaded: " + filename); + } +} + +void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr file) { + if (!this->loaded_guild_card_files.emplace(filename, file).second) { + throw runtime_error("Guild Card file already loaded: " + filename); + } +} + +void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr file) { + if (!this->loaded_bank_files.emplace(filename, file).second) { + throw runtime_error("bank file already loaded: " + filename); + } +} + +void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) { + auto* self = reinterpret_cast(ctx); + size_t num_deleted = erase_unused(self->loaded_system_files); + if (num_deleted) { + player_data_log.info("Cleared %zu expired system file(s)", num_deleted); + } + num_deleted = erase_unused(self->loaded_character_files); + if (num_deleted) { + player_data_log.info("Cleared %zu expired character file(s)", num_deleted); + } + num_deleted = erase_unused(self->loaded_guild_card_files); + if (num_deleted) { + player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted); + } + num_deleted = erase_unused(self->loaded_bank_files); + if (num_deleted) { + player_data_log.info("Cleared %zu expired bank file(s)", num_deleted); + } +} diff --git a/src/PlayerFilesManager.hh b/src/PlayerFilesManager.hh new file mode 100644 index 00000000..69dd4ed5 --- /dev/null +++ b/src/PlayerFilesManager.hh @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "Episode3/DataIndexes.hh" +#include "ItemCreator.hh" +#include "ItemNameIndex.hh" +#include "LevelTable.hh" +#include "PlayerSubordinates.hh" +#include "SaveFileFormats.hh" +#include "Text.hh" +#include "Version.hh" + +class PlayerFilesManager { +public: + explicit PlayerFilesManager(std::shared_ptr base); + ~PlayerFilesManager() = default; + + std::shared_ptr get_system(const std::string& filename); + std::shared_ptr get_character(const std::string& filename); + std::shared_ptr get_guild_card(const std::string& filename); + std::shared_ptr get_bank(const std::string& filename); + + void set_system(const std::string& filename, std::shared_ptr file); + void set_character(const std::string& filename, std::shared_ptr file); + void set_guild_card(const std::string& filename, std::shared_ptr file); + void set_bank(const std::string& filename, std::shared_ptr file); + +private: + std::shared_ptr base; + std::unique_ptr clear_expired_files_event; + + std::unordered_map> loaded_system_files; + std::unordered_map> loaded_character_files; + std::unordered_map> loaded_guild_card_files; + std::unordered_map> loaded_bank_files; + + static void clear_expired_files(evutil_socket_t fd, short events, void* ctx); +}; diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index bdb7fe78..801cf068 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -17,7 +17,7 @@ #include "Text.hh" #include "Version.hh" -struct Client; +class Client; class ItemParameterTable; // PSO V2 stored some extra data in the character structs in a format that I'm diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 50d7db4c..bb666d09 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -295,7 +295,7 @@ void on_login_complete(shared_ptr c) { if (c->version() == Version::BB_V4) { // This implicitly loads the client's account and player data send_complete_player_bb(c); - c->game_data.should_update_play_time = true; + c->should_update_play_time = true; } if (is_ep3(c->version())) { @@ -1040,7 +1040,7 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } c->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : cmd.language; c->bb_connection_phase = cmd.connection_phase; - c->game_data.bb_character_index = cmd.character_slot; + c->bb_character_index = cmd.character_slot; if (cmd.menu_id == MenuID::LOBBY) { c->preferred_lobby_id = cmd.preferred_lobby_id; @@ -1522,7 +1522,7 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { l->battle_record = make_shared(s->ep3_behavior_flags); for (auto existing_c : l->clients) { if (existing_c) { - auto existing_p = existing_c->game_data.character(); + auto existing_p = existing_c->character(); PlayerLobbyDataDCGC lobby_data; lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language()); lobby_data.player_tag = 0x00010000; @@ -1531,7 +1531,7 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { lobby_data, existing_p->inventory, existing_p->disp.to_dcpcv3(c->language(), c->language()), - c->game_data.ep3_config ? (c->game_data.ep3_config->online_clv_exp / 100) : 0); + c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0); } } if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { @@ -1707,7 +1707,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { for (size_t x = 0; x < game->max_clients; x++) { const auto& game_c = game->clients[x]; if (game_c.get()) { - auto player = game_c->game_data.character(); + auto player = game_c->character(); string name = player->disp.name.decode(game_c->language()); if (game->is_ep3()) { info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n", @@ -1892,12 +1892,12 @@ static void on_quest_loaded(shared_ptr l) { // data. On BB, this is instead done in the 6xCF handler (for battle) or // the 02DF handler (for challenge). if (l->base_version != Version::BB_V4) { - lc->game_data.delete_overlay(); + lc->delete_overlay(); if (l->quest->battle_rules) { - lc->game_data.create_battle_overlay(l->quest->battle_rules, s->level_table); + lc->create_battle_overlay(l->quest->battle_rules, s->level_table); lc->log.info("Created battle overlay"); } else if (l->quest->challenge_template_index >= 0) { - lc->game_data.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->log.info("Created challenge overlay"); l->assign_inventory_and_bank_item_ids(lc); } @@ -2044,7 +2044,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { } case MainMenuItemID::PROXY_DESTINATIONS: - if (!c->game_data.character(false, false)) { + if (!c->character(false, false)) { send_get_player_info(c); } send_proxy_destinations_menu(c); @@ -2278,7 +2278,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { send_lobby_message_box(c, "$C6Incorrect password."); break; } - auto p = c->game_data.character(); + auto p = c->character(); if (p->disp.stats.level < game->min_level) { send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame."); break; @@ -2464,7 +2464,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; } if (team_name.empty()) { - team_name = c->game_data.character()->disp.name.decode(c->language()); + team_name = c->character()->disp.name.decode(c->language()); team_name += string_printf("/%" PRIX32, c->license->serial_number); } auto s = c->require_server_state(); @@ -2794,21 +2794,21 @@ static void on_13_A7_V3_BB(shared_ptr c, uint16_t command, uint32_t flag static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, string& data) { auto s = c->require_server_state(); - auto player = c->game_data.character(); + auto player = c->character(); switch (c->version()) { case Version::DC_NTE: case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1: { const auto& cmd = check_size_t(data); - c->game_data.last_reported_disp_v1_v2 = make_unique(cmd.disp); + c->v1_v2_last_reported_disp = make_unique(cmd.disp); player->inventory = cmd.inventory; player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); break; } case Version::DC_V2: { const auto& cmd = check_size_t(data, 0xFFFF); - c->game_data.last_reported_disp_v1_v2 = make_unique(cmd.disp); + c->v1_v2_last_reported_disp = make_unique(cmd.disp); player->inventory = cmd.inventory; player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); player->battle_records = cmd.records.battle; @@ -2818,15 +2818,13 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } case Version::PC_V2: { const auto& cmd = check_size_t(data, 0xFFFF); - c->game_data.last_reported_disp_v1_v2 = make_unique(cmd.disp); + c->v1_v2_last_reported_disp = make_unique(cmd.disp); player->inventory = cmd.inventory; player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); player->battle_records = cmd.records.battle; player->challenge_records = cmd.records.challenge; player->choice_search_config = cmd.choice_search_config; - for (size_t z = 0; z < cmd.blocked_senders.size(); z++) { - c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z]; - } + c->import_blocked_senders(cmd.blocked_senders); if (cmd.auto_reply_enabled) { string auto_reply = data.substr(sizeof(cmd)); strip_trailing_zeroes(auto_reply); @@ -2841,7 +2839,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } case Version::GC_NTE: { const auto& cmd = check_size_t(data, 0xFFFF); - c->game_data.last_reported_disp_v1_v2 = make_unique(cmd.disp); + c->v1_v2_last_reported_disp = make_unique(cmd.disp); auto s = c->require_server_state(); player->inventory = cmd.inventory; @@ -2849,9 +2847,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri player->battle_records = cmd.records.battle; player->challenge_records = cmd.records.challenge; player->choice_search_config = cmd.choice_search_config; - for (size_t z = 0; z < cmd.blocked_senders.size(); z++) { - c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z]; - } + c->import_blocked_senders(cmd.blocked_senders); if (cmd.auto_reply_enabled) { string auto_reply = data.substr(sizeof(cmd), 0xAC); strip_trailing_zeroes(auto_reply); @@ -2873,7 +2869,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri throw runtime_error("non-Episode 3 client sent Episode 3 player data"); } const auto* cmd3 = &check_size_t(data); - c->game_data.ep3_config = make_shared(cmd3->ep3_config); + c->ep3_config = make_shared(cmd3->ep3_config); cmd = reinterpret_cast(cmd3); if (c->config.specific_version == 0x33000000) { c->config.specific_version = 0x33534A30; // 3SJ0 @@ -2924,9 +2920,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri player->challenge_records = cmd->records.challenge; player->choice_search_config = cmd->choice_search_config; player->info_board.encode(cmd->info_board.decode(player->inventory.language), player->inventory.language); - for (size_t z = 0; z < cmd->blocked_senders.size(); z++) { - c->game_data.blocked_senders.at(z) = cmd->blocked_senders[z]; - } + c->import_blocked_senders(cmd->blocked_senders); if (cmd->auto_reply_enabled) { string auto_reply = data.substr(sizeof(cmd), 0xAC); strip_trailing_zeroes(auto_reply); @@ -2945,9 +2939,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri player->challenge_records = cmd.records.challenge; player->choice_search_config = cmd.choice_search_config; player->info_board = cmd.info_board; - for (size_t z = 0; z < cmd.blocked_senders.size(); z++) { - c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z]; - } + c->import_blocked_senders(cmd.blocked_senders); if (cmd.auto_reply_enabled) { string auto_reply = data.substr(sizeof(cmd), 0xAC); strip_trailing_zeroes(auto_reply); @@ -2976,7 +2968,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri // in no lobby (they will send an 84 soon afterward to choose a lobby). if (command == 0x98) { // If the client had an overlay (for battle/challenge modes), delete it - c->game_data.delete_overlay(); + c->delete_overlay(); s->remove_client_from_lobby(c); @@ -2987,11 +2979,11 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri string filename; if (pending_export->is_bb_conversion) { - filename = ClientGameData::character_filename( + filename = Client::character_filename( pending_export->license->bb_username, pending_export->character_index); } else { - filename = ClientGameData::backup_character_filename( + filename = Client::backup_character_filename( pending_export->license->serial_number, pending_export->character_index); } @@ -3025,7 +3017,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri bb_player->challenge_records = player->challenge_records; bb_player->choice_search_config = player->choice_search_config; try { - ClientGameData::save_character_file(filename, c->game_data.system(), bb_player); + Client::save_character_file(filename, c->system_file(), bb_player); send_text_message(c, "$C6Character data saved"); } catch (const exception& e) { send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what()); @@ -3086,7 +3078,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { return; } - auto p = c->game_data.character(); + auto p = c->character(); string from_name = p->disp.name.decode(c->language()); if (from_name.size() >= 2 && from_name[0] == '\t' && (from_name[1] == 'E' || from_name[1] == 'J')) { from_name = from_name.substr(2); @@ -3124,7 +3116,8 @@ static void on_E0_BB(shared_ptr c, uint16_t, uint32_t, string& data) { static void on_E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); - c->game_data.bb_character_index = cmd.character_index; + c->bb_character_index = cmd.character_index; + c->unload_character(); if (c->bb_connection_phase != 0x00) { send_approve_player_choice_bb(c); @@ -3136,14 +3129,8 @@ static void on_E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } auto s = c->require_server_state(); - - ClientGameData temp_gd(s->player_files_manager); - temp_gd.guild_card_number = c->license->serial_number; - temp_gd.set_bb_username(c->license->bb_username); - temp_gd.bb_character_index = cmd.character_index; - try { - auto preview = temp_gd.character()->disp.to_preview(); + auto preview = c->character()->disp.to_preview(); send_player_preview_bb(c, cmd.character_index, &preview); } catch (const exception& e) { @@ -3157,7 +3144,7 @@ static void on_E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { static void on_E8_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { constexpr size_t max_count = sizeof(PSOBBGuildCardFile::entries) / sizeof(PSOBBGuildCardFile::Entry); constexpr size_t max_blocked = sizeof(PSOBBGuildCardFile::blocked) / sizeof(GuildCardBB); - auto gcf = c->game_data.guild_cards(); + auto gcf = c->guild_card_file(); bool should_save = false; switch (command) { case 0x01E8: { // Check guild card file checksum @@ -3287,7 +3274,7 @@ static void on_E8_BB(shared_ptr c, uint16_t command, uint32_t, string& d throw invalid_argument("invalid command"); } if (should_save) { - c->game_data.save_guild_card_file(); + c->save_guild_card_file(); } } @@ -3322,17 +3309,17 @@ static void on_E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { return; } - if (c->game_data.character(false).get()) { + if (c->character(false).get()) { throw runtime_error("player already exists"); } - c->game_data.bb_character_index = -1; - c->game_data.system(); // Ensure system file is loaded - c->game_data.bb_character_index = cmd.character_index; + c->bb_character_index = -1; + c->system_file(); // Ensure system file is loaded + c->bb_character_index = cmd.character_index; if (c->bb_connection_phase == 0x03) { // Dressing room try { - c->game_data.character()->disp.apply_dressing_room(cmd.preview); + c->character()->disp.apply_dressing_room(cmd.preview); } catch (const exception& e) { send_message_box(c, string_printf("$C6Character could not be modified:\n%s", e.what())); return; @@ -3340,7 +3327,7 @@ static void on_E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } else { try { auto s = c->require_server_state(); - c->game_data.create_character_file(c->license->serial_number, c->language(), cmd.preview, s->level_table); + c->create_character_file(c->license->serial_number, c->language(), cmd.preview, s->level_table); } catch (const exception& e) { send_message_box(c, string_printf("$C6New character could not be created:\n%s", e.what())); return; @@ -3351,8 +3338,8 @@ static void on_E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } static void on_ED_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { - auto p = c->game_data.character(); - auto sys = c->game_data.system(); + auto p = c->character(); + auto sys = c->system_file(); switch (command) { case 0x01ED: { const auto& cmd = check_size_t(data); @@ -3372,13 +3359,13 @@ static void on_ED_BB(shared_ptr c, uint16_t command, uint32_t, string& d case 0x04ED: { const auto& cmd = check_size_t(data); sys->key_config = cmd.key_config; - c->game_data.save_system_file(); + c->save_system_file(); break; } case 0x05ED: { const auto& cmd = check_size_t(data); sys->joystick_config = cmd.pad_config; - c->game_data.save_system_file(); + c->save_system_file(); break; } case 0x06ED: { @@ -3407,18 +3394,18 @@ static void on_E7_BB(shared_ptr c, uint16_t, uint32_t, string& data) { // TODO: In the future, we shouldn't need to trust any of the client's data // here. We should instead verify our copy of the player against what the // client sent, and alert on anything that's out of sync. - auto p = c->game_data.character(); + auto p = c->character(); p->challenge_records = cmd.char_file.challenge_records; p->battle_records = cmd.char_file.battle_records; p->death_count = cmd.char_file.death_count; - *c->game_data.system() = cmd.system_file.base; + *c->system_file() = cmd.system_file.base; } static void on_E2_BB(shared_ptr c, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); - auto sys = c->game_data.system(); + auto sys = c->system_file(); *sys = cmd.base; - c->game_data.save_system_file(); + c->save_system_file(); S_SystemFileCreated_00E1_BB out_cmd = {1}; send_command_t(c, 0x00E1, 0x00000000, out_cmd); @@ -3453,7 +3440,7 @@ static void on_DF_BB(shared_ptr c, uint16_t command, uint32_t, string& d } for (auto lc : l->clients) { if (lc) { - lc->game_data.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->log.info("Created challenge overlay"); l->assign_inventory_and_bank_item_ids(lc); } @@ -3499,7 +3486,7 @@ static void on_DF_BB(shared_ptr c, uint16_t command, uint32_t, string& d case 0x07DF: { const auto& cmd = check_size_t(data); - auto p = c->game_data.character(true, false); + auto p = c->character(true, false); auto& award_state = (l->episode == Episode::EP2) ? p->challenge_records.ep2_online_award_state : p->challenge_records.ep1_online_award_state; @@ -3541,7 +3528,7 @@ static void on_C0(shared_ptr c, uint16_t, uint32_t, string&) { } static void on_C2(shared_ptr c, uint16_t, uint32_t, string& data) { - c->game_data.character()->choice_search_config = check_size_t(data); + c->character()->choice_search_config = check_size_t(data); } template @@ -3551,7 +3538,7 @@ static void on_choice_search_t(shared_ptr c, const ChoiceSearchConfig& c vector results; for (const auto& l : s->all_lobbies()) { for (const auto& lc : l->clients) { - if (!lc || lc->game_data.character()->choice_search_config.disabled) { + if (!lc || lc->character()->choice_search_config.disabled) { continue; } @@ -3572,7 +3559,7 @@ static void on_choice_search_t(shared_ptr c, const ChoiceSearchConfig& c } if (is_match) { - auto lp = lc->game_data.character(); + auto lp = lc->character(); auto& result = results.emplace_back(); result.guild_card_number = lc->license->serial_number; result.name.encode(lp->disp.name.decode(lc->language()), c->language()); @@ -3680,15 +3667,13 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, string& data) { } else { // If the sender is blocked, don't forward the mail - for (size_t y = 0; y < 30; y++) { - if (target->game_data.blocked_senders.data()[y] == c->license->serial_number) { - return; - } + if (target->blocked_senders.count(c->license->serial_number)) { + return; } // If the target has auto-reply enabled, send the autoreply. Note that we also // forward the message in this case. - auto target_p = target->game_data.character(); + auto target_p = target->character(); if (!target_p->auto_reply.empty()) { send_simple_mail( c, @@ -3701,7 +3686,7 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, string& data) { send_simple_mail( target, c->license->serial_number, - c->game_data.character()->disp.name.decode(c->language()), + c->character()->disp.name.decode(c->language()), message); } } @@ -3717,7 +3702,7 @@ void on_D9(shared_ptr c, uint16_t, uint32_t, string& data) { if (is_w && (data.size() & 1)) { data.push_back(0); } - c->game_data.character(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language()); + c->character(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language()); } void on_C7(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -3726,26 +3711,22 @@ void on_C7(shared_ptr c, uint16_t, uint32_t, string& data) { if (is_w && (data.size() & 1)) { data.push_back(0); } - c->game_data.character(true, false)->auto_reply.encode(tt_decode_marked(data, c->language(), is_w), c->language()); + c->character(true, false)->auto_reply.encode(tt_decode_marked(data, c->language(), is_w), c->language()); } static void on_C8(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); - c->game_data.character(true, false)->auto_reply.clear(); + c->character(true, false)->auto_reply.clear(); } static void on_C6(shared_ptr c, uint16_t, uint32_t, string& data) { - c->game_data.blocked_senders.fill(0); + c->blocked_senders.clear(); if (c->version() == Version::BB_V4) { const auto& cmd = check_size_t(data); - for (size_t z = 0; z < cmd.blocked_senders.size(); z++) { - c->game_data.blocked_senders[z] = cmd.blocked_senders[z]; - } + c->import_blocked_senders(cmd.blocked_senders); } else { const auto& cmd = check_size_t(data); - for (size_t z = 0; z < cmd.blocked_senders.size(); z++) { - c->game_data.blocked_senders[z] = cmd.blocked_senders[z]; - } + c->import_blocked_senders(cmd.blocked_senders); } } @@ -3805,7 +3786,7 @@ shared_ptr create_game_generic( throw runtime_error("invalid episode"); } - auto p = c->game_data.character(); + auto p = c->character(); if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) { // Note: We don't throw here because this is a situation players might @@ -4311,7 +4292,7 @@ static void on_99(shared_ptr c, uint16_t, uint32_t, string& data) { static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); - if (c->game_data.pending_item_trade) { + if (c->pending_item_trade) { throw runtime_error("player started a trade when one is already pending"); } if (cmd.item_count > 0x20) { @@ -4327,10 +4308,10 @@ static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) throw runtime_error("trade command sent to missing player"); } - c->game_data.pending_item_trade = make_unique(); - c->game_data.pending_item_trade->other_client_id = cmd.target_client_id; + c->pending_item_trade = make_unique(); + c->pending_item_trade->other_client_id = cmd.target_client_id; for (size_t x = 0; x < cmd.item_count; x++) { - auto& item = c->game_data.pending_item_trade->items.emplace_back(cmd.item_datas[x]); + auto& item = c->pending_item_trade->items.emplace_back(cmd.item_datas[x]); item.decode_for_version(c->version()); } @@ -4343,7 +4324,7 @@ static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) // See the description of the D0 command in CommandFormats.hh for more // information on how this sequence is supposed to work. send_command(target_c, 0xD1, 0x00); - if (target_c->game_data.pending_item_trade) { + if (target_c->pending_item_trade) { send_command(c, 0xD1, 0x00); } } @@ -4351,7 +4332,7 @@ static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); - if (!c->game_data.pending_item_trade) { + if (!c->pending_item_trade) { throw runtime_error("player executed a trade with none pending"); } @@ -4359,11 +4340,11 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) if (!l->is_game()) { throw runtime_error("trade command received in non-game lobby"); } - auto target_c = l->clients.at(c->game_data.pending_item_trade->other_client_id); + auto target_c = l->clients.at(c->pending_item_trade->other_client_id); if (!target_c) { throw runtime_error("target player is missing"); } - if (!target_c->game_data.pending_item_trade) { + if (!target_c->pending_item_trade) { throw runtime_error("player executed a trade with no other side pending"); } @@ -4371,9 +4352,9 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) if (c->version() == Version::BB_V4) { // On BB, the server is expected to generate the delete item and create // item commands - auto to_p = to_c->game_data.character(); - auto from_p = from_c->game_data.character(); - for (const auto& trade_item : from_c->game_data.pending_item_trade->items) { + auto to_p = to_c->character(); + auto from_p = from_c->character(); + for (const auto& trade_item : from_c->pending_item_trade->items) { size_t amount = trade_item.stack_size(); auto item = from_p->remove_item(trade_item.id, amount, false); @@ -4396,18 +4377,18 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) } else { // On V3, the clients will handle it; we just send their final trade lists // to each other - send_execute_item_trade(to_c, target_c->game_data.pending_item_trade->items); + send_execute_item_trade(to_c, target_c->pending_item_trade->items); } send_command(to_c, 0xD4, 0x01); }; - c->game_data.pending_item_trade->confirmed = true; - if (target_c->game_data.pending_item_trade->confirmed) { + c->pending_item_trade->confirmed = true; + if (target_c->pending_item_trade->confirmed) { complete_trade_for_side(c, target_c); complete_trade_for_side(target_c, c); - c->game_data.pending_item_trade.reset(); - target_c->game_data.pending_item_trade.reset(); + c->pending_item_trade.reset(); + target_c->pending_item_trade.reset(); } } @@ -4418,11 +4399,11 @@ static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) // trade sequence, the client can get into a state where it sends this command // many times in a row. To deal with this, we just do nothing if the client // has no trade pending. - if (!c->game_data.pending_item_trade) { + if (!c->pending_item_trade) { return; } - uint8_t other_client_id = c->game_data.pending_item_trade->other_client_id; - c->game_data.pending_item_trade.reset(); + uint8_t other_client_id = c->pending_item_trade->other_client_id; + c->pending_item_trade.reset(); send_command(c, 0xD4, 0x00); // Cancel the other side of the trade too, if it's open @@ -4434,10 +4415,10 @@ static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) if (!target_c) { return; } - if (!target_c->game_data.pending_item_trade) { + if (!target_c->pending_item_trade) { return; } - target_c->game_data.pending_item_trade.reset(); + target_c->pending_item_trade.reset(); send_command(target_c, 0xD4, 0x00); } @@ -4453,7 +4434,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat if (flag == 0xD0) { auto& cmd = check_size_t(data); - if (c->game_data.pending_card_trade) { + if (c->pending_card_trade) { throw runtime_error("player started a card trade when one is already pending"); } if (cmd.entry_count > 4) { @@ -4468,10 +4449,10 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat throw runtime_error("card trade target is not Episode 3"); } - c->game_data.pending_card_trade = make_unique(); - c->game_data.pending_card_trade->other_client_id = cmd.target_client_id; + c->pending_card_trade = make_unique(); + c->pending_card_trade->other_client_id = cmd.target_client_id; for (size_t x = 0; x < cmd.entry_count; x++) { - c->game_data.pending_card_trade->card_to_count.emplace_back( + c->pending_card_trade->card_to_count.emplace_back( make_pair(cmd.entries[x].card_type, cmd.entries[x].count)); } @@ -4485,45 +4466,45 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat // is analogous to Episodes 1&2's D0 command.) S_AdvanceCardTradeState_GC_Ep3_EE_FlagD1 resp = {0}; send_command_t(target_c, 0xEE, 0xD1, resp); - if (target_c->game_data.pending_card_trade) { + if (target_c->pending_card_trade) { send_command_t(c, 0xEE, 0xD1, resp); } } else if (flag == 0xD2) { check_size_v(data.size(), 0); - if (!c->game_data.pending_card_trade) { + if (!c->pending_card_trade) { throw runtime_error("player executed a card trade with none pending"); } - auto target_c = l->clients.at(c->game_data.pending_card_trade->other_client_id); + auto target_c = l->clients.at(c->pending_card_trade->other_client_id); if (!target_c) { throw runtime_error("card trade target player is missing"); } - if (!target_c->game_data.pending_card_trade) { + if (!target_c->pending_card_trade) { throw runtime_error("player executed a card trade with no other side pending"); } - c->game_data.pending_card_trade->confirmed = true; - if (target_c->game_data.pending_card_trade->confirmed) { - send_execute_card_trade(c, target_c->game_data.pending_card_trade->card_to_count); - send_execute_card_trade(target_c, c->game_data.pending_card_trade->card_to_count); + c->pending_card_trade->confirmed = true; + if (target_c->pending_card_trade->confirmed) { + send_execute_card_trade(c, target_c->pending_card_trade->card_to_count); + send_execute_card_trade(target_c, c->pending_card_trade->card_to_count); S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {1}; send_command_t(c, 0xEE, 0xD4, resp); send_command_t(target_c, 0xEE, 0xD4, resp); - c->game_data.pending_card_trade.reset(); - target_c->game_data.pending_card_trade.reset(); + c->pending_card_trade.reset(); + target_c->pending_card_trade.reset(); } } else if (flag == 0xD4) { check_size_v(data.size(), 0); // See the D4 handler for why this check exists (and why it doesn't throw) - if (!c->game_data.pending_card_trade) { + if (!c->pending_card_trade) { return; } - uint8_t other_client_id = c->game_data.pending_card_trade->other_client_id; - c->game_data.pending_card_trade.reset(); + uint8_t other_client_id = c->pending_card_trade->other_client_id; + c->pending_card_trade.reset(); S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {0}; send_command_t(c, 0xEE, 0xD4, resp); @@ -4532,10 +4513,10 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat if (!target_c) { return; } - if (!target_c->game_data.pending_card_trade) { + if (!target_c->pending_card_trade) { return; } - target_c->game_data.pending_card_trade.reset(); + target_c->pending_card_trade.reset(); send_command_t(target_c, 0xEE, 0xD4, resp); } else { @@ -4570,7 +4551,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri // TODO: What's the right error code to use here? send_command(c, 0x02EA, 0x00000001); } else { - string player_name = c->game_data.character()->disp.name.decode(c->language()); + string player_name = c->character()->disp.name.decode(c->language()); auto team = s->team_index->create(team_name, c->license->serial_number, player_name); c->license->bb_team_id = team->team_id; c->license->save(); @@ -4611,7 +4592,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri s->team_index->add_member( team->team_id, added_c->license->serial_number, - added_c->game_data.character()->disp.name.decode(added_c->language())); + added_c->character()->disp.name.decode(added_c->language())); send_update_team_metadata_for_client(added_c); send_team_membership_info(added_c); @@ -4811,7 +4792,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } } if (!reward.reward_item.empty()) { - c->game_data.current_bank().add_item(reward.reward_item); + c->current_bank().add_item(reward.reward_item); } } break; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 5241a133..7e0cde7b 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -13,7 +13,6 @@ #include "Loggers.hh" #include "Map.hh" #include "PSOProtocol.hh" -#include "Player.hh" #include "SendCommands.hh" #include "StaticGameData.hh" #include "Text.hh" @@ -324,7 +323,7 @@ static void on_sync_joining_player_item_state(shared_ptr c, uint8_t comm } auto* floor_items = reinterpret_cast(decompressed.data() + sizeof(G_SyncItemState_6x6D_Decompressed)); - size_t target_num_items = target->game_data.character()->inventory.num_items; + size_t target_num_items = target->character()->inventory.num_items; for (size_t z = 0; z < 12; z++) { uint32_t client_next_id = decompressed_cmd->next_item_id_per_player[z]; uint32_t server_next_id = l->next_item_id[z]; @@ -549,12 +548,12 @@ static void on_send_guild_card(shared_ptr c, uint8_t command, uint8_t fl case Version::DC_V1: case Version::DC_V2: { const auto& cmd = check_size_t(data, size); - c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); + c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case Version::PC_V2: { const auto& cmd = check_size_t(data, size); - c->game_data.character(true, false)->guild_card.description = cmd.guild_card.description; + c->character(true, false)->guild_card.description = cmd.guild_card.description; break; } case Version::GC_NTE: @@ -562,12 +561,12 @@ static void on_send_guild_card(shared_ptr c, uint8_t command, uint8_t fl case Version::GC_EP3_TRIAL_EDITION: case Version::GC_EP3: { const auto& cmd = check_size_t(data, size); - c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); + c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case Version::XB_V3: { const auto& cmd = check_size_t(data, size); - c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); + c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case Version::BB_V4: @@ -651,7 +650,7 @@ static void on_word_select_t(shared_ptr c, uint8_t command, uint8_t, con } } catch (const exception& e) { - string name = c->game_data.character()->disp.name.decode(c->language()); + string name = c->character()->disp.name.decode(c->language()); lc->log.warning("Untranslatable Word Select message: %s", e.what()); send_text_message_printf(lc, "$C4Untranslatable Word\nSelect message from\n%s", name.c_str()); } @@ -720,7 +719,7 @@ static void on_player_died(shared_ptr c, uint8_t command, uint8_t flag, if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { try { - auto& inventory = c->game_data.character()->inventory; + auto& inventory = c->character()->inventory; size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG); auto& data = inventory.items[mag_index].data; data.data2[0] = max(static_cast(data.data2[0] - 5), 0); @@ -874,7 +873,7 @@ static void on_player_drop_item(shared_ptr c, uint8_t command, uint8_t f auto l = c->require_lobby(); if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->game_data.character(); + auto p = c->character(); auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4); l->add_item(item, cmd.floor, cmd.x, cmd.z); @@ -933,7 +932,7 @@ static void on_create_inventory_item_t(shared_ptr c, uint8_t command, ui auto l = c->require_lobby(); if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->game_data.character(); + auto p = c->character(); ItemData item = cmd.item_data; item.decode_for_version(c->version()); l->on_item_id_generated_externally(item.id); @@ -992,7 +991,7 @@ static void on_drop_partial_stack_t(shared_ptr c, uint8_t command, uint8 string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5SPLIT %08" PRIX32 "\n%s", item.id.load(), name.c_str()); } - c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index); + c->character()->print_inventory(stderr, c->version(), s->item_name_index); } forward_subcommand_with_item_transcode_t(c, command, flag, cmd); @@ -1021,7 +1020,7 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint throw logic_error("item tracking not enabled in BB game"); } - auto p = c->game_data.character(); + auto p = c->character(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); // If a stack was split, the original item still exists, so the dropped item @@ -1070,7 +1069,7 @@ static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag } if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->game_data.character(); + auto p = c->character(); ItemData item = cmd.item_data; item.data2d = 0; // Clear the price field item.decode_for_version(c->version()); @@ -1169,7 +1168,7 @@ static void on_pick_up_item(shared_ptr c, uint8_t command, uint8_t flag, if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); - auto effective_p = effective_c->game_data.character(); + auto effective_p = effective_c->character(); // It seems the client just plays it fast and loose with these commands. // There can be multiple 6x5A (request to pick up item) commands in flight, @@ -1238,7 +1237,7 @@ static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8 throw logic_error("item tracking not enabled in BB game"); } - auto p = c->game_data.character(); + auto p = c->character(); auto item = l->remove_item(cmd.item_id); p->add_item(item); @@ -1268,7 +1267,7 @@ static void on_equip_item(shared_ptr c, uint8_t command, uint8_t flag, c auto l = c->require_lobby(); if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { EquipSlot slot = static_cast(cmd.equip_slot.load()); - auto p = c->game_data.character(); + auto p = c->character(); p->inventory.equip_item_id(cmd.item_id, slot); c->log.info("Equipped item %08" PRIX32, cmd.item_id.load()); } else if (l->base_version == Version::BB_V4) { @@ -1287,7 +1286,7 @@ static void on_unequip_item(shared_ptr c, uint8_t command, uint8_t flag, auto l = c->require_lobby(); if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { - auto p = c->game_data.character(); + auto p = c->character(); p->inventory.unequip_item_id(cmd.item_id); c->log.info("Unequipped item %08" PRIX32, cmd.item_id.load()); } else if (l->base_version == Version::BB_V4) { @@ -1311,7 +1310,7 @@ static void on_use_item( auto l = c->require_lobby(); if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); size_t index = p->inventory.find_item(cmd.item_id); string name, colored_name; { @@ -1349,7 +1348,7 @@ static void on_feed_mag( auto l = c->require_lobby(); if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); size_t mag_index = p->inventory.find_item(cmd.mag_item_id); size_t fed_index = p->inventory.find_item(cmd.fed_item_id); @@ -1404,21 +1403,21 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr c, uint8_t com } auto s = c->require_server_state(); - size_t level = c->game_data.character()->disp.stats.level + 1; + size_t level = c->character()->disp.stats.level + 1; switch (cmd.shop_type) { case 0: - c->game_data.shop_contents[0] = l->item_creator->generate_tool_shop_contents(level); + c->bb_shop_contents[0] = l->item_creator->generate_tool_shop_contents(level); break; case 1: - c->game_data.shop_contents[1] = l->item_creator->generate_weapon_shop_contents(level); + c->bb_shop_contents[1] = l->item_creator->generate_weapon_shop_contents(level); break; case 2: - c->game_data.shop_contents[2] = l->item_creator->generate_armor_shop_contents(level); + c->bb_shop_contents[2] = l->item_creator->generate_armor_shop_contents(level); break; default: throw runtime_error("invalid shop type"); } - for (auto& item : c->game_data.shop_contents[cmd.shop_type]) { + for (auto& item : c->bb_shop_contents[cmd.shop_type]) { item.id = 0xFFFFFFFF; item.data2d = s->item_parameter_table_for_version(c->version())->price_for_item(item); } @@ -1452,8 +1451,8 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint throw logic_error("item tracking not enabled in BB game"); } - auto p = c->game_data.character(); - auto& bank = c->game_data.current_bank(); + auto p = c->character(); + auto& bank = c->current_bank(); if (cmd.action == 0) { // Deposit if (cmd.item_id == 0xFFFFFFFF) { // Deposit Meseta if (cmd.meseta_amount > p->disp.stats.meseta) { @@ -1484,7 +1483,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint string name = s->item_name_index->describe_item(Version::BB_V4, item); l->log.info("Player %hu deposited item %08" PRIX32 " (x%hhu) (%s) in the bank", c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str()); - c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index); + c->character()->print_inventory(stderr, c->version(), s->item_name_index); } } else if (cmd.action == 1) { // Take @@ -1511,7 +1510,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint string name = s->item_name_index->describe_item(Version::BB_V4, item); l->log.info("Player %hu withdrew item %08" PRIX32 " (x%hhu) (%s) from the bank", c->lobby_client_id, item.id.load(), cmd.item_amount, name.c_str()); - c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index); + c->character()->print_inventory(stderr, c->version(), s->item_name_index); } } else if (cmd.action == 3) { // Leave bank counter @@ -1532,7 +1531,7 @@ static void on_sort_inventory_bb(shared_ptr c, uint8_t, uint8_t, const v throw logic_error("item tracking not enabled in BB game"); } - auto p = c->game_data.character(); + auto p = c->character(); // Make sure the set of item IDs passed in by the client exactly matches the // set of item IDs present in the inventory @@ -1745,7 +1744,7 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla } // TODO: Should we allow overlays here? - auto p = c->game_data.character(true, false); + auto p = c->character(true, false); // The client explicitly checks for both 0 and 1 - any other value means no // operation is performed. @@ -1898,7 +1897,7 @@ static void on_charge_attack_bb(shared_ptr c, uint8_t command, uint8_t f forward_subcommand(c, command, flag, data, size); const auto& cmd = check_size_t(data, size); - auto& disp = c->game_data.character()->disp; + auto& disp = c->character()->disp; if (cmd.meseta_amount > disp.stats.meseta) { disp.stats.meseta = 0; } else { @@ -1914,7 +1913,7 @@ static void on_level_up(shared_ptr c, uint8_t command, uint8_t flag, con return; } - auto p = c->game_data.character(); + auto p = c->character(); p->disp.stats.char_stats.atp = cmd.atp; p->disp.stats.char_stats.mst = cmd.mst; p->disp.stats.char_stats.evp = cmd.evp; @@ -1928,7 +1927,7 @@ static void on_level_up(shared_ptr c, uint8_t command, uint8_t flag, con static void add_player_exp(shared_ptr c, uint32_t exp) { auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); p->disp.stats.experience += exp; send_give_experience(c, exp); @@ -1963,7 +1962,7 @@ static void on_steal_exp_bb(shared_ptr c, uint8_t, uint8_t, const void* const auto& cmd = check_size_t(data, size); - auto p = c->game_data.character(); + auto p = c->character(); const auto& enemy = l->map->enemies.at(cmd.enemy_index); const auto& inventory = p->inventory; const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)]; @@ -2073,14 +2072,14 @@ static void on_enemy_exp_request_bb(shared_ptr c, uint8_t, uint8_t, cons (!cmd.is_killer == !is_killer) ? "" : "$C6!K$C5 ", name_for_enum(e.type)); } - if (c->game_data.character()->disp.stats.level < 199) { + if (c->character()->disp.stats.level < 199) { add_player_exp(c, player_exp); } } } // Update kill counts on unsealable items - auto& inventory = c->game_data.character()->inventory; + auto& inventory = c->character()->inventory; for (size_t z = 0; z < inventory.num_items; z++) { auto& item = inventory.items[z]; if ((item.flags & 0x08) && @@ -2093,7 +2092,7 @@ static void on_enemy_exp_request_bb(shared_ptr c, uint8_t, uint8_t, cons void on_meseta_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); - auto p = c->game_data.character(); + auto p = c->character(); if (cmd.amount < 0) { if (-cmd.amount > static_cast(p->disp.stats.meseta.load())) { p->disp.stats.meseta = 0; @@ -2119,7 +2118,7 @@ void on_item_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const voi ItemData item; item = cmd.item_data; item.id = l->generate_item_id(c->lobby_client_id); - c->game_data.character()->add_item(item); + c->character()->add_item(item); send_create_inventory_item(c, item); } @@ -2148,7 +2147,7 @@ void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, forward_subcommand(c, command, flag, data, size); auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); auto name = s->describe_item(c->version(), item, false); @@ -2167,10 +2166,10 @@ void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, auto target_c = s->find_client(nullptr, cmd.target_guild_card_number); if (target_c && (target_c->version() == Version::BB_V4) && - (target_c->game_data.character(false) != nullptr) && + (target_c->character(false) != nullptr) && !target_c->config.check_flag(Client::Flag::AT_BANK_COUNTER)) { try { - target_c->game_data.current_bank().add_item(item); + target_c->current_bank().add_item(item); item_sent = true; } catch (const runtime_error&) { } @@ -2210,7 +2209,7 @@ void on_exchange_item_for_team_points_bb(shared_ptr c, uint8_t command, } auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); size_t points = s->item_parameter_table_v4->get_item_team_points(item); @@ -2241,7 +2240,7 @@ static void on_destroy_inventory_item(shared_ptr c, uint8_t command, uin if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)", @@ -2293,7 +2292,7 @@ static void on_identify_item_bb(shared_ptr c, uint8_t command, uint8_t f throw logic_error("received item identify subcommand without item creator present"); } - auto p = c->game_data.character(); + auto p = c->character(); size_t x = p->inventory.find_item(cmd.item_id); if (p->inventory.items[x].data.data1[0] != 0) { throw runtime_error("non-weapon items cannot be unidentified"); @@ -2305,9 +2304,9 @@ static void on_identify_item_bb(shared_ptr c, uint8_t command, uint8_t f // ID instead. l->generate_item_id(c->lobby_client_id); p->disp.stats.meseta -= 100; - c->game_data.identify_result = p->inventory.items[x].data; - c->game_data.identify_result.data1[4] &= 0x7F; - l->item_creator->apply_tekker_deltas(c->game_data.identify_result, p->disp.visual.section_id); + c->bb_identify_result = p->inventory.items[x].data; + c->bb_identify_result.data1[4] &= 0x7F; + l->item_creator->apply_tekker_deltas(c->bb_identify_result, p->disp.visual.section_id); send_item_identify_result(c); } else { @@ -2324,15 +2323,15 @@ static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, ui throw logic_error("item tracking not enabled in BB game"); } - if (!c->game_data.identify_result.id || (c->game_data.identify_result.id == 0xFFFFFFFF)) { + if (!c->bb_identify_result.id || (c->bb_identify_result.id == 0xFFFFFFFF)) { throw runtime_error("no identify result present"); } - if (c->game_data.identify_result.id != cmd.item_id) { + if (c->bb_identify_result.id != cmd.item_id) { throw runtime_error("accepted item ID does not match previous identify request"); } - c->game_data.character()->add_item(c->game_data.identify_result); - send_create_inventory_item(c, c->game_data.identify_result); - c->game_data.identify_result.clear(); + c->character()->add_item(c->bb_identify_result); + send_create_inventory_item(c, c->bb_identify_result); + c->bb_identify_result.clear(); } else { forward_subcommand(c, command, flag, data, size); @@ -2350,7 +2349,7 @@ static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8 } auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); size_t price = (s->item_parameter_table_for_version(c->version())->price_for_item(item) >> 3) * cmd.amount; p->add_meseta(price); @@ -2378,7 +2377,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo } ItemData item; - item = c->game_data.shop_contents.at(cmd.shop_type).at(cmd.item_index); + item = c->bb_shop_contents.at(cmd.shop_type).at(cmd.item_index); if (item.is_stackable()) { item.data1[5] = cmd.amount; } else if (cmd.amount != 1) { @@ -2387,7 +2386,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo size_t price = item.data2d * cmd.amount; item.data2d = 0; - auto p = c->game_data.character(); + auto p = c->character(); p->remove_meseta(price, false); item.id = l->generate_item_id(c->lobby_client_id); @@ -2410,7 +2409,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo static void on_medical_center_bb(shared_ptr c, uint8_t, uint8_t, const void*, size_t) { auto l = c->require_lobby(); if (l->is_game() && (l->base_version == Version::BB_V4)) { - c->game_data.character()->remove_meseta(10, false); + c->character()->remove_meseta(10, false); } } @@ -2436,8 +2435,8 @@ static void on_battle_restart_bb(shared_ptr c, uint8_t, uint8_t, const v for (auto& lc : l->clients) { if (lc) { - lc->game_data.delete_overlay(); - lc->game_data.create_battle_overlay(new_rules, s->level_table); + lc->delete_overlay(); + lc->create_battle_overlay(new_rules, s->level_table); } } l->map->clear(); @@ -2462,7 +2461,7 @@ static void on_battle_level_up_bb(shared_ptr c, uint8_t, uint8_t, const auto lc = l->clients.at(cmd.header.client_id); if (lc) { auto s = c->require_server_state(); - auto lp = lc->game_data.character(); + auto lp = lc->character(); uint32_t target_level = lp->disp.stats.level + cmd.num_levels; uint32_t before_exp = lp->disp.stats.experience; lp->disp.stats.advance_to_level(lp->disp.visual.char_class, target_level, s->level_table); @@ -2502,7 +2501,7 @@ static void on_quest_exchange_item_bb(shared_ptr c, uint8_t, uint8_t, co const auto& cmd = check_size_t(data, size); try { - auto p = c->game_data.character(); + auto p = c->character(); size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier()); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, false); @@ -2529,7 +2528,7 @@ static void on_wrap_item_bb(shared_ptr c, uint8_t, uint8_t, const void* if (l->is_game() && (l->base_version == Version::BB_V4)) { const auto& cmd = check_size_t(data, size); - auto p = c->game_data.character(); + auto p = c->character(); auto item = p->remove_item(cmd.item.id, 1, false); send_destroy_item(c, item.id, 1); item.wrap(); @@ -2544,7 +2543,7 @@ static void on_photon_drop_exchange_for_item_bb(shared_ptr c, uint8_t, u const auto& cmd = check_size_t(data, size); try { - auto p = c->game_data.character(); + auto p = c->character(); size_t found_index = p->inventory.find_item_by_primary_identifier(0x031000); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, false); @@ -2572,7 +2571,7 @@ static void on_photon_drop_exchange_for_s_rank_special_bb(shared_ptr c, const auto& cmd = check_size_t(data, size); try { - auto p = c->game_data.character(); + auto p = c->character(); static const array costs({60, 60, 20, 20, 30, 30, 30, 50, 40, 50, 40, 40, 50, 40, 40, 40}); uint8_t cost = costs.at(cmd.special_type); @@ -2610,7 +2609,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr c, uint8_t, throw runtime_error("no secret lottery results are defined"); } - auto p = c->game_data.character(); + auto p = c->character(); ssize_t slt_index = -1; try { slt_index = p->inventory.find_item_by_primary_identifier(0x031003); // Secret Lottery Ticket @@ -2659,7 +2658,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t auto l = c->require_lobby(); if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { check_size_t(data, size); - auto p = c->game_data.character(); + auto p = c->character(); size_t index = p->inventory.find_item_by_primary_identifier(0x031002); auto item = p->remove_item(p->inventory.items[index].data.id, 1, false); send_destroy_item(c, item.id, 1); @@ -2701,7 +2700,7 @@ static void on_quest_F95F_result_bb(shared_ptr c, uint8_t, uint8_t, cons if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto s = c->require_server_state(); - auto p = c->game_data.character(); + auto p = c->character(); const auto& result = s->quest_F95F_results.at(cmd.result_index); if (result.second.empty()) { @@ -2739,7 +2738,7 @@ static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, c auto l = c->require_lobby(); if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); - auto p = c->game_data.character(); + auto p = c->character(); try { size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier()); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, false); @@ -2768,7 +2767,7 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr c, uint8_t, uint8_ auto l = c->require_lobby(); if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); - auto p = c->game_data.character(); + auto p = c->character(); try { size_t item_index = p->inventory.find_item(cmd.item_id); auto& item = p->inventory.items[item_index].data; @@ -2819,7 +2818,7 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr c, uint8_t, uint8_ static void on_write_quest_global_flag_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); - c->game_data.character()->quest_global_flags[cmd.index] = cmd.value; + c->character()->quest_global_flags[cmd.index] = cmd.value; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 353605fb..ad63d5b8 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -498,7 +498,7 @@ void send_system_file_bb(shared_ptr c) { auto team = c->team(); PSOBBFullSystemFile cmd; - cmd.base = *c->game_data.system(); + cmd.base = *c->system_file(); if (team) { cmd.team_membership = team->membership_for_member(c->license->serial_number); } @@ -518,7 +518,7 @@ void send_player_preview_bb(shared_ptr c, int8_t character_index, const } void send_guild_card_header_bb(shared_ptr c) { - uint32_t checksum = c->game_data.guild_cards()->checksum(); + uint32_t checksum = c->guild_card_file()->checksum(); S_GuildCardHeader_BB_01DC cmd = {1, sizeof(PSOBBGuildCardFile), checksum}; send_command_t(c, 0x01DC, 0x00000000, cmd); } @@ -535,7 +535,7 @@ void send_guild_card_chunk_bb(shared_ptr c, size_t chunk_index) { cmd.unknown = 0; cmd.chunk_index = chunk_index; cmd.data.assign_range( - reinterpret_cast(c->game_data.guild_cards().get()) + chunk_offset, + reinterpret_cast(c->guild_card_file().get()) + chunk_offset, data_size, 0); send_command(c, 0x02DC, 0x00000000, &cmd, sizeof(cmd) - sizeof(cmd.data) + data_size); @@ -621,13 +621,13 @@ void send_stream_file_chunk_bb(shared_ptr c, uint32_t chunk_index) { } void send_approve_player_choice_bb(shared_ptr c) { - S_ApprovePlayerChoice_BB_00E4 cmd = {c->game_data.bb_character_index, 1}; + S_ApprovePlayerChoice_BB_00E4 cmd = {c->bb_character_index, 1}; send_command_t(c, 0x00E4, 0x00000000, cmd); } void send_complete_player_bb(shared_ptr c) { - auto p = c->game_data.character(true, false); - auto sys = c->game_data.system(true); + auto p = c->character(true, false); + auto sys = c->system_file(true); auto team = c->team(); if (c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB)) { p->inventory.language = 1; @@ -969,7 +969,7 @@ void send_info_board_t(shared_ptr c) { if (!other_c.get()) { continue; } - auto other_p = other_c->game_data.character(true, false); + auto other_p = other_c->character(true, false); auto& e = entries.emplace_back(); e.name.encode(other_p->disp.name.decode(other_p->inventory.language), c->language()); e.message.encode(add_color(other_p->info_board.decode(other_p->inventory.language)), c->language()); @@ -1055,7 +1055,7 @@ void send_card_search_result_t( cmd.location_string.encode(location_string, c->language()); cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY; cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id; - auto rp = result->game_data.character(true, false); + auto rp = result->character(true, false); cmd.extension.player_name.encode(rp->disp.name.decode(rp->inventory.language), c->language()); send_command_t(c, 0x41, 0x00, cmd); @@ -1209,7 +1209,7 @@ void send_guild_card(shared_ptr c, shared_ptr source) { throw runtime_error("source player does not have a license"); } - auto source_p = source->game_data.character(true, false); + auto source_p = source->character(true, false); auto source_team = source->team(); uint64_t xb_user_id = source->license->xb_user_id @@ -1543,7 +1543,7 @@ template void send_player_records_t(shared_ptr c, shared_ptr l, shared_ptr joining_client) { vector entries; auto add_client = [&](shared_ptr lc) -> void { - auto lp = lc->game_data.character(true, false); + auto lp = lc->character(true, false); auto& e = entries.emplace_back(); e.client_id = lc->lobby_client_id; e.challenge = lp->challenge_records; @@ -1595,7 +1595,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) if (!wc) { continue; } - auto wc_p = wc->game_data.character(); + auto wc_p = wc->character(); auto& p = cmd.players[z]; p.lobby_data.player_tag = 0x00010000; p.lobby_data.guild_card_number = wc->license->serial_number; @@ -1611,8 +1611,8 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) e.guild_card_number = wc->license->serial_number; e.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language()); e.present = 1; - e.level = wc->game_data.ep3_config - ? (wc->game_data.ep3_config->online_clv_exp / 100) + e.level = wc->ep3_config + ? (wc->ep3_config->online_clv_exp / 100) : wc_p->disp.stats.level.load(); e.name_color = wc_p->disp.visual.name_color; @@ -1662,7 +1662,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) for (size_t z = 4; z < 12; z++) { if (l->clients[z]) { auto other_c = l->clients[z]; - auto other_p = other_c->game_data.character(); + auto other_p = other_c->character(); auto& cmd_p = cmd.spectator_players[z - 4]; auto& cmd_e = cmd.entries[z]; cmd_p.lobby_data.player_tag = 0x00010000; @@ -1677,8 +1677,8 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) cmd_e.guild_card_number = other_c->license->serial_number; cmd_e.name = cmd_p.lobby_data.name; cmd_e.present = 1; - cmd_e.level = other_c->game_data.ep3_config - ? (other_c->game_data.ep3_config->online_clv_exp / 100) + cmd_e.level = other_c->ep3_config + ? (other_c->ep3_config->online_clv_exp / 100) : other_p->disp.stats.level.load(); cmd_e.name_color = other_p->disp.visual.name_color; @@ -1704,7 +1704,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { cmd.lobby_data[x].player_tag = 0x00010000; cmd.lobby_data[x].guild_card_number = lc->license->serial_number; cmd.lobby_data[x].client_id = lc->lobby_client_id; - cmd.lobby_data[x].name.encode(lc->game_data.character()->disp.name.decode(lc->language()), c->language()); + cmd.lobby_data[x].name.encode(lc->character()->disp.name.decode(lc->language()), c->language()); player_count++; } else { cmd.lobby_data[x].clear(); @@ -1784,7 +1784,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { auto s = c->require_server_state(); for (size_t x = 0; x < 4; x++) { if (l->clients[x]) { - auto other_p = l->clients[x]->game_data.character(); + auto other_p = l->clients[x]->character(); cmd.players_ep3[x].inventory = other_p->inventory; cmd.players_ep3[x].inventory.encode_for_client(c); cmd.players_ep3[x].disp = convert_player_disp_data(other_p->disp, c->language(), other_p->inventory.language); @@ -1900,7 +1900,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrgame_data.character(); + auto lp = lc->character(); auto& e = cmd.entries[used_entries++]; e.lobby_data.player_tag = 0x00010000; e.lobby_data.guild_card_number = lc->license->serial_number; @@ -1915,8 +1915,8 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrinventory; e.inventory.encode_for_client(c); - if ((lc == c) && is_v1_or_v2(c->version()) && lc->game_data.last_reported_disp_v1_v2) { - e.disp = convert_player_disp_data(*lc->game_data.last_reported_disp_v1_v2, c->language(), lp->inventory.language); + if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) { + e.disp = convert_player_disp_data(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language); } else { e.disp = convert_player_disp_data(lp->disp, c->language(), lp->inventory.language); e.disp.enforce_lobby_join_limits_for_client(c); @@ -1979,7 +1979,7 @@ void send_join_lobby_xb(shared_ptr c, shared_ptr l, shared_ptrgame_data.character(); + auto lp = lc->character(); auto& e = cmd.entries[used_entries++]; e.lobby_data.player_tag = 0x00010000; e.lobby_data.guild_card_number = lc->license->serial_number; @@ -2032,7 +2032,7 @@ void send_join_lobby_dc_nte(shared_ptr c, shared_ptr l, size_t used_entries = 0; for (const auto& lc : lobby_clients) { - auto lp = lc->game_data.character(); + auto lp = lc->character(); auto& e = cmd.entries[used_entries++]; e.lobby_data.player_tag = 0x00010000; e.lobby_data.guild_card_number = lc->license->serial_number; @@ -2354,7 +2354,7 @@ void send_item_identify_result(shared_ptr c) { res.header.subcommand = 0xB9; res.header.size = sizeof(res) / 4; res.header.client_id = c->lobby_client_id; - res.item_data = c->game_data.identify_result; + res.item_data = c->bb_identify_result; send_command_t(l, 0x60, 0x00, res); } @@ -2363,8 +2363,8 @@ void send_bank(shared_ptr c) { throw logic_error("6xBC can only be sent to BB clients"); } - auto p = c->game_data.character(); - auto& bank = c->game_data.current_bank(); + auto p = c->character(); + auto& bank = c->current_bank(); bank.sort(); const auto* items_it = bank.items.data(); vector items(items_it, items_it + bank.num_items); @@ -2383,7 +2383,7 @@ void send_shop(shared_ptr c, uint8_t shop_type) { throw logic_error("6xB6 can only be sent to BB clients"); } - const auto& contents = c->game_data.shop_contents.at(shop_type); + const auto& contents = c->bb_shop_contents.at(shop_type); G_ShopContents_BB_6xB6 cmd = { {0xB6, static_cast(2 + (sizeof(ItemData) >> 2) * contents.size()), 0x0000}, @@ -2401,7 +2401,7 @@ void send_shop(shared_ptr c, uint8_t shop_type) { void send_level_up(shared_ptr c) { auto l = c->require_lobby(); - auto p = c->game_data.character(); + auto p = c->character(); CharacterStats stats = p->disp.stats.char_stats; const ItemData* mag = nullptr; @@ -2667,7 +2667,7 @@ string ep3_description_for_client(shared_ptr c) { if (!is_ep3(c->version())) { throw runtime_error("client is not Episode 3"); } - auto p = c->game_data.character(); + auto p = c->character(); return string_printf( "%s CLv%" PRIu32 " %c", name_for_char_class(p->disp.visual.char_class), @@ -2715,7 +2715,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { if (player.is_human()) { try { auto other_c = serial_number_to_client.at(player.serial_number); - entry.name.encode(other_c->game_data.character()->disp.name.decode(other_c->language()), c->language()); + entry.name.encode(other_c->character()->disp.name.decode(other_c->language()), c->language()); entry.description.encode(ep3_description_for_client(other_c), c->language()); } catch (const out_of_range&) { entry.name.encode(player.player_name, c->language()); @@ -2736,7 +2736,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { for (auto spec_c : l->clients) { if (spec_c) { auto& entry = cmd.spectator_entries[cmd.num_spectators++]; - entry.name.encode(spec_c->game_data.character()->disp.name.decode(spec_c->language()), c->language()); + entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language()); entry.description.encode(ep3_description_for_client(spec_c), c->language()); } } @@ -2753,7 +2753,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { size_t num_players = 0; for (const auto& opp_c : primary_lobby->clients) { if (opp_c) { - cmd.player_entries[num_players].name.encode(opp_c->game_data.character()->disp.name.decode(opp_c->language()), c->language()); + cmd.player_entries[num_players].name.encode(opp_c->character()->disp.name.decode(opp_c->language()), c->language()); cmd.player_entries[num_players].description.encode(ep3_description_for_client(opp_c), c->language()); num_players++; } @@ -2766,7 +2766,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { for (auto spec_c : l->clients) { if (spec_c) { auto& entry = cmd.spectator_entries[num_spectators++]; - entry.name.encode(spec_c->game_data.character()->disp.name.decode(spec_c->language()), c->language()); + entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language()); entry.description.encode(ep3_description_for_client(spec_c), c->language()); } } @@ -2877,7 +2877,7 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar if (player.is_human()) { try { auto pc = serial_number_to_client.at(player.serial_number); - entry.player_names[z].encode(pc->game_data.character()->disp.name.decode(pc->language()), lc->language()); + entry.player_names[z].encode(pc->character()->disp.name.decode(pc->language()), lc->language()); } catch (const out_of_range&) { entry.player_names[z].encode(player.player_name, lc->language()); } @@ -3329,7 +3329,7 @@ static S_TeamInfoForPlayer_BB_13EA_15EA_Entry team_metadata_for_client(shared_pt S_TeamInfoForPlayer_BB_13EA_15EA_Entry cmd; cmd.lobby_client_id = c->lobby_client_id; cmd.guild_card_number2 = c->license->serial_number; - cmd.player_name = c->game_data.character()->disp.name; + cmd.player_name = c->character()->disp.name; if (team) { cmd.guild_card_number = c->license->serial_number; cmd.team_id = team->team_id; @@ -3468,7 +3468,7 @@ void send_team_reward_list(std::shared_ptr c, bool show_purchased) { } auto s = c->require_server_state(); - bool show_item_rewards = show_purchased || (c->game_data.current_bank().num_items < 200); + bool show_item_rewards = show_purchased || (c->current_bank().num_items < 200); vector entries; for (const auto& reward : s->team_index->reward_definitions()) { diff --git a/src/Server.cc b/src/Server.cc index 86b20e86..8e6118fe 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -315,7 +315,7 @@ vector> Server::get_clients_by_identifier(const string& ident continue; } - auto p = c->game_data.character(false, false); + auto p = c->character(false, false); if (p && p->disp.name.eq(ident, p->inventory.language)) { results.emplace_back(std::move(c)); continue; diff --git a/src/ServerState.hh b/src/ServerState.hh index 0832cf11..67879084 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -23,6 +23,7 @@ #include "License.hh" #include "Lobby.hh" #include "Menu.hh" +#include "PlayerFilesManager.hh" #include "Quest.hh" #include "TeamIndex.hh" #include "WordSelectTable.hh"