diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 54c02f34..3ea094eb 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -871,24 +871,31 @@ struct SC_GameCardCheck_BB_0022 { // Command 0122 uses a 4-byte challenge sent in the header.flag field instead. // This version of the command has no other arguments. -// 23 (S->C): Unknown (BB) -// header.flag is used, but the command has no other arguments. +// 23 (S->C): Momoka Item Exchange result (BB) +// Sent in response to a 6xD9 command from the client. +// header.flag indicates if an item was exchanged: 0 means success, 1 means +// failure. -// 24 (S->C): Unknown (BB) +// 24 (S->C): Good Luck 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_Unknown_BB_24 { +struct S_GoodLuckResult_BB_24 { le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; - parray values; + parray unknown_a3; } __packed__; -// 25 (S->C): Unknown (BB) +// 25 (S->C): Gallon's Plan result (BB) +// Sent in response to a 6xE1 command from the client. struct S_Unknown_BB_25 { le_uint16_t unknown_a1 = 0; uint8_t offset1 = 0; - uint8_t value1 = 0; uint8_t offset2 = 0; + uint8_t value1 = 0; uint8_t value2 = 0; le_uint16_t unused = 0; } __packed__; @@ -5299,18 +5306,21 @@ struct G_SplitStackedItem_BB_6xC3 { struct G_SortInventory_BB_6xC4 { G_UnusedHeader header; - le_uint32_t item_ids[30]; + parray item_ids; } __packed__; // 6xC5: Medical center used (BB) +struct G_MedicalCenterUsed_BB_6xC5 { + G_ClientIDHeader header; +} __packed__; + // 6xC6: Steal experience (BB) struct G_StealEXP_BB_6xC6 { G_ClientIDHeader header; - uint8_t unknown_a1; - uint8_t unknown_a2; le_uint16_t enemy_id; + le_uint16_t unknown_a1; } __packed__; // 6xC7: Charge attack (BB) @@ -5328,7 +5338,8 @@ struct G_EnemyKilled_BB_6xC8 { G_EnemyIDHeader header; le_uint16_t enemy_id; le_uint16_t killer_client_id; - le_uint32_t unused; + uint8_t unknown_a1; + parray unused; } __packed__; // 6xC9: Request meseta reward from quest (BB; handled by server) @@ -5338,7 +5349,7 @@ struct G_MesetaRewardRequest_BB_6xC9 { le_int32_t amount; } __packed__; -// 6xCA: Item reward from quest (BB; handled by server) +// 6xCA: Request item reward from quest (BB; handled by server) struct G_ItemRewardRequest_BB_6xCA { G_UnusedHeader header; @@ -5351,6 +5362,7 @@ struct G_ItemTransferRequest_BB_6xCB { G_ClientIDHeader header; le_uint32_t unknown_a1; le_uint32_t unknown_a2; + le_uint32_t unknown_a3; } __packed__; // 6xCC: Exchange item for team points (BB) @@ -5362,16 +5374,35 @@ struct G_ExchangeItemForTeamPoints_BB_6xCC { } __packed__; // 6xCD: Transfer master (BB) -// Same format as 6xC1 - // 6xCE: Accept master transfer (BB) // Same format as 6xC1 // 6xCF: Restart battle (BB) +struct G_RestartBattle_BB_6xCF { + G_UnusedHeader header; + parray unknown_a1; + le_uint32_t unknown_a2; +} __packed__; + // 6xD0: Battle mode level up (BB; handled by server) + +struct G_BattleModeLevelUp_BB_6xD0 { + G_ClientIDHeader header; + le_uint32_t unknown_a1; +} __packed__; + // 6xD1: Challenge mode grave (BB; handled by server) +struct G_ChallengeModeGrave_BB_6xD1 { + G_ClientIDHeader header; + le_uint16_t unknown_a1; + le_uint16_t unknown_a2; + le_uint32_t unknown_a3; + le_uint32_t unknown_a4; + le_uint32_t unknown_a5; +} __packed__; + // 6xD2: Set quest data 2 (BB) // Writes 4 bytes to the 32-bit field specified by index. @@ -5390,16 +5421,72 @@ struct G_Unknown_BB_6xD4 { le_uint16_t action; // Must be in [0, 4] uint8_t unknown_a1; // Must be in [0, 15] uint8_t unused; - // THe format is not fully known; there are likely more fields here. } __packed__; // 6xD5: Exchange item in quest (BB; handled by server) + +struct G_ExchangeItemInQuest_BB_6xD5 { + G_ClientIDHeader header; + ItemData unknown_a1; // Only data1[0]-[2] are used + ItemData unknown_a2; // Only data1[0]-[2] are used + le_uint16_t unknown_a3; + le_uint16_t unknown_a4; +} __packed__; + // 6xD6: Wrap item (BB; handled by server) + +struct G_WrapItem_BB_6xD6 { + G_ClientIDHeader header; + ItemData item_data; + uint8_t unknown_a1; + parray unused; +} __packed__; + // 6xD7: Paganini Photon Drop exchange (BB; handled by server) + +struct G_PaganiniPhotonDropExchange_BB_6xD7 { + G_ClientIDHeader header; + ItemData unknown_a1; // Only data1[0]-[2] are used + le_uint16_t request_id; + le_uint16_t unknown_a3; +} __packed__; + // 6xD8: Add S-rank weapon special (BB; handled by server) + +struct G_AddSRankWeaponSpecial_BB_6xD8 { + G_ClientIDHeader header; + ItemData unknown_a1; // Only data1[0]-[2] are used + le_uint32_t unknown_a2; + le_uint32_t unknown_a3; + le_uint16_t unknown_a4; + le_uint16_t unknown_a5; +} __packed__; + // 6xD9: Momoka item exchange (BB; handled by server) + +struct G_MomokaItemExchange_BB_6xD9 { + G_ClientIDHeader header; + ItemData unknown_a1; + ItemData unknown_a2; + le_uint32_t unknown_a3; + le_uint32_t unknown_a4; + le_uint16_t unknown_a5; + le_uint16_t unknown_a6; +} __packed__; + // 6xDA: Upgrade weapon attribute (BB; handled by server) +struct G_UpgradeWeaponAttribute_BB_6xDA { + G_ClientIDHeader header; + ItemData unknown_a1; // Only data1[0]-[2] are used + le_uint32_t item_id; + le_uint32_t attribute; + le_uint32_t unknown_a4; // 0 or 1 + le_uint32_t unknown_a5; + le_uint16_t request_id; + le_uint16_t unknown_a7; +} __packed__; + // 6xDB: Exchange item in quest (BB) struct G_ExchangeItemInQuest_BB_6xDB { @@ -5426,11 +5513,46 @@ struct G_SetEXPMultiplier_BB_6xDD { } __packed__; // 6xDE: Good Luck quest (BB; handled by server) + +struct G_GoodLuckQuestActions_BB_6xDE { + G_ClientIDHeader header; + uint8_t unknown_a1; + uint8_t unknown_a2; + le_uint16_t unknown_a3; +} __packed__; + // 6xDF: Black Paper's Deal Photon Drop exchange (BB; handled by server) + +struct G_BlackPaperDealPhotonDropExchange_BB_6xE0 { + G_ClientIDHeader header; +} __packed__; + // 6xE0: Black Paper's Deal rewards (BB; handled by server) + +struct G_BlackPaperDealRewards_BB_6xE0 { + G_ClientIDHeader header; + parray unknown_a1; // TODO: There might be uint16_ts and uint32_ts in here. +} __packed__; + // 6xE1: Gallon's Plan quest (BB; handled by server) + +struct G_GallonsPlanQuestActions_BB_6xE1 { + G_ClientIDHeader header; + uint8_t unknown_a1; + uint8_t unknown_a2; + uint8_t unknown_a3; + uint8_t unused; + le_uint16_t unknown_a4; + le_uint16_t unknown_a5; +} __packed__; + // 6xE2: Coren actions (BB) +struct G_CorenActions_BB_6xE2 { + G_ClientIDHeader header; + parray unknown_a1; // TODO: There might be uint16_ts and uint32_ts in here. +} __packed__; + // 6xE3: Unknown (BB) struct G_Unknown_BB_6xE3 { diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 17c34039..8cbf7760 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1413,6 +1413,64 @@ static void on_charge_attack_bb(shared_ptr, } } +static void add_player_exp(shared_ptr s, shared_ptr l, shared_ptr c, uint32_t exp) { + c->game_data.player()->disp.experience += exp; + send_give_experience(l, c, exp); + + bool leveled_up = false; + do { + const auto& level = s->level_table->stats_for_level( + c->game_data.player()->disp.char_class, c->game_data.player()->disp.level + 1); + if (c->game_data.player()->disp.experience >= level.experience) { + leveled_up = true; + level.apply(c->game_data.player()->disp.stats); + c->game_data.player()->disp.level++; + } else { + break; + } + } while (c->game_data.player()->disp.level < 199); + if (leveled_up) { + send_level_up(l, c); + } +} + +static void on_steal_exp_bb(shared_ptr s, + shared_ptr l, shared_ptr c, uint8_t, uint8_t, + const void* data, size_t size) { + if (l->version != GameVersion::BB) { + throw runtime_error("BB-only command sent in non-BB game"); + } + if (!l->map) { + throw runtime_error("map not loaded"); + } + + const auto& cmd = check_size_t(data, size); + + const auto& enemy = l->map->enemies.at(cmd.enemy_id); + const auto& inventory = c->game_data.player()->inventory; + const auto& weapon = inventory.items[inventory.find_equipped_weapon()]; + + uint8_t special = 0; + if (((weapon.data.data1[1] < 0x0A) && (weapon.data.data1[2] < 0x05)) || + ((weapon.data.data1[1] < 0x0D) && (weapon.data.data1[2] < 0x04))) { + special = weapon.data.data1[4] & 0x3F; + } else { + special = s->item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]).special; + } + + if (special >= 0x09 && special <= 0x0B) { + // Master's = 8, Lord's = 10, King's = 12 + uint32_t percent = 8 + ((special - 9) << 1) + (char_class_is_android(c->game_data.player()->disp.char_class) ? 30 : 0); + uint32_t enemy_exp = s->battle_params->get(l->mode == GameMode::SOLO, l->episode, l->difficulty, enemy.type).experience; + uint32_t stolen_exp = min((enemy_exp * percent) / 100, 80); + if (c->options.debug) { + send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", + stolen_exp, cmd.enemy_id.load(), name_for_enum(enemy.type)); + } + add_player_exp(s, l, c, stolen_exp); + } +} + static void on_enemy_killed_bb(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { @@ -1474,29 +1532,11 @@ static void on_enemy_killed_bb(shared_ptr s, // Killer gets full experience, others get 77% bool is_killer = (e.last_hit_by_client_id == other_c->lobby_client_id); uint32_t player_exp = is_killer ? experience : ((experience * 77) / 100); - - other_c->game_data.player()->disp.experience += player_exp; - send_give_experience(l, other_c, player_exp); - if (other_c->options.debug) { - send_text_message_printf(other_c, "$C5+%" PRIu32 " E-%hX %s", + if (c->options.debug) { + send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", player_exp, cmd.enemy_id.load(), name_for_enum(e.type)); } - - bool leveled_up = false; - do { - const auto& level = s->level_table->stats_for_level( - other_c->game_data.player()->disp.char_class, other_c->game_data.player()->disp.level + 1); - if (other_c->game_data.player()->disp.experience >= level.experience) { - leveled_up = true; - level.apply(other_c->game_data.player()->disp.stats); - other_c->game_data.player()->disp.level++; - } else { - break; - } - } while (other_c->game_data.player()->disp.level < 199); - if (leveled_up) { - send_level_up(l, other_c); - } + add_player_exp(s, l, c, player_exp); } } @@ -1933,7 +1973,7 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 6xC3 */ on_drop_partial_stack_bb, /* 6xC4 */ on_sort_inventory_bb, /* 6xC5 */ on_medical_center_bb, - /* 6xC6 */ nullptr, + /* 6xC6 */ on_steal_exp_bb, /* 6xC7 */ on_charge_attack_bb, /* 6xC8 */ on_enemy_killed_bb, /* 6xC9 */ on_meseta_reward_request_bb,