complete implementation of $checkchar and make slot count configurable; closes #645
This commit is contained in:
@@ -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.
|
* `$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.
|
* `$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.
|
* `$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`.
|
* `$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.
|
* `$edit <stat> <value>`: Modify your character data. See the [using $edit](#using-edit) section for details.
|
||||||
|
|
||||||
|
|||||||
+84
-30
@@ -24,6 +24,41 @@
|
|||||||
|
|
||||||
using namespace std;
|
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
|
// Checks
|
||||||
|
|
||||||
@@ -421,7 +456,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
|||||||
|
|
||||||
shared_ptr<Account> dest_account;
|
shared_ptr<Account> dest_account;
|
||||||
shared_ptr<BBLicense> dest_bb_license;
|
shared_ptr<BBLicense> dest_bb_license;
|
||||||
ssize_t dest_character_index = 0;
|
size_t dest_character_index = 0;
|
||||||
if (is_bb_conversion) {
|
if (is_bb_conversion) {
|
||||||
vector<string> tokens = phosg::split(a.text, ' ');
|
vector<string> tokens = phosg::split(a.text, ' ');
|
||||||
if (tokens.size() != 3) {
|
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]
|
// username/password are tokens[0] and [1]
|
||||||
dest_character_index = stoll(tokens[2]) - 1;
|
dest_character_index = stoll(tokens[2]) - 1;
|
||||||
if ((dest_character_index > 3) || (dest_character_index < 0)) {
|
if ((dest_character_index >= 127) || (dest_character_index < 0)) {
|
||||||
throw precondition_failed("$C6Player index must\nbe in range 1-4");
|
throw precondition_failed("$C6Player index must\nbe in range 1-127");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -444,8 +479,8 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
dest_character_index = stoll(a.text) - 1;
|
dest_character_index = stoll(a.text) - 1;
|
||||||
if ((dest_character_index > 15) || (dest_character_index < 0)) {
|
if ((dest_character_index >= s->num_backup_character_slots) || (dest_character_index < 0)) {
|
||||||
throw precondition_failed("$C6Player index must\nbe in range 1-16");
|
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
|
||||||
}
|
}
|
||||||
dest_account = a.c->login->account;
|
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");
|
throw precondition_failed("$C7This command cannot\nbe used on a shared\naccount");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t index = stoull(a.text, nullptr, 0) - 1;
|
auto s = a.c->require_server_state();
|
||||||
if (index > 15) {
|
|
||||||
throw precondition_failed("$C6Player index must\nbe in range 1-16");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (a.text.empty()) {
|
||||||
if (is_ep3(a.c->version())) {
|
bool is_ep3 = ::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);
|
vector<bool> flags;
|
||||||
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nCLv: on {}.{}, off {}.{}",
|
flags.emplace_back(false);
|
||||||
index + 1, ch.disp.visual.name.decode(),
|
for (size_t z = 0; z < s->num_backup_character_slots; z++) {
|
||||||
name_for_section_id(ch.disp.visual.section_id), name_for_char_class(ch.disp.visual.char_class),
|
string filename = a.c->backup_character_filename(a.c->login->account->account_id, z, is_ep3);
|
||||||
(ch.ep3_config.online_clv_exp / 100) + 1, ch.ep3_config.online_clv_exp % 100,
|
flags.emplace_back(std::filesystem::is_regular_file(filename));
|
||||||
(ch.ep3_config.offline_clv_exp / 100) + 1, ch.ep3_config.offline_clv_exp % 100);
|
}
|
||||||
} else {
|
string used_str = str_for_flag_ranges(flags);
|
||||||
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, false);
|
flags.flip();
|
||||||
auto ch = PSOCHARFile::load_shared(filename, false).character_file;
|
flags[0] = false;
|
||||||
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nLevel {}",
|
string free_str = str_for_flag_ranges(flags);
|
||||||
index + 1, ch->disp.name.decode(),
|
send_text_message_fmt(a.c, "Used: {}\nFree: {}", used_str, free_str);
|
||||||
name_for_section_id(ch->disp.visual.section_id), name_for_char_class(ch->disp.visual.char_class),
|
|
||||||
ch->disp.stats.level + 1);
|
} 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;
|
co_return;
|
||||||
@@ -1508,11 +1562,12 @@ ChatCommandDefinition cc_loadchar(
|
|||||||
throw precondition_failed("$C7This command cannot\nbe used on a shared\naccount");
|
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();
|
auto l = a.c->require_lobby();
|
||||||
|
|
||||||
size_t index = stoull(a.text, nullptr, 0) - 1;
|
size_t index = stoull(a.text, nullptr, 0) - 1;
|
||||||
if (index > 15) {
|
if (index >= s->num_backup_character_slots) {
|
||||||
throw precondition_failed("$C6Player index must\nbe in range 1-16");
|
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
|
shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
|
||||||
@@ -1524,7 +1579,6 @@ ChatCommandDefinition cc_loadchar(
|
|||||||
|
|
||||||
if (a.c->version() == Version::BB_V4) {
|
if (a.c->version() == Version::BB_V4) {
|
||||||
// On BB, it suffices to simply send the character file again
|
// 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_complete_player_bb(a.c);
|
||||||
send_player_leave_notification(l, a.c->lobby_client_id);
|
send_player_leave_notification(l, a.c->lobby_client_id);
|
||||||
s->send_lobby_join_notifications(l, a.c);
|
s->send_lobby_join_notifications(l, a.c);
|
||||||
|
|||||||
@@ -1095,6 +1095,7 @@ void ServerState::load_config_early() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true);
|
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->version_name_colors.reset();
|
||||||
this->client_customization_name_color = 0;
|
this->client_customization_name_color = 0;
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
|||||||
bool use_temp_accounts_for_prototypes = true;
|
bool use_temp_accounts_for_prototypes = true;
|
||||||
std::array<uint16_t, NUM_VERSIONS> compatibility_groups = {};
|
std::array<uint16_t, NUM_VERSIONS> compatibility_groups = {};
|
||||||
bool enable_chat_commands = true;
|
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;
|
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
|
||||||
uint32_t client_customization_name_color = 0x00000000;
|
uint32_t client_customization_name_color = 0x00000000;
|
||||||
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
|
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
|
||||||
|
|||||||
@@ -279,6 +279,11 @@
|
|||||||
// use chat commands.
|
// use chat commands.
|
||||||
"EnableChatCommands": true,
|
"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,
|
// Information menu contents. Each entry is a list containing [title,
|
||||||
// short description, full contents]. In the short description and full
|
// short description, full contents]. In the short description and full
|
||||||
// contents, you can use PSO escape codes with the $ character (for example,
|
// contents, you can use PSO escape codes with the $ character (for example,
|
||||||
|
|||||||
Reference in New Issue
Block a user