From 3c779b9e1f8929b82e71b0922016b98ee54b445f Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 04:30:47 -0400 Subject: [PATCH] Track PC Brutal Peeps BattleParam patch tier --- src/Client.hh | 1 + src/ReceiveCommands.cc | 11 ++++ src/SendCommands.cc | 119 +++++++++++++++++++++++++++++------------ 3 files changed, 98 insertions(+), 33 deletions(-) diff --git a/src/Client.hh b/src/Client.hh index d05c605a..5bdf509d 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -153,6 +153,7 @@ public: uint8_t override_lobby_number = 0x80; // 80 = no override int64_t override_random_seed = -1; int8_t selected_brutal_peeps_tier = -1; // -1 = normal lobby/game; 1..11 = requested Brutal Peeps tier + int8_t brutal_peeps_pc_battleparam_patch_tier = -1; // -1 = vanilla; 1..11 = currently applied PC BattleParam BP tier std::unique_ptr override_variations; VectorXYZF pos; uint32_t floor = 0x0F; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 2aec0ca0..e1115f3d 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -5563,7 +5563,15 @@ static asio::awaitable send_brutal_peeps_pc_patch_until_area_load(std::sha } int64_t brutal_peeps_hp_patch_tier = (l->brutal_peeps_tier >= 1) ? l->brutal_peeps_tier : -1; + if (c->brutal_peeps_pc_battleparam_patch_tier == brutal_peeps_hp_patch_tier) { + co_return; + } + co_await send_brutal_peeps_hp_patch_bb(c, brutal_peeps_hp_patch_tier); + + if (c->brutal_peeps_pc_battleparam_patch_tier == brutal_peeps_hp_patch_tier) { + co_return; + } } } @@ -5621,6 +5629,9 @@ static asio::awaitable on_6F(std::shared_ptr c, Channel::Message& int64_t brutal_peeps_hp_patch_tier = (l->brutal_peeps_tier >= 1) ? l->brutal_peeps_tier : -1; co_await send_brutal_peeps_hp_patch_bb(c, brutal_peeps_hp_patch_tier); } else if (loading_flag_cleared && (c->version() == Version::PC_V2)) { + // PC unloads/reloads the area BattleParam table between room loads; when it + // appears again, assume it starts from the file's vanilla values. + c->brutal_peeps_pc_battleparam_patch_tier = -1; auto s = c->require_server_state(); asio::co_spawn(*s->io_context, send_brutal_peeps_pc_patch_until_area_load(c), asio::detached); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 3c705d65..1522112b 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1010,16 +1010,33 @@ static std::vectorrequire_server_state(); - const auto* brutal_peeps_def = brutal_peeps_tier_definition(tier); - if ((tier >= 0) && !brutal_peeps_def) { + + if (tier < -1) { c->log.warning_f("Skipping Brutal Peeps PC client patch for invalid tier {}", tier); return promises; } - const double hp_mult = brutal_peeps_def ? brutal_peeps_def->enemy_hp_multiplier : 1.0; - const double exp_mult = brutal_peeps_def ? brutal_peeps_def->exp_multiplier : 1.0; - const double atp_mult = [&]() -> double { - switch (tier) { + const auto* target_brutal_peeps_def = brutal_peeps_tier_definition(tier); + if ((tier >= 0) && !target_brutal_peeps_def) { + c->log.warning_f("Skipping Brutal Peeps PC client patch for invalid tier {}", tier); + return promises; + } + + int64_t source_tier = c->brutal_peeps_pc_battleparam_patch_tier; + const auto* source_brutal_peeps_def = brutal_peeps_tier_definition(source_tier); + if ((source_tier >= 0) && !source_brutal_peeps_def) { + c->log.warning_f("PC BattleParam patch source tier {} is invalid; resetting assumed source tier to vanilla", source_tier); + source_tier = -1; + source_brutal_peeps_def = nullptr; + c->brutal_peeps_pc_battleparam_patch_tier = -1; + } + + if (source_tier == tier) { + return promises; + } + + auto atp_mult_for_tier = +[](int64_t t) -> double { + switch (t) { case 1: return 1.01; case 2: @@ -1044,7 +1061,7 @@ static std::vector(v & 0xFF)); @@ -1080,6 +1098,11 @@ static std::vector(static_cast(data[offset + 1])) << 8); }; + auto write_u16l = +[](std::string& data, uint32_t offset, uint16_t v) { + data[offset] = static_cast(v & 0xFF); + data[offset + 1] = static_cast((v >> 8) & 0xFF); + }; + auto read_u32l = +[](const std::string& data, uint32_t offset) -> uint32_t { return static_cast(data[offset]) | (static_cast(static_cast(data[offset + 1])) << 8) | @@ -1087,6 +1110,13 @@ static std::vector(static_cast(data[offset + 3])) << 24); }; + auto write_u32l = +[](std::string& data, uint32_t offset, uint32_t v) { + data[offset] = static_cast(v & 0xFF); + data[offset + 1] = static_cast((v >> 8) & 0xFF); + data[offset + 2] = static_cast((v >> 16) & 0xFF); + data[offset + 3] = static_cast((v >> 24) & 0xFF); + }; + auto scale_u16 = +[](uint32_t v, double scale) -> uint16_t { if (v == 0) { return 0; @@ -1123,48 +1153,61 @@ static std::vectorlog.warning_f("Skipping Brutal Peeps PC client patch: {} too small for Ultimate stats table", bp_filename); return promises; } + auto build_block_for_tier = [&](int64_t block_tier) -> std::string { + std::string block = vanilla_data.substr(ultimate_block_offset, ultimate_block_size); + + const auto* def = brutal_peeps_tier_definition(block_tier); + const double hp_mult = def ? def->enemy_hp_multiplier : 1.0; + const double exp_mult = def ? def->exp_multiplier : 1.0; + const double atp_mult = atp_mult_for_tier(block_tier); + + for (uint32_t z = 0; z < num_bp_rows; z++) { + const uint32_t row_offset = z * stats_row_size; + + const uint32_t atp_offset = row_offset + ultimate_atp_row_offset; + const uint32_t hp_offset = row_offset + ultimate_hp_row_offset; + const uint32_t exp_offset = row_offset + ultimate_exp_row_offset; + + write_u16l(block, atp_offset, scale_u16(read_u16l(block, atp_offset), atp_mult)); + write_u16l(block, hp_offset, scale_u16(read_u16l(block, hp_offset), hp_mult)); + write_u32l(block, exp_offset, scale_u32(read_u32l(block, exp_offset), exp_mult)); + } + + return block; + }; + + const std::string source_block = build_block_for_tier(source_tier); + const std::string target_block = build_block_for_tier(tier); + std::string suffix; append_u32l(suffix, scan_start); append_u32l(suffix, scan_end); append_u32l(suffix, signature_size); append_u32l(suffix, 0); // patched below after patch generation - suffix.append(vanilla_data.data() + ultimate_block_offset, signature_size); + suffix.append(source_block.data(), signature_size); uint32_t patch_entry_count = 0; for (uint32_t z = 0; z < num_bp_rows; z++) { - const uint32_t row_file_offset = ultimate_block_offset + (z * stats_row_size); const uint32_t row_patch_offset = z * stats_row_size; - const uint32_t atp_file_offset = row_file_offset + ultimate_atp_row_offset; const uint32_t atp_patch_offset = row_patch_offset + ultimate_atp_row_offset; - uint16_t old_atp = read_u16l(vanilla_data, atp_file_offset); - uint16_t new_atp = scale_u16(old_atp, atp_mult); - - append_patch_entry(suffix, atp_patch_offset, new_atp, 2); - patch_entry_count++; - - const uint32_t hp_file_offset = row_file_offset + ultimate_hp_row_offset; const uint32_t hp_patch_offset = row_patch_offset + ultimate_hp_row_offset; - uint16_t old_hp = read_u16l(vanilla_data, hp_file_offset); - uint16_t new_hp = scale_u16(old_hp, hp_mult); + const uint32_t exp_patch_offset = row_patch_offset + ultimate_exp_row_offset; - append_patch_entry(suffix, hp_patch_offset, new_hp, 2); + append_patch_entry(suffix, atp_patch_offset, read_u16l(target_block, atp_patch_offset), 2); patch_entry_count++; - const uint32_t exp_file_offset = row_file_offset + ultimate_exp_row_offset; - const uint32_t exp_patch_offset = row_patch_offset + ultimate_exp_row_offset; - uint32_t old_exp = read_u32l(vanilla_data, exp_file_offset); - uint32_t new_exp = scale_u32(old_exp, exp_mult); + append_patch_entry(suffix, hp_patch_offset, read_u16l(target_block, hp_patch_offset), 2); + patch_entry_count++; - append_patch_entry(suffix, exp_patch_offset, new_exp, 4); + append_patch_entry(suffix, exp_patch_offset, read_u32l(target_block, exp_patch_offset), 4); patch_entry_count++; } @@ -1191,8 +1234,13 @@ static std::vectorlog.info_f("Brutal Peeps PC ATP/HP/EXP client patch sent for {}: tier={} hp_mult={:g} atp_mult={:g} exp_mult={:g} patch_entries={} suffix_size={} scan={:08X}-{:08X}", - bp_filename, tier, hp_mult, atp_mult, exp_mult, patch_entry_count, suffix.size(), scan_start, scan_end); + const auto* target_def_for_log = brutal_peeps_tier_definition(tier); + const double hp_mult_for_log = target_def_for_log ? target_def_for_log->enemy_hp_multiplier : 1.0; + const double exp_mult_for_log = target_def_for_log ? target_def_for_log->exp_multiplier : 1.0; + const double atp_mult_for_log = atp_mult_for_tier(tier); + + c->log.info_f("Brutal Peeps PC ATP/HP/EXP client patch sent for {}: source_tier={} target_tier={} hp_mult={:g} atp_mult={:g} exp_mult={:g} patch_entries={} suffix_size={} scan={:08X}-{:08X}", + bp_filename, source_tier, tier, hp_mult_for_log, atp_mult_for_log, exp_mult_for_log, patch_entry_count, suffix.size(), scan_start, scan_end); return promises; @@ -1232,6 +1280,11 @@ asio::awaitable send_brutal_peeps_hp_patch_bb(std::shared_ptr c, i return_value, static_cast(result.checksum)); + if (is_pc_bp_patch && return_value) { + c->brutal_peeps_pc_battleparam_patch_tier = static_cast(tier); + c->log.info_f("Brutal Peeps PC BattleParam patch state is now tier {}", tier); + } + if (return_value) { any_success = true; } else {