From 2087edcd774bc6902173273166c92d2ee9f93b54 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 24 Nov 2023 14:49:42 +0100 Subject: [PATCH 1/5] Battle: Initial escape chance is between 0-100, not 64-100. Fix #3122 --- src/scene_battle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scene_battle.cpp b/src/scene_battle.cpp index d4f4a8f7a7..149022488e 100644 --- a/src/scene_battle.cpp +++ b/src/scene_battle.cpp @@ -143,7 +143,7 @@ void Scene_Battle::InitEscapeChance() { int avg_actor_agi = Main_Data::game_party->GetAverageAgility(); int base_chance = Utils::RoundTo(100.0 * static_cast(avg_enemy_agi) / static_cast(avg_actor_agi)); - this->escape_chance = Utils::Clamp(150 - base_chance, 64, 100); + this->escape_chance = Utils::Clamp(150 - base_chance, 0, 100); } bool Scene_Battle::TryEscape() { From 11449f01243630ccdcfba0f0b8cdc1824260c40b Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 24 Nov 2023 14:54:10 +0100 Subject: [PATCH 2/5] Battle 2k: Do not schedule actions for hidden enemies. Before it was possible that an enemy appeared during the turn and made an action but only when slower. Fix #3158 --- src/scene_battle_rpg2k.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/scene_battle_rpg2k.cpp b/src/scene_battle_rpg2k.cpp index 430ad18b31..12a768b48f 100644 --- a/src/scene_battle_rpg2k.cpp +++ b/src/scene_battle_rpg2k.cpp @@ -1828,6 +1828,10 @@ void Scene_Battle_Rpg2k::CreateEnemyActions() { } for (auto* enemy : Main_Data::game_enemyparty->GetEnemies()) { + if (enemy->IsHidden()) { + continue; + } + if (!EnemyAi::SetStateRestrictedAction(*enemy)) { if (enemy->GetEnemyAi() == -1) { enemyai_algos[default_enemyai_algo]->SetEnemyAiAction(*enemy); From 44378750efbadcf24f649313439cd76d240aaffb Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 24 Nov 2023 14:56:21 +0100 Subject: [PATCH 3/5] Battle: Do not select invalid actions. This happened when the valid actions were close to zero. Fix #3146 --- src/enemyai.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/enemyai.cpp b/src/enemyai.cpp index 872d27c0f0..01d9d342cd 100644 --- a/src/enemyai.cpp +++ b/src/enemyai.cpp @@ -312,7 +312,9 @@ void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs) { if (max_prio) { for (auto& pr: prios) { - pr = std::max(0, pr - max_prio + 10); + if (pr > 0) { + pr = std::max(0, pr - max_prio + 10); + } } } From 01975b51516e1fb1cc7d23be2ca5aa71336fa6ef Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 24 Nov 2023 15:22:20 +0100 Subject: [PATCH 4/5] Battle Interpreter: Add sanity checks to prevent crashes on invalid actors/enemies --- src/game_interpreter_battle.cpp | 116 +++++++++++++++++++++++--------- src/game_interpreter_battle.h | 1 + 2 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index 1e28cf5b2e..3a8b9cde3a 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -25,6 +25,7 @@ #include "game_system.h" #include "game_variables.h" #include +#include "main_data.h" #include "output.h" #include "player.h" #include "game_map.h" @@ -69,15 +70,25 @@ bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageConditio return false; if (condition.flags.turn_enemy) { - const auto& enemy = (*Main_Data::game_enemyparty)[condition.turn_enemy_id]; - if (source && source != &enemy) + const auto* enemy = Main_Data::game_enemyparty->GetEnemy(condition.turn_enemy_id); + if (!enemy) { + Output::Warning("AreConditionsMet: Invalid enemy ID {}", condition.turn_enemy_id); return false; - if (!Game_Battle::CheckTurns(enemy.GetBattleTurn(), condition.turn_enemy_b, condition.turn_enemy_a)) + } + + if (source && source != enemy) + return false; + if (!Game_Battle::CheckTurns(enemy->GetBattleTurn(), condition.turn_enemy_b, condition.turn_enemy_a)) return false; } if (condition.flags.turn_actor) { const auto* actor = Main_Data::game_actors->GetActor(condition.turn_actor_id); + if (!actor) { + Output::Warning("AreConditionsMet: Invalid actor ID {}", condition.turn_actor_id); + return false; + } + if (source && source != actor) return false; if (!Game_Battle::CheckTurns(actor->GetBattleTurn(), condition.turn_actor_b, condition.turn_actor_a)) @@ -91,14 +102,24 @@ bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageConditio } if (condition.flags.enemy_hp) { - Game_Battler& enemy = (*Main_Data::game_enemyparty)[condition.enemy_id]; - int result = 100 * enemy.GetHp() / enemy.GetMaxHp(); + const auto* enemy = Main_Data::game_enemyparty->GetEnemy(condition.enemy_id); + if (!enemy) { + Output::Warning("AreConditionsMet: Invalid enemy ID {}", condition.enemy_id); + return false; + } + + int result = 100 * enemy->GetHp() / enemy->GetMaxHp(); if (result < condition.enemy_hp_min || result > condition.enemy_hp_max) return false; } if (condition.flags.actor_hp) { - Game_Actor* actor = Main_Data::game_actors->GetActor(condition.actor_id); + const auto* actor = Main_Data::game_actors->GetActor(condition.actor_id); + if (!actor) { + Output::Warning("AreConditionsMet: Invalid actor ID {}", condition.actor_id); + return false; + } + int result = 100 * actor->GetHp() / actor->GetMaxHp(); if (result < condition.actor_hp_min || result > condition.actor_hp_max) return false; @@ -108,6 +129,11 @@ bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageConditio if (!source) return false; const auto* actor = Main_Data::game_actors->GetActor(condition.command_actor_id); + if (!actor) { + Output::Warning("AreConditionsMet: Invalid actor ID {}", condition.command_actor_id); + return false; + } + if (source != actor) return false; if (condition.command_id != actor->GetLastBattleAction()) @@ -246,6 +272,11 @@ bool Game_Interpreter_Battle::CommandForceFlee(lcf::rpg::EventCommand const& com case 2: if (!check || Game_Battle::GetBattleCondition() != lcf::rpg::System::BattleCondition_surround) { auto* enemy = Main_Data::game_enemyparty->GetEnemy(com.parameters[1]); + if (!enemy) { + Output::Warning("ForceFlee: Invalid enemy ID {}", com.parameters[1]); + return true; + } + if (enemy->Exists()) { enemy->SetHidden(true); enemy->SetDeathTimer(); @@ -285,13 +316,17 @@ bool Game_Interpreter_Battle::CommandEnableCombo(lcf::rpg::EventCommand const& c } bool Game_Interpreter_Battle::CommandChangeMonsterHP(lcf::rpg::EventCommand const& com) { - int id = com.parameters[0]; - Game_Enemy& enemy = (*Main_Data::game_enemyparty)[id]; + auto* enemy = Main_Data::game_enemyparty->GetEnemy(com.parameters[0]); + if (!enemy) { + Output::Warning("ChangeMonsterHP: Invalid enemy ID {}", com.parameters[0]); + return true; + } + bool lose = com.parameters[1] > 0; bool lethal = com.parameters[4] > 0; - int hp = enemy.GetHp(); + int hp = enemy->GetHp(); - if (enemy.IsDead()) + if (enemy->IsDead()) return true; int change = 0; @@ -311,26 +346,30 @@ bool Game_Interpreter_Battle::CommandChangeMonsterHP(lcf::rpg::EventCommand cons change = -change; } - enemy.ChangeHp(change, lethal); + enemy->ChangeHp(change, lethal); auto& scene = Scene::instance; if (scene) { - scene->OnEventHpChanged(&enemy, change); + scene->OnEventHpChanged(enemy, change); } - if (enemy.IsDead()) { + if (enemy->IsDead()) { Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyKill)); - enemy.SetDeathTimer(); + enemy->SetDeathTimer(); } return true; } bool Game_Interpreter_Battle::CommandChangeMonsterMP(lcf::rpg::EventCommand const& com) { - int id = com.parameters[0]; - Game_Enemy& enemy = (*Main_Data::game_enemyparty)[id]; + auto* enemy = Main_Data::game_enemyparty->GetEnemy(com.parameters[0]); + if (!enemy) { + Output::Warning("ChangeMonsterMP: Invalid enemy ID {}", com.parameters[0]); + return true; + } + bool lose = com.parameters[1] > 0; - int sp = enemy.GetSp(); + int sp = enemy->GetSp(); int change = 0; switch (com.parameters[2]) { @@ -347,27 +386,37 @@ bool Game_Interpreter_Battle::CommandChangeMonsterMP(lcf::rpg::EventCommand cons sp += change; - enemy.SetSp(sp); + enemy->SetSp(sp); return true; } bool Game_Interpreter_Battle::CommandChangeMonsterCondition(lcf::rpg::EventCommand const& com) { - Game_Enemy& enemy = (*Main_Data::game_enemyparty)[com.parameters[0]]; + auto* enemy = Main_Data::game_enemyparty->GetEnemy(com.parameters[0]); + if (!enemy) { + Output::Warning("ChangeMonsterCondition: Invalid enemy ID {}", com.parameters[0]); + return true; + } + bool remove = com.parameters[1] > 0; int state_id = com.parameters[2]; if (remove) { // RPG_RT BUG: Monster dissapears immediately and doesn't animate death - enemy.RemoveState(state_id, false); + enemy->RemoveState(state_id, false); } else { - enemy.AddState(state_id, true); + enemy->AddState(state_id, true); } return true; } bool Game_Interpreter_Battle::CommandShowHiddenMonster(lcf::rpg::EventCommand const& com) { - Game_Enemy& enemy = (*Main_Data::game_enemyparty)[com.parameters[0]]; - enemy.SetHidden(false); + auto* enemy = Main_Data::game_enemyparty->GetEnemy(com.parameters[0]); + if (!enemy) { + Output::Warning("ShowHiddenMonster: Invalid enemy ID {}", com.parameters[0]); + return true; + } + + enemy->SetHidden(false); return true; } @@ -482,25 +531,26 @@ bool Game_Interpreter_Battle::CommandConditionalBranchBattle(lcf::rpg::EventComm break; case 2: { // Hero can act - Game_Actor* actor = Main_Data::game_actors->GetActor(com.parameters[1]); + const auto* actor = Main_Data::game_actors->GetActor(com.parameters[1]); if (!actor) { Output::Warning("ConditionalBranchBattle: Invalid actor ID {}", com.parameters[1]); - // Use Else Branch - SetSubcommandIndex(com.indent, 1); - SkipToNextConditional({Cmd::ElseBranch_B, Cmd::EndBranch_B}, com.indent); - return true; + } else { + result = actor->CanAct(); } - - result = actor->CanAct(); break; } - case 3: + case 3: { // Monster can act - if (com.parameters[1] < Main_Data::game_enemyparty->GetBattlerCount()) { - result = (*Main_Data::game_enemyparty)[com.parameters[1]].CanAct(); + const auto* enemy = Main_Data::game_enemyparty->GetEnemy(com.parameters[1]); + + if (!enemy) { + Output::Warning("ConditionalBranchBattle: Invalid enemy ID {}", com.parameters[1]); + } else { + result = enemy->CanAct(); } break; + } case 4: // Monster is the current target result = (targets_single_enemy && target_enemy_index == com.parameters[1]); diff --git a/src/game_interpreter_battle.h b/src/game_interpreter_battle.h index 547164c412..96ec7d191f 100644 --- a/src/game_interpreter_battle.h +++ b/src/game_interpreter_battle.h @@ -29,6 +29,7 @@ #include "system.h" #include "game_interpreter.h" +class Game_Enemy; class Game_Event; class Game_CommonEvent; From 1367c230a96b408c785f4c32edd42c4db5098fad Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 24 Nov 2023 15:26:03 +0100 Subject: [PATCH 5/5] Battle Interpreter: Guard 2k3 event commands --- src/game_interpreter_battle.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index 3a8b9cde3a..7fba49a61d 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -69,7 +69,7 @@ bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageConditio if (condition.flags.turn && !Game_Battle::CheckTurns(Game_Battle::GetTurn(), condition.turn_b, condition.turn_a)) return false; - if (condition.flags.turn_enemy) { + if (Player::IsRPG2k3Commands() && condition.flags.turn_enemy) { const auto* enemy = Main_Data::game_enemyparty->GetEnemy(condition.turn_enemy_id); if (!enemy) { Output::Warning("AreConditionsMet: Invalid enemy ID {}", condition.turn_enemy_id); @@ -82,7 +82,7 @@ bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageConditio return false; } - if (condition.flags.turn_actor) { + if (Player::IsRPG2k3Commands() && condition.flags.turn_actor) { const auto* actor = Main_Data::game_actors->GetActor(condition.turn_actor_id); if (!actor) { Output::Warning("AreConditionsMet: Invalid actor ID {}", condition.turn_actor_id); @@ -95,7 +95,7 @@ bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageConditio return false; } - if (condition.flags.fatigue) { + if (Player::IsRPG2k3Commands() && condition.flags.fatigue) { int fatigue = Main_Data::game_party->GetFatigue(); if (fatigue < condition.fatigue_min || fatigue > condition.fatigue_max) return false; @@ -125,7 +125,7 @@ bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageConditio return false; } - if (condition.flags.command_actor) { + if (Player::IsRPG2k3Commands() && condition.flags.command_actor) { if (!source) return false; const auto* actor = Main_Data::game_actors->GetActor(condition.command_actor_id); @@ -553,11 +553,13 @@ bool Game_Interpreter_Battle::CommandConditionalBranchBattle(lcf::rpg::EventComm } case 4: // Monster is the current target - result = (targets_single_enemy && target_enemy_index == com.parameters[1]); + if (Player::IsRPG2k3Commands()) { + result = (targets_single_enemy && target_enemy_index == com.parameters[1]); + } break; case 5: { // Hero uses the ... command - if (current_actor_id == com.parameters[1]) { + if (Player::IsRPG2k3Commands() && current_actor_id == com.parameters[1]) { auto *actor = Main_Data::game_actors->GetActor(current_actor_id); if (actor) { result = actor->GetLastBattleAction() == com.parameters[2];