diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index b186a646..c5fe69ee 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -6404,12 +6404,21 @@ struct G_Episode4BossActions_BB_6xDC { // 6xDD: Set EXP multiplier (BB) // header.param specifies the EXP multiplier. It is 1-based, so the value 2 -// means all EXP is doubled, for example. +// 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_SetEXPMultiplier_BB_6xDD, 4); + // 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/Lobby.cc b/src/Lobby.cc index 904854f9..96989b7b 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -154,8 +154,8 @@ Lobby::Lobby(shared_ptr s, uint32_t id, bool is_game) episode(Episode::NONE), mode(GameMode::NORMAL), difficulty(0), - base_exp_multiplier(1), - exp_share_multiplier(0.5), + base_exp_multiplier(1.0f), + exp_share_multiplier(0.5f), challenge_exp_multiplier(1.0f), random_seed(phosg::random_object()), rand_crypt(make_shared()), diff --git a/src/Lobby.hh b/src/Lobby.hh index 3dd6928f..b95a8a15 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -120,7 +120,7 @@ struct Lobby : public std::enable_shared_from_this { Episode episode; GameMode mode; uint8_t difficulty; // 0-3 - uint16_t base_exp_multiplier; + float base_exp_multiplier; float exp_share_multiplier; float challenge_exp_multiplier; std::string password; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index cd38e39c..e641acbb 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -4040,11 +4040,7 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco l->challenge_exp_multiplier * (is_ep2 ? 1.3 : 1.0); 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)); + send_text_message_fmt(lc, "$C5+{} E-{:03X} {}", player_exp, ene_st->e_id, phosg::name_for_enum(type)); } if (lc->character_file()->disp.stats.level < 199) { add_player_exp(lc, player_exp); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index d2f92ac0..666233c7 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -3261,7 +3261,11 @@ 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_SetEXPMultiplier_BB_6xDD cmd = {{0xDD, sizeof(G_SetEXPMultiplier_BB_6xDD) / 4, (l->mode == GameMode::CHALLENGE) ? 1 : l->base_exp_multiplier}}; + G_SetFractionalEXPMultiplier_Extension_BB_6xDD cmd = {{0xDD, sizeof(G_SetEXPMultiplier_BB_6xDD) / 4, 1}, 1.0f}; + 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)) { send_command_t(lc, 0x60, 0x00, cmd); diff --git a/src/ServerState.cc b/src/ServerState.cc index 73839761..6b2499f9 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -1083,9 +1083,9 @@ void ServerState::load_config_early() { } } - this->bb_global_exp_multiplier = this->config_json->get_int("BBGlobalEXPMultiplier", 1); - this->exp_share_multiplier = this->config_json->get_float("BBEXPShareMultiplier", 0.5); - this->server_global_drop_rate_multiplier = this->config_json->get_float("ServerGlobalDropRateMultiplier", 1); + this->bb_global_exp_multiplier = this->config_json->get_float("BBGlobalEXPMultiplier", 1.0f); + this->exp_share_multiplier = this->config_json->get_float("BBEXPShareMultiplier", 0.5f); + this->server_global_drop_rate_multiplier = this->config_json->get_float("ServerGlobalDropRateMultiplier", 1.0f); if (this->is_debug) { set_all_log_levels(phosg::LogLevel::L_DEBUG); diff --git a/src/ServerState.hh b/src/ServerState.hh index ab95fa23..ef8d2f3d 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -241,9 +241,9 @@ struct ServerState : public std::enable_shared_from_this { std::vector quest_F960_success_results; QuestF960Result quest_F960_failure_results; std::vector secret_lottery_results; - uint16_t bb_global_exp_multiplier = 1; - float exp_share_multiplier = 0.5; - double server_global_drop_rate_multiplier = 1.0; + float bb_global_exp_multiplier = 1.0f; + float exp_share_multiplier = 0.5f; + float server_global_drop_rate_multiplier = 1.0f; std::shared_ptr ep3_tournament_index; diff --git a/system/client-functions/BlueBurstExclusive/FractionalEXPMultiplier.59NL.patch.s b/system/client-functions/BlueBurstExclusive/FractionalEXPMultiplier.59NL.patch.s new file mode 100644 index 00000000..0d86716e --- /dev/null +++ b/system/client-functions/BlueBurstExclusive/FractionalEXPMultiplier.59NL.patch.s @@ -0,0 +1,63 @@ +# This patch changes the 6xDD command to support fractional multipliers. + +.meta name="Fractional EXP multiplier" +.meta description="" +.meta hide_from_patches_menu + +entry_ptr: +reloc0: + .offsetof start +start: + call install_hook + call apply_static_patches + fild st0, dword [0x009F9EE0] + fstp dword [0x009F9EE0], st0 + ret + + + +install_hook: + pop ecx + push 7 + push 0x0078747E + call get_code_size + .deltaof hook_start, hook_end +get_code_size: + pop eax + push dword [eax] + call hook_end +hook_start: # [eax, ebx]() -> void + push edx + fild st0, dword [esp] + fld st0, dword [0x009F9EE0] + fmulp st1, st0 + fistp dword [esp], st0 + pop edx + ret +hook_end: + push ecx + .include WriteCallToCode-59NL + + + +apply_static_patches: + .include WriteCodeBlocksBB + .data 0x00787998 + .deltaof handle_6xDD_start, handle_6xDD_end +handle_6xDD_start: # [std](G_6xDD* cmd @ [esp + 4]) -> void + mov eax, [esp + 4] + test eax, eax + je handle_6xDD_ret + cmp byte [eax + 1], 1 + jg handle_6xDD_use_float + fild st0, word [eax + 2] + jmp handle_6xDD_write_float +handle_6xDD_use_float: + fld st0, dword [eax + 4] +handle_6xDD_write_float: + fstp dword [0x009F9EE0], st0 +handle_6xDD_ret: + ret +handle_6xDD_end: + .data 0x00000000 + .data 0x00000000