diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 2dc6815c..09a5a639 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -849,15 +849,17 @@ struct SC_GameGuardCheck_BB_0022 { // header.flag indicates if an item was exchanged: 0 means success, 1 means // failure. -// 24 (S->C): Good Luck result (BB) +// 24 (S->C): Secret Lottery Ticket exchange result (BB) // Sent in response to a 6xDE command from the client. // header.flag indicates whether the client had any Secret Lottery Tickets in // their inventory (and hence could participate): 0 means success, 1 means // failure. -struct S_GoodLuckResult_BB_24 { - le_uint16_t unknown_a1 = 0; +struct S_ExchangeSecretLotteryTicketResult_BB_24 { + // These fields map to unknown_a1 and unknown_a2 in the 6xDE command (but + // their order is swapped here). le_uint16_t unknown_a2 = 0; + le_uint16_t unknown_a1 = 0; parray unknown_a3; } __packed__; @@ -5672,7 +5674,7 @@ struct G_UpgradeWeaponAttribute_BB_6xDA { struct G_ExchangeItemInQuest_BB_6xDB { G_ClientIDHeader header; le_uint32_t unknown_a1 = 0; - le_uint32_t unknown_a2 = 0; + le_uint32_t item_id = 0; le_uint32_t unknown_a3 = 0; } __packed__; @@ -5692,20 +5694,20 @@ struct G_SetEXPMultiplier_BB_6xDD { G_ParameterHeader header; } __packed__; -// 6xDE: Good Luck quest (BB; handled by server) +// 6xDE: Exchange Secret Lottery Ticket (BB; handled by server) // The client sends this when it executes an F95C quest opcode. -struct G_GoodLuckQuestActions_BB_6xDE { +struct G_ExchangeSecretLotteryTicket_BB_6xDE { G_ClientIDHeader header; uint8_t unknown_a1 = 0; uint8_t unknown_a2 = 0; le_uint16_t unknown_a3 = 0; } __packed__; -// 6xDF: Black Paper's Deal Photon Crystal exchange (BB; handled by server) +// 6xDF: Exchange Photon Crystals (BB; handled by server) // The client sends this when it executes an F95D quest opcode. -struct G_BlackPaperDealPhotonCrystalExchange_BB_6xDF { +struct G_ExchangePhotonCrystals_BB_6xDF { G_ClientIDHeader header; } __packed__; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 168340b2..64efeec1 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2291,10 +2291,65 @@ static void on_photon_drop_exchange_bb(shared_ptr c, uint8_t, uint8_t, c } } +static void on_secret_lottery_ticket_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); + if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { + const auto& cmd = check_size_t(data, size); + + if (s->secret_lottery_results.empty()) { + throw runtime_error("no secret lottery results are defined"); + } + + auto p = c->game_data.character(); + ssize_t slt_index = -1; + try { + slt_index = p->inventory.find_item_by_primary_identifier(0x031003); // Secret Lottery Ticket + } catch (const out_of_range&) { + } + + if (slt_index >= 0) { + uint32_t slt_item_id = p->inventory.items[slt_index].data.id; + + G_ExchangeItemInQuest_BB_6xDB exchange_cmd; + exchange_cmd.header.subcommand = 0xDB; + exchange_cmd.header.size = 4; + exchange_cmd.header.client_id = c->lobby_client_id; + exchange_cmd.unknown_a1 = 1; + exchange_cmd.item_id = slt_item_id; + exchange_cmd.unknown_a3 = 1; + send_command_t(c, 0x60, 0x00, exchange_cmd); + + send_destroy_item(c, slt_item_id, 1); + + ItemData item = (s->secret_lottery_results.size() == 1) + ? s->secret_lottery_results[0] + : s->secret_lottery_results[random_object() % s->secret_lottery_results.size()]; + item.id = l->generate_item_id(c->lobby_client_id); + p->add_item(item); + send_create_inventory_item(c, item); + } + + S_ExchangeSecretLotteryTicketResult_BB_24 out_cmd; + out_cmd.unknown_a1 = cmd.unknown_a1; + out_cmd.unknown_a2 = cmd.unknown_a2; + if (s->secret_lottery_results.empty()) { + out_cmd.unknown_a3.clear(0); + } else if (s->secret_lottery_results.size() == 1) { + out_cmd.unknown_a3.clear(1); + } else { + for (size_t z = 0; z < out_cmd.unknown_a3.size(); z++) { + out_cmd.unknown_a3[z] = random_object() % s->secret_lottery_results.size(); + } + } + send_command_t(c, 0x24, (slt_index >= 0) ? 0 : 1, out_cmd); + } +} + static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { - check_size_t(data, size); + check_size_t(data, size); auto p = c->game_data.character(); size_t index = p->inventory.find_item_by_primary_identifier(0x031002); auto item = p->remove_item(p->inventory.items[index].data.id, 1, false); @@ -2648,7 +2703,7 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 6xDB */ nullptr, /* 6xDC */ on_forward_check_size_game, /* 6xDD */ nullptr, - /* 6xDE */ nullptr, + /* 6xDE */ on_secret_lottery_ticket_exchange_bb, /* 6xDF */ on_photon_crystal_exchange_bb, /* 6xE0 */ on_quest_F95E_result_bb, /* 6xE1 */ nullptr, diff --git a/src/ServerState.cc b/src/ServerState.cc index 1b6a4cad..2b749cea 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -715,6 +715,15 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { } catch (const out_of_range&) { } + try { + this->secret_lottery_results.clear(); + for (const auto& it : json.get_list("SecretLotteryResultItems")) { + string data = parse_data_string(it->as_string()); + this->secret_lottery_results.emplace_back(ItemData::from_data(data)); + } + } catch (const out_of_range&) { + } + set_log_levels_from_json(json.get("LogLevels", JSON::dict())); if (!is_reload) { diff --git a/src/ServerState.hh b/src/ServerState.hh index 7da542c5..711d5203 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -115,6 +115,7 @@ struct ServerState : public std::enable_shared_from_this { // Indexed as [type][difficulty][random_choice] std::vector>> quest_F95E_results; + std::vector secret_lottery_results; std::shared_ptr ep3_tournament_index; diff --git a/system/config.example.json b/system/config.example.json index be45a9bb..de187517 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -537,6 +537,16 @@ ["00BA00", "00B400", "000D03", "00B600", "00B300", "000708", "004301", "00C900", "010136", "01028A", "010299", "010285", "010348", "010351", "01035B", "010352"], ], ], + // Reward item definitions for Secret Lottery Ticket exchange (quest opcode + // F95C, used in the Good Luck quest). + "SecretLotteryResultItems": [ + "000106", "000107", "000206", "000407", "000606", "000807", "000D01", + "001300", "002000", "002700", "002C00", "003400", "003900", "003C00", + "003E00", "004100", "004400", "004500", "004C00", "006A00", "008F07", + "009A00", "01011B", "01011C", "010129", "010129", "010130", "010131", + "010132", "010133", "010221", "010224", "010229", "01022B", "010235", + "031000", + ], // Cheat mode behavior. There are three values: // "Off": Cheat mode is disabled on the entire server. Cheat mode cannot be diff --git a/tests/config.json b/tests/config.json index 677bb20d..24157f8a 100644 --- a/tests/config.json +++ b/tests/config.json @@ -146,7 +146,7 @@ [0x40, "download-ep3", "Download", "$E$C6Quests to download\nto your Memory Card"], ], - "BlackPaperRewardItems": [ + "QuestF95ERewardItems": [ [ ["009000", "009001", "009002", "009003", "009004", "009005", "009006", "009007", "009008", "00B400", "01014E", "010307", "010341", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], ["00B900", "003400", "000901", "009002", "009007", "002C00", "002D00", "010235", "000106", "000105", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], @@ -169,4 +169,12 @@ ["00BA00", "00B400", "000D03", "00B600", "00B300", "000708", "004301", "00C900", "010136", "01028A", "010299", "010285", "010348", "010351", "01035B", "010352"], ], ], + "SecretLotteryResultItems": [ + "000106", "000107", "000206", "000407", "000606", "000807", "000D01", + "001300", "002000", "002700", "002C00", "003400", "003900", "003C00", + "003E00", "004100", "004400", "004500", "004C00", "006A00", "008F07", + "009A00", "01011B", "01011C", "010129", "010129", "010130", "010131", + "010132", "010133", "010221", "010224", "010229", "01022B", "010235", + "031000", + ], }