diff --git a/README.md b/README.md index 34b4e8f1..67b4e92d 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$patch `: Run a patch on your client. `` must exactly match the name of a patch on the server. * Blue Burst player commands (game server only) - * `$bbchar <1-4>`: Use this command when playing on a non-BB version of PSO. If the username and password are correct, this command converts your current character to BB format and saves it on the server in the given slot. Any character already in that slot is overwritten. + * `$bbchar <1-4>`: Use this command when playing on a non-BB version of PSO. If the username and password are correct, this command converts your current character to BB format and saves it on the server in the given slot. Any character already in that slot is overwritten. Note that the character's chat data, quick menu config, and bank contents are not copied, since there is no way for the server to request those types of data. * `$edit `: Modifies your character data. If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing. * `$bank [number]`: Switches your current bank, so you can access your other character's banks (if `number` is 1-4) or your shared account bank (if `number` is 0). If `number` is not given, switches back to your current character's bank. * `$save`: Saves your character, system, and Guild Card data immediately. (By default, your character is saved every 60 seconds while online, and your account and Guild Card data are saved whenever they change.) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 80095028..8adf594f 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1029,14 +1029,12 @@ static void server_command_convert_char_to_bb(shared_ptr c, const std::s } try { - s->license_index->verify_bb(tokens[0].c_str(), tokens[1].c_str()); + c->pending_bb_save_license = s->license_index->verify_bb(tokens[0].c_str(), tokens[1].c_str()); } catch (const exception& e) { send_text_message_printf(c, "$C6Login failed: %s", e.what()); return; } - c->pending_bb_save_username = tokens[0]; - // Request the player data. The client will respond with a 61, and the handler // for that command will execute the conversion send_get_player_info(c); diff --git a/src/Client.hh b/src/Client.hh index 5bc06ca1..f6e61e32 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -201,7 +201,7 @@ struct Client : public std::enable_shared_from_this { uint32_t next_exp_value; // next EXP value to give G_SwitchStateChanged_6x05 last_switch_enabled_command; bool can_chat; - std::string pending_bb_save_username; + std::shared_ptr pending_bb_save_license; uint8_t pending_bb_save_character_index; std::deque> function_call_response_queue; diff --git a/src/Player.cc b/src/Player.cc index 1e0a7d9a..b8f00644 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -302,6 +302,16 @@ string ClientGameData::system_filename() const { 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::character_filename(int8_t index) const { if (this->bb_username.empty()) { throw logic_error("non-BB players do not have character data"); @@ -312,7 +322,7 @@ string ClientGameData::character_filename(int8_t index) const { if (index < 0) { throw logic_error("character index is not set"); } - return string_printf("system/players/player_%s_%hhd.psochar", this->bb_username.c_str(), index); + return this->character_filename(this->bb_username, index); } string ClientGameData::guild_card_filename() const { diff --git a/src/Player.hh b/src/Player.hh index 34e9e90a..7528aeff 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -112,6 +112,15 @@ public: 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); + 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( @@ -147,12 +156,4 @@ private: void save_and_clear_external_bank(); void load_all_files(); - - std::string system_filename() const; - 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; }; diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 23607822..70e31fef 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -194,13 +194,6 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const { return pre; } -void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) { - this->stats.level = pre.level; - this->stats.experience = pre.experience; - this->visual = pre.visual; - this->name = pre.name; -} - void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { this->visual.name_color = pre.visual.name_color; this->visual.extra_model = pre.visual.extra_model; diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 7820a199..c94169f3 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -112,7 +112,7 @@ struct PlayerDispDataBB; struct PlayerVisualConfig { /* 00 */ pstring name; /* 10 */ parray unknown_a2; - /* 18 */ le_uint32_t name_color = 0x00000000; // ARGB + /* 18 */ le_uint32_t name_color = 0xFFFFFFFF; // ARGB /* 1C */ uint8_t extra_model = 0; /* 1D */ parray unused; // See compute_name_color_checksum for details on how this is computed. This diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 8b77759a..a22de898 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2973,42 +2973,42 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri s->remove_client_from_lobby(c); } else if (command == 0x61) { - if (!c->pending_bb_save_username.empty()) { - string prev_bb_username = c->game_data.get_bb_username(); - int8_t prev_bb_character_index = c->game_data.bb_character_index; + if (c->pending_bb_save_license) { + shared_ptr dest_license = c->pending_bb_save_license; + c->pending_bb_save_license.reset(); - c->game_data.set_bb_username(c->pending_bb_save_username); - c->game_data.bb_character_index = c->pending_bb_save_character_index; + string filename = ClientGameData::character_filename(dest_license->bb_username, c->pending_bb_save_character_index); + if (s->player_files_manager->get_character(filename)) { + send_text_message(c, "$C6The target player\nis currently loaded.\nSign off in Blue\nBurst and try again."); - // Update a few fields for BB - const auto& p = c->game_data.character(); - uint8_t prev_version = p->disp.visual.version; - p->disp.visual.version = 4; - uint32_t prev_name_color_checksum = p->disp.visual.name_color_checksum; - p->disp.visual.name_color_checksum = 0x00000000; - - bool failure = false; - try { - c->game_data.save_character_file(); - } catch (const exception& e) { - send_text_message_printf(c, "$C6PSOBB player data could\nnot be saved:\n%s", e.what()); - failure = true; + } else { + auto bb_player = PSOBBCharacterFile::create_from_config( + dest_license->serial_number, + c->language(), + player->disp.visual, + player->disp.name.decode(c->language()), + s->level_table); + bb_player->disp.visual.version = 4; + bb_player->disp.visual.name_color_checksum = 0x00000000; + bb_player->inventory = player->inventory; + bb_player->disp.stats.advance_to_level(bb_player->disp.visual.char_class, player->disp.stats.level, s->level_table); + bb_player->disp.stats.experience = player->disp.stats.experience; + bb_player->disp.technique_levels_v1 = player->disp.technique_levels_v1; + bb_player->auto_reply = player->auto_reply; + bb_player->info_board = player->info_board; + bb_player->battle_records = player->battle_records; + 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); + send_text_message_printf(c, + "$C6BB player data saved\nas player %hhu for user\n%s", + static_cast(c->pending_bb_save_character_index + 1), + dest_license->bb_username.c_str()); + } catch (const exception& e) { + send_text_message_printf(c, "$C6PSOBB player data could\nnot be saved:\n%s", e.what()); + } } - - p->disp.visual.version = prev_version; - p->disp.visual.name_color_checksum = prev_name_color_checksum; - - if (!failure) { - send_text_message_printf(c, - "$C6BB player data saved\nas player %hhu for user\n%s", - static_cast(c->pending_bb_save_character_index + 1), - c->pending_bb_save_username.c_str()); - } - - c->game_data.set_bb_username(prev_bb_username); - c->game_data.bb_character_index = prev_bb_character_index; - - c->pending_bb_save_username.clear(); } // We use 61 during the lobby server init sequence to trigger joining an diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index ce59afe8..f88b1432 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -208,10 +208,11 @@ PSOBBBaseSystemFile::PSOBBBaseSystemFile() { } } -shared_ptr PSOBBCharacterFile::create_from_preview( +shared_ptr PSOBBCharacterFile::create_from_config( uint32_t guild_card_number, uint8_t language, - const PlayerDispDataBBPreview& preview, + const PlayerVisualConfig& visual, + const std::string& name, shared_ptr level_table) { static const array, 12> initial_inventory{{ { @@ -300,7 +301,7 @@ shared_ptr PSOBBCharacterFile::create_from_preview( }, }}; - array config_hunter_ranger{ + static const array config_hunter_ranger{ {0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -316,7 +317,7 @@ shared_ptr PSOBBCharacterFile::create_from_preview( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; - array config_force{ + static const array config_force{ {0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -334,8 +335,10 @@ shared_ptr PSOBBCharacterFile::create_from_preview( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; auto ret = make_shared(); + ret->disp.visual = visual; + ret->disp.name.encode(name, language); - const auto& initial_items = initial_inventory.at(preview.visual.char_class); + const auto& initial_items = initial_inventory.at(visual.char_class); ret->inventory.num_items = initial_items.size(); for (size_t z = 0; z < initial_items.size(); z++) { ret->inventory.items[z] = initial_items[z]; @@ -347,7 +350,6 @@ shared_ptr PSOBBCharacterFile::create_from_preview( ret->disp.config[z] = config[z]; } - ret->disp.apply_preview(preview); ret->disp.stats.reset_to_base(ret->disp.visual.char_class, level_table); ret->disp.technique_levels_v1.clear(0xFF); if (ret->disp.visual.class_flags & 0x80) { @@ -369,6 +371,15 @@ shared_ptr PSOBBCharacterFile::create_from_preview( return ret; } +shared_ptr PSOBBCharacterFile::create_from_preview( + uint32_t guild_card_number, + uint8_t language, + const PlayerDispDataBBPreview& preview, + shared_ptr level_table) { + return PSOBBCharacterFile::create_from_config( + guild_card_number, language, preview.visual, preview.name.decode(language), level_table); +} + PSOBBCharacterFile::SymbolChatEntry PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry() const { SymbolChatEntry ret; ret.present = 1; diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 28f70b54..4df96502 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -219,6 +219,12 @@ struct PSOBBCharacterFile { PSOBBCharacterFile() = default; + static std::shared_ptr create_from_config( + uint32_t guild_card_number, + uint8_t language, + const PlayerVisualConfig& visual, + const std::string& name, + std::shared_ptr level_table); static std::shared_ptr create_from_preview( uint32_t guild_card_number, uint8_t language,