diff --git a/CMakeLists.txt b/CMakeLists.txt index 5654eb6037..5b086391ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,8 @@ add_library(${PROJECT_NAME} OBJECT src/game_pictures.h src/game_player.cpp src/game_player.h + src/game_runtime_patches.cpp + src/game_runtime_patches.h src/game_quit.cpp src/game_quit.h src/game_screen.cpp diff --git a/Makefile.am b/Makefile.am index 6185035dac..5d00e6cdcd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -178,6 +178,8 @@ libeasyrpg_player_a_SOURCES = \ src/game_pictures.h \ src/game_player.cpp \ src/game_player.h \ + src/game_runtime_patches.cpp \ + src/game_runtime_patches.h \ src/game_screen.cpp \ src/game_screen.h \ src/game_strings.cpp \ diff --git a/src/algo.cpp b/src/algo.cpp index 8426799af6..8416400146 100644 --- a/src/algo.cpp +++ b/src/algo.cpp @@ -171,6 +171,9 @@ int VarianceAdjustEffect(int base, int var) { } int AdjustDamageForDefend(int dmg, const Game_Battler& target) { + if (RuntimePatches::GuardRevamp::OverrideDamageAdjustment(dmg, target)) { + return dmg; + } if (target.IsDefending()) { dmg /= 2; if (target.HasStrongDefense()) { diff --git a/src/game_config_game.cpp b/src/game_config_game.cpp index 4a719b5556..0789bd17f9 100644 --- a/src/game_config_game.cpp +++ b/src/game_config_game.cpp @@ -16,6 +16,7 @@ */ #include "game_config_game.h" +#include "game_runtime_patches.h" #include "cmdline_parser.h" #include "directory_tree.h" #include "filefinder.h" @@ -24,8 +25,8 @@ #include "player.h" #include -Game_ConfigGame Game_ConfigGame::Create(CmdlineParser& cp) { - Game_ConfigGame cfg; +void Game_ConfigGame::Initialize(CmdlineParser& cp) { + Game_ConfigGame& cfg = *this; auto cli_config = FileFinder::Game().OpenFile(EASYRPG_INI_NAME); if (cli_config) { @@ -56,8 +57,6 @@ Game_ConfigGame Game_ConfigGame::Create(CmdlineParser& cp) { cfg.engine = Player::EngineRpg2k3 | Player::EngineMajorUpdated | Player::EngineEnglish; } } - - return cfg; } void Game_ConfigGame::LoadFromArgs(CmdlineParser& cp) { @@ -88,6 +87,8 @@ void Game_ConfigGame::LoadFromArgs(CmdlineParser& cp) { patch_rpg2k3_commands.Lock(false); patch_anti_lag_switch.Lock(0); patch_direct_menu.Lock(0); + + RuntimePatches::LockPatchesAsDiabled(); patch_override = true; continue; } @@ -155,6 +156,10 @@ void Game_ConfigGame::LoadFromArgs(CmdlineParser& cp) { } continue; } + if (RuntimePatches::ParseFromCommandLine(cp)) { + patch_override = true; + continue; + } if (cp.ParseNext(arg, 6, "--patch")) { // For backwards compatibility only for (int i = 0; i < arg.NumValues(); ++i) { @@ -229,6 +234,10 @@ void Game_ConfigGame::LoadFromStream(Filesystem_Stream::InputStream& is) { if (patch_direct_menu.FromIni(ini)) { patch_override = true; } + + if (RuntimePatches::ParseFromIni(ini)) { + patch_override = true; + } } void Game_ConfigGame::PrintActivePatches() { @@ -258,6 +267,8 @@ void Game_ConfigGame::PrintActivePatches() { add_int(patch_anti_lag_switch); add_int(patch_direct_menu); + RuntimePatches::DetermineActivePatches(patches); + if (patches.empty()) { Output::Debug("Patch configuration: None"); } else { diff --git a/src/game_config_game.h b/src/game_config_game.h index fe9e3ec0c0..354d1d9bb8 100644 --- a/src/game_config_game.h +++ b/src/game_config_game.h @@ -49,6 +49,28 @@ struct Game_ConfigGame { ConfigParam patch_anti_lag_switch{ "Anti-Lag Switch", "Disable event page refreshes when switch is set", "Patch", "AntiLagSwitch", 0 }; ConfigParam patch_direct_menu{ "Direct Menu", " Allows direct access to subscreens of the default menu", "Patch", "DirectMenu", 0 }; + ConfigParam patch_encounter_random_alert_sw{ "Encounter Randomness Alert", "Set troop id to a variable, activate a switch and skip random battle", "Patch", "EncounterAlert.Switch", 0 }; + ConfigParam patch_encounter_random_alert_var{ "Encounter Randomness Alert", "Set troop id to a variable, activate a switch and skip random battle", "Patch", "EncounterAlert.Var", 0 }; + + ConfigParam patch_monsca_maxhp{ "MonSca", "Scales enemy battle stats by variable value. (MaxHP)", "Patch", "MonSca.MaxHP", 0 }; + ConfigParam patch_monsca_maxsp{ "MonSca", "Scales enemy battle stats by variable value. (MaxSP)", "Patch", "MonSca.MaxSP", 0 }; + ConfigParam patch_monsca_atk{ "MonSca", "Scales enemy battle stats by variable value. (Attack)", "Patch", "MonSca.Attack", 0 }; + ConfigParam patch_monsca_def{ "MonSca", "Scales enemy battle stats by variable value. (Defense)", "Patch", "MonSca.Defense", 0 }; + ConfigParam patch_monsca_spi{ "MonSca", "Scales enemy battle stats by variable value. (Spirit)", "Patch", "MonSca.Spirit", 0 }; + ConfigParam patch_monsca_agi{ "MonSca", "Scales enemy battle stats by variable value. (Agility)", "Patch", "MonSca.Agility", 0 }; + ConfigParam patch_monsca_exp{ "MonSca", "Scales enemy battle stats by variable value. (Gained EXP)", "Patch", "MonSca.Experience", 0 }; + ConfigParam patch_monsca_gold{ "MonSca", "Scales enemy battle stats by variable value. (Gained Money)", "Patch", "MonSca.Money", 0 }; + ConfigParam patch_monsca_item{ "MonSca", "Scales enemy battle stats by variable value. (Gained Item)", "Patch", "MonSca.ItemId", 0 }; + ConfigParam patch_monsca_droprate{ "MonSca", "Scales enemy battle stats by variable value. (Item Drop Rate)", "Patch", "MonSca.ItemDropRate", 0 }; + ConfigParam patch_monsca_levelscaling{ "MonSca", "Scales enemy battle stats by variable value. (Alternate formula)", "Patch", "MonSca.LevelScaling", 0 }; + ConfigParam patch_monsca_plus{ "MonSca", "Scale enemies individually based on troop index", "Patch", "MonSca.Plus", 0 }; + + ConfigParam patch_explus_var{ "EXPlus", "Boosts party EXP by set percentages", "Patch", "EXPlus.VarExpBoost", 0 }; + ConfigParam patch_explusplus_var{ "EXPlus", "Allows for setting party index of given actors to a variable", "Patch", "EXPlus.VarActorInParty", 0 }; + + ConfigParam patch_guardrevamp_normal{ "GuardRevamp", "Changes damage calculation for defense situations (Normal)", "Patch", "GuardRevamp.NormalDefense", 0 }; + ConfigParam patch_guardrevamp_strong{ "GuardRevamp", "Changes damage calculation for defense situations (Strong)", "Patch", "GuardRevamp.StrongDefense", 0 }; + // Command line only BoolConfigParam patch_support{ "Support patches", "When OFF all patch support is disabled", "", "", true }; @@ -58,9 +80,9 @@ struct Game_ConfigGame { int engine = 0; /** - * Create a game config from the config file in the game directory, then loads command line arguments. + * Initializes a game config from the config file in the game directory, then loads command line arguments. */ - static Game_ConfigGame Create(CmdlineParser& cp); + void Initialize(CmdlineParser& cp); /** * Load configuration values from a stream; diff --git a/src/game_enemy.h b/src/game_enemy.h index 416cecc008..b1561aa7c2 100644 --- a/src/game_enemy.h +++ b/src/game_enemy.h @@ -20,6 +20,7 @@ // Headers #include "game_battler.h" +#include "game_runtime_patches.h" #include "sprite_enemy.h" #include "player.h" #include @@ -327,27 +328,39 @@ inline int Game_Enemy::GetTroopMemberId() const { } inline int Game_Enemy::GetBaseMaxHp() const { - return enemy->max_hp; + auto max_hp = enemy->max_hp; + RuntimePatches::MonSca::ModifyMaxHp(*this, max_hp); + return max_hp; } inline int Game_Enemy::GetBaseMaxSp() const { - return enemy->max_sp; + auto max_sp = enemy->max_sp; + RuntimePatches::MonSca::ModifyMaxSp(*this, max_sp); + return max_sp; } inline int Game_Enemy::GetBaseAtk(Weapon) const { - return enemy->attack; + auto attack = enemy->attack; + RuntimePatches::MonSca::ModifyAtk(*this, attack); + return attack; } inline int Game_Enemy::GetBaseDef(Weapon) const { - return enemy->defense; + auto defense = enemy->defense; + RuntimePatches::MonSca::ModifyDef(*this, defense); + return defense; } inline int Game_Enemy::GetBaseSpi(Weapon) const { - return enemy->spirit; + auto spirit = enemy->spirit; + RuntimePatches::MonSca::ModifySpi(*this, spirit); + return spirit; } inline int Game_Enemy::GetBaseAgi(Weapon) const { - return enemy->agility; + auto agility = enemy->agility; + RuntimePatches::MonSca::ModifyAgi(*this, agility); + return agility; } inline int Game_Enemy::GetHp() const { diff --git a/src/game_enemyparty.cpp b/src/game_enemyparty.cpp index 76023f35e5..0635c70e47 100644 --- a/src/game_enemyparty.cpp +++ b/src/game_enemyparty.cpp @@ -20,6 +20,7 @@ #include #include "game_interpreter.h" #include "game_enemyparty.h" +#include "game_runtime_patches.h" #include "main_data.h" #include #include "utils.h" @@ -102,7 +103,10 @@ int Game_EnemyParty::GetExp() const { int sum = 0; for (auto& enemy: enemies) { if (enemy.IsDead()) { - sum += enemy.GetExp(); + auto exp = enemy.GetExp(); + RuntimePatches::MonSca::ModifyExpGained(enemy, exp); + + sum += exp; } } return sum; @@ -112,7 +116,10 @@ int Game_EnemyParty::GetMoney() const { int sum = 0; for (auto& enemy: enemies) { if (enemy.IsDead()) { - sum += enemy.GetMoney(); + auto money = enemy.GetMoney(); + RuntimePatches::MonSca::ModifyMoneyGained(enemy, money); + + sum += money; } } return sum; @@ -121,10 +128,16 @@ int Game_EnemyParty::GetMoney() const { void Game_EnemyParty::GenerateDrops(std::vector& out) const { for (auto& enemy: enemies) { if (enemy.IsDead()) { + auto drop_id = enemy.GetDropId(); + RuntimePatches::MonSca::ModifyItemGained(enemy, drop_id); + // Only roll if the enemy has something to drop - if (enemy.GetDropId() != 0) { - if (Rand::ChanceOf(enemy.GetDropProbability(), 100)) { - out.push_back(enemy.GetDropId()); + if (drop_id > 0) { + auto drop_rate = enemy.GetDropProbability(); + RuntimePatches::MonSca::ModifyItemDropRate(enemy, drop_rate); + + if (Rand::ChanceOf(drop_rate, 100)) { + out.push_back(drop_id); } } } diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 19b0366812..9412ab98ad 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -3566,6 +3566,7 @@ bool Game_Interpreter::CommandConditionalBranch(lcf::rpg::EventCommand const& co case 0: // Is actor in party result = Main_Data::game_party->IsActorInParty(actor_id); + RuntimePatches::EXPlus::StoreActorPosition(actor_id); break; case 1: // Name @@ -4847,7 +4848,11 @@ bool Game_Interpreter::CommandManiacKeyInputProcEx(lcf::rpg::EventCommand const& } } else if (operation == 2) { int key_id = ValueOrVariable(com.parameters[2], com.parameters[3]); - bool key_state = ManiacPatch::GetKeyState(key_id); + auto key = RuntimePatches::VirtualKeys::VirtualKeyToInputKey(key_id); + if (key == Input::Keys::NONE) { + Output::Debug("Maniac KeyInputProcEx: Unsupported keycode {}", key_id); + } + bool key_state = Input::IsRawKeyPressed(key); Main_Data::game_variables->Set(start_var_id, key_state ? 1 : 0); } else { Output::Warning("Maniac KeyInputProcEx: Joypad not supported"); diff --git a/src/game_map.cpp b/src/game_map.cpp index 22187d0905..d2363e6a98 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -38,6 +38,7 @@ #include "game_message.h" #include "game_screen.h" #include "game_pictures.h" +#include "game_variables.h" #include "scene_battle.h" #include "scene_map.h" #include @@ -1636,6 +1637,11 @@ bool Game_Map::PrepareEncounter(BattleArgs& args) { args.troop_id = encounters[Rand::GetRandomNumber(0, encounters.size() - 1)]; + if (RuntimePatches::EncounterRandomnessAlert::HandleEncounter(args.troop_id)) { + //Cancel the battle setup + return false; + } + if (Feature::HasRpg2kBattleSystem()) { if (Rand::ChanceOf(1, 32)) { args.first_strike = true; diff --git a/src/game_runtime_patches.cpp b/src/game_runtime_patches.cpp new file mode 100644 index 0000000000..8222f13077 --- /dev/null +++ b/src/game_runtime_patches.cpp @@ -0,0 +1,291 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +// Headers +#include "game_runtime_patches.h" + +#include "game_map.h" +#include "game_party.h" +#include "game_switches.h" +#include "game_variables.h" +#include "game_actor.h" +#include "game_battler.h" +#include "game_enemy.h" +#include "main_data.h" +#include "player.h" + +namespace { + template + void LockPatchArguments(std::array const& patch_args) { + for (auto& patch_arg : patch_args) { + patch_arg.config_param.Lock(0); + } + } + + template + bool ParsePatchArguments(CmdlineParser& cp, CmdlineArg arg, std::array const& patch_args) { + if (arg.ArgIsOff()) { + for (auto& patch_arg : patch_args) { + patch_arg.config_param.Set(0); + } + return true; + } + if (arg.ArgIsOn()) { + for (auto& patch_arg : patch_args) { + patch_arg.config_param.Set(patch_arg.default_value); + } + bool parsed; + long li_value = 0; + do { + parsed = false; + for (int i = 0; i < static_cast(patch_args.size()); ++i) { + if (cp.ParseNext(arg, 1, patch_args[i].cmd_arg)) { + parsed = true; + if (arg.ParseValue(0, li_value)) { + patch_args[i].config_param.Set(li_value); + } + } + } + } while (parsed); + + return true; + } + return false; + } + + template + bool ParsePatchFromIni(lcf::INIReader& ini, std::array const& patch_args) { + bool patch_override = false; + for (auto& patch_arg : patch_args) { + patch_override |= patch_arg.config_param.FromIni(ini); + } + return patch_override; + } + + template + void PrintPatch(std::vector& patches, std::array const& patch_args) { + assert(patch_args.size() > 0); + + bool is_set = false; + for (auto& patch_arg : patch_args) { + if (patch_arg.config_param.Get() > 0) { + is_set = true; + break; + } + } + if (!is_set) { + return; + } + + if (patch_args.size() == 1) { + patches.push_back(fmt::format("{} ({})", patch_args[0].config_param.GetName(), patch_args[0].config_param.Get())); + return; + } + + std::string out = fmt::format("{} (", patch_args[0].config_param.GetName()); + for (int i = 0; i < static_cast(patch_args.size()); ++i) { + if (i > 0) { + out += ", "; + } + out += fmt::format("{}", patch_args[i].config_param.Get()); + } + out += ")"; + + patches.push_back(out); + } +} + +void RuntimePatches::LockPatchesAsDiabled() { + LockPatchArguments(EncounterRandomnessAlert::patch_args); + LockPatchArguments(MonSca::patch_args); + LockPatchArguments(EXPlus::patch_args); + LockPatchArguments(GuardRevamp::patch_args); +} + +bool RuntimePatches::ParseFromCommandLine(CmdlineParser& cp) { + CmdlineArg arg; + if (cp.ParseNext(arg, 1, { "--patch-encounter-alert", "--no-patch-encounter-alert" })) { + return ParsePatchArguments(cp, arg, EncounterRandomnessAlert::patch_args); + } + if (cp.ParseNext(arg, 1, { "--patch-monsca", "--no-patch-monsca" })) { + return ParsePatchArguments(cp, arg, MonSca::patch_args); + } + if (cp.ParseNext(arg, 1, { "--patch-explus", "--no-patch-explus" })) { + return ParsePatchArguments(cp, arg, EXPlus::patch_args); + } + if (cp.ParseNext(arg, 1, { "--patch-guardrevamp", "--no-patch-guardrevamp" })) { + return ParsePatchArguments(cp, arg, GuardRevamp::patch_args); + } + return false; +} + +bool RuntimePatches::ParseFromIni(lcf::INIReader& ini) { + bool patch_override = false; + patch_override |= ParsePatchFromIni(ini, EncounterRandomnessAlert::patch_args); + patch_override |= ParsePatchFromIni(ini, MonSca::patch_args); + patch_override |= ParsePatchFromIni(ini, EXPlus::patch_args); + patch_override |= ParsePatchFromIni(ini, GuardRevamp::patch_args); + return patch_override; +} + +void RuntimePatches::DetermineActivePatches(std::vector& patches) { + PrintPatch(patches, EncounterRandomnessAlert::patch_args); + PrintPatch(patches, MonSca::patch_args); + PrintPatch(patches, EXPlus::patch_args); + PrintPatch(patches, GuardRevamp::patch_args); +} + +bool RuntimePatches::EncounterRandomnessAlert::HandleEncounter(int troop_id) { + if (auto var_id = Player::game_config.patch_encounter_random_alert_var.Get(); var_id > 0) { + Main_Data::game_player->SetTotalEncounterRate(0); + Main_Data::game_player->SetEncounterCalling(false); + + Main_Data::game_variables->Set(var_id, troop_id); + Game_Map::SetNeedRefreshForVarChange(var_id); + + if (auto switch_id = Player::game_config.patch_encounter_random_alert_sw.Get(); switch_id > 0) { + Main_Data::game_switches->Set(switch_id, true); + Game_Map::SetNeedRefreshForSwitchChange(switch_id); + } + // Always refresh the map (Original patch does this only for the MEPR variant) + Game_Map::Refresh(); + return true; + } + return false; +} + +namespace RuntimePatches::MonSca { + bool UseLevelBasedFormula() { + auto switch_id = Player::game_config.patch_monsca_levelscaling.Get(); + return switch_id > 0 && Main_Data::game_switches->Get(switch_id); + } + + int GetVariableId(Game_Enemy const& enemy, int var_id) { + if (Player::game_config.patch_monsca_plus.Get() > 0) { + return var_id + enemy.GetTroopMemberId(); + } + return var_id; + } + + template + void ApplyScaling(Game_Enemy const& enemy, T& val, int var_id) { + int mod = Main_Data::game_variables->Get(GetVariableId(enemy, var_id)); + if (mod == 0) { + return; + } + if (UseLevelBasedFormula()) { + mod *= Main_Data::game_party->GetAverageLevel(); + } + val *= mod; + val /= 1000; + } +} + +void RuntimePatches::MonSca::ModifyMaxHp(Game_Enemy const& enemy, int32_t& val) { + if (auto var_id = Player::game_config.patch_monsca_maxhp.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifyMaxSp(Game_Enemy const& enemy, int32_t& val) { + if (auto var_id = Player::game_config.patch_monsca_maxsp.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifyAtk(Game_Enemy const& enemy, int32_t& val) { + if (auto var_id = Player::game_config.patch_monsca_atk.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifyDef(Game_Enemy const& enemy, int32_t& val) { + if (auto var_id = Player::game_config.patch_monsca_def.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifySpi(Game_Enemy const& enemy, int32_t& val) { + if (auto var_id = Player::game_config.patch_monsca_spi.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifyAgi(Game_Enemy const& enemy, int32_t& val) { + if (auto var_id = Player::game_config.patch_monsca_agi.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifyExpGained(Game_Enemy const& enemy, int& val) { + if (auto var_id = Player::game_config.patch_monsca_exp.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifyMoneyGained(Game_Enemy const& enemy, int& val) { + if (auto var_id = Player::game_config.patch_monsca_gold.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::MonSca::ModifyItemGained(Game_Enemy const& enemy, int& item_id) { + if (auto var_id = Player::game_config.patch_monsca_item.Get(); var_id > 0) { + item_id += Main_Data::game_variables->Get(GetVariableId(enemy, var_id)); + } +} + +void RuntimePatches::MonSca::ModifyItemDropRate(Game_Enemy const& enemy, int& val) { + if (auto var_id = Player::game_config.patch_monsca_droprate.Get(); var_id > 0) { + ApplyScaling(enemy, val, var_id); + } +} + +void RuntimePatches::EXPlus::ModifyExpGain(Game_Actor& actor, int& exp_gain) { + if (auto base_var_id = Player::game_config.patch_explus_var.Get(); base_var_id > 0) { + exp_gain *= (100 + Main_Data::game_variables->Get(base_var_id + actor.GetPartyIndex())); + exp_gain /= 100; + } +} + +void RuntimePatches::EXPlus::StoreActorPosition(int actor_id) { + if (auto var_id = Player::game_config.patch_explusplus_var.Get(); var_id > 0) { + Main_Data::game_variables->Set(var_id, Main_Data::game_party->GetActorPositionInParty(actor_id) + 1); + } +} + +bool RuntimePatches::GuardRevamp::OverrideDamageAdjustment(int& dmg, const Game_Battler& target) { + auto rate_normal = Player::game_config.patch_guardrevamp_normal.Get(); + auto rate_strong = Player::game_config.patch_guardrevamp_strong.Get(); + + if ((rate_normal > 0 || rate_strong > 0) && target.IsDefending()) { + if (!target.HasStrongDefense()) { + if (rate_normal == 0) { + return false; + } + dmg *= rate_normal; + } else { + if (rate_strong == 0) { + return false; + } + dmg *= rate_strong; + } + dmg /= 100; + return true; + } + return false; +} diff --git a/src/game_runtime_patches.h b/src/game_runtime_patches.h new file mode 100644 index 0000000000..08504b4322 --- /dev/null +++ b/src/game_runtime_patches.h @@ -0,0 +1,408 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_GAME_RUNTIMEPATCHES_H +#define EP_GAME_RUNTIMEPATCHES_H + +#include "game_config_game.h" +#include "cmdline_parser.h" +#include "player.h" + +class Game_Actor; +class Game_Battler; +class Game_Enemy; + +namespace RuntimePatches { + // TODO: Consider adding global functions like "ModifyMaxHp" which delegate to various patches + + struct PatchArg { + ConfigParam& config_param; + char const* cmd_arg; + int const default_value = 0; + + constexpr PatchArg(ConfigParam& config_param, char const* cmd_arg, int const default_value) + : config_param(config_param), cmd_arg(cmd_arg), default_value(default_value) { + } + }; + + void LockPatchesAsDiabled(); + + bool ParseFromCommandLine(CmdlineParser& cp); + + bool ParseFromIni(lcf::INIReader& ini); + + void DetermineActivePatches(std::vector& patches); + + /** + * Support for RPG_RT patch 'Encounter Randomness Alert'. + * This patch skips the normal battle startup logic whenever a random + * encounter would be triggered. + * Instead a switch (default: S[1018]) is set to ON and the troop ID + * is stored into a variable (default: V[3355]). + * + * This implementation always triggers a page-refresh for all + * events on the current map. + */ + namespace EncounterRandomnessAlert { + constexpr std::array patch_args = { { + { Player::game_config.patch_encounter_random_alert_sw, "-sw", 1018}, + { Player::game_config.patch_encounter_random_alert_var, "-var", 3355 } + } }; + + /** + * Sets the configured switch & variable according to ERA's rules. + * @return if normal battle processing should be skipped. + */ + bool HandleEncounter(int troop_id); + } + + /** + * Support for RPG_RT patch 'MonSca' & 'MonScaPlus'. + * This patch scales the default battle parameters of an enemy + * based on the contents of some in-game variables. + * (Default: V[1001] - V[1010]) + * + * When a switch is set (default: S[1001]) to ON, an alternative + * scaling formula, based on the average party level, is used. + * + * Default formula: val = val * V[...] / 1000 + * Alternative formula: val = val * avg_level * V[...] / 1000 + * + * Variant 'MonScaPlus': + * If set, the variable IDs used for scaling will be offset + * by the enemy's troop index. + * -> V[base_var_id + troop_index] + */ + namespace MonSca { + constexpr std::array patch_args = { { + { Player::game_config.patch_monsca_maxhp, "-maxhp", 1001 }, + { Player::game_config.patch_monsca_maxsp, "-maxsp", 1002 }, + { Player::game_config.patch_monsca_atk, "-atk", 1003 }, + { Player::game_config.patch_monsca_def, "-def", 1004 }, + { Player::game_config.patch_monsca_spi, "-spi", 1005 }, + { Player::game_config.patch_monsca_agi, "-agi", 1006 }, + { Player::game_config.patch_monsca_exp, "-exp", 1007 }, + { Player::game_config.patch_monsca_gold, "-gold", 1008 }, + { Player::game_config.patch_monsca_item, "-item", 1009 }, + { Player::game_config.patch_monsca_droprate, "-droprate", 1010 }, + { Player::game_config.patch_monsca_levelscaling, "-lvlscale", 1001 }, // Switch + { Player::game_config.patch_monsca_plus, "-plus", 0 } + } }; + + /** Scales an enemies's maximum HP stat, based on the value of variable V[1001] */ + void ModifyMaxHp(Game_Enemy const& enemy, int32_t& val); + /** Scales an enemies's maximum SP stat, based on the value of variable V[1002] */ + void ModifyMaxSp(Game_Enemy const& enemy, int32_t& val); + /** Scales an enemies's attack stat, based on the value of variable V[1003] */ + void ModifyAtk(Game_Enemy const& enemy, int32_t& val); + /** Scales an enemies's defense stat, based on the value of variable V[1004] */ + void ModifyDef(Game_Enemy const& enemy, int32_t& val); + /** Scales an enemies's spirit stat, based on the value of variable V[1005] */ + void ModifySpi(Game_Enemy const& enemy, int32_t& val); + /** Scales an enemies's agility stat, based on the value of variable V[1006] */ + void ModifyAgi(Game_Enemy const& enemy, int32_t& val); + /** Scales the experience points gained by defating an enemy, based on the value of variable V[1007] */ + void ModifyExpGained(Game_Enemy const& enemy, int& val); + /** Scales the money gained by defating an enemy, based on the value of variable V[1008] */ + void ModifyMoneyGained(Game_Enemy const& enemy, int& val); + /** + * Modifies the item dropped by defating an enemy, based on the value of variable V[1009] + * In contrast to other modifers of this patch, this skips the normal formula and just + * adds the variable value to the result. + */ + void ModifyItemGained(Game_Enemy const& enemy, int& item_id); + /** Scales the item drop rate of an enemy, based on the value of variable V[1010] */ + void ModifyItemDropRate(Game_Enemy const& enemy, int& val); + } + + /** + * Support for RPG_RT patch 'EXPlus' & 'EXPLus[+]'. + * This patch allows to individually boost the 4 party members' + * gained experience inside battles by applying an extra percentage + * based on the values of in-game variables. + * (default: V[3333] for party member #1; the amounts for other + * party members are read from the subsequent 3 variables) + * + * If the '[+]' option is enabled, a side effect is added to one + * of the Actor clauses of 'CommandConditionalBranch': + * Whenever this command is used to check for the existence of + * an actor in the current party, the current party slot (1-4) + * of this actor is set to an in-game variable. (default: V[3332]) + */ + namespace EXPlus { + constexpr std::array patch_args = { { + { Player::game_config.patch_explus_var, "-var", 3333}, + { Player::game_config.patch_explusplus_var, "-var", 3332 } + } }; + + /** + * Boosts the gained experience points which would be added to + * the given actor's stats by an extra amount which is calculated + * from the value of the in-game variable V[X + party_index - 1]. + */ + void ModifyExpGain(Game_Actor& actor, int& exp_gain); + + /** + * Store's the current party position of the given actor inside + * the configured in-game variable for the '[+]' variant of + * te EXPlus patch. + */ + void StoreActorPosition(int actor_id); + } + + /** + * Support for RPG_RT patch 'GuardRevamp'. + * This patch changes the way the damage adjustment is calculated + * whenever the target of an attack is defending. + * + * Normally this calculation is done by simply dividing the damage + * in half for normal defense situations, and by quartering it + * when the target has the 'strong defense' attribute. + * With 'GuardRevamp' enabled, this is changed to a percentage + * calculation, allowing for more granular control over the output. + * The given default values of '50%', and '25%' would provide + * the same results in most situations. + */ + namespace GuardRevamp { + constexpr std::array patch_args = { { + { Player::game_config.patch_guardrevamp_normal, "-normal", 50}, + { Player::game_config.patch_guardrevamp_strong, "-strong", 25 } + } }; + + /** + * Adjusts the damage value taken by the given battler according + * the GuardRevamp patches' rules. + * @return if normal damage calculation should skipped. + */ + bool OverrideDamageAdjustment(int& dmg, const Game_Battler& target); + } + + namespace VirtualKeys { + constexpr Input::Keys::InputKey VirtualKeyToInputKey(uint32_t key_id); + + constexpr uint32_t InputKeyToVirtualKey(Input::Keys::InputKey input_key); + } +} + +constexpr Input::Keys::InputKey RuntimePatches::VirtualKeys::VirtualKeyToInputKey(uint32_t key_id) { + // see https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + switch (key_id) { +#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) + case 0x1: return Input::Keys::MOUSE_LEFT; + case 0x2: return Input::Keys::MOUSE_RIGHT; + case 0x4: return Input::Keys::MOUSE_MIDDLE; + case 0x5: return Input::Keys::MOUSE_XBUTTON1; + case 0x6: return Input::Keys::MOUSE_XBUTTON2; +#endif + case 0x8: return Input::Keys::BACKSPACE; + case 0x9: return Input::Keys::TAB; + case 0xD: return Input::Keys::RETURN; + case 0x10: return Input::Keys::SHIFT; + case 0x11: return Input::Keys::CTRL; + case 0x12: return Input::Keys::ALT; + case 0x13: return Input::Keys::PAUSE; + case 0x14: return Input::Keys::CAPS_LOCK; + case 0x1B: return Input::Keys::ESCAPE; + case 0x20: return Input::Keys::SPACE; + case 0x21: return Input::Keys::PGUP; + case 0x22: return Input::Keys::PGDN; + case 0x23: return Input::Keys::ENDS; + case 0x24: return Input::Keys::HOME; + case 0x25: return Input::Keys::LEFT; + case 0x26: return Input::Keys::UP; + case 0x27: return Input::Keys::RIGHT; + case 0x28: return Input::Keys::DOWN; + case 0x2D: return Input::Keys::INSERT; + case 0x2E: return Input::Keys::DEL; + case 0x30: return Input::Keys::N0; + case 0x31: return Input::Keys::N1; + case 0x32: return Input::Keys::N2; + case 0x33: return Input::Keys::N3; + case 0x34: return Input::Keys::N4; + case 0x35: return Input::Keys::N5; + case 0x36: return Input::Keys::N6; + case 0x37: return Input::Keys::N7; + case 0x38: return Input::Keys::N8; + case 0x39: return Input::Keys::N9; + case 0x41: return Input::Keys::A; + case 0x42: return Input::Keys::B; + case 0x43: return Input::Keys::C; + case 0x44: return Input::Keys::D; + case 0x45: return Input::Keys::E; + case 0x46: return Input::Keys::F; + case 0x47: return Input::Keys::G; + case 0x48: return Input::Keys::H; + case 0x49: return Input::Keys::I; + case 0x4A: return Input::Keys::J; + case 0x4B: return Input::Keys::K; + case 0x4C: return Input::Keys::L; + case 0x4D: return Input::Keys::M; + case 0x4E: return Input::Keys::N; + case 0x4F: return Input::Keys::O; + case 0x50: return Input::Keys::P; + case 0x51: return Input::Keys::Q; + case 0x52: return Input::Keys::R; + case 0x53: return Input::Keys::S; + case 0x54: return Input::Keys::T; + case 0x55: return Input::Keys::U; + case 0x56: return Input::Keys::V; + case 0x57: return Input::Keys::W; + case 0x58: return Input::Keys::X; + case 0x59: return Input::Keys::Y; + case 0x5A: return Input::Keys::Z; + case 0x60: return Input::Keys::KP0; + case 0x61: return Input::Keys::KP1; + case 0x62: return Input::Keys::KP2; + case 0x63: return Input::Keys::KP3; + case 0x64: return Input::Keys::KP4; + case 0x65: return Input::Keys::KP5; + case 0x66: return Input::Keys::KP6; + case 0x67: return Input::Keys::KP7; + case 0x68: return Input::Keys::KP8; + case 0x69: return Input::Keys::KP9; + case 0x6A: return Input::Keys::KP_MULTIPLY; + case 0x6B: return Input::Keys::KP_ADD; + case 0x6D: return Input::Keys::KP_SUBTRACT; + case 0x6E: return Input::Keys::KP_PERIOD; + case 0x6F: return Input::Keys::KP_DIVIDE; + case 0x70: return Input::Keys::F1; + case 0x71: return Input::Keys::F2; + case 0x72: return Input::Keys::F3; + case 0x73: return Input::Keys::F4; + case 0x74: return Input::Keys::F5; + case 0x75: return Input::Keys::F6; + case 0x76: return Input::Keys::F7; + case 0x77: return Input::Keys::F8; + case 0x78: return Input::Keys::F9; + case 0x79: return Input::Keys::F10; + case 0x7A: return Input::Keys::F11; + case 0x7B: return Input::Keys::F12; + case 0x90: return Input::Keys::NUM_LOCK; + case 0x91: return Input::Keys::SCROLL_LOCK; + case 0xA0: return Input::Keys::LSHIFT; + case 0xA1: return Input::Keys::RSHIFT; + case 0xA2: return Input::Keys::LCTRL; + case 0xA3: return Input::Keys::RCTRL; + + default: return Input::Keys::NONE; + } +} + +constexpr uint32_t RuntimePatches::VirtualKeys::InputKeyToVirtualKey(Input::Keys::InputKey input_key) { + switch (input_key) { +#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) + case Input::Keys::MOUSE_LEFT: return 0x1; + case Input::Keys::MOUSE_RIGHT: return 0x2; + case Input::Keys::MOUSE_MIDDLE: return 0x4; + case Input::Keys::MOUSE_XBUTTON1: return 0x5; + case Input::Keys::MOUSE_XBUTTON2: return 0x6; +#endif + case Input::Keys::BACKSPACE: return 0x8; + case Input::Keys::TAB: return 0x9; + case Input::Keys::RETURN: return 0xD; + case Input::Keys::SHIFT: return 0x10; + case Input::Keys::CTRL: return 0x11; + case Input::Keys::ALT: return 0x12; + case Input::Keys::PAUSE: return 0x13; + case Input::Keys::CAPS_LOCK: return 0x14; + case Input::Keys::ESCAPE: return 0x1B; + case Input::Keys::SPACE: return 0x20; + case Input::Keys::PGUP: return 0x21; + case Input::Keys::PGDN: return 0x22; + case Input::Keys::ENDS: return 0x23; + case Input::Keys::HOME: return 0x24; + case Input::Keys::LEFT: return 0x25; + case Input::Keys::UP: return 0x26; + case Input::Keys::RIGHT: return 0x27; + case Input::Keys::DOWN: return 0x28; + case Input::Keys::INSERT: return 0x2D; + case Input::Keys::DEL: return 0x2E; + case Input::Keys::N0: return 0x30; + case Input::Keys::N1: return 0x31; + case Input::Keys::N2: return 0x32; + case Input::Keys::N3: return 0x33; + case Input::Keys::N4: return 0x34; + case Input::Keys::N5: return 0x35; + case Input::Keys::N6: return 0x36; + case Input::Keys::N7: return 0x37; + case Input::Keys::N8: return 0x38; + case Input::Keys::N9: return 0x39; + case Input::Keys::A: return 0x41; + case Input::Keys::B: return 0x42; + case Input::Keys::C: return 0x43; + case Input::Keys::D: return 0x44; + case Input::Keys::E: return 0x45; + case Input::Keys::F: return 0x46; + case Input::Keys::G: return 0x47; + case Input::Keys::H: return 0x48; + case Input::Keys::I: return 0x49; + case Input::Keys::J: return 0x4A; + case Input::Keys::K: return 0x4B; + case Input::Keys::L: return 0x4C; + case Input::Keys::M: return 0x4D; + case Input::Keys::N: return 0x4E; + case Input::Keys::O: return 0x4F; + case Input::Keys::P: return 0x50; + case Input::Keys::Q: return 0x51; + case Input::Keys::R: return 0x52; + case Input::Keys::S: return 0x53; + case Input::Keys::T: return 0x54; + case Input::Keys::U: return 0x55; + case Input::Keys::V: return 0x56; + case Input::Keys::W: return 0x57; + case Input::Keys::X: return 0x58; + case Input::Keys::Y: return 0x59; + case Input::Keys::Z: return 0x5A; + case Input::Keys::KP0: return 0x60; + case Input::Keys::KP1: return 0x61; + case Input::Keys::KP2: return 0x62; + case Input::Keys::KP3: return 0x63; + case Input::Keys::KP4: return 0x64; + case Input::Keys::KP5: return 0x65; + case Input::Keys::KP6: return 0x66; + case Input::Keys::KP7: return 0x67; + case Input::Keys::KP8: return 0x68; + case Input::Keys::KP9: return 0x69; + case Input::Keys::KP_MULTIPLY: return 0x6A; + case Input::Keys::KP_ADD: return 0x6B; + case Input::Keys::KP_SUBTRACT: return 0x6D; + case Input::Keys::KP_PERIOD: return 0x6E; + case Input::Keys::KP_DIVIDE: return 0x6F; + case Input::Keys::F1: return 0x70; + case Input::Keys::F2: return 0x71; + case Input::Keys::F3: return 0x72; + case Input::Keys::F4: return 0x73; + case Input::Keys::F5: return 0x74; + case Input::Keys::F6: return 0x75; + case Input::Keys::F7: return 0x76; + case Input::Keys::F8: return 0x77; + case Input::Keys::F9: return 0x78; + case Input::Keys::F10: return 0x79; + case Input::Keys::F11: return 0x7A; + case Input::Keys::F12: return 0x7B; + case Input::Keys::NUM_LOCK: return 0x90; + case Input::Keys::SCROLL_LOCK: return 0x91; + case Input::Keys::LSHIFT: return 0xA0; + case Input::Keys::RSHIFT: return 0xA1; + case Input::Keys::LCTRL: return 0xA2; + case Input::Keys::RCTRL: return 0xA3; + + default: return 0; + } +} + +#endif diff --git a/src/maniac_patch.cpp b/src/maniac_patch.cpp index 92dc26361e..e54901ea2d 100644 --- a/src/maniac_patch.cpp +++ b/src/maniac_patch.cpp @@ -681,119 +681,6 @@ std::array ManiacPatch::GetKeyRange() { return pressed; } -bool ManiacPatch::GetKeyState(uint32_t key_id) { - Input::Keys::InputKey key; - - // see https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes - switch (key_id) { -#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) - case 0x1: key = Input::Keys::MOUSE_LEFT; break; - case 0x2: key = Input::Keys::MOUSE_RIGHT; break; - case 0x4: key = Input::Keys::MOUSE_MIDDLE; break; - case 0x5: key = Input::Keys::MOUSE_XBUTTON1; break; - case 0x6: key = Input::Keys::MOUSE_XBUTTON2; break; -#endif - case 0x8: key = Input::Keys::BACKSPACE; break; - case 0x9: key = Input::Keys::TAB; break; - case 0xD: key = Input::Keys::RETURN; break; - case 0x10: key = Input::Keys::SHIFT; break; - case 0x11: key = Input::Keys::CTRL; break; - case 0x12: key = Input::Keys::ALT; break; - case 0x13: key = Input::Keys::PAUSE; break; - case 0x14: key = Input::Keys::CAPS_LOCK; break; - case 0x1B: key = Input::Keys::ESCAPE; break; - case 0x20: key = Input::Keys::SPACE; break; - case 0x21: key = Input::Keys::PGUP; break; - case 0x22: key = Input::Keys::PGDN; break; - case 0x23: key = Input::Keys::ENDS; break; - case 0x24: key = Input::Keys::HOME; break; - case 0x25: key = Input::Keys::LEFT; break; - case 0x26: key = Input::Keys::UP; break; - case 0x27: key = Input::Keys::RIGHT; break; - case 0x28: key = Input::Keys::DOWN; break; - case 0x2D: key = Input::Keys::INSERT; break; - case 0x2E: key = Input::Keys::DEL; break; - case 0x30: key = Input::Keys::N0; break; - case 0x31: key = Input::Keys::N1; break; - case 0x32: key = Input::Keys::N2; break; - case 0x33: key = Input::Keys::N3; break; - case 0x34: key = Input::Keys::N4; break; - case 0x35: key = Input::Keys::N5; break; - case 0x36: key = Input::Keys::N6; break; - case 0x37: key = Input::Keys::N7; break; - case 0x38: key = Input::Keys::N8; break; - case 0x39: key = Input::Keys::N9; break; - case 0x41: key = Input::Keys::A; break; - case 0x42: key = Input::Keys::B; break; - case 0x43: key = Input::Keys::C; break; - case 0x44: key = Input::Keys::D; break; - case 0x45: key = Input::Keys::E; break; - case 0x46: key = Input::Keys::F; break; - case 0x47: key = Input::Keys::G; break; - case 0x48: key = Input::Keys::H; break; - case 0x49: key = Input::Keys::I; break; - case 0x4A: key = Input::Keys::J; break; - case 0x4B: key = Input::Keys::K; break; - case 0x4C: key = Input::Keys::L; break; - case 0x4D: key = Input::Keys::M; break; - case 0x4E: key = Input::Keys::N; break; - case 0x4F: key = Input::Keys::O; break; - case 0x50: key = Input::Keys::P; break; - case 0x51: key = Input::Keys::Q; break; - case 0x52: key = Input::Keys::R; break; - case 0x53: key = Input::Keys::S; break; - case 0x54: key = Input::Keys::T; break; - case 0x55: key = Input::Keys::U; break; - case 0x56: key = Input::Keys::V; break; - case 0x57: key = Input::Keys::W; break; - case 0x58: key = Input::Keys::X; break; - case 0x59: key = Input::Keys::Y; break; - case 0x5A: key = Input::Keys::Z; break; - case 0x60: key = Input::Keys::KP0; break; - case 0x61: key = Input::Keys::KP1; break; - case 0x62: key = Input::Keys::KP2; break; - case 0x63: key = Input::Keys::KP3; break; - case 0x64: key = Input::Keys::KP4; break; - case 0x65: key = Input::Keys::KP5; break; - case 0x66: key = Input::Keys::KP6; break; - case 0x67: key = Input::Keys::KP7; break; - case 0x68: key = Input::Keys::KP8; break; - case 0x69: key = Input::Keys::KP9; break; - case 0x6A: key = Input::Keys::KP_MULTIPLY; break; - case 0x6B: key = Input::Keys::KP_ADD; break; - case 0x6D: key = Input::Keys::KP_SUBTRACT; break; - case 0x6E: key = Input::Keys::KP_PERIOD; break; - case 0x6F: key = Input::Keys::KP_DIVIDE; break; - case 0x70: key = Input::Keys::F1; break; - case 0x71: key = Input::Keys::F2; break; - case 0x72: key = Input::Keys::F3; break; - case 0x73: key = Input::Keys::F4; break; - case 0x74: key = Input::Keys::F5; break; - case 0x75: key = Input::Keys::F6; break; - case 0x76: key = Input::Keys::F7; break; - case 0x77: key = Input::Keys::F8; break; - case 0x78: key = Input::Keys::F9; break; - case 0x79: key = Input::Keys::F10; break; - case 0x7A: key = Input::Keys::F11; break; - case 0x7B: key = Input::Keys::F12; break; - case 0x90: key = Input::Keys::NUM_LOCK; break; - case 0x91: key = Input::Keys::SCROLL_LOCK; break; - case 0xA0: key = Input::Keys::LSHIFT; break; - case 0xA1: key = Input::Keys::RSHIFT; break; - case 0xA2: key = Input::Keys::LCTRL; break; - case 0xA3: key = Input::Keys::RCTRL; break; - default: - Output::Debug("Maniac KeyInputProcEx: Unsupported keycode {}", key_id); - key = Input::Keys::NONE; - break; - } - - return Input::IsRawKeyPressed(key); - - - -} - bool ManiacPatch::CheckString(std::string_view str_l, std::string_view str_r, int op, bool ignore_case) { auto check = [op](const auto& l, const auto& r) { switch (op) { diff --git a/src/maniac_patch.h b/src/maniac_patch.h index 34d26bc16c..b458c87d34 100644 --- a/src/maniac_patch.h +++ b/src/maniac_patch.h @@ -35,8 +35,6 @@ namespace ManiacPatch { std::array GetKeyRange(); - bool GetKeyState(uint32_t key_id); - bool CheckString(std::string_view str_l, std::string_view str_r, int op, bool ignore_case); std::string_view GetLcfName(int data_type, int id, bool is_dynamic); diff --git a/src/player.cpp b/src/player.cpp index 8a209c678e..ce96529224 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -685,7 +685,8 @@ Game_Config Player::ParseCommandLine() { void Player::CreateGameObjects() { // Parse game specific settings CmdlineParser cp(arguments); - game_config = Game_ConfigGame::Create(cp); + game_config = Game_ConfigGame(); + game_config.Initialize(cp); // Reinit MIDI MidiDecoder::Reset(); @@ -1463,6 +1464,8 @@ Engine options: --patch-direct-menu VAR Directly access subscreens of the default menu by setting VAR. + --patch-encounter-alert VAR + Set troop id to variable VAR and skip random battles. --patch-dynrpg Enable support of DynRPG patch by Cherry (very limited). --patch-easyrpg Enable EasyRPG extensions. --patch-key-patch Enable Key Patch by Ineluki. diff --git a/src/scene_battle_rpg2k.cpp b/src/scene_battle_rpg2k.cpp index 0185282773..d9b235c8c7 100644 --- a/src/scene_battle_rpg2k.cpp +++ b/src/scene_battle_rpg2k.cpp @@ -882,9 +882,11 @@ Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionVict pm.PushPageEnd(); - for (auto& ally: ally_battlers) { - Game_Actor* actor = static_cast(ally); - actor->ChangeExp(actor->GetExp() + exp, &pm); + for (int i = 0; i < static_cast(ally_battlers.size()); ++i) { + Game_Actor* actor = static_cast(ally_battlers[i]); + int exp_gain = exp; + RuntimePatches::EXPlus::ModifyExpGain(*actor, exp_gain); + actor->ChangeExp(actor->GetExp() + exp_gain, &pm); } Main_Data::game_party->GainGold(money); for (auto& item: drops) { diff --git a/src/scene_battle_rpg2k3.cpp b/src/scene_battle_rpg2k3.cpp index 55a3ffaab1..32c2d0fee5 100644 --- a/src/scene_battle_rpg2k3.cpp +++ b/src/scene_battle_rpg2k3.cpp @@ -1870,7 +1870,9 @@ Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionVi for (auto* actor: Main_Data::game_party->GetActors()) { if (actor->Exists()) { - actor->ChangeExp(actor->GetExp() + exp, &pm); + int exp_gain = exp; + RuntimePatches::EXPlus::ModifyExpGain(*actor, exp_gain); + actor->ChangeExp(actor->GetExp() + exp_gain, &pm); } }