diff --git a/TODO.md b/TODO.md index badb469d..72a92578 100644 --- a/TODO.md +++ b/TODO.md @@ -29,7 +29,6 @@ ## PSOBB - Fix some edge cases on the BB proxy server (e.g. Change Ship) -- Implement less-common subcommands - - 6xD8: Add S-rank weapon special +- Test all quest item subcommands - Check if Commander Blade effect works and implement it if not - Implement story progress flags for unlocking quests diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index d36aa99f..842a5fd6 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -5715,10 +5715,10 @@ struct G_PaganiniPhotonDropExchange_BB_6xD7 { struct G_AddSRankWeaponSpecial_BB_6xD8 { G_ClientIDHeader header; ItemData unknown_a1; // Only data1[0]-[2] are used - le_uint32_t unknown_a2 = 0; - le_uint32_t unknown_a3 = 0; - le_uint16_t unknown_a4 = 0; - le_uint16_t unknown_a5 = 0; + le_uint32_t item_id = 0; + le_uint32_t special_type = 0; + le_uint16_t success_function_id = 0; + le_uint16_t failure_function_id = 0; } __packed__; // 6xD9: Momoka item exchange (BB; handled by server) diff --git a/src/QuestScript.cc b/src/QuestScript.cc index eef1a577..3fe2eb95 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -823,7 +823,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF953, "bb_swap_item", {INT32, INT32, INT32, INT32, INT32, INT32, SCRIPT16, SCRIPT16}, F_V4 | F_ARGS}, // Sends 6xD5 {0xF954, "bb_check_wrap", {INT32, REG}, F_V4 | F_ARGS}, {0xF955, "bb_exchange_pd_item", {INT32, INT32, INT32, LABEL16, LABEL16}, F_V4 | F_ARGS}, // Sends 6xD7 - {0xF956, "bb_exchange_pd_srank", {INT32, INT32, INT32, INT32, INT32, INT32, INT32}, F_V4 | F_ARGS}, // Sends 6xD8 + {0xF956, "bb_exchange_pd_srank", {INT32, INT32, INT32, INT32, INT32, LABEL16, LABEL16}, F_V4 | F_ARGS}, // Sends 6xD8; argsA[0] is item ID; argsA[1]-[3] are item.data1[0]-[2]; argsA[4] is special type; argsA[5] is success label; argsA[6] is failure label {0xF957, "bb_exchange_pd_percent", {INT32, INT32, INT32, INT32, INT32, INT32, LABEL16, LABEL16}, F_V4 | F_ARGS}, // Sends 6xDA {0xF958, "bb_exchange_ps_percent", {INT32, INT32, INT32, INT32, INT32, INT32, LABEL16, LABEL16}, F_V4 | F_ARGS}, // Sends 6xDA {0xF959, "bb_set_ep4_boss_can_escape", {INT32}, F_V4 | F_ARGS}, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 838c588f..2bd1214e 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2531,7 +2531,7 @@ static void on_wrap_item_bb(shared_ptr c, uint8_t, uint8_t, const void* } } -static void on_photon_drop_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { +static void on_photon_drop_exchange_for_item_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 == Version::BB_V4)) { const auto& cmd = check_size_t(data, size); @@ -2553,7 +2553,41 @@ static void on_photon_drop_exchange_bb(shared_ptr c, uint8_t, uint8_t, c send_quest_function_call(c, cmd.success_function_id); } catch (const exception& e) { - c->log.warning("Quest Photon Drop exchange failed: %s", e.what()); + c->log.warning("Quest Photon Drop exchange for item failed: %s", e.what()); + send_quest_function_call(c, cmd.failure_function_id); + } + } +} + +static void on_photon_drop_exchange_for_s_rank_special_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 == Version::BB_V4)) { + const auto& cmd = check_size_t(data, size); + + try { + auto p = c->game_data.character(); + + static const array costs({60, 60, 20, 20, 30, 30, 30, 50, 40, 50, 40, 40, 50, 40, 40, 40}); + uint8_t cost = costs.at(cmd.special_type); + + size_t payment_item_index = p->inventory.find_item_by_primary_identifier(0x031000); + // Ensure weapon exists before removing PDs, so inventory state will be + // consistent in case of error + p->inventory.find_item(cmd.item_id); + + auto payment_item = p->remove_item(p->inventory.items[payment_item_index].data.id, cost, false); + send_destroy_item(c, payment_item.id, cost); + + auto item = p->remove_item(cmd.item_id, 1, false); + send_destroy_item(c, item.id, cost); + item.data1[2] = cmd.special_type; + p->add_item(item); + send_create_inventory_item(c, item); + + send_quest_function_call(c, cmd.success_function_id); + + } catch (const exception& e) { + c->log.warning("Quest Photon Drop exchange for S-rank special failed: %s", e.what()); send_quest_function_call(c, cmd.failure_function_id); } } @@ -2622,7 +2656,6 @@ static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t size_t index = p->inventory.find_item_by_primary_identifier(0x031002); auto item = p->remove_item(p->inventory.items[index].data.id, 1, false); send_destroy_item(c, item.id, 1); - // TODO: Should we disable drops here? } } @@ -3008,8 +3041,8 @@ SubcommandDefinition subcommand_definitions[0x100] = { /* 6xD4 */ {0x00, 0x00, 0xD4, nullptr}, /* 6xD5 */ {0x00, 0x00, 0xD5, on_quest_exchange_item_bb}, /* 6xD6 */ {0x00, 0x00, 0xD6, on_wrap_item_bb}, - /* 6xD7 */ {0x00, 0x00, 0xD7, on_photon_drop_exchange_bb}, - /* 6xD8 */ {0x00, 0x00, 0xD8, nullptr}, + /* 6xD7 */ {0x00, 0x00, 0xD7, on_photon_drop_exchange_for_item_bb}, + /* 6xD8 */ {0x00, 0x00, 0xD8, on_photon_drop_exchange_for_s_rank_special_bb}, /* 6xD9 */ {0x00, 0x00, 0xD9, on_momoka_item_exchange_bb}, /* 6xDA */ {0x00, 0x00, 0xDA, on_upgrade_weapon_attribute_bb}, /* 6xDB */ {0x00, 0x00, 0xDB, nullptr},