complete implementation of $checkchar and make slot count configurable; closes #645

This commit is contained in:
Martin Michelsen
2025-05-26 21:55:19 -07:00
parent 33b0ab3ed3
commit 5f4d2ec891
5 changed files with 92 additions and 31 deletions
+1 -1
View File
@@ -602,7 +602,7 @@ Some commands only work for clients not in proxy sessions. The chat commands are
* `$savechar <slot>`: Save your current character data on the server in the specified slot. See the [server-side saves section](#server-side-saves) for more details.
* `$loadchar <slot>`: Save your current character data on the server in the specified slot. See the [server-side saves section](#server-side-saves) for more details.
* `$bbchar <username> <password> <slot>`: Save your current character data on the server in a different account's BB character slots. See the [server-side saves section](#server-side-saves) for more details.
* `$checkchar <slot>`: Tells you basic information about a server-side character previously saved using `$savechar`.
* `$checkchar [slot]`: Tells you basic information about a server-side character previously saved using `$savechar`. If `slot` is not given, tells you which slots are used and which are free.
* `$deletechar <slot>`: Deletes a server-side character previously saved using `$savechar`.
* `$edit <stat> <value>`: Modify your character data. See the [using $edit](#using-edit) section for details.
+84 -30
View File
@@ -24,6 +24,41 @@
using namespace std;
////////////////////////////////////////////////////////////////////////////////
// Tools
string str_for_flag_ranges(const vector<bool>& flags) {
string ret;
auto add_result = [&](size_t start, size_t end) {
if (!ret.empty()) {
ret.push_back(',');
}
if (start == end) {
ret += std::format("{}", start);
} else if (start == end - 1) {
ret += std::format("{},{}", start, end);
} else {
ret += std::format("{}-{}", start, end);
}
};
size_t range_start = 0;
bool in_range = false;
for (size_t z = 0; z < flags.size(); z++) {
if (flags[z] && !in_range) {
in_range = true;
range_start = z;
} else if (!flags[z] && in_range) {
in_range = false;
add_result(range_start, z - 1);
}
}
if (in_range) {
add_result(range_start, flags.size() - 1);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////
// Checks
@@ -421,7 +456,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
shared_ptr<Account> dest_account;
shared_ptr<BBLicense> dest_bb_license;
ssize_t dest_character_index = 0;
size_t dest_character_index = 0;
if (is_bb_conversion) {
vector<string> tokens = phosg::split(a.text, ' ');
if (tokens.size() != 3) {
@@ -430,8 +465,8 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
// username/password are tokens[0] and [1]
dest_character_index = stoll(tokens[2]) - 1;
if ((dest_character_index > 3) || (dest_character_index < 0)) {
throw precondition_failed("$C6Player index must\nbe in range 1-4");
if ((dest_character_index >= 127) || (dest_character_index < 0)) {
throw precondition_failed("$C6Player index must\nbe in range 1-127");
}
try {
@@ -444,8 +479,8 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
} else {
dest_character_index = stoll(a.text) - 1;
if ((dest_character_index > 15) || (dest_character_index < 0)) {
throw precondition_failed("$C6Player index must\nbe in range 1-16");
if ((dest_character_index >= s->num_backup_character_slots) || (dest_character_index < 0)) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
}
dest_account = a.c->login->account;
}
@@ -574,30 +609,49 @@ ChatCommandDefinition cc_checkchar(
throw precondition_failed("$C7This command cannot\nbe used on a shared\naccount");
}
size_t index = stoull(a.text, nullptr, 0) - 1;
if (index > 15) {
throw precondition_failed("$C6Player index must\nbe in range 1-16");
}
auto s = a.c->require_server_state();
try {
if (is_ep3(a.c->version())) {
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, true);
auto ch = phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename);
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nCLv: on {}.{}, off {}.{}",
index + 1, ch.disp.visual.name.decode(),
name_for_section_id(ch.disp.visual.section_id), name_for_char_class(ch.disp.visual.char_class),
(ch.ep3_config.online_clv_exp / 100) + 1, ch.ep3_config.online_clv_exp % 100,
(ch.ep3_config.offline_clv_exp / 100) + 1, ch.ep3_config.offline_clv_exp % 100);
} else {
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, false);
auto ch = PSOCHARFile::load_shared(filename, false).character_file;
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nLevel {}",
index + 1, ch->disp.name.decode(),
name_for_section_id(ch->disp.visual.section_id), name_for_char_class(ch->disp.visual.char_class),
ch->disp.stats.level + 1);
if (a.text.empty()) {
bool is_ep3 = ::is_ep3(a.c->version());
vector<bool> flags;
flags.emplace_back(false);
for (size_t z = 0; z < s->num_backup_character_slots; z++) {
string filename = a.c->backup_character_filename(a.c->login->account->account_id, z, is_ep3);
flags.emplace_back(std::filesystem::is_regular_file(filename));
}
string used_str = str_for_flag_ranges(flags);
flags.flip();
flags[0] = false;
string free_str = str_for_flag_ranges(flags);
send_text_message_fmt(a.c, "Used: {}\nFree: {}", used_str, free_str);
} else {
size_t index = stoull(a.text, nullptr, 0) - 1;
if (index >= s->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
}
try {
if (is_ep3(a.c->version())) {
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, true);
auto ch = phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename);
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nCLv: on {}.{}, off {}.{}",
index + 1, ch.disp.visual.name.decode(),
name_for_section_id(ch.disp.visual.section_id), name_for_char_class(ch.disp.visual.char_class),
(ch.ep3_config.online_clv_exp / 100) + 1, ch.ep3_config.online_clv_exp % 100,
(ch.ep3_config.offline_clv_exp / 100) + 1, ch.ep3_config.offline_clv_exp % 100);
} else {
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, false);
auto ch = PSOCHARFile::load_shared(filename, false).character_file;
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nLevel {}",
index + 1, ch->disp.name.decode(),
name_for_section_id(ch->disp.visual.section_id), name_for_char_class(ch->disp.visual.char_class),
ch->disp.stats.level + 1);
}
} catch (const phosg::cannot_open_file&) {
send_text_message_fmt(a.c, "No character in\nslot {}", index + 1);
}
} catch (const phosg::cannot_open_file&) {
send_text_message_fmt(a.c, "No character in\nslot {}", index + 1);
}
co_return;
@@ -1508,11 +1562,12 @@ ChatCommandDefinition cc_loadchar(
throw precondition_failed("$C7This command cannot\nbe used on a shared\naccount");
}
auto s = a.c->require_server_state();
auto l = a.c->require_lobby();
size_t index = stoull(a.text, nullptr, 0) - 1;
if (index > 15) {
throw precondition_failed("$C6Player index must\nbe in range 1-16");
if (index >= s->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
}
shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
@@ -1524,7 +1579,6 @@ ChatCommandDefinition cc_loadchar(
if (a.c->version() == Version::BB_V4) {
// On BB, it suffices to simply send the character file again
auto s = a.c->require_server_state();
send_complete_player_bb(a.c);
send_player_leave_notification(l, a.c->lobby_client_id);
s->send_lobby_join_notifications(l, a.c);
+1
View File
@@ -1095,6 +1095,7 @@ void ServerState::load_config_early() {
}
this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true);
this->num_backup_character_slots = this->config_json->get_int("BackupCharacterSlots", 16);
this->version_name_colors.reset();
this->client_customization_name_color = 0;
+1
View File
@@ -115,6 +115,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool use_temp_accounts_for_prototypes = true;
std::array<uint16_t, NUM_VERSIONS> compatibility_groups = {};
bool enable_chat_commands = true;
size_t num_backup_character_slots = 16;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint32_t client_customization_name_color = 0x00000000;
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
+5
View File
@@ -279,6 +279,11 @@
// use chat commands.
"EnableChatCommands": true,
// Number of backup character slots for each account, accessible with the
// $savechar, $loadchar, and $checkchar commands. This can be any value, but
// it's recommended to use a small number such as 16.
"BackupCharacterSlots": 16,
// Information menu contents. Each entry is a list containing [title,
// short description, full contents]. In the short description and full
// contents, you can use PSO escape codes with the $ character (for example,