implement 6xE1

This commit is contained in:
Martin Michelsen
2023-11-16 15:43:53 -08:00
parent c84d4b134f
commit 27608d9c11
7 changed files with 92 additions and 28 deletions
+3 -4
View File
@@ -35,10 +35,9 @@
- Find any remaining mismatches in enemy indexes / experience
- Fix some edge cases on the BB proxy server (e.g. Change Ship)
- Implement less-common subcommands
- 6xAC: Sort inventory
- 6xC1, 6xC2, 6xCD, 6xCE
- 6xCC: Exchange item for team points
- 6xD8: Add S-rank weapon special
- 6xE1: Gallon's Plan quest
- Implement teams
- All EA subcommands
- 6xC1, 6xC2, 6xCD, 6xCE: Team invites/administration
- 6xCC: Exchange item for team points
- Implement story progress flags for unlocking quests
+15 -14
View File
@@ -858,8 +858,9 @@ struct SC_GameGuardCheck_BB_0022 {
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;
le_uint16_t function_id = 0;
uint8_t start_index = 0;
uint8_t unused = 0;
parray<le_uint32_t, 8> unknown_a3;
} __packed__;
@@ -867,7 +868,7 @@ struct S_ExchangeSecretLotteryTicketResult_BB_24 {
// Sent in response to a 6xE1 command from the client.
struct S_GallonPlanResult_BB_25 {
le_uint16_t unknown_a1 = 0;
le_uint16_t function_id = 0;
uint8_t offset1 = 0;
uint8_t offset2 = 0;
uint8_t value1 = 0;
@@ -5695,7 +5696,7 @@ struct G_ExchangeItemInQuest_BB_6xDB {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t item_id = 0;
le_uint32_t unknown_a3 = 0;
le_uint32_t amount = 0;
} __packed__;
// 6xDC: Saint-Million boss actions (BB)
@@ -5719,9 +5720,9 @@ struct G_SetEXPMultiplier_BB_6xDD {
struct G_ExchangeSecretLotteryTicket_BB_6xDE {
G_ClientIDHeader header;
uint8_t unknown_a1 = 0;
uint8_t unknown_a2 = 0;
le_uint16_t unknown_a3 = 0;
uint8_t index = 0;
uint8_t function_id1 = 0;
le_uint16_t function_id2 = 0;
} __packed__;
// 6xDF: Exchange Photon Crystals (BB; handled by server)
@@ -5744,17 +5745,17 @@ struct G_RequestItemDropFromQuest_BB_6xE0 {
le_float z = 0.0f; // argsA[2]
} __packed__;
// 6xE1: Gallon's Plan quest (BB; handled by server)
// 6xE1: Exchange Photon Tickets (BB; handled by server)
// The client sends this when it executes an F95F quest opcode.
struct G_GallonsPlanQuestActions_BB_6xE1 {
struct G_ExchangePhotonTickets_BB_6xE1 {
G_ClientIDHeader header;
uint8_t unknown_a1 = 0;
uint8_t unknown_a2 = 0;
uint8_t unknown_a3 = 0;
uint8_t unknown_a1 = 0; // argsA[0]
uint8_t unknown_a2 = 0; // argsA[1]
uint8_t result_index = 0; // argsA[2]
uint8_t unused = 0;
le_uint16_t unknown_a4 = 0;
le_uint16_t unknown_a5 = 0;
le_uint16_t function_id1 = 0; // argsA[3]
le_uint16_t unknown_a5 = 0; // argsA[4]
} __packed__;
// 6xE2: Coren actions (BB)
+43 -4
View File
@@ -2344,7 +2344,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
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;
exchange_cmd.amount = 1;
send_command_t(c, 0x60, 0x00, exchange_cmd);
send_destroy_item(c, slt_item_id, 1);
@@ -2358,8 +2358,8 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
}
S_ExchangeSecretLotteryTicketResult_BB_24 out_cmd;
out_cmd.unknown_a1 = cmd.unknown_a1;
out_cmd.unknown_a2 = cmd.unknown_a2;
out_cmd.start_index = cmd.index;
out_cmd.function_id = cmd.function_id1;
if (s->secret_lottery_results.empty()) {
out_cmd.unknown_a3.clear(0);
} else if (s->secret_lottery_results.size() == 1) {
@@ -2415,6 +2415,45 @@ static void on_quest_F95E_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
}
}
static void on_quest_F95F_result_bb(shared_ptr<Client> 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<G_ExchangePhotonTickets_BB_6xE1>(data, size);
auto s = c->require_server_state();
auto p = c->game_data.character();
const auto& result = s->quest_F95F_results.at(cmd.result_index);
if (result.second.empty()) {
throw runtime_error("invalid result index");
}
size_t index = p->inventory.find_item_by_primary_identifier(0x031004); // Photon Ticket
auto ticket_item = p->remove_item(p->inventory.items[index].data.id, result.first, false);
// TODO: Shouldn't we send a 6x29 here? Check if this causes desync in an
// actual game
G_ExchangeItemInQuest_BB_6xDB cmd_6xDB;
cmd_6xDB.header = {0xDB, 0x04, c->lobby_client_id};
cmd_6xDB.unknown_a1 = 1;
cmd_6xDB.item_id = ticket_item.id;
cmd_6xDB.amount = result.first;
send_command_t(c, 0x60, 0x00, cmd_6xDB);
ItemData new_item = result.second;
new_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(new_item);
send_create_inventory_item(c, new_item);
S_GallonPlanResult_BB_25 out_cmd;
out_cmd.function_id = cmd.function_id1;
out_cmd.offset1 = 0x3C;
out_cmd.offset2 = 0x08;
out_cmd.value1 = 0x00;
out_cmd.value2 = cmd.result_index;
send_command_t(c, 0x25, 0x00, out_cmd);
}
}
static void on_momoka_item_exchange_bb(shared_ptr<Client> 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)) {
@@ -2733,7 +2772,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 6xDE */ on_secret_lottery_ticket_exchange_bb,
/* 6xDF */ on_photon_crystal_exchange_bb,
/* 6xE0 */ on_quest_F95E_result_bb,
/* 6xE1 */ nullptr,
/* 6xE1 */ on_quest_F95F_result_bb,
/* 6xE2 */ nullptr,
/* 6xE3 */ nullptr,
/* 6xE4 */ nullptr,
+11 -2
View File
@@ -703,7 +703,7 @@ 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")) {
for (const auto& type_it : json.get_list("QuestF95EResultItems")) {
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();
@@ -715,7 +715,16 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
}
} catch (const out_of_range&) {
}
try {
this->quest_F95F_results.clear();
for (const auto& it : json.get_list("QuestF95FResultItems")) {
auto& list = it->as_list();
size_t price = list.at(0)->as_int();
string data = parse_data_string(list.at(1)->as_string());
this->quest_F95F_results.emplace_back(make_pair(price, ItemData::from_data(data)));
}
} catch (const out_of_range&) {
}
try {
this->secret_lottery_results.clear();
for (const auto& it : json.get_list("SecretLotteryResultItems")) {
+1
View File
@@ -115,6 +115,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
// Indexed as [type][difficulty][random_choice]
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
std::vector<ItemData> secret_lottery_results;
uint16_t bb_global_exp_multiplier;
+12 -3
View File
@@ -507,14 +507,14 @@
[0x40, "download-ep3", "Download", "$E$C6Quests to download\nto your Memory Card"],
],
// Quest reward item definitions for opcode F95E (used in Black Paper's
// Quest result 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": [
"QuestF95EResultItems": [
[
["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"],
@@ -537,7 +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
// Results for quest opcode F95F (used in Gallon's Plan). This list is indexed
// by type (which is the third argument to the opcode). The entries here are
// [num_photon_tickets, item_hex].
"QuestF95FResultItems": [
[0, ""], // Unused
[10, "00D500"],
[15, "000A07"],
[20, "010157"],
],
// Result item definitions for Secret Lottery Ticket exchange (quest opcode
// F95C, used in the Good Luck quest).
"SecretLotteryResultItems": [
"000106", "000107", "000206", "000407", "000606", "000807", "000D01",
+7 -1
View File
@@ -146,7 +146,7 @@
[0x40, "download-ep3", "Download", "$E$C6Quests to download\nto your Memory Card"],
],
"QuestF95ERewardItems": [
"QuestF95EResultItems": [
[
["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,6 +169,12 @@
["00BA00", "00B400", "000D03", "00B600", "00B300", "000708", "004301", "00C900", "010136", "01028A", "010299", "010285", "010348", "010351", "01035B", "010352"],
],
],
"QuestF95FResultItems": [
[0, ""], // Unused
[10, "00D500"],
[15, "000A07"],
[20, "010157"],
],
"SecretLotteryResultItems": [
"000106", "000107", "000206", "000407", "000606", "000807", "000D01",
"001300", "002000", "002700", "002C00", "003400", "003900", "003C00",