make quest item exchange implementations more complete
This commit is contained in:
+23
-20
@@ -814,19 +814,20 @@ struct SC_GameGuardCheck_BB_0022 {
|
|||||||
// used in the case described above; there are no other conditions that cause it to be sent.
|
// used in the case described above; there are no other conditions that cause it to be sent.
|
||||||
|
|
||||||
// 23 (S->C): Momoka Item Exchange result (BB)
|
// 23 (S->C): Momoka Item Exchange result (BB)
|
||||||
// Sent in response to a 6xD9 command from the client. This command is not valid on BB Trial Edition. header.flag
|
// This command is not valid on BB Trial Edition.
|
||||||
// indicates the result code:
|
// Sent in response to a 6xD9 command from the client. header.flag indicates the result code:
|
||||||
// 0 = success
|
// 0 = success
|
||||||
// 1 = currency item not found
|
// 1 = currency item not found
|
||||||
// 2 = inventory is full
|
// 2 = inventory is full
|
||||||
// Anything else = generic failure
|
// Anything else = generic failure
|
||||||
|
|
||||||
// 24 (S->C): Secret Lottery Ticket exchange result (BB)
|
// 24 (S->C): Secret Lottery Ticket exchange result (BB)
|
||||||
|
// This command is not valid on BB Trial Edition.
|
||||||
// Sent in response to a 6xDE command from the client. The client sets 8 sequential quest registers, starting with
|
// Sent in response to a 6xDE command from the client. The client sets 8 sequential quest registers, starting with
|
||||||
// start_reg_num, to the values specified in reg_values. Then it starts a new quest thread at the specified label.
|
// start_reg_num, to the values specified in reg_values. Then it starts a new quest thread at the specified label.
|
||||||
// header.flag indicates whether the client had any Secret Lottery Tickets in their inventory (and hence could
|
// According to logs from Sega's servers, header.flag indicates whether the client had any Secret Lottery Tickets in
|
||||||
// participate): 0 means success, 1 means failure. However, this value is unused by the client.
|
// their inventory (and hence could participate): 0 means success, 1 means failure. However, this value is unused by
|
||||||
// This command is not valid on BB Trial Edition.
|
// the client.
|
||||||
|
|
||||||
struct S_ExchangeSecretLotteryTicketResult_BB_24 {
|
struct S_ExchangeSecretLotteryTicketResult_BB_24 {
|
||||||
le_uint16_t label = 0;
|
le_uint16_t label = 0;
|
||||||
@@ -836,16 +837,17 @@ struct S_ExchangeSecretLotteryTicketResult_BB_24 {
|
|||||||
} __packed_ws__(S_ExchangeSecretLotteryTicketResult_BB_24, 0x24);
|
} __packed_ws__(S_ExchangeSecretLotteryTicketResult_BB_24, 0x24);
|
||||||
|
|
||||||
// 25 (S->C): Gallon's Plan result (BB)
|
// 25 (S->C): Gallon's Plan result (BB)
|
||||||
// Sent in response to a 6xE1 command from the client. The client sets the quest registers reg_num1 and reg_num2 to
|
|
||||||
// value1 and value2 respectively, then starts a new quest thread at the specified label.
|
|
||||||
// This command is not valid on BB Trial Edition.
|
// This command is not valid on BB Trial Edition.
|
||||||
|
// Sent in response to a 6xE1 command from the client. The client sets the quest registers result_code_reg and
|
||||||
|
// result_index_reg to result_code_value and result_index_value respectively, then starts a new quest thread at the
|
||||||
|
// specified label.
|
||||||
|
|
||||||
struct S_GallonPlanResult_BB_25 {
|
struct S_GallonPlanResult_BB_25 {
|
||||||
le_uint16_t label = 0;
|
le_uint16_t label = 0;
|
||||||
uint8_t reg_num1 = 0;
|
uint8_t result_code_reg = 0;
|
||||||
uint8_t reg_num2 = 0;
|
uint8_t result_index_reg = 0;
|
||||||
uint8_t value1 = 0;
|
uint8_t result_code_value = 0; // See description of F95F (bb_exchange_pt) in QuestScript.cc for values here
|
||||||
uint8_t value2 = 0;
|
uint8_t result_index_value = 0;
|
||||||
le_uint16_t unused = 0;
|
le_uint16_t unused = 0;
|
||||||
} __packed_ws__(S_GallonPlanResult_BB_25, 8);
|
} __packed_ws__(S_GallonPlanResult_BB_25, 8);
|
||||||
|
|
||||||
@@ -3969,7 +3971,7 @@ struct G_FeedMag_6x28 {
|
|||||||
le_uint32_t fed_item_id = 0;
|
le_uint32_t fed_item_id = 0;
|
||||||
} __packed_ws__(G_FeedMag_6x28, 0x0C);
|
} __packed_ws__(G_FeedMag_6x28, 0x0C);
|
||||||
|
|
||||||
// 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) (protected on GC NTE/V3 but not on V4)
|
// 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) (protected on GC NTE/V3 but not on BB)
|
||||||
// This subcommand is also used for reducing the size of stacks - if amount is less than the stack count, the item is
|
// This subcommand is also used for reducing the size of stacks - if amount is less than the stack count, the item is
|
||||||
// not deleted and its ID remains valid.
|
// not deleted and its ID remains valid.
|
||||||
|
|
||||||
@@ -5957,16 +5959,17 @@ struct G_UpgradeWeaponAttribute_BB_6xDA {
|
|||||||
le_uint16_t failure_label = 0; // labelH
|
le_uint16_t failure_label = 0; // labelH
|
||||||
} __packed_ws__(G_UpgradeWeaponAttribute_BB_6xDA, 0x2C);
|
} __packed_ws__(G_UpgradeWeaponAttribute_BB_6xDA, 0x2C);
|
||||||
|
|
||||||
// 6xDB: Exchange item in quest (BB)
|
// 6xDB: Extended delete inventory item (BB)
|
||||||
|
|
||||||
struct G_ExchangeItemInQuest_BB_6xDB {
|
struct G_ExtendedDeleteInventoryItem_BB_6xDB {
|
||||||
G_ClientIDHeader header;
|
G_ClientIDHeader header;
|
||||||
// If this is 0, the command is identical to 6x29. If this is 1, a function similar to find_item_by_id is called
|
// If exclude_wrapped is 0, the command is identical to 6x29; if this is 1, the command won't delete any item which
|
||||||
// instead of find_item_by_id, but I don't yet know what exactly the logic differences are (TODO).
|
// is wrapped. This seems like an odd feature; shouldn't we expect that the server and client have the same item
|
||||||
le_uint32_t unknown_a1 = 0;
|
// state, so the server would already know if the item was wrapped or not before sending this?
|
||||||
|
le_uint32_t exclude_wrapped = 0;
|
||||||
le_uint32_t item_id = 0;
|
le_uint32_t item_id = 0;
|
||||||
le_uint32_t amount = 0;
|
le_uint32_t amount = 0;
|
||||||
} __packed_ws__(G_ExchangeItemInQuest_BB_6xDB, 0x10);
|
} __packed_ws__(G_ExtendedDeleteInventoryItem_BB_6xDB, 0x10);
|
||||||
|
|
||||||
// 6xDC: Saint-Milion/Shambertin/Kondrieu boss actions (BB)
|
// 6xDC: Saint-Milion/Shambertin/Kondrieu boss actions (BB)
|
||||||
|
|
||||||
@@ -6027,8 +6030,8 @@ struct G_RequestItemDropFromQuest_BB_6xE0 {
|
|||||||
|
|
||||||
struct G_ExchangePhotonTickets_BB_6xE1 {
|
struct G_ExchangePhotonTickets_BB_6xE1 {
|
||||||
G_ClientIDHeader header;
|
G_ClientIDHeader header;
|
||||||
uint8_t unknown_a1 = 0; // valueA
|
uint8_t result_code_reg = 0; // valueA
|
||||||
uint8_t unknown_a2 = 0; // valueB
|
uint8_t result_index_reg = 0; // valueB
|
||||||
uint8_t result_index = 0; // valueC
|
uint8_t result_index = 0; // valueC
|
||||||
uint8_t unused = 0;
|
uint8_t unused = 0;
|
||||||
le_uint16_t success_label = 0; // valueD
|
le_uint16_t success_label = 0; // valueD
|
||||||
|
|||||||
+7
-3
@@ -2587,12 +2587,16 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
|
|||||||
{0xF95E, "bb_box_create_bp", "BB_box_create_BP", {I32, FLOAT32, FLOAT32}, F_V4 | F_ARGS},
|
{0xF95E, "bb_box_create_bp", "BB_box_create_BP", {I32, FLOAT32, FLOAT32}, F_V4 | F_ARGS},
|
||||||
|
|
||||||
// Requests an exchange of Photon Tickets for items. Sends 6xE1.
|
// Requests an exchange of Photon Tickets for items. Sends 6xE1.
|
||||||
// valueA = unknown_a1
|
// regA = result code reg (set to result code upon server response); the result codes are:
|
||||||
// valueB = unknown_a2
|
// 0 = success
|
||||||
|
// 1 = player doesn't have enough Photon Tickets
|
||||||
|
// 2 = inventory is full
|
||||||
|
// 3, 4, or 5 = "server send error" (from Gallon's Plan script; newserv never sends these)
|
||||||
|
// regB = result index reg (set to valueC upon server response)
|
||||||
// valueC = result index (index into QuestF95FResultItems in config.json)
|
// valueC = result index (index into QuestF95FResultItems in config.json)
|
||||||
// labelD = label to call on success
|
// labelD = label to call on success
|
||||||
// labelE = label to call on failure
|
// labelE = label to call on failure
|
||||||
{0xF95F, "bb_exchange_pt", "BB_exchage_PT", {I32, I32, I32, I32, I32}, F_V4 | F_ARGS},
|
{0xF95F, "bb_exchange_pt", "BB_exchage_PT", {W_REG32, W_REG32, I32, I32, I32}, F_V4 | F_ARGS},
|
||||||
|
|
||||||
// Requests a prize from the Meseta gambling prize list. Sends 6xE2. The server responds with 6xE3, which sets the
|
// Requests a prize from the Meseta gambling prize list. Sends 6xE2. The server responds with 6xE3, which sets the
|
||||||
// <meseta_slot_prize> replacement token in message strings. The status of this can be checked with
|
// <meseta_slot_prize> replacement token in message strings. The status of this can be checked with
|
||||||
|
|||||||
+28
-28
@@ -5058,16 +5058,9 @@ static asio::awaitable<void> on_secret_lottery_ticket_exchange_bb(shared_ptr<Cli
|
|||||||
|
|
||||||
uint32_t slt_item_id = p->inventory.items[currency_index].data.id;
|
uint32_t slt_item_id = p->inventory.items[currency_index].data.id;
|
||||||
|
|
||||||
G_ExchangeItemInQuest_BB_6xDB exchange_cmd;
|
// Note: It seems Sega used 6xDB here; we use 6x29 instead.
|
||||||
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.amount = 1;
|
|
||||||
send_command_t(c, 0x60, 0x00, exchange_cmd);
|
|
||||||
|
|
||||||
p->remove_item(slt_item_id, 1, limits);
|
p->remove_item(slt_item_id, 1, limits);
|
||||||
|
send_destroy_item_to_lobby(c, slt_item_id, 1);
|
||||||
|
|
||||||
item.id = l->generate_item_id(c->lobby_client_id);
|
item.id = l->generate_item_id(c->lobby_client_id);
|
||||||
p->add_item(item, limits);
|
p->add_item(item, limits);
|
||||||
@@ -5163,30 +5156,38 @@ static asio::awaitable<void> on_quest_F95F_result_bb(shared_ptr<Client> c, Subco
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto& limits = *s->item_stack_limits(c->version());
|
const auto& limits = *s->item_stack_limits(c->version());
|
||||||
size_t index = p->inventory.find_item_by_primary_identifier(0x03100400); // Photon Ticket
|
|
||||||
auto ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, limits);
|
|
||||||
// TODO: Shouldn't we send a 6x29 here? Check if this causes desync in an actual game
|
|
||||||
|
|
||||||
G_ExchangeItemInQuest_BB_6xDB cmd_6xDB;
|
bool failed = false;
|
||||||
cmd_6xDB.header = {0xDB, 0x04, c->lobby_client_id};
|
ItemData ticket_item;
|
||||||
cmd_6xDB.unknown_a1 = 1;
|
try {
|
||||||
cmd_6xDB.item_id = ticket_item.id;
|
size_t index = p->inventory.find_item_by_primary_identifier(0x03100400); // Photon Ticket
|
||||||
cmd_6xDB.amount = result.first;
|
ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, limits);
|
||||||
send_command_t(c, 0x60, 0x00, cmd_6xDB);
|
} catch (const out_of_range&) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
if (failed) {
|
||||||
|
send_gallon_plan_result(c, cmd.failure_label, cmd.result_code_reg, 1, cmd.result_index_reg, cmd.result_index);
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
ItemData new_item = result.second;
|
ItemData new_item = result.second;
|
||||||
|
try {
|
||||||
new_item.enforce_stack_size_limits(limits);
|
new_item.enforce_stack_size_limits(limits);
|
||||||
new_item.id = l->generate_item_id(c->lobby_client_id);
|
new_item.id = l->generate_item_id(c->lobby_client_id);
|
||||||
p->add_item(new_item, limits);
|
p->add_item(new_item, limits);
|
||||||
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
|
} catch (const out_of_range&) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
if (failed) {
|
||||||
|
p->add_item(ticket_item, limits);
|
||||||
|
send_gallon_plan_result(c, cmd.failure_label, cmd.result_code_reg, 2, cmd.result_index_reg, cmd.result_index);
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
S_GallonPlanResult_BB_25 out_cmd;
|
// Note: It seems Sega used 6xDB here; we use 6x29 instead.
|
||||||
out_cmd.label = cmd.success_label;
|
send_destroy_item_to_lobby(c, ticket_item.id, result.first);
|
||||||
out_cmd.reg_num1 = 0x3C;
|
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
|
||||||
out_cmd.reg_num2 = 0x08;
|
send_gallon_plan_result(c, cmd.success_label, cmd.result_code_reg, 0, cmd.result_index_reg, cmd.result_index);
|
||||||
out_cmd.value1 = 0x00;
|
|
||||||
out_cmd.value2 = cmd.result_index;
|
|
||||||
send_command_t(c, 0x25, 0x00, out_cmd);
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5325,8 +5326,7 @@ static asio::awaitable<void> on_momoka_item_exchange_bb(shared_ptr<Client> c, Su
|
|||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
G_ExchangeItemInQuest_BB_6xDB cmd_6xDB = {{0xDB, 0x04, c->lobby_client_id}, 1, found_item.id, 1};
|
// Note: It seems Sega used 6xDB here; we use 6x29 instead.
|
||||||
send_command_t(c, 0x60, 0x00, cmd_6xDB);
|
|
||||||
send_destroy_item_to_lobby(c, found_item.id, 1);
|
send_destroy_item_to_lobby(c, found_item.id, 1);
|
||||||
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
|
send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item);
|
||||||
send_command(c, 0x23, 0x00);
|
send_command(c, 0x23, 0x00);
|
||||||
|
|||||||
@@ -3307,6 +3307,22 @@ void send_quest_function_call(shared_ptr<Client> c, uint16_t label) {
|
|||||||
send_quest_function_call(c->channel, label);
|
send_quest_function_call(c->channel, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void send_gallon_plan_result(
|
||||||
|
shared_ptr<Client> c,
|
||||||
|
uint16_t label,
|
||||||
|
uint8_t result_code_reg,
|
||||||
|
uint32_t result_code,
|
||||||
|
uint8_t result_index_reg,
|
||||||
|
uint32_t result_index) {
|
||||||
|
S_GallonPlanResult_BB_25 cmd;
|
||||||
|
cmd.label = label;
|
||||||
|
cmd.result_code_reg = result_code_reg;
|
||||||
|
cmd.result_index_reg = result_index_reg;
|
||||||
|
cmd.result_code_value = result_code;
|
||||||
|
cmd.result_index_value = result_index;
|
||||||
|
send_command_t(c, 0x25, 0x00, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
void send_ep3_card_list_update(shared_ptr<Client> c) {
|
void send_ep3_card_list_update(shared_ptr<Client> c) {
|
||||||
if (!c->check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) {
|
if (!c->check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|||||||
@@ -380,6 +380,14 @@ void send_rare_enemy_index_list(std::shared_ptr<Client> c, const std::vector<siz
|
|||||||
void send_quest_function_call(std::shared_ptr<Channel> ch, uint16_t label);
|
void send_quest_function_call(std::shared_ptr<Channel> ch, uint16_t label);
|
||||||
void send_quest_function_call(std::shared_ptr<Client> c, uint16_t label);
|
void send_quest_function_call(std::shared_ptr<Client> c, uint16_t label);
|
||||||
|
|
||||||
|
void send_gallon_plan_result(
|
||||||
|
std::shared_ptr<Client> c,
|
||||||
|
uint16_t label,
|
||||||
|
uint8_t result_code_reg,
|
||||||
|
uint32_t result_code,
|
||||||
|
uint8_t result_index_reg,
|
||||||
|
uint32_t result_index);
|
||||||
|
|
||||||
void send_ep3_card_list_update(std::shared_ptr<Client> c);
|
void send_ep3_card_list_update(std::shared_ptr<Client> c);
|
||||||
void send_ep3_media_update(
|
void send_ep3_media_update(
|
||||||
std::shared_ptr<Client> c, uint32_t type, uint32_t which, const std::string& compressed_data);
|
std::shared_ptr<Client> c, uint32_t type, uint32_t which, const std::string& compressed_data);
|
||||||
|
|||||||
Reference in New Issue
Block a user