diff --git a/README.md b/README.md index f89c0a9e..914c7614 100644 --- a/README.md +++ b/README.md @@ -376,12 +376,13 @@ Some commands only work on the game server and not on the proxy server. The chat * The rest of the commands in this section are enabled on the game server. (They are always enabled on the proxy server.) * `$quest ` (game server only): Load a quest by quest number. Can be used to load battle or challenge quests with only one player present. * `$qcall `: Call a quest function on your client. - * `$qcheck ` (game server only): Show the value of a quest flag. - * `$qset ` or `$qclear `: Set or clear a global quest flag for everyone in the game. + * `$qcheck ` (game server only): Show the value of a quest flag. This command can be used without debug mode enabled. + * `$qset ` or `$qclear `: Set or clear a quest flag for everyone in the game. + * `$qgread ` (game server only): Get the value of a quest counter ("global flag"). This command can be used without debug mode enabled. + * `$qgwrite ` (game server only): Set the value of a quest counter ("global flag") for yourself. * `$qsync `: Set a quest register's value for yourself only. `` should be either rXX (e.g. r60) or fXX (e.g. f60); if the latter, `` is parsed as a floating-point value instead of as an integer. * `$qsyncall `: Set a quest register's value for everyone in the game. `` should be either rXX (e.g. r60) or fXX (e.g. f60); if the latter, `` is parsed as a floating-point value instead of as an integer. * `$gc` (game server only): Send your own Guild Card to yourself. - * `$persist` (game server only): Enable or disable persistence for the current game. When persistence is on, the game will not be deleted when the last player leaves. The state of enemies and objects on the map will be reset when the last player leaves. * `$sc `: Send a command to yourself. * `$ss ` (proxy server only): Send a command to the remote server. * `$meseta ` (game server only; Episode 3 only): Add the given amount to your Meseta total. @@ -402,6 +403,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$loadchar ` (v1 and v2 only): Loads your character data from the specified slot. The changes will be undone if you join a game - to save your changes, disconnect from the lobby. * `$bbchar `: 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 (1-4). Any character already in that slot is overwritten. (This command is similar to `$savechar`, except it overwrites a BB character slot, and can transfer characters across accounts.) 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 you are on V3 (GameCube/Xbox), this command does nothing. If you are on V1 or V2 (DC or PC, not BB), your changes will be undone if you join a game - to save your changes, disconnect from the lobby. If cheats are allowed on the server, `` can be any of `atp`, `mst`, `evp`, `hp`, `dfp`, `ata`, `lck`, `meseta`, `exp`, `level`, `namecolor`, `secid`, `name`, `npc`, or `tech`. If cheats are not allowed, only `namecolor`, `name`, and `npc` can be used. + * Blue Burst player commands (game server only) * `$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.) @@ -411,6 +413,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$minlevel `: Sets the minimum level for players to join the current game. * `$password `: Sets the game's join password. To unlock the game, run `$password` with nothing after it. * `$dropmode [mode]`: Changes the way item drops behave in the current game. `mode` can be `none`, `client`, `shared`, `private`, or `duplicate`. If `mode` is not given, tells you the current drop mode without changing it. See the "Item tables and drop modes" section for more information. + * `$persist`: Enable or disable persistence for the current game. When persistence is on, the game will not be deleted when the last player leaves. The state of enemies and objects on the map will be reset when the last player leaves, but dropped items will not be deleted. If the game is empty for too long (15 minutes by default), it is then deleted. * Episode 3 commands (game server only) * `$spec`: Toggles the allow spectators flag for Episode 3 games. If any players are spectating when this flag is disabled, they will be sent back to the lobby. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 7625d84f..74ab9be1 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -375,6 +375,51 @@ static void proxy_command_qclear(shared_ptr ses, con return proxy_command_qset_qclear(ses, args, false); } +static void server_command_qgread(shared_ptr c, const std::string& args) { + uint8_t flag_num = stoul(args, nullptr, 0); + const auto& flags = c->character()->quest_counters; + if (flag_num >= flags.size()) { + send_text_message_printf(c, "$C7Flag number must be\nless than %zu", flags.size()); + } else { + send_text_message_printf(c, "$C7Quest counter %hhu\nhas value %" PRIu32, + flag_num, flags[flag_num].load()); + } +} + +static void server_command_qgwrite(shared_ptr c, const std::string& args) { + if (c->version() != Version::BB_V4) { + send_text_message(c, "$C6This command can\nonly be used on BB"); + return; + } + if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); + return; + } + auto l = c->require_lobby(); + if (!l->is_game()) { + send_text_message(c, "$C6This command cannot\nbe used in the lobby"); + return; + } + + auto tokens = split(args, ' '); + if (tokens.size() != 2) { + send_text_message(c, "$C6Incorrect number\nof arguments"); + return; + } + + uint8_t flag_num = stoul(tokens[0], nullptr, 0); + uint32_t value = stoul(tokens[1], nullptr, 0); + auto& flags = c->character()->quest_counters; + if (flag_num >= flags.size()) { + send_text_message_printf(c, "$C7Flag number must be\nless than %zu", flags.size()); + } else { + c->character()->quest_counters[flag_num] = value; + G_SetQuestCounter_BB_6xD2 cmd = {{0xD2, sizeof(G_SetQuestCounter_BB_6xD2) / 4, c->lobby_client_id}, flag_num, value}; + send_command_t(c, 0x60, 0x00, cmd); + send_text_message_printf(c, "$C7Quest counter %hhu\nset to %" PRIu32, flag_num, value); + } +} + static void server_command_qsync_qsyncall(shared_ptr c, const std::string& args, bool send_to_lobby) { if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); @@ -2021,6 +2066,8 @@ static const unordered_map chat_commands({ {"$qcall", {server_command_qcall, proxy_command_qcall}}, {"$qcheck", {server_command_qcheck, nullptr}}, {"$qclear", {server_command_qclear, proxy_command_qclear}}, + {"$qgread", {server_command_qgread, nullptr}}, + {"$qgwrite", {server_command_qgwrite, nullptr}}, {"$qset", {server_command_qset, proxy_command_qset}}, {"$qsync", {server_command_qsync, proxy_command_qsync}}, {"$qsyncall", {server_command_qsyncall, proxy_command_qsyncall}}, diff --git a/src/Client.cc b/src/Client.cc index 8b9a8acb..47312453 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -811,7 +811,7 @@ void Client::load_all_files() { 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; + this->character_data->quest_counters = nsc_data.quest_counters; if (nsa_data) { this->character_data->option_flags = nsa_data->option_flags; this->character_data->symbol_chats = nsa_data->symbol_chats; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 832fa1bf..0ffcbfdd 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -5881,10 +5881,10 @@ struct G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1 { le_uint32_t item_type = 0; // Should be < 6 } __packed__; -// 6xD2: Set quest global flag (BB) +// 6xD2: Set quest counter (BB) // Writes 4 bytes to the 32-bit field specified by index. -struct G_SetQuestGlobalFlag_BB_6xD2 { +struct G_SetQuestCounter_BB_6xD2 { G_ClientIDHeader header; le_uint32_t index = 0; // There are 0x10 of them (0x00-0x0F) le_uint32_t value = 0; diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index b73cf3e4..6ca99763 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -827,7 +827,7 @@ struct PlayerConfig { /* 000C:---- */ parray unknown_a1; /* 0028:---- */ parray tech_menu_shortcut_entries; /* 0050:---- */ parray choice_search_config; - // This field maps to quest_global_flags on Episodes 1 & 2 + // This field maps to quest_counters on Episodes 1 & 2 /* 0078:---- */ parray scenario_progress; // place_counts[0] and [1] from this field are added to the player's win and // loss count respectively when they're shown in the status menu. However, diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 0ca21013..c2684045 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -279,11 +279,11 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0x0010, "set", {REG}, F_V0_V4}, // Sets a register to 1 {0x0011, "clear", {REG}, F_V0_V4}, // Sets a register to 0 {0x0012, "rev", {REG}, F_V0_V4}, // Sets a register to 0 if it's nonzero and vice versa - {0x0013, "gset", {INT16}, F_V0_V4}, // Sets a global flag - {0x0014, "gclear", {INT16}, F_V0_V4}, // Clears a global flag - {0x0015, "grev", {INT16}, F_V0_V4}, // Flips a global flag - {0x0016, "glet", {INT16, REG}, F_V0_V4}, // Sets a global flag to a specific value - {0x0017, "gget", {INT16, REG}, F_V0_V4}, // Gets a global flag + {0x0013, "gset", {INT16}, F_V0_V4}, // Sets a quest flag + {0x0014, "gclear", {INT16}, F_V0_V4}, // Clears a quest flag + {0x0015, "grev", {INT16}, F_V0_V4}, // Flips a quest flag + {0x0016, "glet", {INT16, REG}, F_V0_V4}, // Sets a quest flag to a specific value + {0x0017, "gget", {INT16, REG}, F_V0_V4}, // Gets a quest flag {0x0018, "add", {REG, REG}, F_V0_V4}, // regA += regB {0x0019, "addi", {REG, INT32}, F_V0_V4}, // regA += imm {0x001A, "sub", {REG, REG}, F_V0_V4}, // regA -= regB @@ -779,8 +779,8 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF922, "get_player_hp", {CLIENT_ID, {REG_SET_FIXED, 4}}, F_V3_V4 | F_ARGS}, {0xF923, "get_floor_number", {CLIENT_ID, {REG_SET_FIXED, 2}}, F_V3_V4 | F_ARGS}, {0xF924, "get_coord_player_detect", {{REG_SET_FIXED, 3}, {REG_SET_FIXED, 4}}, F_V3_V4}, - {0xF925, "read_global_flag", {INT32, REG}, F_V3_V4 | F_ARGS}, - {0xF926, "write_global_flag", {INT32, INT32}, F_V3_V4 | F_ARGS}, + {0xF925, "read_counter", {INT32, REG}, F_V3_V4 | F_ARGS}, + {0xF926, "write_counter", {INT32, INT32}, F_V3_V4 | F_ARGS}, {0xF927, "item_detect_bank2", {{REG_SET_FIXED, 4}, REG}, F_V3_V4}, {0xF928, "floor_player_detect", {{REG_SET_FIXED, 4}}, F_V3_V4}, {0xF929, "prepare_gba_rom_from_disk", {CSTRING}, F_V3 | F_ARGS}, // Prepares to load a GBA ROM from a local GSL file diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index ddba8d07..19049f12 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -3567,9 +3567,9 @@ 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, void* data, size_t size) { - const auto& cmd = check_size_t(data, size); - c->character()->quest_global_flags[cmd.index] = cmd.value; +static void on_write_quest_counter_bb(shared_ptr c, uint8_t, uint8_t, void* data, size_t size) { + const auto& cmd = check_size_t(data, size); + c->character()->quest_counters[cmd.index] = cmd.value; } //////////////////////////////////////////////////////////////////////////////// @@ -3785,7 +3785,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = { /* 6xCF */ {0x00, 0x00, 0xCF, on_battle_restart_bb}, /* 6xD0 */ {0x00, 0x00, 0xD0, on_battle_level_up_bb}, /* 6xD1 */ {0x00, 0x00, 0xD1, on_request_challenge_grave_recovery_item_bb}, - /* 6xD2 */ {0x00, 0x00, 0xD2, on_write_quest_global_flag_bb}, + /* 6xD2 */ {0x00, 0x00, 0xD2, on_write_quest_counter_bb}, /* 6xD3 */ {0x00, 0x00, 0xD3, on_invalid}, /* 6xD4 */ {0x00, 0x00, 0xD4, on_forward_check_game}, /* 6xD5 */ {0x00, 0x00, 0xD5, on_quest_exchange_item_bb}, diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index b36bcee5..c0f7b353 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -210,7 +210,7 @@ struct PSOBBCharacterFile { /* 2DF8 */ parray tech_menu_config; /* 2E20 */ ChoiceSearchConfig choice_search_config; /* 2E38 */ parray unknown_a6; - /* 2E48 */ parray quest_global_flags; + /* 2E48 */ parray quest_counters; /* 2E88 */ parray unknown_a7; /* 2EA4 */ @@ -360,7 +360,7 @@ struct PSOGCCharacterFile { /* 25E0:21C4 */ PlayerRecordsV3_Challenge challenge_records; /* 26E0:22C4 */ parray tech_menu_shortcut_entries; /* 2708:22EC */ parray unknown_a6; - /* 2730:2314 */ parray quest_global_flags; + /* 2730:2314 */ parray quest_counters; /* 2770:2354 */ PlayerRecords_Battle offline_battle_records; /* 2788:236C */ parray unknown_f5; /* 278C:2370 */ be_uint32_t unknown_f6; @@ -766,7 +766,7 @@ struct LegacySavedPlayerDataBB { // .nsc file format /* 1D00 */ parray unknown_a2; /* 1D04 */ QuestFlags quest_flags; /* 1F04 */ le_uint32_t death_count; - /* 1F08 */ parray quest_global_flags; + /* 1F08 */ parray quest_counters; /* 1F60 */ parray tech_menu_config; /* 1F88 */ } __attribute__((packed));