From 62d484472f74f135bfe9511451bc57c693d6a66b Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 14 Nov 2023 20:37:49 -0800 Subject: [PATCH] implement 6xE0 command --- TODO.md | 1 - src/CommandFormats.hh | 14 +++++++------- src/ItemData.cc | 15 +++++++++++++++ src/ItemData.hh | 1 + src/ReceiveSubcommands.cc | 32 +++++++++++++++++++++++++++++++- src/ServerState.cc | 15 +++++++++++++++ src/ServerState.hh | 3 +++ system/config.example.json | 31 +++++++++++++++++++++++++++++++ tests/config.json | 24 ++++++++++++++++++++++++ 9 files changed, 127 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index 258dad3d..e6795b95 100644 --- a/TODO.md +++ b/TODO.md @@ -49,7 +49,6 @@ - 6xCC: Exchange item for team points - 6xD8: Add S-rank weapon special - 6xDE: Good Luck quest - - 6xE0 - 6xE1: Gallon's Plan quest - Implement teams - Implement story progress flags for unlocking quests diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 564b935d..2dc6815c 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -5709,17 +5709,17 @@ struct G_BlackPaperDealPhotonCrystalExchange_BB_6xDF { G_ClientIDHeader header; } __packed__; -// 6xE0: Black Paper's Deal rewards (BB; handled by server) +// 6xE0: Request item drop from quest (BB; handled by server) // The client sends this when it executes an F95E quest opcode. -struct G_BlackPaperDealRewards_BB_6xE0 { +struct G_RequestItemDropFromQuest_BB_6xE0 { G_ClientIDHeader header; - uint8_t unknown_a1 = 0; - uint8_t unknown_a2 = 0; // argsA[0] + uint8_t floor = 0; + uint8_t type = 0; // argsA[0] uint8_t unknown_a3 = 0; - uint8_t unknown_a4 = 0; - le_float unknown_a5 = 0.0f; // argsA[1] - le_float unknown_a6 = 0.0f; // argsA[2] + uint8_t unused = 0; + le_float x = 0.0f; // argsA[1] + le_float z = 0.0f; // argsA[2] } __packed__; // 6xE1: Gallon's Plan quest (BB; handled by server) diff --git a/src/ItemData.cc b/src/ItemData.cc index c28c2308..ab5e26f8 100644 --- a/src/ItemData.cc +++ b/src/ItemData.cc @@ -585,6 +585,21 @@ bool ItemData::compare_for_sort(const ItemData& a, const ItemData& b) { return false; } +ItemData ItemData::from_data(const string& data) { + if (data.size() > 0x10) { + throw runtime_error("data is too long"); + } + + ItemData ret; + for (size_t z = 0; z < min(data.size(), 12); z++) { + ret.data1[z] = data[z]; + } + for (size_t z = 12; z < min(data.size(), 16); z++) { + ret.data2[z - 12] = data[z]; + } + return ret; +} + string ItemData::hex() const { return string_printf("%02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX (%08" PRIX32 ") %02hhX%02hhX%02hhX%02hhX", this->data1[0], this->data1[1], this->data1[2], this->data1[3], diff --git a/src/ItemData.hh b/src/ItemData.hh index 5d5a3bd6..f3c79e29 100644 --- a/src/ItemData.hh +++ b/src/ItemData.hh @@ -111,6 +111,7 @@ struct ItemData { // 0x14 bytes void clear(); + static ItemData from_data(const std::string& data); std::string hex() const; uint32_t primary_identifier() const; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 1c351ce9..060abddf 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2286,6 +2286,36 @@ static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t } } +static void on_quest_F95E_result_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)) { + const auto& cmd = check_size_t(data, size); + auto s = c->require_server_state(); + + size_t count = (cmd.type > 0x03) ? 1 : (l->difficulty + 1); + for (size_t z = 0; z < count; z++) { + const auto& results = s->quest_F95E_results.at(cmd.type).at(l->difficulty); + if (results.empty()) { + throw runtime_error("invalid result type"); + } + ItemData item = (results.size() == 1) ? results[0] : results[random_object() % results.size()]; + if (item.data1[0] == 0x04) { // Meseta + // TODO: What is the right amount of Meseta to use here? Presumably it + // should be random within a certain range, but it's not obvious what + // that range should be. + item.data2d = 100; + } else if (item.data1[0] == 0x00) { + item.data1[4] |= 0x80; // Unidentified + } + + item.id = l->generate_item_id(0xFF); + l->add_item(item, cmd.floor, cmd.x, cmd.z); + + send_drop_stacked_item(l, item, cmd.floor, cmd.x, cmd.z); + } + } +} + static void on_momoka_item_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)) { @@ -2603,7 +2633,7 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 6xDD */ nullptr, /* 6xDE */ nullptr, /* 6xDF */ on_photon_crystal_exchange_bb, - /* 6xE0 */ nullptr, + /* 6xE0 */ on_quest_F95E_result_bb, /* 6xE1 */ nullptr, /* 6xE2 */ nullptr, /* 6xE3 */ nullptr, diff --git a/src/ServerState.cc b/src/ServerState.cc index d906f941..1b6a4cad 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -700,6 +700,21 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { } } + try { + this->quest_F95E_results.clear(); + for (const auto& type_it : json.get_list("QuestF95ERewardItems")) { + auto& type_res = this->quest_F95E_results.emplace_back(); + for (const auto& difficulty_it : type_it->as_list()) { + auto& difficulty_res = type_res.emplace_back(); + for (const auto& item_it : difficulty_it->as_list()) { + string data = parse_data_string(item_it->as_string()); + difficulty_res.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 ec3fabea..7da542c5 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -113,6 +113,9 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr item_name_index; std::shared_ptr word_select_table; + // Indexed as [type][difficulty][random_choice] + std::vector>> quest_F95E_results; + std::shared_ptr ep3_tournament_index; uint16_t ep3_card_auction_points; diff --git a/system/config.example.json b/system/config.example.json index 9323e1b4..be45a9bb 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -507,6 +507,37 @@ [0x40, "download-ep3", "Download", "$E$C6Quests to download\nto your Memory Card"], ], + // Quest reward item definitions for opcode F95E (used in Black Paper's + // Dangerous Deal 1 and 2). This list is indexed as [reward_type][difficulty]. + // Reward types 0, 1, 2, and 4 are used by vanilla PSOBB; other reward types + // can be specified when using the F95E quest opcode in custom quests. Rewards + // are chosen uniformly at random from the list corresponding to the reward + // location and difficulty level. All items in these lists must be hex item + // codes (1-16 bytes); textual descriptions are not supported here. + "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"], + ["00B600", "008A01", "001001", "001002", "001003", "001004", "001005", "001006", "002700", "000107", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00B700", "001001", "001002", "001003", "001004", "001005", "001006", "002900", "008A00", "008A02", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ], [ + ["01028B", "010228", "010134", "010303", "01030B", "031807", "005500", "010329", "01032F", "01032C", "010323", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["01028C", "010215", "01028A", "010140", "010344", "010346", "010345", "010347", "031807", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00CB00", "003A00", "008C02", "01022B", "005000", "000B06", "000A06", "000A04", "005500", "002300", "003B00", "031807", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["005100", "010352", "010320", "01033E", "010229", "031807", "000B04", "000A06", "005600", "003B00", "002300", "000A05", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ], [ + ["010132", "002F01", "00B300", "005E00", "000E02", "002E00", "009500", "009A00", "002F00", "01031B", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00C000", "00D200", "008D00", "01012E", "008B00", "000907", "004E00", "006D00", "001500", "008B02", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00AA00", "010141", "010151", "010223", "003F00", "004100", "000507", "000506", "000505", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00AF00", "004300", "010351", "00CD00", "009900", "006C00", "004500", "006B00", "001200", "006500", "010229", "001300", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ], [ + ["00BA00", "000D03", "004301", "000708", "004201", "00C900", "031000", "010295", "01028F", "010291"], + ["00BB00", "000D03", "00B700", "004201", "000708", "00C900", "010136", "01028A", "010299", "010351", "01035B", "010352", "031000", "03180A"], + ["00BA00", "00B400", "000D03", "00B600", "00B300", "000708", "004301", "00C900", "010136", "01028A", "010299", "010285", "010348", "010351", "01035B", "010352", "031000"], + ["00BA00", "00B400", "000D03", "00B600", "00B300", "000708", "004301", "00C900", "010136", "01028A", "010299", "010285", "010348", "010351", "01035B", "010352"], + ], + ], + // Cheat mode behavior. There are three values: // "Off": Cheat mode is disabled on the entire server. Cheat mode cannot be // enabled in games, and the $cheat command does nothing. This also diff --git a/tests/config.json b/tests/config.json index b4939a0e..677bb20d 100644 --- a/tests/config.json +++ b/tests/config.json @@ -145,4 +145,28 @@ [0x40, "download-ep3-trial", "Trial Download", "$E$C6Quests to download\nto your Memory Card\nfrom Episode 3\nTrial Edition"], [0x40, "download-ep3", "Download", "$E$C6Quests to download\nto your Memory Card"], ], + + "BlackPaperRewardItems": [ + [ + ["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"], + ["00B600", "008A01", "001001", "001002", "001003", "001004", "001005", "001006", "002700", "000107", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00B700", "001001", "001002", "001003", "001004", "001005", "001006", "002900", "008A00", "008A02", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ], [ + ["01028B", "010228", "010134", "010303", "01030B", "031807", "005500", "010329", "01032F", "01032C", "010323", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["01028C", "010215", "01028A", "010140", "010344", "010346", "010345", "010347", "031807", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00CB00", "003A00", "008C02", "01022B", "005000", "000B06", "000A06", "000A04", "005500", "002300", "003B00", "031807", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["005100", "010352", "010320", "01033E", "010229", "031807", "000B04", "000A06", "005600", "003B00", "002300", "000A05", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ], [ + ["010132", "002F01", "00B300", "005E00", "000E02", "002E00", "009500", "009A00", "002F00", "01031B", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00C000", "00D200", "008D00", "01012E", "008B00", "000907", "004E00", "006D00", "001500", "008B02", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00AA00", "010141", "010151", "010223", "003F00", "004100", "000507", "000506", "000505", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ["00AF00", "004300", "010351", "00CD00", "009900", "006C00", "004500", "006B00", "001200", "006500", "010229", "001300", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000", "040000"], + ], [ + ["00BA00", "000D03", "004301", "000708", "004201", "00C900", "031000", "010295", "01028F", "010291"], + ["00BB00", "000D03", "00B700", "004201", "000708", "00C900", "010136", "01028A", "010299", "010351", "01035B", "010352", "031000", "03180A"], + ["00BA00", "00B400", "000D03", "00B600", "00B300", "000708", "004301", "00C900", "010136", "01028A", "010299", "010285", "010348", "010351", "01035B", "010352", "031000"], + ["00BA00", "00B400", "000D03", "00B600", "00B300", "000708", "004301", "00C900", "010136", "01028A", "010299", "010285", "010348", "010351", "01035B", "010352"], + ], + ], }