Skip to content

Allow Running From Trainer Battles

voloved edited this page Apr 8, 2023 · 13 revisions

By devolov
Goal: Allow running from a battle with an NPC trainer. When you run from them, you can still talk to them to battle, they just won't "see" you until you either change maps or walk in front of someone else who can battle you.

bar1

------------------------------ src/battle_setup.c ------------------------------
index 03bc0a08e7..e17db26d14 100644
@@ -113,8 +113,9 @@ EWRAM_DATA static u8 *sTrainerBattleEndScript = NULL;
 EWRAM_DATA static u8 *sTrainerABattleScriptRetAddr = NULL;
 EWRAM_DATA static u8 *sTrainerBBattleScriptRetAddr = NULL;
 EWRAM_DATA static bool8 sShouldCheckTrainerBScript = FALSE;
 EWRAM_DATA static u8 sNoOfPossibleTrainerRetScripts = 0;
+EWRAM_DATA static u32 sPrevTrainerSeeing = 0;
 
 // The first transition is used if the enemy pokemon are lower level than our pokemon.
 // Otherwise, the second transition is used.
 static const u8 sBattleTransitionTable_Wild[][2] =
@@ -1271,9 +1272,13 @@ void SetUpTwoTrainersBattle(void)
 
 bool32 GetTrainerFlagFromScriptPointer(const u8 *data)
 {
     u32 flag = TrainerBattleLoadArg16(data + 2);
-    return FlagGet(TRAINER_FLAGS_START + flag);
+    if (flag != sPrevTrainerSeeing){
+        sPrevTrainerSeeing = flag;
+        FlagClear(FLAG_RAN_FROM_TRAINER);
+    }
+    return (FlagGet(TRAINER_FLAGS_START + flag) || FlagGet(FLAG_RAN_FROM_TRAINER));
 }
 
 // Set trainer's movement type so they stop and remain facing that direction
 // Note: Only for trainers who are spoken to directly
@@ -1412,10 +1422,16 @@ static void CB2_EndTrainerBattle(void)
     {
         SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic);
         if (!InBattlePyramid() && !InTrainerHillChallenge())
         {
-            RegisterTrainerInMatchCall();
-            SetBattledTrainersFlags();
+            if (gBattleOutcome == B_OUTCOME_RAN){
+                FlagSet(FLAG_RAN_FROM_TRAINER);
+            }
+            else
+            {
+                FlagClear(FLAG_RAN_FROM_TRAINER);
+                RegisterTrainerInMatchCall();
+                SetBattledTrainersFlags();
+            }
         }
     }
 }
------------------------------ src/trainer_see.c ------------------------------
index 7b7533a337..642b6bd0f1 100644
@@ -421,16 +421,14 @@ static u8 CheckTrainer(u8 objectEventId)
     {
         if (GetHillTrainerFlag(objectEventId))
             return 0;
     }
-    else
-    {
-        if (GetTrainerFlagFromScriptPointer(scriptPtr))
-            return 0;
-    }

     approachDistance = GetTrainerApproachDistance(&gObjectEvents[objectEventId]);

     if (approachDistance != 0)
     {
+        if (GetTrainerFlagFromScriptPointer(scriptPtr))
+            return 0;
         if (scriptPtr[1] == TRAINER_BATTLE_DOUBLE
             || scriptPtr[1] == TRAINER_BATTLE_REMATCH_DOUBLE
------------------------------- src/event_data.c -------------------------------
index 50f6f68da4..4a00bcac40 100644
@@ -44,8 +44,9 @@ void InitEventData(void)
 void ClearTempFieldEventData(void)
 {
     memset(gSaveBlock1Ptr->flags + (TEMP_FLAGS_START / 8), 0, TEMP_FLAGS_SIZE);
     memset(gSaveBlock1Ptr->vars + ((TEMP_VARS_START - VARS_START) * 2), 0, TEMP_VARS_SIZE);
+    FlagClear(FLAG_RAN_FROM_TRAINER);
     FlagClear(FLAG_SYS_ENC_UP_ITEM);
     FlagClear(FLAG_SYS_ENC_DOWN_ITEM);
     FlagClear(FLAG_SYS_USE_STRENGTH);
     FlagClear(FLAG_SYS_CTRL_OBJ_DELETE);
-------------------------- include/constants/flags.h --------------------------
index 7f36cacc8f..2a25e074f6 100644
@@ -1241,9 +1241,9 @@
-#define FLAG_UNUSED_0x4AC                                           0x4AC // Unused Flag
+#define FLAG_RAN_FROM_TRAINER                                       0x4AC
 #define FLAG_UNUSED_0x4AD                                           0x4AD // Unused Flag
 #define FLAG_UNUSED_0x4AE                                           0x4AE // Unused Flag
 #define FLAG_UNUSED_0x4AF                                           0x4AF // Unused Flag
 #define FLAG_UNUSED_0x4B0                                           0x4B0 // Unused Flag
------------------------------ src/battle_main.c ------------------------------
@@ -4552,14 +4577,26 @@ static void HandleTurnActionSelectionState(void)
                     *(gBattleStruct->selectionScriptFinished + gActiveBattler) = FALSE;
                     *(gBattleStruct->stateIdAfterSelScript + gActiveBattler) = STATE_BEFORE_ACTION_CHOSEN;
                     return;
                 }
                 else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER
                          && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))
                          && gBattleBufferB[gActiveBattler][1] == B_ACTION_RUN)
                 {
-                    BattleScriptExecute(BattleScript_PrintCantRunFromTrainer);
-                    gBattleCommunication[gActiveBattler] = STATE_BEFORE_ACTION_CHOSEN;
+                    ;  // Allow to passthrough to the below logic of IsRunningFromBattleImpossible
                 }
-                else if (IsRunningFromBattleImpossible() != BATTLE_RUN_SUCCESS
+                if (IsRunningFromBattleImpossible() != BATTLE_RUN_SUCCESS
                          && gBattleBufferB[gActiveBattler][1] == B_ACTION_RUN)
                 {
                     gSelectionBattleScripts[gActiveBattler] = BattleScript_PrintCantEscapeFromBattle;
                     gBattleCommunication[gActiveBattler] = STATE_SELECTION_SCRIPT;
                     *(gBattleStruct->selectionScriptFinished + gActiveBattler) = FALSE;
------------------------------ src/battle_setup.c ------------------------------
index 184f1f2336..be89322a0a 100644
@@ -1497,8 +1497,10 @@ const u8 *BattleSetup_GetScriptAddrAfterBattle(void)
 }
 
 const u8 *BattleSetup_GetTrainerPostBattleScript(void)
 {
+    if (FlagGet(FLAG_RAN_FROM_TRAINER))
+        return EventScript_TryGetTrainerScript;  // Stops things like registering to Pokenav after the battle ends
     if (sShouldCheckTrainerBScript)
     {
         sShouldCheckTrainerBScript = FALSE;
         if (sTrainerBBattleScriptRetAddr != NULL)
----------------------------- data/trainer_script.inc -----------------------------
	delay 30
	return

EventScript_TryGetTrainerScript::
+	goto_if_set FLAG_RAN_FROM_TRAINER, EventScript_TryGetTrainerScript_Cont
	special ShouldTryGetTrainerScript
	goto_if_eq VAR_RESULT, TRUE, EventScript_GotoTrainerScript
+EventScript_TryGetTrainerScript_Cont:
	releaseall
	end

EventScript_GotoTrainerScript::
	gotobeatenscript

Make Important Trainer Battles Ones That You Still Can't Run From

Some battles (Battle Frontier, Rival, Elite Four), may be ones that you don't want to run from. The following tweak can be added in battle_main.c:

static void HandleEndTurn_BattleLost(void);
static void HandleEndTurn_RanFromBattle(void);
static void HandleEndTurn_MonFled(void);
static void HandleEndTurn_FinishBattle(void);
static void SpriteCB_UnusedBattleInit(struct Sprite *sprite);
static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite);
+static bool8 IsTrainerCantRunFrom(void);

EWRAM_DATA u16 gBattle_BG0_X = 0;
        return BATTLE_RUN_FORBIDDEN;
    }
    return BATTLE_RUN_SUCCESS;
}

+static u8 IsTrainerCantRunFrom(void){
+    u8 trainerClass;
+    if (gBattleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_TRAINER_HILL))
+        return BATTLE_RUN_FORBIDDEN;
+    trainerClass = gTrainers[gTrainerBattleOpponent_A].trainerClass;
+    switch (trainerClass)
+    {
+    case TRAINER_CLASS_AQUA_LEADER:
+    case TRAINER_CLASS_MAGMA_LEADER:
+    case TRAINER_CLASS_TEAM_AQUA:
+    case TRAINER_CLASS_TEAM_MAGMA:
+    case TRAINER_CLASS_AQUA_ADMIN:
+    case TRAINER_CLASS_MAGMA_ADMIN:
+    case TRAINER_CLASS_LEADER:
+    case TRAINER_CLASS_CHAMPION:
+    case TRAINER_CLASS_RIVAL:
+    case TRAINER_CLASS_ELITE_FOUR:
+        return BATTLE_RUN_FORBIDDEN;
+    default:
+        return BATTLE_RUN_SUCCESS;
+    }
+}
+
void SwitchPartyOrder(u8 battler)
{
    s32 i;
                else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER 
                         && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))
                         && gBattleBufferB[gActiveBattler][1] == B_ACTION_RUN)
                {
-                   gBattleCommunication[gActiveBattler]++;
+                   if (IsTrainerCantRunFrom())
+                   {
+                       BattleScriptExecute(BattleScript_PrintCantRunFromTrainer);
+                       gBattleCommunication[gActiveBattler] = STATE_BEFORE_ACTION_CHOSEN;
+                   }
+                   else
+                   {
+                       ;  // Allow to passthrough to the below logic of IsRunningFromBattleImpossible
+                   }
                }

For proper wording, in battle_messages.c:

----------------------------- src/battle_message.c -----------------------------
index 11f7f8d5f3..8cc21e0fc5 100644
@@ -332,9 +332,10 @@ static const u8 sText_PlayerLostToTwo[] = _("Player lost to {B_LINK_OPPONENT1_NA
 static const u8 sText_PlayerBattledToDrawLinkTrainer[] = _("Player battled to a draw against\n{B_LINK_OPPONENT1_NAME}!");
 static const u8 sText_PlayerBattledToDrawVsTwo[] = _("Player battled to a draw against\n{B_LINK_OPPONENT1_NAME} and {B_LINK_OPPONENT2_NAME}!");
 static const u8 sText_WildFled[] = _("{PLAY_SE SE_FLEE}{B_LINK_OPPONENT1_NAME} fled!");
 static const u8 sText_TwoWildFled[] = _("{PLAY_SE SE_FLEE}{B_LINK_OPPONENT1_NAME} and\n{B_LINK_OPPONENT2_NAME} fled!");
-static const u8 sText_NoRunningFromTrainers[] = _("No! There's no running\nfrom a TRAINER battle!\p");
+static const u8 sText_NoRunningFromTrainers[] = _("No! There's no running\nfrom this TRAINER battle!\p");
 static const u8 sText_CantEscape[] = _("Can't escape!\p");
 static const u8 sText_DontLeaveBirch[] = _("PROF. BIRCH: Don't leave me like this!\p");
 static const u8 sText_ButNothingHappened[] = _("But nothing happened!");
 static const u8 sText_ButItFailed[] = _("But it failed!");
Clone this wiki locally