diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index c9459e47..e3c4747d 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -6172,12 +6172,23 @@ struct G_ChangeLobbyMusic_Ep3_6xBF { } __packed_ws__(G_ChangeLobbyMusic_Ep3_6xBF, 8); // 6xBF: Give EXP (BB) (server->client only) +// newserv implements an extension that causes this command to show the purple +// EXP numbers which are normally generated by the client instead. This +// requires the server to also send the enemy ID that generated the EXP, hence +// the extension struct here. See ServerEXPDisplay.59NL.patch.s for details. struct G_GiveExperience_BB_6xBF { G_ClientIDHeader header; le_uint32_t amount = 0; } __packed_ws__(G_GiveExperience_BB_6xBF, 8); +struct G_GiveExperience_Extension_BB_6xBF { + G_ClientIDHeader header; + le_uint32_t amount = 0; + le_uint16_t from_enemy_id = 0; + le_uint16_t unused = 0; +} __packed_ws__(G_GiveExperience_Extension_BB_6xBF, 0x0C); + // 6xC0: Sell item at shop (BB) (protected on V3/V4) struct G_SellItemAtShop_BB_6xC0 { @@ -6433,18 +6444,11 @@ struct G_Episode4BossActions_BB_6xDC { // means all EXP is doubled, for example. This only affects what the client // shows when an enemy is killed; actual EXP gains are controlled by the server // in response to the 6xC8 command. -// newserv supports an extension to this command that supports fractional -// multipliers. This is implemented in FractionalEXPMultiplier.59NL.patch.s. struct G_SetEXPMultiplier_BB_6xDD { G_ParameterHeader header; } __packed_ws__(G_SetEXPMultiplier_BB_6xDD, 4); -struct G_SetFractionalEXPMultiplier_Extension_BB_6xDD { - G_ParameterHeader header; - le_float multiplier; -} __packed_ws__(G_SetFractionalEXPMultiplier_Extension_BB_6xDD, 8); - // 6xDE: Exchange Secret Lottery Ticket (BB; handled by server) // The client sends this when it executes an F95C quest opcode. // There appears to be a bug in the client here: it sets the subcommand size to diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index e14b725b..ba790fee 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -3863,13 +3863,13 @@ static asio::awaitable on_level_up(shared_ptr c, SubcommandMessage forward_subcommand(c, msg); } -static void add_player_exp(shared_ptr c, uint32_t exp) { +static void add_player_exp(shared_ptr c, uint32_t exp, uint16_t from_enemy_id) { auto s = c->require_server_state(); auto p = c->character_file(); p->disp.stats.experience += exp; if (c->version() == Version::BB_V4) { - send_give_experience(c, exp); + send_give_experience(c, exp, from_enemy_id); } bool leveled_up = false; @@ -3994,7 +3994,7 @@ static asio::awaitable on_steal_exp_bb(shared_ptr c, SubcommandMes ene_st->e_id, enemy_exp, percent, stolen_exp); send_text_message_fmt(c, "$C5+{} E-{:03X} {}", stolen_exp, ene_st->e_id, phosg::name_for_enum(type)); } - add_player_exp(c, stolen_exp); + add_player_exp(c, stolen_exp, cmd.enemy_index | 0x1000); } static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, SubcommandMessage& msg) { @@ -4081,7 +4081,7 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco if (lc->check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_fmt(lc, "$C5+{} E-{:03X} {}", player_exp, ene_st->e_id, phosg::name_for_enum(type)); } - add_player_exp(lc, player_exp); + add_player_exp(lc, player_exp, cmd.enemy_index | 0x1000); } } @@ -4582,7 +4582,7 @@ static asio::awaitable on_battle_level_up_bb(shared_ptr c, Subcomm if (exp_delta > 0) { s->level_table(lc->version())->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class); if (lc->version() == Version::BB_V4) { - send_give_experience(lc, exp_delta); + send_give_experience(lc, exp_delta, 0xFFFF); send_level_up(lc); } } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index ac405418..50359fc6 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -3311,14 +3311,14 @@ void send_level_up(shared_ptr c) { send_command_t(l, 0x60, 0x00, cmd); } -void send_give_experience(shared_ptr c, uint32_t amount) { +void send_give_experience(shared_ptr c, uint32_t amount, uint16_t from_enemy_id) { auto l = c->require_lobby(); if (c->version() != Version::BB_V4) { throw logic_error("6xBF can only be sent to BB clients"); } uint16_t client_id = c->lobby_client_id; - G_GiveExperience_BB_6xBF cmd = { - {0xBF, sizeof(G_GiveExperience_BB_6xBF) / 4, client_id}, amount}; + G_GiveExperience_Extension_BB_6xBF cmd = { + {0xBF, sizeof(G_GiveExperience_Extension_BB_6xBF) / 4, client_id}, amount, from_enemy_id, 0}; send_command_t(l, 0x60, 0x00, cmd); } @@ -3326,11 +3326,9 @@ void send_set_exp_multiplier(shared_ptr l) { if (!l->is_game()) { throw logic_error("6xDD can only be sent in games (not in lobbies)"); } - G_SetFractionalEXPMultiplier_Extension_BB_6xDD cmd = { - {0xDD, sizeof(G_SetFractionalEXPMultiplier_Extension_BB_6xDD) / 4, 1}, 1.0f}; + G_SetEXPMultiplier_BB_6xDD cmd = {0xDD, sizeof(G_SetEXPMultiplier_BB_6xDD) / 4, 1}; if (l->mode != GameMode::CHALLENGE) { cmd.header.param = l->base_exp_multiplier; - cmd.multiplier = l->base_exp_multiplier; } for (auto lc : l->clients) { if (lc && (lc->version() == Version::BB_V4)) { diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 228ed975..a904f549 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -399,7 +399,7 @@ void send_item_identify_result(std::shared_ptr c); void send_bank(std::shared_ptr c); void send_shop(std::shared_ptr c, uint8_t shop_type); void send_level_up(std::shared_ptr c); -void send_give_experience(std::shared_ptr c, uint32_t amount); +void send_give_experience(std::shared_ptr c, uint32_t amount, uint16_t entity_id); void send_set_exp_multiplier(std::shared_ptr l); void send_rare_enemy_index_list(std::shared_ptr c, const std::vector& indexes); diff --git a/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s b/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s index f4e9198b..8db81055 100644 --- a/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s +++ b/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s @@ -18,7 +18,7 @@ write_call_to_code: # [esp + 0x04] = code ptr # [esp + 0x08] = code size # [esp + 0x0C] = jump callsite - # [esp + 0x10] = callsite size + # [esp + 0x10] = callsite size (if zero, write the address instead of a call) # Allocate memory for the copied code mov ecx, [0x00AAB404] @@ -41,8 +41,16 @@ memcpy_again: jne memcpy_again pop ebx - # Write the call or jmp opcode mov edx, [esp + 0x0C] # edx = jump callsite + + # If the callsite size is zero, just write the address directly + cmp dword [esp + 0x10], 0 + jne write_call_or_jmp + mov [edx], eax + jmp done + + # Write the call or jmp opcode +write_call_or_jmp: lea ecx, [eax - 5] sub ecx, edx # ecx = (dest code addr) - (jump callsite) - 5 cmp dword [esp + 0x10], 0 diff --git a/system/config.example.json b/system/config.example.json index 761c4f01..03a1cf1c 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -811,8 +811,9 @@ "Friday": ["Monomate x1", "Dimate x1", "Trimate x1", "Monofluid x1", "Difluid x1", "Trifluid x1", "Sol Atomizer x1", "Moon Atomizer x1", "Antidote x1", "Antiparalysis x1", "Telepipe x1", "Trap Vision x1"], "Saturday": ["Monomate x1", "Dimate x1", "Trimate x1", "Monofluid x1", "Difluid x1", "Trifluid x1", "Sol Atomizer x1", "Moon Atomizer x1", "Antidote x1", "Antiparalysis x1", "Telepipe x1", "Trap Vision x1"], }, - // EXP multiplier for BB games. This must be an integer due to a client - // limitation, and must be at least 1. + // EXP multiplier for BB games. If this is not an integer, the client will + // display incorrect EXP values, unless you also add "ServerEXPDisplay" to + // BBRequiredPatches. "BBGlobalEXPMultiplier": 1, // EXP share multiplier for BB games. The logic for EXP computation is: // - If the player lands the killing blow on an enemy they get full EXP (or