diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 0da988c14..7ca6bfa6d 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1776,7 +1776,21 @@ .macro pickpocketsteal various 0, VARIOUS_PICKPOCKET .endm - + + .macro doterrainseed battler:req, ptr:req + various \battler, VARIOUS_TERRAIN_SEED + .4byte \ptr + .endm + + .macro makeinvisible battler:req + various \battler, VARIOUS_MAKE_INVISIBLE + .endm + + .macro tryroomservice battler:req, ptr:req + various \battler, VARIOUS_ROOM_SERVICE + .4byte \ptr + .endm + .macro jumpifterrainaffected battler:req, terrainFlags:req, ptr:req various \battler, VARIOUS_JUMP_IF_TERRAIN_AFFECTED .4byte \terrainFlags diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index f02dd9a3e..0a3fafd5b 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1530,6 +1530,7 @@ BattleScript_EffectPsychicTerrain: printfromtable gTerrainStringIds waitmessage B_WAIT_TIME_LONG playanimation BS_SCRIPTING, B_ANIM_RESTORE_BG, NULL + call BattleScript_TerrainSeedLoop goto BattleScript_MoveEnd BattleScript_EffectTopsyTurvy: @@ -1852,6 +1853,26 @@ BattleScript_EffectMagnetRise: goto BattleScript_MoveEnd BattleScript_EffectTrickRoom: + attackcanceler + attackstring + ppreduce + setroom + attackanimation + waitanimation + printfromtable gRoomsStringIds + waitmessage B_WAIT_TIME_LONG + savetarget + setbyte gBattlerTarget, 0 +BattleScript_RoomServiceLoop: + copybyte sBATTLER, gBattlerTarget + tryroomservice BS_TARGET, BattleScript_RoomServiceLoop_NextBattler + removeitem BS_TARGET +BattleScript_RoomServiceLoop_NextBattler: + addbyte gBattlerTarget, 0x1 + jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_RoomServiceLoop + restoretarget + goto BattleScript_MoveEnd + BattleScript_EffectWonderRoom: BattleScript_EffectMagicRoom: attackcanceler @@ -2185,18 +2206,21 @@ BattleScript_AlreadyAsleep:: pause B_WAIT_TIME_SHORT printstring STRINGID_PKMNALREADYASLEEP waitmessage B_WAIT_TIME_LONG + orhalfword gMoveResultFlags, MOVE_RESULT_FAILED goto BattleScript_MoveEnd BattleScript_WasntAffected:: pause B_WAIT_TIME_SHORT printstring STRINGID_PKMNWASNTAFFECTED waitmessage B_WAIT_TIME_LONG + orhalfword gMoveResultFlags, MOVE_RESULT_FAILED goto BattleScript_MoveEnd BattleScript_CantMakeAsleep:: pause B_WAIT_TIME_SHORT printfromtable gUproarAwakeStringIds waitmessage B_WAIT_TIME_LONG + orhalfword gMoveResultFlags, MOVE_RESULT_FAILED goto BattleScript_MoveEnd BattleScript_EffectPoisonHit: @@ -5574,16 +5598,28 @@ BattleScript_RoarSuccessSwitch:: waitstate printstring STRINGID_PKMNWASDRAGGEDOUT switchineffects BS_TARGET + jumpifbyte CMP_EQUAL, sSWITCH_CASE, B_SWITCH_RED_CARD, BattleScript_RoarSuccessSwitch_Ret + setbyte sSWITCH_CASE, B_SWITCH_NORMAL goto BattleScript_MoveEnd +BattleScript_RoarSuccessSwitch_Ret: + swapattackerwithtarget @ continuation of RedCardActivates + restoretarget + removeitem BS_TARGET + setbyte sSWITCH_CASE, B_SWITCH_NORMAL + return BattleScript_RoarSuccessEndBattle:: call BattleScript_RoarSuccessRet + setbyte sSWITCH_CASE, B_SWITCH_NORMAL setoutcomeonteleport BS_ATTACKER finishaction BattleScript_RoarSuccessRet: + jumpifbyte CMP_EQUAL, sSWITCH_CASE, B_SWITCH_HIT, BattleScript_RoarSuccessRet_Ret + jumpifbyte CMP_EQUAL, sSWITCH_CASE, B_SWITCH_RED_CARD, BattleScript_RoarSuccessRet_Ret attackanimation waitanimation +BattleScript_RoarSuccessRet_Ret: switchoutabilities BS_TARGET returntoball BS_TARGET waitstate @@ -5628,6 +5664,21 @@ BattleScript_TargetItemStatRaise:: removeitem BS_TARGET BattleScript_TargetItemStatRaiseRemoveItemRet: return + +BattleScript_AttackerItemStatRaise:: + copybyte sBATTLER, gBattlerAttacker + statbuffchange MOVE_EFFECT_AFFECTS_USER, BattleScript_AttackerItemStatRaiseRet + jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, 0x2, BattleScript_AttackerItemStatRaiseRet + playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT, NULL + waitanimation + setgraphicalstatchangevalues + playanimation BS_ATTACKER, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1 + waitanimation + printstring STRINGID_USINGITEMSTATOFPKMNROSE + waitmessage 0x40 + removeitem BS_ATTACKER +BattleScript_AttackerItemStatRaiseRet: + return BattleScript_MistProtected:: pause B_WAIT_TIME_SHORT @@ -6911,6 +6962,19 @@ BattleScript_SnowWarningActivates:: playanimation BS_BATTLER_0, B_ANIM_HAIL_CONTINUES, NULL call BattleScript_WeatherFormChanges end3 + +BattleScript_TerrainSeedLoop: + savetarget + setbyte gBattlerTarget, 0 +BattleScript_TerrainSeedLoopIter: + copybyte sBATTLER, gBattlerTarget + doterrainseed BS_TARGET, BattleScript_TerrainSeedLoop_NextBattler + removeitem BS_TARGET +BattleScript_TerrainSeedLoop_NextBattler: + addbyte gBattlerTarget, 0x1 + jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_TerrainSeedLoopIter + restoretarget + return BattleScript_ElectricSurgeActivates:: pause B_WAIT_TIME_SHORT @@ -6918,6 +6982,7 @@ BattleScript_ElectricSurgeActivates:: printstring STRINGID_TERRAINBECOMESELECTRIC waitmessage B_WAIT_TIME_LONG playanimation BS_SCRIPTING, B_ANIM_RESTORE_BG, NULL + call BattleScript_TerrainSeedLoop end3 BattleScript_MistySurgeActivates:: @@ -6926,6 +6991,7 @@ BattleScript_MistySurgeActivates:: printstring STRINGID_TERRAINBECOMESMISTY waitmessage B_WAIT_TIME_LONG playanimation BS_SCRIPTING, B_ANIM_RESTORE_BG, NULL + call BattleScript_TerrainSeedLoop end3 BattleScript_GrassySurgeActivates:: @@ -6934,6 +7000,7 @@ BattleScript_GrassySurgeActivates:: printstring STRINGID_TERRAINBECOMESGRASSY waitmessage B_WAIT_TIME_LONG playanimation BS_SCRIPTING, B_ANIM_RESTORE_BG, NULL + call BattleScript_TerrainSeedLoop end3 BattleScript_PsychicSurgeActivates:: @@ -6942,6 +7009,7 @@ BattleScript_PsychicSurgeActivates:: printstring STRINGID_TERRAINBECOMESPSYCHIC waitmessage B_WAIT_TIME_LONG playanimation BS_SCRIPTING, B_ANIM_RESTORE_BG, NULL + call BattleScript_TerrainSeedLoop end3 BattleScript_BadDreamsActivates:: @@ -7093,6 +7161,7 @@ BattleScript_SoundproofProtected:: call BattleScript_AbilityPopUp printstring STRINGID_PKMNSXBLOCKSY waitmessage B_WAIT_TIME_LONG + orhalfword gMoveResultFlags, MOVE_RESULT_DOESNT_AFFECT_FOE goto BattleScript_MoveEnd BattleScript_DazzlingProtected:: @@ -7136,6 +7205,7 @@ BattleScript_AbilityNoSpecificStatLoss:: printstring STRINGID_PKMNSXPREVENTSYLOSS waitmessage B_WAIT_TIME_LONG setbyte cMULTISTRING_CHOOSER, B_MSG_STAT_FELL_EMPTY + orhalfword gMoveResultFlags, MOVE_RESULT_NO_EFFECT return BattleScript_StickyHoldActivates:: @@ -7571,6 +7641,19 @@ BattleScript_BerryCureChosenStatusRet:: removeitem BS_SCRIPTING return +BattleScript_MentalHerbCureRet:: + playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT, NULL + printfromtable gMentalHerbCureStringIds + waitmessage B_WAIT_TIME_LONG + updatestatusicon BS_SCRIPTING + removeitem BS_SCRIPTING + copybyte gBattlerAttacker, sSAVED_BATTLER @ restore the original attacker just to be safe + return + +BattleScript_MentalHerbCureEnd2:: + call BattleScript_MentalHerbCureRet + end2 + BattleScript_WhiteHerbEnd2:: call BattleScript_WhiteHerbRet end2 @@ -7724,12 +7807,13 @@ BattleScript_BerryStatRaiseEnd2:: BattleScript_BerryStatRaiseEnd2_AbilityPopup: call BattleScript_AbilityPopUp BattleScript_BerryStatRaiseEnd2_Anim: - playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT, NULL - statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_BUFF_ALLOW_PTR, BattleScript_BerryStatRaiseDoStatUp -BattleScript_BerryStatRaiseDoStatUp:: + statbuffchange STAT_BUFF_ALLOW_PTR, BattleScript_BerryStatRaiseEnd2_End + setgraphicalstatchangevalues + playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT, sB_ANIM_ARG1 setbyte cMULTISTRING_CHOOSER, B_MSG_STAT_ROSE_ITEM call BattleScript_StatUp removeitem BS_ATTACKER +BattleScript_BerryStatRaiseEnd2_End:: end2 BattleScript_BerryStatRaiseRet:: @@ -7738,12 +7822,13 @@ BattleScript_BerryStatRaiseRet:: BattleScript_BerryStatRaiseRet_AbilityPopup: call BattleScript_AbilityPopUp BattleScript_BerryStatRaiseRet_Anim: - playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, NULL statbuffchange STAT_BUFF_ALLOW_PTR, BattleScript_BerryStatRaiseRet_End -BattleScript_BerryStatRaiseRet_End: + setgraphicalstatchangevalues + playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, sB_ANIM_ARG1 setbyte cMULTISTRING_CHOOSER, B_MSG_STAT_ROSE_ITEM call BattleScript_StatUp removeitem BS_SCRIPTING +BattleScript_BerryStatRaiseRet_End: return BattleScript_BerryFocusEnergyEnd2:: @@ -7997,3 +8082,61 @@ BattleScript_StickyBarbTransfer:: removeitem BS_TARGET return +BattleScript_RedCardActivates:: + playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, NULL + printstring STRINGID_REDCARDACTIVATE + waitmessage 0x40 + swapattackerwithtarget + jumpifstatus3 BS_EFFECT_BATTLER, STATUS3_ROOTED, BattleScript_RedCardIngrain + jumpifability BS_EFFECT_BATTLER, ABILITY_SUCTION_CUPS, BattleScript_RedCardSuctionCups + setbyte sSWITCH_CASE, B_SWITCH_RED_CARD + forcerandomswitch BattleScript_RedCardEnd + @ changes the current battle script. the rest happens in BattleScript_RoarSuccessSwitch_Ret, if switch is successful +BattleScript_RedCardEnd: + return +BattleScript_RedCardIngrain: + printstring STRINGID_PKMNANCHOREDITSELF + waitmessage 0x40 + removeitem BS_SCRIPTING + swapattackerwithtarget + return +BattleScript_RedCardSuctionCups: + printstring STRINGID_PKMNANCHORSITSELFWITH + waitmessage 0x40 + removeitem BS_SCRIPTING + swapattackerwithtarget + return + +BattleScript_EjectButtonActivates:: + makevisible BS_ATTACKER + playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, NULL + printstring STRINGID_EJECTBUTTONACTIVATE + waitmessage 0x40 + removeitem BS_SCRIPTING + makeinvisible BS_SCRIPTING + openpartyscreen BS_SCRIPTING, BattleScript_EjectButtonEnd + switchoutabilities BS_SCRIPTING + waitstate + switchhandleorder BS_SCRIPTING 0x2 + returntoball BS_SCRIPTING + getswitchedmondata BS_SCRIPTING + switchindataupdate BS_SCRIPTING + hpthresholds BS_SCRIPTING + printstring 0x3 + switchinanim BS_SCRIPTING 0x1 + waitstate + switchineffects BS_SCRIPTING +BattleScript_EjectButtonEnd: + return + +BattleScript_EjectPackActivate_Ret:: + goto BattleScript_EjectButtonActivates + +BattleScript_EjectPackActivate_End2:: + call BattleScript_EjectPackActivate_Ret + end2 + +BattleScript_EjectPackActivates:: + jumpifcantswitch BS_SCRIPTING, BattleScript_EjectButtonEnd + goto BattleScript_EjectPackActivate_Ret + diff --git a/include/battle.h b/include/battle.h index 3fbefe55b..36646c9fe 100644 --- a/include/battle.h +++ b/include/battle.h @@ -126,7 +126,7 @@ struct ProtectStruct u32 stealMove:1; u32 prlzImmobility:1; u32 confusionSelfDmg:1; - u32 targetNotAffected:1; + u32 targetAffected:1; u32 chargingTurn:1; u32 fleeFlag:2; // For RunAway and Smoke Ball. u32 usedImprisonedMove:1; @@ -145,6 +145,7 @@ struct ProtectStruct u32 micle:1; u32 custap:1; // also quick claw u32 touchedProtectLike:1; + u32 disableEjectPack:1; u32 physicalDmg; u32 specialDmg; u8 physicalBattlerId; @@ -153,6 +154,7 @@ struct ProtectStruct struct SpecialStatus { + u8 statFell:1; u8 statLowered:1; u8 lightningRodRedirected:1; u8 restoredBattlerSprite: 1; @@ -596,6 +598,7 @@ struct BattleStruct u16 changedSpecies[PARTY_SIZE]; // For Zygarde or future forms when multiple mons can change into the same pokemon. u8 quickClawBattlerId; struct StolenItem itemStolen[PARTY_SIZE]; // Player's team that had items stolen (two bytes per party member) + u8 blunderPolicy:1; // should blunder policy activate }; #define GET_MOVE_TYPE(move, typeArg) \ @@ -667,8 +670,9 @@ struct BattleScripting u16 moveEffect; u16 multihitMoveEffect; u8 illusionNickHack; // To properly display nick in STRINGID_ENEMYABOUTTOSWITCHPKMN. - bool8 fixedPopup; // force ability popup to stick until manually called back + bool8 fixedPopup; // Force ability popup to stick until manually called back u16 abilityPopupOverwrite; + u8 switchCase; // Special switching conditions, eg. red card }; // rom_80A5C6C diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 30066438b..b15ab3aeb 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -373,6 +373,14 @@ extern const u8 BattleScript_BattlerShookOffTaunt[]; extern const u8 BattleScript_BattlerGotOverItsInfatuation[]; extern const u8 BattleScript_Pickpocket[]; extern const u8 BattleScript_StickyBarbTransfer[]; +extern const u8 BattleScript_AttackerItemStatRaise[]; +extern const u8 BattleScript_RedCardActivates[]; +extern const u8 BattleScript_EjectButtonActivates[]; +extern const u8 BattleScript_EjectPackActivate_Ret[]; +extern const u8 BattleScript_EjectPackActivate_End2[]; +extern const u8 BattleScript_EjectPackActivates[]; +extern const u8 BattleScript_MentalHerbCureRet[]; +extern const u8 BattleScript_MentalHerbCureEnd2[]; extern const u8 BattleScript_TerrainPreventsEnd2[]; extern const u8 BattleScript_MistyTerrainPrevents[]; extern const u8 BattleScript_ElectricTerrainPrevents[]; diff --git a/include/battle_util.h b/include/battle_util.h index 202cdbddb..4dac63111 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -28,9 +28,10 @@ #define ITEMEFFECT_ON_SWITCH_IN 0x0 #define ITEMEFFECT_MOVE_END 0x3 -#define ITEMEFFECT_KINGSROCK_SHELLBELL 0x4 +#define ITEMEFFECT_KINGSROCK 0x4 #define ITEMEFFECT_TARGET 0x5 #define ITEMEFFECT_ORBS 0x6 +#define ITEMEFFECT_LIFEORB_SHELLBELL 0x7 #define WEATHER_HAS_EFFECT ((!IsAbilityOnField(ABILITY_CLOUD_NINE) && !IsAbilityOnField(ABILITY_AIR_LOCK))) @@ -144,6 +145,12 @@ void TryRestoreStolenItems(void); bool32 CanStealItem(u8 battlerStealing, u8 battlerItem, u16 item); void TrySaveExchangedItem(u8 battlerId, u16 stolenItem); bool32 IsPartnerMonFromSameTrainer(u8 battlerId); +u8 TryHandleSeed(u8 battler, u32 terrainFlag, u8 statId, u16 itemId, bool32 execute); +bool32 IsBattlerAffectedByHazards(u8 battlerId, bool32 toxicSpikes); +void SortBattlersBySpeed(u8 *battlers, bool8 slowToFast); +bool32 CompareStat(u8 battlerId, u8 statId, u8 cmpTo, u8 cmpKind); +bool32 TryRoomService(u8 battlerId); +void BufferStatChange(u8 battlerId, u8 statId, u8 stringId); // ability checks bool32 IsRolePlayBannedAbilityAtk(u16 ability); diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index b87af3122..edf1cbb4f 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -130,7 +130,7 @@ #define B_HP_BERRIES GEN_7 // In Gen4+, berries which restore hp activate immediately after HP drops to half. In Gen3, the effect occurs at the end of the turn. #define B_BERRIES_INSTANT GEN_7 // In Gen4+, most berries activate on battle start/switch-in if applicable. In Gen3, they only activate either at the move end or turn end. #define B_X_ITEMS_BUFF GEN_7 // In Gen7+, the X Items raise a stat by 2 stages instead of 1. -#define B_MENTAL_HERB GEN_5 // In Gen5+, mental herb cures Taunt, Encore, Heal Block, and Disable +#define B_MENTAL_HERB GEN_5 // In Gen5+, the Mental Herb cures Infatuation, Taunt, Encore, Torment, Heal Block, and Disable // Flag settings // To use the following features in scripting, replace the 0s with the flag ID you're assigning it to. diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index fd5913832..66f592503 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -37,6 +37,7 @@ #define sILLUSION_NICK_HACK gBattleScripting + 0x32 #define sFIXED_ABILITY_POPUP gBattleScripting + 0x33 #define sABILITY_OVERWRITE gBattleScripting + 0x34 +#define sSWITCH_CASE gBattleScripting + 0x36 #define cMULTISTRING_CHOOSER gBattleCommunication + 5 #define cMISS_TYPE gBattleCommunication + 6 @@ -174,7 +175,10 @@ #define VARIOUS_TOTEM_BOOST 103 #define VARIOUS_TRY_ACTIVATE_GRIM_NEIGH 104 #define VARIOUS_MOVEEND_ITEM_EFFECTS 105 -#define VARIOUS_JUMP_IF_TERRAIN_AFFECTED 106 +#define VARIOUS_TERRAIN_SEED 106 +#define VARIOUS_MAKE_INVISIBLE 107 +#define VARIOUS_ROOM_SERVICE 108 +#define VARIOUS_JUMP_IF_TERRAIN_AFFECTED 109 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 @@ -229,16 +233,24 @@ #define MOVEEND_ITEM_EFFECTS_TARGET 13 #define MOVEEND_MOVE_EFFECTS2 14 #define MOVEEND_ITEM_EFFECTS_ALL 15 -#define MOVEEND_KINGSROCK_SHELLBELL 16 +#define MOVEEND_KINGSROCK 16 // These item effects will occur each strike of a multi-hit move #define MOVEEND_SUBSTITUTE 17 #define MOVEEND_UPDATE_LAST_MOVES 18 #define MOVEEND_MIRROR_MOVE 19 -#define MOVEEND_NEXT_TARGET 20 -#define MOVEEND_LIFE_ORB 21 -#define MOVEEND_PICKPOCKET 22 -#define MOVEEND_DANCER 23 -#define MOVEEND_EMERGENCY_EXIT 24 -#define MOVEEND_CLEAR_BITS 25 -#define MOVEEND_COUNT 26 +#define MOVEEND_NEXT_TARGET 20 // Everything up until here is handled for each strike of a multi-hit move +#define MOVEEND_EJECT_BUTTON 21 +#define MOVEEND_RED_CARD 22 +#define MOVEEND_EJECT_PACK 23 +#define MOVEEND_LIFEORB_SHELLBELL 24 // Includes shell bell, throat spray, etc +#define MOVEEND_PICKPOCKET 25 +#define MOVEEND_DANCER 26 +#define MOVEEND_EMERGENCY_EXIT 27 +#define MOVEEND_CLEAR_BITS 28 +#define MOVEEND_COUNT 29 + +// switch cases +#define B_SWITCH_NORMAL 0 +#define B_SWITCH_HIT 1 // dragon tail, circle throw +#define B_SWITCH_RED_CARD 2 #endif // GUARD_CONSTANTS_BATTLE_SCRIPT_COMMANDS_H diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index e0b466b17..e32fd5778 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -573,8 +573,13 @@ #define STRINGID_ITEMCANNOTBEREMOVED 569 #define STRINGID_STICKYBARBTRANSFER 570 #define STRINGID_PKMNBURNHEALED 571 +#define STRINGID_REDCARDACTIVATE 572 +#define STRINGID_EJECTBUTTONACTIVATE 573 +#define STRINGID_ATKGOTOVERINFATUATION 574 +#define STRINGID_TORMENTEDNOMORE 575 +#define STRINGID_HEALBLOCKEDNOMORE 576 -#define BATTLESTRINGS_COUNT 572 +#define BATTLESTRINGS_COUNT 577 // The below IDs are all indexes into battle message tables, // used to determine which of a set of messages to print. @@ -810,6 +815,14 @@ #define B_MSG_SWITCHIN_ASONE 13 #define B_MSG_SWITCHIN_CURIOUS_MEDICINE 14 +// gMentalHerbCureStringIds +#define B_MSG_MENTALHERBCURE_INFATUATION 0 +#define B_MSG_MENTALHERBCURE_TAUNT 1 +#define B_MSG_MENTALHERBCURE_ENCORE 2 +#define B_MSG_MENTALHERBCURE_TORMENT 3 +#define B_MSG_MENTALHERBCURE_HEALBLOCK 4 +#define B_MSG_MENTALHERBCURE_DISABLE 5 + // gTerrainPreventsStringIds #define B_MSG_TERRAINPREVENTS_MISTY 0 #define B_MSG_TERRAINPREVENTS_ELECTRIC 1 diff --git a/include/constants/hold_effects.h b/include/constants/hold_effects.h index 13e886944..3dc4a1389 100644 --- a/include/constants/hold_effects.h +++ b/include/constants/hold_effects.h @@ -29,7 +29,7 @@ #define HOLD_EFFECT_EXP_SHARE 25 #define HOLD_EFFECT_QUICK_CLAW 26 #define HOLD_EFFECT_FRIENDSHIP_UP 27 -#define HOLD_EFFECT_CURE_ATTRACT 28 +#define HOLD_EFFECT_MENTAL_HERB 28 #define HOLD_EFFECT_CHOICE_BAND 29 #define HOLD_EFFECT_FLINCH 30 #define HOLD_EFFECT_BUG_POWER 31 @@ -154,4 +154,10 @@ #define HOLD_EFFECT_CHOICE(holdEffect)((holdEffect == HOLD_EFFECT_CHOICE_BAND || holdEffect == HOLD_EFFECT_CHOICE_SCARF || holdEffect == HOLD_EFFECT_CHOICE_SPECS)) +// Terrain seed params +#define HOLD_EFFECT_PARAM_ELECTRIC_TERRAIN 0 +#define HOLD_EFFECT_PARAM_GRASSY_TERRAIN 1 +#define HOLD_EFFECT_PARAM_MISTY_TERRAIN 2 +#define HOLD_EFFECT_PARAM_PSYCHIC_TERRAIN 3 + #endif // GUARD_HOLD_EFFECTS_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 09ea93c16..03472271d 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1204,7 +1204,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_DISABLE: if (gDisableStructs[battlerDef].disableTimer == 0 - && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_ATTRACT) + && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB) && !PartnerHasSameMoveEffectWithoutTarget(AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove)) { if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first @@ -1224,7 +1224,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_ENCORE: if (gDisableStructs[battlerDef].encoreTimer == 0 - && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_ATTRACT) + && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB) && !DoesPartnerHaveSameMoveEffect(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) { if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first @@ -1458,7 +1458,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; } - if (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect == HOLD_EFFECT_CURE_ATTRACT) + if (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect == HOLD_EFFECT_MENTAL_HERB) score -= 6; break; case EFFECT_WILL_O_WISP: @@ -3408,7 +3408,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_DISABLE: if (gDisableStructs[battlerDef].disableTimer == 0 - && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_ATTRACT)) // mental herb + && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB)) // mental herb { if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // AI goes first { @@ -3430,7 +3430,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_ENCORE: if (gDisableStructs[battlerDef].encoreTimer == 0 - && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_ATTRACT)) // mental herb + && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB)) // mental herb { if (IsEncoreEncouragedEffect(gBattleMoves[gLastMoves[battlerDef]].effect)) score += 3; diff --git a/src/battle_main.c b/src/battle_main.c index 77e71c5b7..74995bbf3 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3032,6 +3032,10 @@ void SwitchInClearSetData(void) gBattleResources->flags->flags[gActiveBattler] = 0; gCurrentMove = 0; gBattleStruct->arenaTurnCounter = 0xFF; + + // Reset damage to prevent things like red card activating if the switched-in mon is holding it + gSpecialStatuses[gActiveBattler].physicalDmg = 0; + gSpecialStatuses[gActiveBattler].specialDmg = 0; ClearBattlerMoveHistory(gActiveBattler); ClearBattlerAbilityHistory(gActiveBattler); @@ -3074,7 +3078,7 @@ void FaintClearSetData(void) gProtectStructs[gActiveBattler].stealMove = 0; gProtectStructs[gActiveBattler].prlzImmobility = 0; gProtectStructs[gActiveBattler].confusionSelfDmg = 0; - gProtectStructs[gActiveBattler].targetNotAffected = 0; + gProtectStructs[gActiveBattler].targetAffected = 0; gProtectStructs[gActiveBattler].chargingTurn = 0; gProtectStructs[gActiveBattler].fleeFlag = 0; gProtectStructs[gActiveBattler].usedImprisonedMove = 0; diff --git a/src/battle_message.c b/src/battle_message.c index 1e9fd918b..9d17a2a25 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -699,9 +699,20 @@ static const u8 sText_PkmnGotOverItsInfatuation[] = _("{B_SCR_ACTIVE_NAME_WITH_P static const u8 sText_ItemCannotBeRemoved[] = _("{B_ATK_NAME_WITH_PREFIX}'s item cannot be removed!"); static const u8 sText_StickyBarbTransfer[] = _("The {B_LAST_ITEM} attached itself to\n{B_ATK_NAME_WITH_PREFIX}!"); static const u8 sText_PkmnBurnHealed[] = _("{B_DEF_NAME_WITH_PREFIX}'s\nburn was healed."); +static const u8 sText_RedCardActivate[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} held up its {B_LAST_ITEM}\nagainst {B_ATK_NAME_WITH_PREFIX}!"); +static const u8 sText_EjectButtonActivate[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} is switched\nout with the {B_LAST_ITEM}!"); +static const u8 sText_AttackerGotOverInfatuation[] =_("{B_ATK_NAME_WITH_PREFIX} got over\nits infatuation!"); +static const u8 sText_TormentedNoMore[] = _("{B_ATK_NAME_WITH_PREFIX} is\ntormented no more!"); +static const u8 sText_HealBlockedNoMore[] = _("{B_ATK_NAME_WITH_PREFIX} is cured of\nits heal block!"); + const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_HEALBLOCKEDNOMORE - 12] = sText_HealBlockedNoMore, + [STRINGID_TORMENTEDNOMORE - 12] = sText_TormentedNoMore, + [STRINGID_ATKGOTOVERINFATUATION - 12] = sText_AttackerGotOverInfatuation, + [STRINGID_EJECTBUTTONACTIVATE - 12] = sText_EjectButtonActivate, + [STRINGID_REDCARDACTIVATE - 12] = sText_RedCardActivate, [STRINGID_PKMNBURNHEALED - 12] = sText_PkmnBurnHealed, [STRINGID_STICKYBARBTRANSFER - 12] = sText_StickyBarbTransfer, [STRINGID_ITEMCANNOTBEREMOVED - 12] = sText_ItemCannotBeRemoved, @@ -1264,6 +1275,16 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = [STRINGID_SCREENCLEANERENTERS - 12] = sText_ScreenCleanerActivates, }; +const u16 gMentalHerbCureStringIds[] = +{ + [B_MSG_MENTALHERBCURE_INFATUATION] = STRINGID_ATKGOTOVERINFATUATION, + [B_MSG_MENTALHERBCURE_TAUNT] = STRINGID_BUFFERENDS, + [B_MSG_MENTALHERBCURE_ENCORE] = STRINGID_PKMNENCOREENDED, + [B_MSG_MENTALHERBCURE_TORMENT] = STRINGID_TORMENTEDNOMORE, + [B_MSG_MENTALHERBCURE_HEALBLOCK] = STRINGID_HEALBLOCKEDNOMORE, + [B_MSG_MENTALHERBCURE_DISABLE] = STRINGID_PKMNMOVEDISABLEDNOMORE, +}; + const u16 gTerrainStringIds[] = { STRINGID_TERRAINBECOMESMISTY, STRINGID_TERRAINBECOMESGRASSY, STRINGID_TERRAINBECOMESELECTRIC, STRINGID_TERRAINBECOMESPSYCHIC diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b8a37704c..00a52d6c5 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1681,6 +1681,9 @@ static void Cmd_accuracycheck(void) if ((Random() % 100 + 1) > GetTotalAccuracy(gBattlerAttacker, gBattlerTarget, move)) { gMoveResultFlags |= MOVE_RESULT_MISSED; + if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_BLUNDER_POLICY) + gBattleStruct->blunderPolicy = TRUE; // Only activates from missing through acc/evasion checks + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && (gBattleMoves[move].target == MOVE_TARGET_BOTH || gBattleMoves[move].target == MOVE_TARGET_FOES_AND_ALLY)) gBattleCommunication[MISS_TYPE] = B_MSG_AVOIDED_ATK; @@ -3563,52 +3566,11 @@ static void Cmd_jumpifstat(void) { bool32 ret = 0; u8 battlerId = GetBattlerForBattleScript(gBattlescriptCurrInstr[1]); - u8 statValue = gBattleMons[battlerId].statStages[gBattlescriptCurrInstr[3]]; + u8 statId = gBattlescriptCurrInstr[3]; u8 cmpTo = gBattlescriptCurrInstr[4]; u8 cmpKind = gBattlescriptCurrInstr[2]; - // Because this command is used as a way of checking if a stat can be lowered/raised, - // we need to do some modification at run-time. - if (GetBattlerAbility(battlerId) == ABILITY_CONTRARY) - { - if (cmpKind == CMP_GREATER_THAN) - cmpKind = CMP_LESS_THAN; - else if (cmpKind == CMP_LESS_THAN) - cmpKind = CMP_GREATER_THAN; - - if (cmpTo == 0) - cmpTo = 0xC; - else if (cmpTo == 0xC) - cmpTo = 0; - } - - switch (cmpKind) - { - case CMP_EQUAL: - if (statValue == cmpTo) - ret++; - break; - case CMP_NOT_EQUAL: - if (statValue != cmpTo) - ret++; - break; - case CMP_GREATER_THAN: - if (statValue > cmpTo) - ret++; - break; - case CMP_LESS_THAN: - if (statValue < cmpTo) - ret++; - break; - case CMP_COMMON_BITS: - if (statValue & cmpTo) - ret++; - break; - case CMP_NO_COMMON_BITS: - if (!(statValue & cmpTo)) - ret++; - break; - } + ret = CompareStat(battlerId, statId, cmpTo, cmpKind); if (ret) gBattlescriptCurrInstr = T2_READ_PTR(gBattlescriptCurrInstr + 5); @@ -4810,7 +4772,7 @@ static void Cmd_moveend(void) && gBattleMons[gBattlerTarget].hp != 0 && gBattlerAttacker != gBattlerTarget && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget) && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && TARGET_TURN_DAMAGED - && gBattleMoves[gCurrentMove].power && gBattleMons[gBattlerTarget].statStages[STAT_ATK] < MAX_STAT_STAGE) + && gBattleMoves[gCurrentMove].power && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattleMons[gBattlerTarget].statStages[STAT_ATK]++; BattleScriptPushCursor(); @@ -4917,8 +4879,9 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; - case MOVEEND_KINGSROCK_SHELLBELL: // king's rock and shell bell - if (ItemBattleEffects(ITEMEFFECT_KINGSROCK_SHELLBELL, 0, FALSE)) + case MOVEEND_KINGSROCK: // King's rock + // These effects will occur at each hit in a multi-strike move + if (ItemBattleEffects(ITEMEFFECT_KINGSROCK, 0, FALSE)) effect = TRUE; gBattleScripting.moveendState++; break; @@ -5046,6 +5009,11 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_NEXT_TARGET: // For moves hitting two opposing Pokemon. + // Set a flag if move hits either target (for throat spray that can't check damage) + if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) + && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) + gProtectStructs[gBattlerAttacker].targetAffected = 1; + if (!(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && gBattleTypeFlags & BATTLE_TYPE_DOUBLE && !gProtectStructs[gBattlerAttacker].chargingTurn @@ -5090,23 +5058,106 @@ static void Cmd_moveend(void) RecordLastUsedMoveBy(gBattlerAttacker, gCurrentMove); gBattleScripting.moveendState++; break; - case MOVEEND_LIFE_ORB: - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIFE_ORB - && IsBattlerAlive(gBattlerAttacker) - && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) - && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD - && gSpecialStatuses[gBattlerAttacker].damagedMons) + case MOVEEND_EJECT_BUTTON: + if (gCurrentMove != MOVE_DRAGON_TAIL + && gCurrentMove != MOVE_CIRCLE_THROW + && IsBattlerAlive(gBattlerAttacker) + && !TestSheerForceFlag(gBattlerAttacker, gCurrentMove) + && (GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER || (gBattleTypeFlags & BATTLE_TYPE_TRAINER))) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 10; - if (gBattleMoveDamage == 0) - gBattleMoveDamage = 1; - effect = TRUE; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_ItemHurtRet; - gLastUsedItem = gBattleMons[gBattlerAttacker].item; + // Since we check if battler was damaged, we don't need to check move result. + // In fact, doing so actually prevents multi-target moves from activating eject button properly + u8 battlers[4] = {0, 1, 2, 3}; + SortBattlersBySpeed(battlers, FALSE); + for (i = 0; i < gBattlersCount; i++) + { + u8 battler = battlers[i]; + // Attacker is the damage-dealer, battler is mon to be switched out + if (IsBattlerAlive(battler) + && GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_EJECT_BUTTON + && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battler) + && (gSpecialStatuses[battler].physicalDmg != 0 || gSpecialStatuses[battler].specialDmg != 0) + && CountUsablePartyMons(battler) > 0) // Has mon to switch into + { + gActiveBattler = gBattleScripting.battler = battler; + gLastUsedItem = gBattleMons[battler].item; + if (gBattleMoves[gCurrentMove].effect == EFFECT_HIT_ESCAPE) + gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EjectButtonActivates; + effect = TRUE; + break; // Only the fastest Eject Button activates + } + } } gBattleScripting.moveendState++; break; + case MOVEEND_RED_CARD: + if (gCurrentMove != MOVE_DRAGON_TAIL + && gCurrentMove != MOVE_CIRCLE_THROW + && IsBattlerAlive(gBattlerAttacker) + && !TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) + { + // Since we check if battler was damaged, we don't need to check move result. + // In fact, doing so actually prevents multi-target moves from activating red card properly + u8 battlers[4] = {0, 1, 2, 3}; + SortBattlersBySpeed(battlers, FALSE); + for (i = 0; i < gBattlersCount; i++) + { + u8 battler = battlers[i]; + // Search for fastest hit pokemon with a red card + // Attacker is the one to be switched out, battler is one with red card + if (battler != gBattlerAttacker + && IsBattlerAlive(battler) + && !DoesSubstituteBlockMove(gCurrentMove, gBattlerAttacker, battler) + && GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_RED_CARD + && (gSpecialStatuses[battler].physicalDmg != 0 || gSpecialStatuses[battler].specialDmg != 0) + && CanBattlerSwitch(gBattlerAttacker)) + { + gLastUsedItem = gBattleMons[battler].item; + gActiveBattler = gBattleStruct->savedBattlerTarget = gBattleScripting.battler = battler; // Battler with red card + gEffectBattler = gBattlerAttacker; + if (gBattleMoves[gCurrentMove].effect == EFFECT_HIT_ESCAPE) + gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_RedCardActivates; + effect = TRUE; + break; // Only fastest red card activates + } + } + } + gBattleScripting.moveendState++; + break; + case MOVEEND_EJECT_PACK: + { + u8 battlers[4] = {0, 1, 2, 3}; + SortBattlersBySpeed(battlers, FALSE); + for (i = 0; i < gBattlersCount; i++) + { + u8 battler = battlers[i]; + if (IsBattlerAlive(battler) + && gSpecialStatuses[battler].statFell + && GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_EJECT_PACK + && !(gCurrentMove == MOVE_PARTING_SHOT && CanBattlerSwitch(gBattlerAttacker)) // Does not activate if attacker used Parting Shot and can switch out + && CountUsablePartyMons(battler) > 0) // Has mon to switch into + { + gSpecialStatuses[battler].statFell = FALSE; + gActiveBattler = gBattleScripting.battler = battler; + gLastUsedItem = gBattleMons[battler].item; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EjectPackActivates; + effect = TRUE; + break; // Only fastest eject pack activates + } + } + } + gBattleScripting.moveendState++; + break; + case MOVEEND_LIFEORB_SHELLBELL: + if (ItemBattleEffects(ITEMEFFECT_LIFEORB_SHELLBELL, 0, FALSE)) + effect = TRUE; + gBattleScripting.moveendState++; + break; case MOVEEND_PICKPOCKET: if (IsBattlerAlive(gBattlerAttacker) && gBattleMons[gBattlerAttacker].item != ITEM_NONE // Attacker must be holding an item @@ -5207,6 +5258,7 @@ static void Cmd_moveend(void) if (gSpecialStatuses[gBattlerAttacker].dancerOriginalTarget) *(gBattleStruct->moveTarget + gBattlerAttacker) = gSpecialStatuses[gBattlerAttacker].dancerOriginalTarget & 0x3; gProtectStructs[gBattlerAttacker].usesBouncedMove = 0; + gProtectStructs[gBattlerAttacker].targetAffected = 0; gBattleStruct->ateBoost[gBattlerAttacker] = 0; gStatuses3[gBattlerAttacker] &= ~(STATUS3_ME_FIRST); gSpecialStatuses[gBattlerAttacker].gemBoost = 0; @@ -5476,6 +5528,7 @@ bool32 CanBattlerSwitch(u32 battlerId) } else { + // Check if attacker side has mon to switch into battlerIn1 = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) @@ -5910,6 +5963,7 @@ static void Cmd_switchineffects(void) if (!(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES_DAMAGED) && (gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES) && GetBattlerAbility(gActiveBattler) != ABILITY_MAGIC_GUARD + && IsBattlerAffectedByHazards(gActiveBattler, FALSE) && IsBattlerGrounded(gActiveBattler)) { u8 spikesDmg = (5 - gSideTimers[GetBattlerSide(gActiveBattler)].spikesAmount) * 2; @@ -5922,6 +5976,7 @@ static void Cmd_switchineffects(void) } else if (!(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_STEALTH_ROCK_DAMAGED) && (gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_STEALTH_ROCK) + && IsBattlerAffectedByHazards(gActiveBattler, FALSE) && GetBattlerAbility(gActiveBattler) != ABILITY_MAGIC_GUARD) { gSideStatuses[GetBattlerSide(gActiveBattler)] |= SIDE_STATUS_STEALTH_ROCK_DAMAGED; @@ -5943,7 +5998,7 @@ static void Cmd_switchineffects(void) BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_ToxicSpikesAbsorbed; } - else + else if (IsBattlerAffectedByHazards(gActiveBattler, TRUE)) { if (!(gBattleMons[gActiveBattler].status1 & STATUS1_ANY) && !IS_BATTLER_OF_TYPE(gActiveBattler, TYPE_STEEL) @@ -5966,6 +6021,7 @@ static void Cmd_switchineffects(void) } else if (!(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_STICKY_WEB_DAMAGED) && (gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_STICKY_WEB) + && IsBattlerAffectedByHazards(gActiveBattler, FALSE) && IsBattlerGrounded(gActiveBattler)) { gSideStatuses[GetBattlerSide(gActiveBattler)] |= SIDE_STATUS_STICKY_WEB_DAMAGED; @@ -7421,7 +7477,7 @@ static void Cmd_various(void) bits = 0; for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) { - if (gBattleMons[gActiveBattler].statStages[i] != 12) + if (CompareStat(gActiveBattler, i, MAX_STAT_STAGE, CMP_LESS_THAN)) bits |= gBitTable[i]; } if (bits) @@ -7658,7 +7714,7 @@ static void Cmd_various(void) || GetBattlerAbility(gActiveBattler) == ABILITY_AS_ONE_ICE_RIDER) && HasAttackerFaintedTarget() && !NoAliveMonsForEitherParty() - && gBattleMons[gBattlerAttacker].statStages[STAT_ATK] != 12) + && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattleMons[gBattlerAttacker].statStages[STAT_ATK]++; SET_STATCHANGER(STAT_ATK, 1, FALSE); @@ -7676,7 +7732,7 @@ static void Cmd_various(void) || GetBattlerAbility(gActiveBattler) == ABILITY_AS_ONE_SHADOW_RIDER) && HasAttackerFaintedTarget() && !NoAliveMonsForEitherParty() - && gBattleMons[gBattlerAttacker].statStages[STAT_SPATK] != 12) + && CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattleMons[gBattlerAttacker].statStages[STAT_SPATK]++; SET_STATCHANGER(STAT_SPATK, 1, FALSE); @@ -7721,7 +7777,7 @@ static void Cmd_various(void) if (GetBattlerAbility(gActiveBattler) == ABILITY_BEAST_BOOST && HasAttackerFaintedTarget() && !NoAliveMonsForEitherParty() - && gBattleMons[gBattlerAttacker].statStages[i] != 12) + && CompareStat(gBattlerAttacker, i, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattleMons[gBattlerAttacker].statStages[i]++; SET_STATCHANGER(i, 1, FALSE); @@ -7738,7 +7794,7 @@ static void Cmd_various(void) if (GetBattlerAbility(gBattleScripting.battler) == ABILITY_SOUL_HEART && IsBattlerAlive(gBattleScripting.battler) && !NoAliveMonsForEitherParty() - && gBattleMons[gBattleScripting.battler].statStages[STAT_SPATK] != 12) + && CompareStat(gBattleScripting.battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattleMons[gBattleScripting.battler].statStages[STAT_SPATK]++; SET_STATCHANGER(STAT_SPATK, 1, FALSE); @@ -7754,7 +7810,7 @@ static void Cmd_various(void) if (gBattleMoves[gCurrentMove].effect == EFFECT_FELL_STINGER && HasAttackerFaintedTarget() && !NoAliveMonsForEitherParty() - && gBattleMons[gBattlerAttacker].statStages[STAT_ATK] != 12) + && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { if (B_FELL_STINGER_STAT_RAISE >= GEN_7) SET_STATCHANGER(STAT_ATK, 3, FALSE); @@ -8080,6 +8136,7 @@ static void Cmd_various(void) && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && TARGET_TURN_DAMAGED) { + gBattleScripting.switchCase = B_SWITCH_HIT; gBattlescriptCurrInstr = BattleScript_ForceRandomSwitch; } else @@ -8465,6 +8522,50 @@ static void Cmd_various(void) if (ItemBattleEffects(1, gActiveBattler, FALSE)) return; break; + case VARIOUS_ROOM_SERVICE: + if (GetBattlerHoldEffect(gActiveBattler, TRUE) == HOLD_EFFECT_ROOM_SERVICE && TryRoomService(gActiveBattler)) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_BerryStatRaiseRet; + } + else + { + gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 3); + } + return; + case VARIOUS_TERRAIN_SEED: + if (GetBattlerHoldEffect(gActiveBattler, TRUE) == HOLD_EFFECT_SEEDS) + { + u8 effect = 0; + u16 item = gBattleMons[gActiveBattler].item; + switch (GetBattlerHoldEffectParam(gActiveBattler)) + { + case HOLD_EFFECT_PARAM_ELECTRIC_TERRAIN: + effect = TryHandleSeed(gActiveBattler, STATUS_FIELD_ELECTRIC_TERRAIN, STAT_DEF, item, FALSE); + break; + case HOLD_EFFECT_PARAM_GRASSY_TERRAIN: + effect = TryHandleSeed(gActiveBattler, STATUS_FIELD_GRASSY_TERRAIN, STAT_DEF, item, FALSE); + break; + case HOLD_EFFECT_PARAM_MISTY_TERRAIN: + effect = TryHandleSeed(gActiveBattler, STATUS_FIELD_MISTY_TERRAIN, STAT_SPDEF, item, FALSE); + break; + case HOLD_EFFECT_PARAM_PSYCHIC_TERRAIN: + effect = TryHandleSeed(gActiveBattler, STATUS_FIELD_PSYCHIC_TERRAIN, STAT_SPDEF, item, FALSE); + break; + } + + if (effect) + return; + } + gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 3); + return; + case VARIOUS_MAKE_INVISIBLE: + if (gBattleControllerExecFlags) + break; + + BtlController_EmitSpriteInvisibility(0, TRUE); + MarkBattlerForControllerExec(gActiveBattler); + break; case VARIOUS_JUMP_IF_TERRAIN_AFFECTED: { u32 flags = T1_READ_32(gBattlescriptCurrInstr + 3); @@ -9166,11 +9267,18 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr gBattleTextBuff2[index] = STRINGID_STATFELL >> 8; index++; gBattleTextBuff2[index] = B_BUFF_EOS; - + if (gBattleMons[gActiveBattler].statStages[statId] == MIN_STAT_STAGE) + { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STAT_WONT_DECREASE; + } else + { + // Check eject pack. disableEjectPack set for edge cases (e.g. attacking weak armor'd eject pack holder with u-turn) + if (gProtectStructs[gActiveBattler].disableEjectPack == 0) + gSpecialStatuses[gActiveBattler].statFell = TRUE; gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == gActiveBattler); // B_MSG_ATTACKER_STAT_FELL or B_MSG_DEFENDER_STAT_FELL + } } } else // stat increase @@ -9345,6 +9453,36 @@ static void Cmd_forcerandomswitch(void) struct Pokemon* party = NULL; s32 validMons = 0; s32 minNeeded; + + bool32 redCardForcedSwitch = FALSE; + + // Red card checks against wild pokemon. If we have reached here, the player has a mon to switch into + // Red card swaps attacker with target to get the animation correct, so here we check attacker which is really the target. Thanks GF... + if (gBattleScripting.switchCase == B_SWITCH_RED_CARD + && !(gBattleTypeFlags & BATTLE_TYPE_TRAINER) + && GetBattlerSide(gBattlerAttacker) == B_SIDE_OPPONENT) // Check opponent's red card activating + { + if (!WILD_DOUBLE_BATTLE) + { + // Wild mon with red card will end single battle + gBattlescriptCurrInstr = BattleScript_RoarSuccessEndBattle; + return; + } + else + { + // Wild double battle, wild mon red card activation on player + if (IS_WHOLE_SIDE_ALIVE(gBattlerTarget)) + { + // Both player's battlers are alive + redCardForcedSwitch = FALSE; + } + else + { + // Player has only one mon alive -> force red card switch before manually switching to other mon + redCardForcedSwitch = TRUE; + } + } + } // Swapping pokemon happens in: // trainer battles @@ -9358,8 +9496,9 @@ static void Cmd_forcerandomswitch(void) || (WILD_DOUBLE_BATTLE && GetBattlerSide(gBattlerAttacker) == B_SIDE_PLAYER && GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) + || redCardForcedSwitch ) - { + { if (GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) party = gPlayerParty; else @@ -9467,7 +9606,7 @@ static void Cmd_forcerandomswitch(void) } } - if (validMons <= minNeeded) + if (!redCardForcedSwitch && validMons <= minNeeded) { gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 1); } @@ -10636,8 +10775,8 @@ static void Cmd_handlerollout(void) static void Cmd_jumpifconfusedandstatmaxed(void) { if (gBattleMons[gBattlerTarget].status2 & STATUS2_CONFUSION - && gBattleMons[gBattlerTarget].statStages[gBattlescriptCurrInstr[1]] == MAX_STAT_STAGE) - gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 2); + && !CompareStat(gBattlerTarget, gBattlescriptCurrInstr[1], MAX_STAT_STAGE, CMP_LESS_THAN)) + gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 2); // Fails if we're confused AND stat cannot be raised else gBattlescriptCurrInstr += 6; } @@ -10846,7 +10985,8 @@ static void Cmd_maxattackhalvehp(void) // belly drum if (!(gBattleMons[gBattlerAttacker].maxHP / 2)) halfHp = 1; - + + // Belly Drum fails if the user's current HP is less than half its maximum, or if the user's Attack is already at +6 (even if the user has Contrary). if (gBattleMons[gBattlerAttacker].statStages[STAT_ATK] < MAX_STAT_STAGE && gBattleMons[gBattlerAttacker].hp > halfHp) { @@ -12531,14 +12671,14 @@ static void Cmd_trainerslideout(void) static const u16 sTelekinesisBanList[] = { - SPECIES_DIGLETT, - SPECIES_DUGTRIO, + SPECIES_DIGLETT, + SPECIES_DUGTRIO, #ifdef POKEMON_EXPANSION - SPECIES_DIGLETT_ALOLAN, - SPECIES_DUGTRIO_ALOLAN, - SPECIES_SANDYGAST, - SPECIES_PALOSSAND, - SPECIES_GENGAR_MEGA, + SPECIES_DIGLETT_ALOLAN, + SPECIES_DUGTRIO_ALOLAN, + SPECIES_SANDYGAST, + SPECIES_PALOSSAND, + SPECIES_GENGAR_MEGA, #endif }; diff --git a/src/battle_util.c b/src/battle_util.c index 9c1061b33..ccc0c558a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1410,7 +1410,6 @@ void CancelMultiTurnMoves(u8 battler) bool8 WasUnableToUseMove(u8 battler) { if (gProtectStructs[battler].prlzImmobility - || gProtectStructs[battler].targetNotAffected || gProtectStructs[battler].usedImprisonedMove || gProtectStructs[battler].loveImmobility || gProtectStructs[battler].usedDisabledMove @@ -1444,8 +1443,8 @@ void PrepareStringBattle(u16 stringId, u8 battler) // Check Defiant and Competitive stat raise whenever a stat is lowered. else if ((stringId == STRINGID_DEFENDERSSTATFELL || stringId == STRINGID_PKMNCUTSATTACKWITH) - && ((GetBattlerAbility(gBattlerTarget) == ABILITY_DEFIANT && gBattleMons[gBattlerTarget].statStages[STAT_ATK] != MAX_STAT_STAGE) - || (GetBattlerAbility(gBattlerTarget) == ABILITY_COMPETITIVE && gBattleMons[gBattlerTarget].statStages[STAT_SPATK] != MAX_STAT_STAGE)) + && ((GetBattlerAbility(gBattlerTarget) == ABILITY_DEFIANT && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + || (GetBattlerAbility(gBattlerTarget) == ABILITY_COMPETITIVE && CompareStat(gBattlerTarget, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN))) && gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != BATTLE_PARTNER(gBattlerTarget) && gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != gBattlerTarget) { @@ -4066,7 +4065,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move gSpecialStatuses[battler].switchInAbilityDone = 1; - if (gBattleMons[battler].statStages[statId] != MAX_STAT_STAGE) + if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) { gBattleMons[battler].statStages[statId]++; SET_STATCHANGER(statId, 1, FALSE); @@ -4317,7 +4316,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move } break; case ABILITY_SPEED_BOOST: - if (gBattleMons[battler].statStages[STAT_SPEED] < MAX_STAT_STAGE && gDisableStructs[battler].isFirstTurn != 2) + if (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) && gDisableStructs[battler].isFirstTurn != 2) { gBattleMons[battler].statStages[STAT_SPEED]++; gBattleScripting.animArg1 = 14 + STAT_SPEED; @@ -4335,9 +4334,9 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move for (i = STAT_ATK; i < statsNum; i++) { - if (gBattleMons[battler].statStages[i] != MIN_STAT_STAGE) + if (CompareStat(battler, i, MIN_STAT_STAGE, CMP_GREATER_THAN)) validToLower |= gBitTable[i]; - if (gBattleMons[battler].statStages[i] != MAX_STAT_STAGE) + if (CompareStat(battler, i, MAX_STAT_STAGE, CMP_LESS_THAN)) validToRaise |= gBitTable[i]; } @@ -4540,7 +4539,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move } else if (effect == 2) // Boost Stat ability; { - if (gBattleMons[battler].statStages[statId] == MAX_STAT_STAGE) + if (!CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) { if ((gProtectStructs[gBattlerAttacker].notFirstStrike)) gBattlescriptCurrInstr = BattleScript_MonMadeMoveUseless; @@ -4569,7 +4568,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) && moveType == TYPE_DARK - && gBattleMons[battler].statStages[STAT_ATK] != MAX_STAT_STAGE) + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptPushCursor(); @@ -4582,7 +4581,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) && (moveType == TYPE_DARK || moveType == TYPE_BUG || moveType == TYPE_GHOST) - && gBattleMons[battler].statStages[STAT_SPEED] != MAX_STAT_STAGE) + && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_SPEED, 1, FALSE); BattleScriptPushCursor(); @@ -4595,7 +4594,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) && moveType == TYPE_WATER - && gBattleMons[battler].statStages[STAT_DEF] != MAX_STAT_STAGE) + && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_DEF, 2, FALSE); BattleScriptPushCursor(); @@ -4607,7 +4606,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) - && gBattleMons[battler].statStages[STAT_DEF] != MAX_STAT_STAGE) + && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_DEF, 1, FALSE); BattleScriptPushCursor(); @@ -4624,7 +4623,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && gBattleMons[battler].hp < gBattleMons[battler].maxHP / 2 && (gMultiHitCounter == 0 || gMultiHitCounter == 1) && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) - && gBattleMons[battler].statStages[STAT_SPATK] != MAX_STAT_STAGE) + && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_SPATK, 1, FALSE); BattleScriptPushCursor(); @@ -4655,8 +4654,12 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) && IS_MOVE_PHYSICAL(gCurrentMove) - && (gBattleMons[battler].statStages[STAT_SPEED] != MAX_STAT_STAGE || gBattleMons[battler].statStages[STAT_DEF] != MIN_STAT_STAGE)) + && (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) // Don't activate if speed cannot be raised + || CompareStat(battler, STAT_DEF, MIN_STAT_STAGE, CMP_GREATER_THAN))) // Don't activate if defense cannot be lowered { + if (gBattleMoves[gCurrentMove].effect == EFFECT_HIT_ESCAPE && CanBattlerSwitch(gBattlerAttacker)) + gProtectStructs[battler].disableEjectPack = 1; // Set flag for target + BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_WeakArmorActivates; effect++; @@ -4712,7 +4715,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && gIsCriticalHit && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) - && gBattleMons[battler].statStages[STAT_ATK] != MAX_STAT_STAGE) + && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { SET_STATCHANGER(STAT_ATK, MAX_STAT_STAGE - gBattleMons[battler].statStages[STAT_ATK], FALSE); BattleScriptPushCursor(); @@ -4739,7 +4742,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move case ABILITY_TANGLING_HAIR: if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && gBattleMons[gBattlerAttacker].hp != 0 - && gBattleMons[gBattlerAttacker].statStages[STAT_SPEED] != MIN_STAT_STAGE + && CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN) && !gProtectStructs[gBattlerAttacker].confusionSelfDmg && TARGET_TURN_DAMAGED && IsMoveMakingContact(move, gBattlerAttacker)) @@ -4919,7 +4922,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && TARGET_TURN_DAMAGED && IsBattlerAlive(battler) - && gBattleMons[battler].statStages[STAT_SPEED] != MAX_STAT_STAGE + && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) && (moveType == TYPE_FIRE || moveType == TYPE_WATER)) { SET_STATCHANGER(STAT_SPEED, 6, FALSE); @@ -5532,11 +5535,9 @@ static u8 HealConfuseBerry(u32 battlerId, u32 itemId, u8 flavorId, bool32 end2) static u8 StatRaiseBerry(u32 battlerId, u32 itemId, u32 statId, bool32 end2) { - if (gBattleMons[battlerId].statStages[statId] < MAX_STAT_STAGE && HasEnoughHpToEatBerry(battlerId, GetBattlerHoldEffectParam(battlerId), itemId)) + if (CompareStat(battlerId, statId, MAX_STAT_STAGE, CMP_LESS_THAN) && HasEnoughHpToEatBerry(battlerId, GetBattlerHoldEffectParam(battlerId), itemId)) { - PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); - PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATROSE); - + BufferStatChange(battlerId, statId, STRINGID_STATROSE); gEffectBattler = battlerId; if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) SET_STATCHANGER(statId, 2, FALSE); @@ -5563,10 +5564,11 @@ static u8 StatRaiseBerry(u32 battlerId, u32 itemId, u32 statId, bool32 end2) static u8 RandomStatRaiseBerry(u32 battlerId, u32 itemId, bool32 end2) { s32 i; + u16 stringId; for (i = 0; i < 5; i++) { - if (gBattleMons[battlerId].statStages[STAT_ATK + i] < MAX_STAT_STAGE) + if (CompareStat(battlerId, STAT_ATK + i, MAX_STAT_STAGE, CMP_LESS_THAN)) break; } if (i != 5 && HasEnoughHpToEatBerry(battlerId, GetBattlerHoldEffectParam(battlerId), itemId)) @@ -5574,21 +5576,19 @@ static u8 RandomStatRaiseBerry(u32 battlerId, u32 itemId, bool32 end2) do { i = Random() % 5; - } while (gBattleMons[battlerId].statStages[STAT_ATK + i] == MAX_STAT_STAGE); + } while (!CompareStat(battlerId, STAT_ATK + i, MAX_STAT_STAGE, CMP_LESS_THAN)); PREPARE_STAT_BUFFER(gBattleTextBuff1, i + 1); - + stringId = (GetBattlerAbility(battlerId) == ABILITY_CONTRARY) ? STRINGID_STATFELL : STRINGID_STATROSE; gBattleTextBuff2[0] = B_BUFF_PLACEHOLDER_BEGIN; gBattleTextBuff2[1] = B_BUFF_STRING; gBattleTextBuff2[2] = STRINGID_STATSHARPLY; gBattleTextBuff2[3] = STRINGID_STATSHARPLY >> 8; gBattleTextBuff2[4] = B_BUFF_STRING; - gBattleTextBuff2[5] = STRINGID_STATROSE; - gBattleTextBuff2[6] = STRINGID_STATROSE >> 8; + gBattleTextBuff2[5] = stringId; + gBattleTextBuff2[6] = stringId >> 8; gBattleTextBuff2[7] = EOS; - gEffectBattler = battlerId; - if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) SET_STATCHANGER(i + 1, 4, FALSE); else @@ -5596,7 +5596,6 @@ static u8 RandomStatRaiseBerry(u32 battlerId, u32 itemId, bool32 end2) gBattleScripting.animArg1 = 0x21 + i + 6; gBattleScripting.animArg2 = 0; - if (end2) { BattleScriptExecute(BattleScript_BerryStatRaiseEnd2); @@ -5606,6 +5605,7 @@ static u8 RandomStatRaiseBerry(u32 battlerId, u32 itemId, bool32 end2) BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_BerryStatRaiseRet; } + return ITEM_STATS_CHANGE; } return 0; @@ -5635,13 +5635,11 @@ static u8 DamagedStatBoostBerryEffect(u8 battlerId, u8 statId, u8 split) { if (IsBattlerAlive(battlerId) && TARGET_TURN_DAMAGED - && gBattleMons[battlerId].statStages[statId] < MAX_STAT_STAGE + && CompareStat(battlerId, statId, MAX_STAT_STAGE, CMP_LESS_THAN) && !DoesSubstituteBlockMove(gBattlerAttacker, battlerId, gCurrentMove) && GetBattleMoveSplit(gCurrentMove) == split) { - - PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); - PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATROSE); + BufferStatChange(battlerId, statId, STRINGID_STATROSE); gEffectBattler = battlerId; if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) @@ -5658,6 +5656,30 @@ static u8 DamagedStatBoostBerryEffect(u8 battlerId, u8 statId, u8 split) return 0; } +u8 TryHandleSeed(u8 battler, u32 terrainFlag, u8 statId, u16 itemId, bool32 execute) +{ + if (gFieldStatuses & terrainFlag && CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + BufferStatChange(battler, statId, STRINGID_STATROSE); + gLastUsedItem = itemId; // For surge abilities + gEffectBattler = gBattleScripting.battler = battler; + SET_STATCHANGER(statId, 1, FALSE); + gBattleScripting.animArg1 = 0xE + statId; + gBattleScripting.animArg2 = 0; + if (execute) + { + BattleScriptExecute(BattleScript_BerryStatRaiseEnd2); + } + else + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_BerryStatRaiseRet; + } + return ITEM_STATS_CHANGE; + } + return 0; +} + static u8 ItemHealHp(u32 battlerId, u32 itemId, bool32 end2, bool32 percentHeal) { if (HasEnoughHpToEatBerry(battlerId, 2, itemId)) @@ -5693,6 +5715,61 @@ static bool32 UnnerveOn(u32 battlerId, u32 itemId) return FALSE; } +static bool32 GetMentalHerbEffect(u8 battlerId) +{ + bool32 ret = FALSE; + + // Check infatuation + if (gBattleMons[battlerId].status2 & STATUS2_INFATUATION) + { + gBattleMons[battlerId].status2 &= ~(STATUS2_INFATUATION); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_MENTALHERBCURE_INFATUATION; // STRINGID_TARGETGOTOVERINFATUATION + StringCopy(gBattleTextBuff1, gStatusConditionString_LoveJpn); + ret = TRUE; + } + #if B_MENTAL_HERB >= GEN_5 + // Check taunt + if (gDisableStructs[battlerId].tauntTimer != 0) + { + gDisableStructs[battlerId].tauntTimer = gDisableStructs[battlerId].tauntTimer2 = 0; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_MENTALHERBCURE_TAUNT; + PREPARE_MOVE_BUFFER(gBattleTextBuff1, MOVE_TAUNT); + ret = TRUE; + } + // Check encore + if (gDisableStructs[battlerId].encoreTimer != 0) + { + gDisableStructs[battlerId].encoredMove = 0; + gDisableStructs[battlerId].encoreTimerStartValue = gDisableStructs[battlerId].encoreTimer = 0; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_MENTALHERBCURE_ENCORE; // STRINGID_PKMNENCOREENDED + ret = TRUE; + } + // Check torment + if (gBattleMons[battlerId].status2 & STATUS2_TORMENT) + { + gBattleMons[battlerId].status2 &= ~(STATUS2_TORMENT); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_MENTALHERBCURE_TORMENT; + ret = TRUE; + } + // Check heal block + if (gStatuses3[battlerId] & STATUS3_HEAL_BLOCK) + { + gStatuses3[battlerId] &= ~(STATUS3_HEAL_BLOCK); + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_MENTALHERBCURE_HEALBLOCK; + ret = TRUE; + } + // Check disable + if (gDisableStructs[battlerId].disableTimer != 0) + { + gDisableStructs[battlerId].disableTimer = gDisableStructs[battlerId].disableTimerStartValue = 0; + gDisableStructs[battlerId].disabledMove = 0; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_MENTALHERBCURE_DISABLE; + ret = TRUE; + } + #endif + return ret; +} + u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) { int i = 0, moveType; @@ -5892,6 +5969,48 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) BattleScriptPushCursorAndCallback(BattleScript_AirBaloonMsgIn); RecordItemEffectBattle(battlerId, HOLD_EFFECT_AIR_BALLOON); break; + case HOLD_EFFECT_ROOM_SERVICE: + if (TryRoomService(battlerId)) + { + BattleScriptExecute(BattleScript_BerryStatRaiseEnd2); + effect = ITEM_STATS_CHANGE; + } + break; + case HOLD_EFFECT_SEEDS: + switch (GetBattlerHoldEffectParam(battlerId)) + { + case HOLD_EFFECT_PARAM_ELECTRIC_TERRAIN: + effect = TryHandleSeed(battlerId, STATUS_FIELD_ELECTRIC_TERRAIN, STAT_DEF, gLastUsedItem, TRUE); + break; + case HOLD_EFFECT_PARAM_GRASSY_TERRAIN: + effect = TryHandleSeed(battlerId, STATUS_FIELD_GRASSY_TERRAIN, STAT_DEF, gLastUsedItem, TRUE); + break; + case HOLD_EFFECT_PARAM_MISTY_TERRAIN: + effect = TryHandleSeed(battlerId, STATUS_FIELD_MISTY_TERRAIN, STAT_SPDEF, gLastUsedItem, TRUE); + break; + case HOLD_EFFECT_PARAM_PSYCHIC_TERRAIN: + effect = TryHandleSeed(battlerId, STATUS_FIELD_PSYCHIC_TERRAIN, STAT_SPDEF, gLastUsedItem, TRUE); + break; + } + break; + case HOLD_EFFECT_EJECT_PACK: + if (gSpecialStatuses[battlerId].statFell + && !(gCurrentMove == MOVE_PARTING_SHOT && CanBattlerSwitch(gBattlerAttacker))) // Does not activate if attacker used Parting Shot and can switch out + { + gSpecialStatuses[battlerId].statFell = FALSE; + gActiveBattler = gBattleScripting.battler = battlerId; + effect = ITEM_STATS_CHANGE; + if (moveTurn) + { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EjectPackActivate_Ret; + } + else + { + BattleScriptExecute(BattleScript_EjectPackActivate_End2); + } + } + break; } if (effect) @@ -6144,13 +6263,12 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) effect = ITEM_STATUS_CHANGE; } break; - case HOLD_EFFECT_CURE_ATTRACT: - if (gBattleMons[battlerId].status2 & STATUS2_INFATUATION) + case HOLD_EFFECT_MENTAL_HERB: + if (GetMentalHerbEffect(battlerId)) { - gBattleMons[battlerId].status2 &= ~(STATUS2_INFATUATION); - StringCopy(gBattleTextBuff1, gStatusConditionString_LoveJpn); - BattleScriptExecute(BattleScript_BerryCureChosenStatusEnd2); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_CURED_PROBLEM; + gBattleScripting.savedBattler = gBattlerAttacker; + gBattlerAttacker = battlerId; + BattleScriptExecute(BattleScript_MentalHerbCureEnd2); effect = ITEM_EFFECT_OTHER; } break; @@ -6295,14 +6413,13 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) effect = ITEM_EFFECT_OTHER; } break; - case HOLD_EFFECT_CURE_ATTRACT: - if (gBattleMons[battlerId].status2 & STATUS2_INFATUATION) + case HOLD_EFFECT_MENTAL_HERB: + if (GetMentalHerbEffect(battlerId)) { - gBattleMons[battlerId].status2 &= ~(STATUS2_INFATUATION); - StringCopy(gBattleTextBuff1, gStatusConditionString_LoveJpn); + gBattleScripting.savedBattler = gBattlerAttacker; + gBattlerAttacker = battlerId; BattleScriptPushCursor(); - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_CURED_PROBLEM; - gBattlescriptCurrInstr = BattleScript_BerryCureChosenStatusRet; + gBattlescriptCurrInstr = BattleScript_MentalHerbCureRet; effect = ITEM_EFFECT_OTHER; } break; @@ -6374,45 +6491,91 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) } } break; - case ITEMEFFECT_KINGSROCK_SHELLBELL: - if (gBattleMoveDamage) + case ITEMEFFECT_KINGSROCK: + // Occur on each hit of a multi-strike move + switch (atkHoldEffect) { - switch (atkHoldEffect) + case HOLD_EFFECT_FLINCH: + if (gBattleMoveDamage != 0 // Need to have done damage + && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + && TARGET_TURN_DAMAGED + && (Random() % 100) < atkHoldEffectParam + && gBattleMoves[gCurrentMove].flags & FLAG_KINGS_ROCK_AFFECTED + && gBattleMons[gBattlerTarget].hp) { - case HOLD_EFFECT_FLINCH: - if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) - && TARGET_TURN_DAMAGED - && (Random() % 100) < atkHoldEffectParam - && gBattleMoves[gCurrentMove].flags & FLAG_KINGS_ROCK_AFFECTED - && gBattleMons[gBattlerTarget].hp) - { - gBattleScripting.moveEffect = MOVE_EFFECT_FLINCH; - BattleScriptPushCursor(); - SetMoveEffect(FALSE, 0); - BattleScriptPop(); - } - break; - case HOLD_EFFECT_SHELL_BELL: - if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) - && gSpecialStatuses[gBattlerTarget].dmg != 0 - && gSpecialStatuses[gBattlerTarget].dmg != 0xFFFF - && gBattlerAttacker != gBattlerTarget - && gBattleMons[gBattlerAttacker].hp != gBattleMons[gBattlerAttacker].maxHP - && gBattleMons[gBattlerAttacker].hp != 0) - { - gLastUsedItem = atkItem; - gPotentialItemEffectBattler = gBattlerAttacker; - gBattleScripting.battler = gBattlerAttacker; - gBattleMoveDamage = (gSpecialStatuses[gBattlerTarget].dmg / atkHoldEffectParam) * -1; - if (gBattleMoveDamage == 0) - gBattleMoveDamage = -1; - gSpecialStatuses[gBattlerTarget].dmg = 0; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_ItemHealHP_Ret; - effect++; - } - break; + gBattleScripting.moveEffect = MOVE_EFFECT_FLINCH; + BattleScriptPushCursor(); + SetMoveEffect(FALSE, 0); + BattleScriptPop(); } + break; + case HOLD_EFFECT_BLUNDER_POLICY: + if (gBattleStruct->blunderPolicy + && gBattleMons[gBattlerAttacker].hp != 0 + && CompareStat(gBattlerAttacker, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gBattleStruct->blunderPolicy = FALSE; + gLastUsedItem = atkItem; + gBattleScripting.statChanger = SET_STATCHANGER(STAT_SPEED, 2, FALSE); + effect = ITEM_STATS_CHANGE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AttackerItemStatRaise; + } + break; + } + break; + case ITEMEFFECT_LIFEORB_SHELLBELL: + // Occur after the final hit of a multi-strike move + switch (atkHoldEffect) + { + case HOLD_EFFECT_SHELL_BELL: + if (gSpecialStatuses[gBattlerAttacker].damagedMons // Need to have done damage + && gBattlerAttacker != gBattlerTarget + && gBattleMons[gBattlerAttacker].hp != gBattleMons[gBattlerAttacker].maxHP + && gBattleMons[gBattlerAttacker].hp != 0) + { + gLastUsedItem = atkItem; + gPotentialItemEffectBattler = gBattlerAttacker; + gBattleScripting.battler = gBattlerAttacker; + gBattleMoveDamage = (gSpecialStatuses[gBattlerTarget].dmg / atkHoldEffectParam) * -1; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = -1; + gSpecialStatuses[gBattlerTarget].dmg = 0; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_ItemHealHP_Ret; + effect = ITEM_HP_CHANGE; + } + break; + case HOLD_EFFECT_LIFE_ORB: + if (IsBattlerAlive(gBattlerAttacker) + && !(TestSheerForceFlag(gBattlerAttacker, gCurrentMove)) + && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD + && gSpecialStatuses[gBattlerAttacker].damagedMons) + { + gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 10; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; + effect = ITEM_HP_CHANGE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_ItemHurtRet; + gLastUsedItem = gBattleMons[gBattlerAttacker].item; + } + break; + case HOLD_EFFECT_THROAT_SPRAY: // Does NOT need to be a damaging move + if (gProtectStructs[gBattlerAttacker].targetAffected + && gBattleMons[gBattlerAttacker].hp != 0 + && gBattleMoves[gCurrentMove].flags & FLAG_SOUND + && CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN) + && !NoAliveMonsForEitherParty()) // Don't activate if battle will end + { + gLastUsedItem = atkItem; + gBattleScripting.battler = gBattlerAttacker; + gBattleScripting.statChanger = SET_STATCHANGER(STAT_SPATK, 1, FALSE); + effect = ITEM_STATS_CHANGE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_AttackerItemStatRaise; + } + break; } break; case ITEMEFFECT_TARGET: @@ -8818,14 +8981,6 @@ void SortBattlersBySpeed(u8 *battlers, bool8 slowToFast) } } -bool32 TestSheerForceFlag(u8 battler, u16 move) -{ - if (GetBattlerAbility(battler) == ABILITY_SHEER_FORCE && gBattleMoves[move].flags & FLAG_SHEER_FORCE_BOOST) - return TRUE; - else - return FALSE; -} - void TryRestoreStolenItems(void) { u32 i; @@ -8896,3 +9051,122 @@ void TrySaveExchangedItem(u8 battlerId, u16 stolenItem) #endif } +bool32 IsBattlerAffectedByHazards(u8 battlerId, bool32 toxicSpikes) +{ + bool32 ret = TRUE; + u32 holdEffect = GetBattlerHoldEffect(gActiveBattler, TRUE); + if (toxicSpikes && holdEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS && !IS_BATTLER_OF_TYPE(battlerId, TYPE_POISON)) + { + ret = FALSE; + RecordItemEffectBattle(battlerId, holdEffect); + } + else if (holdEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS) + { + ret = FALSE; + RecordItemEffectBattle(battlerId, holdEffect); + } + return ret; +} + +bool32 TestSheerForceFlag(u8 battler, u16 move) +{ + if (GetBattlerAbility(battler) == ABILITY_SHEER_FORCE && gBattleMoves[move].flags & FLAG_SHEER_FORCE_BOOST) + return TRUE; + else + return FALSE; +} + +// This function is the body of "jumpifstat", but can be used dynamically in a function +bool32 CompareStat(u8 battlerId, u8 statId, u8 cmpTo, u8 cmpKind) +{ + bool8 ret = FALSE; + u8 statValue = gBattleMons[battlerId].statStages[statId]; + + // Because this command is used as a way of checking if a stat can be lowered/raised, + // we need to do some modification at run-time. + if (GetBattlerAbility(battlerId) == ABILITY_CONTRARY) + { + if (cmpKind == CMP_GREATER_THAN) + cmpKind = CMP_LESS_THAN; + else if (cmpKind == CMP_LESS_THAN) + cmpKind = CMP_GREATER_THAN; + + if (cmpTo == MIN_STAT_STAGE) + cmpTo = MAX_STAT_STAGE; + else if (cmpTo == MAX_STAT_STAGE) + cmpTo = MIN_STAT_STAGE; + } + + switch (cmpKind) + { + case CMP_EQUAL: + if (statValue == cmpTo) + ret = TRUE; + break; + case CMP_NOT_EQUAL: + if (statValue != cmpTo) + ret = TRUE; + break; + case CMP_GREATER_THAN: + if (statValue > cmpTo) + ret = TRUE; + break; + case CMP_LESS_THAN: + if (statValue < cmpTo) + ret = TRUE; + break; + case CMP_COMMON_BITS: + if (statValue & cmpTo) + ret = TRUE; + break; + case CMP_NO_COMMON_BITS: + if (!(statValue & cmpTo)) + ret = TRUE; + break; + } + + return ret; +} + +void BufferStatChange(u8 battlerId, u8 statId, u8 stringId) +{ + bool8 hasContrary = (GetBattlerAbility(battlerId) == ABILITY_CONTRARY); + + PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); + if (stringId == STRINGID_STATFELL) + { + if (hasContrary) + PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATROSE) + else + PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATFELL) + } + else if (stringId == STRINGID_STATROSE) + { + if (hasContrary) + PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATFELL) + else + PREPARE_STRING_BUFFER(gBattleTextBuff2, STRINGID_STATROSE) + } + else + { + PREPARE_STRING_BUFFER(gBattleTextBuff2, stringId) + } +} + +bool32 TryRoomService(u8 battlerId) +{ + if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM && CompareStat(battlerId, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN)) + { + BufferStatChange(battlerId, STAT_SPEED, STRINGID_STATFELL); + gEffectBattler = gBattleScripting.battler = battlerId; + SET_STATCHANGER(STAT_SPEED, 1, TRUE); + gBattleScripting.animArg1 = 0xE + STAT_SPEED; + gBattleScripting.animArg2 = 0; + gLastUsedItem = gBattleMons[battlerId].item; + return TRUE; + } + else + { + return FALSE; + } +} diff --git a/src/data/items.h b/src/data/items.h index 370cd2022..ef5c8825d 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -2262,7 +2262,7 @@ const struct Item gItems[] = .name = _("MENTAL HERB"), .itemId = ITEM_MENTAL_HERB, .price = 100, - .holdEffect = HOLD_EFFECT_CURE_ATTRACT, + .holdEffect = HOLD_EFFECT_MENTAL_HERB, .description = sMentalHerbDesc, .pocket = POCKET_ITEMS, .type = ITEM_USE_BAG_MENU,