diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 994ba7e6ee..d4ab0e8e61 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -10120,3 +10120,15 @@ BattleScript_SleepClausePreventsEnd:: printstring STRINGID_BLOCKEDBYSLEEPCLAUSE waitmessage B_WAIT_TIME_LONG end2 + +BattleScript_QuestionForfeitBattle:: + printselectionstring STRINGID_QUESTIONFORFEITBATTLE + forfeityesnobox BS_ATTACKER + endselectionscript + +BattleScript_ForfeitBattleGaveMoney:: + getmoneyreward + printstring STRINGID_FORFEITBATTLEGAVEMONEY + waitmessage B_WAIT_TIME_LONG + end2 + diff --git a/include/battle_main.h b/include/battle_main.h index 18f2210588..2d2483d84b 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -87,6 +87,8 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer void ModifyPersonalityForNature(u32 *personality, u32 newNature); u32 GeneratePersonalityForGender(u32 gender, u32 species); void CustomTrainerPartyAssignMoves(struct Pokemon *mon, const struct TrainerMon *partyEntry); +bool32 CanPlayerForfeitNormalTrainerBattle(void); +bool32 DidPlayerForfeitNormalTrainerBattle(void); extern struct MultiPartnerMenuPokemon gMultiPartnerParty[MULTI_PARTY_SIZE]; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index e02a562be5..027b69ef1f 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -856,5 +856,7 @@ extern const u8 BattleScript_EffectSpicyExtract[]; extern const u8 BattleScript_DamageToQuarterTargetHP[]; extern const u8 BattleScript_EffectFickleBeam[]; extern const u8 BattleScript_FickleBeamDoubled[]; +extern const u8 BattleScript_QuestionForfeitBattle[]; +extern const u8 BattleScript_ForfeitBattleGaveMoney[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/include/config/battle.h b/include/config/battle.h index 0aa37db354..33dbade379 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -256,6 +256,7 @@ #define B_HIDE_HEALTHBOX_IN_ANIMS TRUE // If set to TRUE, hides healthboxes during move animations. #define B_WAIT_TIME_MULTIPLIER 16 // This determines how long text pauses in battle last. Vanilla is 16. Lower values result in faster battles. #define B_QUICK_MOVE_CURSOR_TO_RUN FALSE // If set to TRUE, pushing B in the battle options against a wild encounter will move the cursor to the run option +#define B_RUN_TRAINER_BATTLE TRUE // If set to TRUE, players can run from Trainer battles. This is treated as a whiteout. #define B_MOVE_DESCRIPTION_BUTTON L_BUTTON // If set to a button other than B_LAST_USED_BALL_BUTTON, pressing this button will open the move description menu #define B_SHOW_USELESS_Z_MOVE_INFO FALSE // If set to TRUE, Z-moves without additional effects like newer gen status moves will say "no additional effect" #define B_ANIMATE_MON_AFTER_KO TRUE // If set to TRUE, if a Pokémon on the opposite site faints, the non-fainted Pokemon will display a victory animation. diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index db5ee7b16e..9eb09a2fb4 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -732,8 +732,10 @@ #define STRINGID_PKMNGIGANTAMAXED 730 #define STRINGID_TIMETODYNAMAX 731 #define STRINGID_TIMETOGIGANTAMAX 732 +#define STRINGID_QUESTIONFORFEITBATTLE 733 +#define STRINGID_FORFEITBATTLEGAVEMONEY 734 -#define BATTLESTRINGS_COUNT 733 +#define BATTLESTRINGS_COUNT 735 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/include/strings.h b/include/strings.h index 588d513d4b..2bc6b23563 100644 --- a/include/strings.h +++ b/include/strings.h @@ -2414,6 +2414,8 @@ extern const u8 gText_PM[]; // FRLG whiteout extern const u8 gText_PlayerScurriedToCenter[]; extern const u8 gText_PlayerScurriedBackHome[]; +extern const u8 gText_PlayerRegroupCenter[]; +extern const u8 gText_PlayerRegroupHome[]; extern const u8 gText_Relearn[]; // move relearner from summary screen extern const u8 gText_Rename[]; // change nickname from summary screen diff --git a/src/battle_main.c b/src/battle_main.c index d9e811014b..104b49b434 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4230,6 +4230,15 @@ static void HandleTurnActionSelectionState(void) gChosenActionByBattler[battler] = B_ACTION_NOTHING_FAINTED; // Not fainted, but it cannot move, because of the throwing ball. gBattleCommunication[battler] = STATE_WAIT_ACTION_CONFIRMED_STANDBY; } + else if (B_RUN_TRAINER_BATTLE + && gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && position == B_POSITION_PLAYER_RIGHT + && gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)] == B_ACTION_RUN + && gChosenActionByBattler[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)] != B_ACTION_NOTHING_FAINTED) + { + gChosenActionByBattler[battler] = B_ACTION_USE_MOVE; + gBattleCommunication[battler] = STATE_WAIT_ACTION_CONFIRMED_STANDBY; + } else { gBattleStruct->itemPartyIndex[battler] = PARTY_SIZE; @@ -4413,9 +4422,17 @@ static void HandleTurnActionSelectionState(void) gBattleStruct->stateIdAfterSelScript[battler] = STATE_BEFORE_ACTION_CHOSEN; return; } + else if (CanPlayerForfeitNormalTrainerBattle() && gBattleResources->bufferB[battler][1] == B_ACTION_RUN) + { + gSelectionBattleScripts[battler] = BattleScript_QuestionForfeitBattle; + gBattleCommunication[battler] = STATE_SELECTION_SCRIPT_MAY_RUN; + gBattleStruct->selectionScriptFinished[battler] = FALSE; + gBattleStruct->stateIdAfterSelScript[battler] = STATE_BEFORE_ACTION_CHOSEN; + return; + } else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER - && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK)) - && gBattleResources->bufferB[battler][1] == B_ACTION_RUN) + && !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK)) + && gBattleResources->bufferB[battler][1] == B_ACTION_RUN) { BattleScriptExecute(BattleScript_PrintCantRunFromTrainer); gBattleCommunication[battler] = STATE_BEFORE_ACTION_CHOSEN; @@ -5492,6 +5509,11 @@ static void HandleEndTurn_RanFromBattle(void) gBattlescriptCurrInstr = BattleScript_PrintPlayerForfeited; gBattleOutcome = B_OUTCOME_FORFEITED; } + else if (CanPlayerForfeitNormalTrainerBattle()) + { + gBattlescriptCurrInstr = BattleScript_ForfeitBattleGaveMoney; + gBattleOutcome = B_OUTCOME_FORFEITED; + } else { switch (gProtectStructs[gBattlerAttacker].fleeType) @@ -6114,3 +6136,25 @@ static s32 Factorial(s32 n) f *= i; return f; } + +bool32 CanPlayerForfeitNormalTrainerBattle(void) +{ + if (!B_RUN_TRAINER_BATTLE) + return FALSE; + + if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER) + return FALSE; + + if (gBattleTypeFlags & BATTLE_TYPE_RECORDED_INVALID) + return FALSE; + + return (gBattleTypeFlags & BATTLE_TYPE_TRAINER); +} + +bool32 DidPlayerForfeitNormalTrainerBattle(void) +{ + if (!CanPlayerForfeitNormalTrainerBattle()) + return FALSE; + + return (gBattleOutcome == B_OUTCOME_FORFEITED); +} diff --git a/src/battle_message.c b/src/battle_message.c index ccc6ff8963..cf25e7c221 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -896,6 +896,8 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = [STRINGID_PKMNGIGANTAMAXED] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} grew huge\ninto its Gigantamax form!"), [STRINGID_TIMETODYNAMAX] = COMPOUND_STRING("Time to Dynamax!"), [STRINGID_TIMETOGIGANTAMAX] = COMPOUND_STRING("Time to Gigantamax!"), + [STRINGID_QUESTIONFORFEITBATTLE] = COMPOUND_STRING("Would you like to give up on this battle and quit now? Quitting the battle is the same as losing the battle."), + [STRINGID_FORFEITBATTLEGAVEMONEY] = COMPOUND_STRING("You gave ¥{B_BUFF1} to the winner…{PAUSE_UNTIL_PRESS}"), }; const u16 gTrainerUsedItemStringIds[] = diff --git a/src/battle_setup.c b/src/battle_setup.c index 96d23dc42a..5a96c0b9c3 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -1084,9 +1084,9 @@ void ConfigureTwoTrainersBattle(u8 trainerObjEventId, const u8 *trainerScript) gSelectedObjectEvent = trainerObjEventId; gSpecialVar_LastTalked = gObjectEvents[trainerObjEventId].localId; - if (gApproachingTrainerId == 0) + if (gApproachingTrainerId == 0) TrainerBattleLoadArgs(trainerScript + 1); - else + else TrainerBattleLoadArgsSecondTrainer(trainerScript + 1); BattleSetup_ConfigureTrainerBattle(trainerScript + 1); @@ -1264,6 +1264,10 @@ static void CB2_EndTrainerBattle(void) else SetMainCallback2(CB2_WhiteOut); } + else if (DidPlayerForfeitNormalTrainerBattle()) + { + SetMainCallback2(CB2_WhiteOut); + } else { SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic); diff --git a/src/battle_util.c b/src/battle_util.c index c308417191..91e7a7c2d0 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -562,6 +562,10 @@ bool32 TryRunFromBattle(u32 battler) { effect++; } + else if (CanPlayerForfeitNormalTrainerBattle()) + { + effect++; + } else { u8 runningFromBattler = BATTLE_OPPOSITE(battler); diff --git a/src/field_screen_effect.c b/src/field_screen_effect.c index e07d067afc..4d272b9700 100644 --- a/src/field_screen_effect.c +++ b/src/field_screen_effect.c @@ -39,6 +39,7 @@ #include "constants/rgb.h" #include "trainer_hill.h" #include "fldeff.h" +#include "battle.h" static void Task_ExitNonAnimDoor(u8); static void Task_ExitNonDoor(u8); @@ -1342,6 +1343,21 @@ enum { FRLG_WHITEOUT_HEAL_SCRIPT, }; +static const u8 *GenerateRecoveryMessage(u8 taskId) +{ + bool32 forfeitTrainer = DidPlayerForfeitNormalTrainerBattle(); + bool32 destinationIsPlayersHouse = (gTasks[taskId].tIsPlayerHouse == TRUE); + + if (forfeitTrainer && destinationIsPlayersHouse) + return gText_PlayerRegroupHome; + else if (forfeitTrainer && !destinationIsPlayersHouse) + return gText_PlayerRegroupCenter; + else if (!forfeitTrainer && destinationIsPlayersHouse) + return gText_PlayerScurriedBackHome; + else + return gText_PlayerScurriedToCenter; +} + static void Task_RushInjuredPokemonToCenter(u8 taskId) { u32 windowId; @@ -1361,7 +1377,8 @@ static void Task_RushInjuredPokemonToCenter(u8 taskId) break; case FRLG_WHITEOUT_PRINT_MSG: { - const u8 *recoveryMessage = gTasks[taskId].tIsPlayerHouse == TRUE ? gText_PlayerScurriedBackHome : gText_PlayerScurriedToCenter; + const u8 *recoveryMessage = GenerateRecoveryMessage(taskId); + if (PrintWhiteOutRecoveryMessage(taskId, recoveryMessage, 2, 8)) { ObjectEventTurn(&gObjectEvents[gPlayerAvatar.objectEventId], DIR_NORTH); diff --git a/src/strings.c b/src/strings.c index c112db4616..fee143ea68 100644 --- a/src/strings.c +++ b/src/strings.c @@ -806,6 +806,8 @@ const u8 gText_ThankYou[] = _("THANK YOU"); const u8 gText_ByeBye[] = _("BYE-BYE!"); const u8 gText_PlayerScurriedToCenter[] = _("{PLAYER} scurried to a POKéMON CENTER,\nprotecting the exhausted and fainted\nPOKéMON from further harm…\p"); const u8 gText_PlayerScurriedBackHome[] = _("{PLAYER} scurried back home, protecting\nthe exhausted and fainted POKéMON from\nfurther harm…\p"); +const u8 gText_PlayerRegroupCenter[] = _("{PLAYER} went running back to a POKéMON CENTER\nto regroup and reconsider their battle\lstrategy…\p"); +const u8 gText_PlayerRegroupHome[] = _("{PLAYER} went running back home to regroup\nand reconsider their battle strategy…\p"); const u8 gText_HatchedFromEgg[] = _("{STR_VAR_1} hatched from the EGG!"); const u8 gText_NicknameHatchPrompt[] = _("Would you like to nickname the newly\nhatched {STR_VAR_1}?"); ALIGNED(4) const u8 gText_ReadyPickBerry[] = _("Are you ready to BERRY-CRUSH?\nPlease pick a BERRY for use.\p");