diff --git a/notes/quest-flags.txt b/notes/quest-flags.txt index aa492274..3d636f9d 100644 --- a/notes/quest-flags.txt +++ b/notes/quest-flags.txt @@ -17,7 +17,7 @@ 0019 = P2 Scientist after defeating dragon 001E = Entered Caves 1 (Gov 2-1) 001F = Entered De Rol Le in 2-4 -0020 = De Ro lee defeated +0020 = De Rol Le defeated 0021 = Mines unlocked (P2 Tyrell after defeating De Rol Le) 0028 = Entered Mines 1 0029 = Entered Vol Opt Area diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 02cf1d4f..4bc8f6a6 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -674,15 +674,26 @@ struct S_LegacyJoinGame_XB_0E { // 10 (C->S): Menu selection // Internal name: SndAction -// header.flag is a bit field containing two flags: 02 specifies if a password -// is present, and 01 specifies if a name is present. (If both are set, the -// name comes first, as described below). These two bits directly correspond to -// the two lowest bits in the flags field of the game menu: 02 specifies that -// the game is locked, but the function of 01 is unknown. The ability to send -// a name along with a menu choice is unused in all client versions except -// Episode 3, where it's used in the tourname entries menu. It's not clear why -// all other versions have the ability send a name here - it may be a relic -// from very early development. +// header.flag has different meanings depending on which menu is selected. +// For most menus, header.flag is a bit field containing two flags: 02 +// specifies if a password is present, and 01 specifies if a name is present. +// (If both are set, the name comes first, as described below). These two bits +// directly correspond to the two lowest bits in the flags field of the game +// menu: 02 specifies that the game is locked, but the function of 01 is +// unknown. The ability to send a name along with a menu choice is unused in +// all client versions except Episode 3, where it's used in the tournament +// entries menu. It's not clear why all other versions have the ability send a +// name here - it may be a relic from very early development. +// For the quest categories menu, header.flag specifies the player's +// progression through the story. The values are: +// 0 = has not yet defeated Dragon +// 1 = has defeated Dragon but not De Rol Le +// 2 = has defeated De Rol Le but not Vol Opt +// 3 = has defeated Vol Opt but not Dark Falz +// 4 = has defeated Dark Falz +// For the challenge categories menu, header.flag specifies something related +// to challenge stage completion (TODO: reverse-engineer function at +// 59NL:004DA300 to see what this is) struct C_MenuSelectionBase_10 { le_uint32_t menu_id = 0; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index d981966e..c6923f10 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2978,83 +2978,76 @@ template static asio::awaitable on_10(shared_ptr c, Channel::Message& msg) { constexpr TextEncoding Encoding = UsesUTF16 ? TextEncoding::UTF16 : TextEncoding::MARKED; - uint32_t menu_id; - uint32_t item_id; + const auto& base_cmd = check_size_t(msg.data, 0xFFFF); + string name; string password; - switch (msg.flag) { - case 0: { - const auto& cmd = check_size_t(msg.data); - menu_id = cmd.menu_id; - item_id = cmd.item_id; + switch (msg.data.size()) { + case sizeof(C_MenuSelectionBase_10): + break; + case sizeof(C_MenuSelectionWithNameT_10): { + static_assert( + sizeof(C_MenuSelectionWithNameT_10) == sizeof(C_MenuSelectionWithPasswordT_10), + "Single-flag 10 commands should be the same size"); + if (msg.flag & 1) { + const auto& cmd = check_size_t>(msg.data); + name = cmd.name.decode(c->language()); + } else if (msg.flag & 2) { + const auto& cmd = check_size_t>(msg.data); + password = cmd.password.decode(c->language()); + } break; } - case 1: { - const auto& cmd = check_size_t>(msg.data); - name = cmd.name.decode(c->language()); - menu_id = cmd.menu_id; - item_id = cmd.item_id; - break; - } - case 2: { - const auto& cmd = check_size_t>(msg.data); - password = cmd.password.decode(c->language()); - menu_id = cmd.menu_id; - item_id = cmd.item_id; - break; - } - case 3: { + case sizeof(C_MenuSelectionWithNameAndPasswordT_10): { const auto& cmd = check_size_t>(msg.data); name = cmd.name.decode(c->language()); password = cmd.password.decode(c->language()); - menu_id = cmd.menu_id; - item_id = cmd.item_id; break; } default: - throw std::runtime_error("invalid options for menu selection command"); + throw runtime_error("unknown menu selection format"); } auto s = c->require_server_state(); - switch (menu_id) { + switch (base_cmd.menu_id) { case MenuID::MAIN: - co_await on_10_main_menu(c, item_id); + co_await on_10_main_menu(c, base_cmd.item_id); break; case MenuID::CLEAR_LICENSE_CONFIRMATION: - co_await on_10_clear_license_confirmation(c, item_id); + co_await on_10_clear_license_confirmation(c, base_cmd.item_id); break; case MenuID::INFORMATION: - co_await on_10_information(c, item_id); + co_await on_10_information(c, base_cmd.item_id); break; case MenuID::PROXY_OPTIONS: - co_await on_10_proxy_options(c, item_id); + co_await on_10_proxy_options(c, base_cmd.item_id); break; case MenuID::PROXY_DESTINATIONS: - co_await on_10_proxy_destinations(c, item_id); + co_await on_10_proxy_destinations(c, base_cmd.item_id); break; case MenuID::GAME: - co_await on_10_game_menu(c, item_id, std::move(password)); + co_await on_10_game_menu(c, base_cmd.item_id, std::move(password)); break; case MenuID::QUEST_CATEGORIES_EP1: case MenuID::QUEST_CATEGORIES_EP2: - co_await on_10_quest_categories(c, item_id); + co_await on_10_quest_categories(c, base_cmd.item_id); break; case MenuID::QUEST_EP1: case MenuID::QUEST_EP2: - co_await on_10_quest_menu(c, item_id); + co_await on_10_quest_menu(c, base_cmd.item_id); break; case MenuID::PATCH_SWITCHES: - co_await on_10_patch_switches(c, item_id); + co_await on_10_patch_switches(c, base_cmd.item_id); break; case MenuID::PROGRAMS: - co_await on_10_programs(c, item_id); + co_await on_10_programs(c, base_cmd.item_id); break; case MenuID::TOURNAMENTS_FOR_SPEC: case MenuID::TOURNAMENTS: - co_await on_10_tournaments(c, menu_id, item_id); + co_await on_10_tournaments(c, base_cmd.menu_id, base_cmd.item_id); break; case MenuID::TOURNAMENT_ENTRIES: - co_await on_10_tournament_entries(c, item_id, std::move(name), std::move(password)); + co_await on_10_tournament_entries(c, base_cmd.item_id, std::move(name), std::move(password)); break; default: send_message_box(c, "Incorrect menu ID");