add $savechar and $loadchar commands
This commit is contained in:
+61
-29
@@ -52,12 +52,6 @@ static void check_version(shared_ptr<Client> c, Version version) {
|
||||
}
|
||||
}
|
||||
|
||||
static void check_not_version(shared_ptr<Client> c, Version version) {
|
||||
if (c->version() == version) {
|
||||
throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO.");
|
||||
}
|
||||
}
|
||||
|
||||
static void check_is_game(shared_ptr<Lobby> l, bool is_game) {
|
||||
if (l->is_game() != is_game) {
|
||||
throw precondition_failed(is_game ? "$C6This command cannot\nbe used in lobbies." : "$C6This command cannot\nbe used in games.");
|
||||
@@ -1024,38 +1018,74 @@ static void server_command_change_bank(shared_ptr<Client> c, const std::string&
|
||||
send_text_message_printf(c, "%" PRIu32 " items\n%" PRIu32 " Meseta", bank.num_items.load(), bank.meseta.load());
|
||||
}
|
||||
|
||||
// TODO: This can be implemented on the proxy server too.
|
||||
static void server_command_convert_char_to_bb(shared_ptr<Client> c, const std::string& args) {
|
||||
static void server_command_bbchar_savechar(shared_ptr<Client> c, const std::string& args, bool is_bb_conversion) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, false);
|
||||
check_not_version(c, Version::BB_V4);
|
||||
|
||||
vector<string> tokens = split(args, ' ');
|
||||
if (tokens.size() != 3) {
|
||||
send_text_message(c, "$C6Incorrect argument count");
|
||||
return;
|
||||
}
|
||||
|
||||
// username/password are tokens[0] and [1]
|
||||
c->pending_bb_save_character_index = stoul(tokens[2]) - 1;
|
||||
if (c->pending_bb_save_character_index > 3) {
|
||||
send_text_message(c, "$C6Player index must be 1-4");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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;
|
||||
auto pending_export = make_unique<Client::PendingCharacterExport>();
|
||||
pending_export->is_bb_conversion = is_bb_conversion;
|
||||
|
||||
if (is_bb_conversion) {
|
||||
vector<string> tokens = split(args, ' ');
|
||||
if (tokens.size() != 3) {
|
||||
send_text_message(c, "$C6Incorrect argument count");
|
||||
return;
|
||||
}
|
||||
|
||||
// username/password are tokens[0] and [1]
|
||||
pending_export->character_index = stoll(tokens[2]) - 1;
|
||||
if ((pending_export->character_index > 3) || (pending_export->character_index < 0)) {
|
||||
send_text_message(c, "$C6Player index must\nbe in range 1-4");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
c->pending_character_export->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;
|
||||
}
|
||||
|
||||
} else {
|
||||
pending_export->character_index = stoll(args) - 1;
|
||||
if ((pending_export->character_index > 3) || (pending_export->character_index < 0)) {
|
||||
send_text_message(c, "$C6Player index must\nbe in range 1-4");
|
||||
return;
|
||||
}
|
||||
pending_export->license = c->license;
|
||||
}
|
||||
|
||||
c->pending_character_export = std::move(pending_export);
|
||||
// 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);
|
||||
}
|
||||
|
||||
static void server_command_bbchar(shared_ptr<Client> c, const std::string& args) {
|
||||
server_command_bbchar_savechar(c, args, true);
|
||||
}
|
||||
|
||||
static void server_command_savechar(shared_ptr<Client> c, const std::string& args) {
|
||||
server_command_bbchar_savechar(c, args, false);
|
||||
}
|
||||
|
||||
static void server_command_loadchar(shared_ptr<Client> c, const std::string& args) {
|
||||
if (!is_v1_or_v2(c->version())) {
|
||||
send_text_message(c, "$C7This command can only\nbe used on v1 or v2");
|
||||
return;
|
||||
}
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, false);
|
||||
|
||||
size_t index = stoull(args, nullptr, 0);
|
||||
c->game_data.load_backup_character(c->license->serial_number, index);
|
||||
|
||||
auto s = c->require_server_state();
|
||||
send_player_leave_notification(l, c->lobby_client_id);
|
||||
s->send_lobby_join_notifications(l, c);
|
||||
}
|
||||
|
||||
static void server_command_save(shared_ptr<Client> c, const std::string&) {
|
||||
check_version(c, Version::BB_V4);
|
||||
try {
|
||||
@@ -1702,12 +1732,13 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$ax", {server_command_ax, nullptr}},
|
||||
{"$ban", {server_command_ban, nullptr}},
|
||||
{"$bank", {server_command_change_bank, nullptr}},
|
||||
{"$bbchar", {server_command_convert_char_to_bb, nullptr}},
|
||||
{"$bbchar", {server_command_bbchar, nullptr}},
|
||||
{"$cheat", {server_command_cheat, nullptr}},
|
||||
{"$debug", {server_command_debug, nullptr}},
|
||||
{"$defrange", {server_command_ep3_set_def_dice_range, nullptr}},
|
||||
{"$drop", {server_command_drop, nullptr}},
|
||||
{"$edit", {server_command_edit, nullptr}},
|
||||
{"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}},
|
||||
{"$event", {server_command_lobby_event, proxy_command_lobby_event}},
|
||||
{"$exit", {server_command_exit, proxy_command_exit}},
|
||||
{"$gc", {server_command_get_self_card, proxy_command_get_player_card}},
|
||||
@@ -1720,7 +1751,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$kick", {server_command_kick, nullptr}},
|
||||
{"$li", {server_command_lobby_info, proxy_command_lobby_info}},
|
||||
{"$ln", {server_command_lobby_type, proxy_command_lobby_type}},
|
||||
{"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}},
|
||||
{"$loadchar", {server_command_loadchar, nullptr}},
|
||||
{"$matcount", {server_command_show_material_counts, nullptr}},
|
||||
{"$maxlevel", {server_command_max_level, nullptr}},
|
||||
{"$meseta", {server_command_meseta, nullptr}},
|
||||
@@ -1739,6 +1770,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$quest", {server_command_quest, nullptr}},
|
||||
{"$rand", {server_command_rand, proxy_command_rand}},
|
||||
{"$save", {server_command_save, nullptr}},
|
||||
{"$savechar", {server_command_savechar, nullptr}},
|
||||
{"$saverec", {server_command_saverec, nullptr}},
|
||||
{"$sc", {server_command_send_client, proxy_command_send_client}},
|
||||
{"$secid", {server_command_secid, proxy_command_secid}},
|
||||
|
||||
@@ -174,7 +174,6 @@ Client::Client(
|
||||
card_battle_table_seat_state(0),
|
||||
next_exp_value(0),
|
||||
can_chat(true),
|
||||
pending_bb_save_character_index(0),
|
||||
dol_base_addr(0) {
|
||||
|
||||
this->config.set_flags_for_version(version, -1);
|
||||
|
||||
+6
-2
@@ -202,8 +202,12 @@ struct Client : public std::enable_shared_from_this<Client> {
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
G_SwitchStateChanged_6x05 last_switch_enabled_command;
|
||||
bool can_chat;
|
||||
std::shared_ptr<License> pending_bb_save_license;
|
||||
uint8_t pending_bb_save_character_index;
|
||||
struct PendingCharacterExport {
|
||||
std::shared_ptr<const License> license;
|
||||
ssize_t character_index = -1;
|
||||
bool is_bb_conversion = false;
|
||||
};
|
||||
std::unique_ptr<PendingCharacterExport> pending_character_export;
|
||||
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
|
||||
|
||||
// File loading state
|
||||
|
||||
@@ -312,6 +312,10 @@ string ClientGameData::character_filename(const std::string& bb_username, int8_t
|
||||
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");
|
||||
@@ -613,6 +617,23 @@ void ClientGameData::save_guild_card_file() const {
|
||||
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<PSOCommandHeaderBB>(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<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
||||
this->last_reported_disp_v1_v2.reset();
|
||||
}
|
||||
|
||||
PlayerBank& ClientGameData::current_bank() {
|
||||
if (this->external_bank) {
|
||||
return *this->external_bank;
|
||||
|
||||
@@ -114,6 +114,7 @@ public:
|
||||
|
||||
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;
|
||||
@@ -131,6 +132,8 @@ public:
|
||||
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<PSOBBCharacterFile> current_bank_character();
|
||||
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
|
||||
|
||||
+17
-10
@@ -2981,17 +2981,27 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
s->remove_client_from_lobby(c);
|
||||
|
||||
} else if (command == 0x61) {
|
||||
if (c->pending_bb_save_license) {
|
||||
shared_ptr<License> dest_license = c->pending_bb_save_license;
|
||||
c->pending_bb_save_license.reset();
|
||||
if (c->pending_character_export) {
|
||||
unique_ptr<Client::PendingCharacterExport> pending_export = std::move(c->pending_character_export);
|
||||
c->pending_character_export.reset();
|
||||
|
||||
string filename;
|
||||
if (pending_export->is_bb_conversion) {
|
||||
filename = ClientGameData::character_filename(
|
||||
pending_export->license->bb_username,
|
||||
pending_export->character_index);
|
||||
} else {
|
||||
filename = ClientGameData::backup_character_filename(
|
||||
pending_export->license->serial_number,
|
||||
pending_export->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.");
|
||||
|
||||
} else {
|
||||
auto bb_player = PSOBBCharacterFile::create_from_config(
|
||||
dest_license->serial_number,
|
||||
pending_export->license->serial_number,
|
||||
c->language(),
|
||||
player->disp.visual,
|
||||
player->disp.name.decode(c->language()),
|
||||
@@ -3016,12 +3026,9 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
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<uint8_t>(c->pending_bb_save_character_index + 1),
|
||||
dest_license->bb_username.c_str());
|
||||
send_text_message(c, "$C6Character data saved");
|
||||
} catch (const exception& e) {
|
||||
send_text_message_printf(c, "$C6PSOBB player data could\nnot be saved:\n%s", e.what());
|
||||
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user