diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index cc06b4bf41..a279ba7569 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1380,6 +1380,17 @@ callnative BS_TryRelicSong .endm + .macro setpledge jumpInstr:req + callnative BS_SetPledge + .4byte \jumpInstr + .endm + + .macro setpledgestatus battler:req sidestatus:req + callnative BS_SetPledgeStatus + .byte \battler + .4byte \sidestatus + .endm + .macro setzeffect callnative BS_SetZEffect .endm diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index be0ae0adbb..3a1cd720c3 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -1010,6 +1010,9 @@ gBattleAnims_General:: .4byte General_DynamaxGrowth @ B_ANIM_DYNAMAX_GROWTH .4byte General_SetWeather @ B_ANIM_MAX_SET_WEATHER .4byte General_SyrupBombSpeedDrop @ B_ANIM_SYRUP_BOMB_SPEED_DROP + .4byte General_Rainbow @ B_ANIM_RAINBOW + .4byte General_SeaOfFire @ B_ANIM_SEA_OF_FIRE + .4byte General_Swamp @ B_ANIM_SWAMP .align 2 gBattleAnims_Special:: @@ -27060,6 +27063,10 @@ General_HangedOn: end General_Rain: + call RainDrops + end + +RainDrops: loadspritegfx ANIM_TAG_RAIN_DROPS playsewithpan SE_M_RAIN_DANCE, SOUND_PAN_ATTACKER createvisualtask AnimTask_BlendBattleAnimPal, 10, (F_PAL_BG | F_PAL_BATTLERS), 2, 0, 4, RGB_BLACK @@ -27070,7 +27077,7 @@ General_Rain: waitforvisualfinish createvisualtask AnimTask_BlendBattleAnimPal, 10, (F_PAL_BG | F_PAL_BATTLERS), 2, 4, 0, RGB_BLACK waitforvisualfinish - end + return General_Sun: goto Move_SUNNY_DAY @@ -27515,6 +27522,75 @@ General_AffectionHangedOn_3Hearts: General_SaltCureDamage:: goto Status_Freeze +General_Rainbow:: + call RainDrops + delay 30 + loadspritegfx ANIM_TAG_SUNLIGHT + createvisualtask AnimTask_BlendBattleAnimPal, 10, (F_PAL_BG | F_PAL_BATTLERS), 1, 0, 6, RGB_WHITE + waitforvisualfinish + panse_adjustnone SE_M_PETAL_DANCE, SOUND_PAN_ATTACKER, SOUND_PAN_TARGET, +1, 0 + call SunnyDayLightRay + call SunnyDayLightRay + call SunnyDayLightRay + waitforvisualfinish + createvisualtask AnimTask_BlendBattleAnimPal, 10, (F_PAL_BG | F_PAL_BATTLERS), 1, 6, 0, RGB_WHITE + waitforvisualfinish + delay 30 + fadetobg BG_RAINBOW + panse_adjustnone SE_M_ABSORB_2, SOUND_PAN_ATTACKER, SOUND_PAN_TARGET, +1, 0 + delay 90 + blendoff + restorebg + waitbgfadein + clearmonbg ANIM_ATK_PARTNER + end + +General_SeaOfFire:: + loadspritegfx ANIM_TAG_SMALL_EMBER + monbg ANIM_DEF_PARTNER + splitbgprio ANIM_TARGET + playsewithpan SE_M_SACRED_FIRE2, SOUND_PAN_TARGET + call SeaOfFireTwisterDos + delay 3 + call SeaOfFireTwisterTres + waitforvisualfinish + clearmonbg ANIM_DEF_PARTNER + blendoff + end + +SeaOfFireTwisterDos: + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 45, 90, 5, 70, 30 + delay 2 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 50, 85, 6, 60, 30 + delay 1 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 47, 77, 7, 60, 30 + delay 2 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 40, 86, 8, 50, 30 + delay 3 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 42, 82, 7, 45, 30 + delay 1 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 47, 83, 5, 38, 30 + delay 2 + return + +SeaOfFireTwisterTres: + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 45, 90, 3, 45, 30 + delay 2 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 50, 85, 4, 39, 30 + delay 1 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 47, 77, 5, 39, 30 + delay 2 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 40, 86, 6, 32, 30 + delay 3 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 42, 82, 5, 27, 30 + delay 1 + createsprite gTwisterEmberSpriteTemplate, ANIM_TARGET, 2, 47, 83, 3, 24, 30 + delay 2 + return + +General_Swamp:: @ To do + goto Move_HAZE + SnatchMoveTrySwapFromSubstitute: createvisualtask AnimTask_IsAttackerBehindSubstitute, 2 jumprettrue SnatchMoveSwapSubstituteForMon diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 6581f5ca38..2e5494a03a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -231,7 +231,7 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectCalmMind @ EFFECT_CALM_MIND .4byte BattleScript_EffectDragonDance @ EFFECT_DRAGON_DANCE .4byte BattleScript_EffectCamouflage @ EFFECT_CAMOUFLAGE - .4byte BattleScript_EffectHit @ EFFECT_PLEDGE + .4byte BattleScript_EffectPledge @ EFFECT_PLEDGE .4byte BattleScript_EffectFling @ EFFECT_FLING .4byte BattleScript_EffectNaturalGift @ EFFECT_NATURAL_GIFT .4byte BattleScript_EffectWakeUpSlap @ EFFECT_WAKE_UP_SLAP @@ -472,6 +472,90 @@ BattleScript_EffectMatchaGotcha:: setmoveeffect MOVE_EFFECT_BURN goto BattleScript_EffectAbsorb +BattleScript_EffectPledge:: + attackcanceler + setpledge BattleScript_HitFromAccCheck + attackstring + pause B_WAIT_TIME_MED + ppreduce + printstring STRINGID_WAITINGFORPARTNERSMOVE + waitmessage B_WAIT_TIME_LONG + goto BattleScript_MoveEnd + +BattleScript_EffectCombinedPledge_Water:: + call BattleScript_EffectHit_Pledge + setpledgestatus BS_ATTACKER, SIDE_STATUS_RAINBOW + pause B_WAIT_TIME_SHORTEST + printstring STRINGID_ARAINBOWAPPEAREDONSIDE + waitmessage B_WAIT_TIME_LONG + playanimation BS_ATTACKER, B_ANIM_RAINBOW + waitanimation + goto BattleScript_MoveEnd + +BattleScript_TheRainbowDisappeared:: + printstring STRINGID_THERAINBOWDISAPPEARED + waitmessage B_WAIT_TIME_LONG + end2 + +BattleScript_EffectCombinedPledge_Fire:: + call BattleScript_EffectHit_Pledge + setpledgestatus BS_TARGET, SIDE_STATUS_SEA_OF_FIRE + pause B_WAIT_TIME_SHORTEST + printstring STRINGID_SEAOFFIREENVELOPEDSIDE + waitmessage B_WAIT_TIME_LONG + playanimation BS_TARGET, B_ANIM_SEA_OF_FIRE + waitanimation + goto BattleScript_MoveEnd + +BattleScript_HurtByTheSeaOfFire:: + printstring STRINGID_HURTBYTHESEAOFFIRE + waitmessage B_WAIT_TIME_LONG + goto BattleScript_DoTurnDmg + +BattleScript_TheSeaOfFireDisappeared:: + printstring STRINGID_THESEAOFFIREDISAPPEARED + waitmessage B_WAIT_TIME_LONG + end2 + +BattleScript_EffectCombinedPledge_Grass:: + call BattleScript_EffectHit_Pledge + setpledgestatus BS_TARGET, SIDE_STATUS_SWAMP + pause B_WAIT_TIME_SHORTEST + printstring STRINGID_SWAMPENVELOPEDSIDE + waitmessage B_WAIT_TIME_LONG + playanimation BS_TARGET, B_ANIM_SWAMP + waitanimation + goto BattleScript_MoveEnd + +BattleScript_TheSwampDisappeared:: + printstring STRINGID_THESWAMPDISAPPEARED + waitmessage B_WAIT_TIME_LONG + end2 + +BattleScript_EffectHit_Pledge:: + pause B_WAIT_TIME_MED + printstring STRINGID_THETWOMOVESBECOMEONE + waitmessage B_WAIT_TIME_LONG + accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE + ppreduce + critcalc + damagecalc + adjustdamage + attackanimation + waitanimation + effectivenesssound + hitanimation BS_TARGET + waitstate + healthbarupdate BS_TARGET + datahpupdate BS_TARGET + critmessage + waitmessage B_WAIT_TIME_LONG + resultmessage + waitmessage B_WAIT_TIME_LONG + seteffectwithchance + tryfaintmon BS_TARGET + return + BattleScript_EffectSaltCure: call BattleScript_EffectHit_Ret tryfaintmon BS_TARGET diff --git a/graphics/battle_anims/backgrounds/rainbow.bin b/graphics/battle_anims/backgrounds/rainbow.bin new file mode 100644 index 0000000000..770389abf4 Binary files /dev/null and b/graphics/battle_anims/backgrounds/rainbow.bin differ diff --git a/graphics/battle_anims/backgrounds/rainbow.pal b/graphics/battle_anims/backgrounds/rainbow.pal new file mode 100644 index 0000000000..9b62b7b25b --- /dev/null +++ b/graphics/battle_anims/backgrounds/rainbow.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +109 92 75 +255 255 255 +255 107 122 +255 200 102 +255 255 107 +143 255 160 +107 255 255 +107 129 255 +220 114 255 +199 255 250 +232 240 248 +224 232 240 +208 224 240 +191 202 224 +183 189 202 +157 166 181 diff --git a/graphics/battle_anims/backgrounds/rainbow.png b/graphics/battle_anims/backgrounds/rainbow.png new file mode 100644 index 0000000000..bd41645b35 Binary files /dev/null and b/graphics/battle_anims/backgrounds/rainbow.png differ diff --git a/include/battle.h b/include/battle.h index 1578e1e53b..902295e873 100644 --- a/include/battle.h +++ b/include/battle.h @@ -235,6 +235,9 @@ struct SideTimer u8 retaliateTimer; u8 damageNonTypesTimer; u8 damageNonTypesType; + u8 rainbowTimer; + u8 seaOfFireTimer; + u8 swampTimer; }; struct FieldTimer @@ -726,6 +729,7 @@ struct BattleStruct u32 aiDelayTimer; // Counts number of frames AI takes to choose an action. u32 aiDelayFrames; // Number of frames it took to choose an action. bool8 transformZeroToHero[PARTY_SIZE][NUM_BATTLE_SIDES]; + u8 pledgeMove:1; }; // The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, diff --git a/include/battle_scripts.h b/include/battle_scripts.h index d2addbc4c3..4790998cc4 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -483,6 +483,13 @@ extern const u8 BattleScript_SelectingNotAllowedCurrentMoveInPalace[]; extern const u8 BattleScript_SaltCureExtraDamage[]; extern const u8 BattleScript_SyrupBombEndTurn[]; extern const u8 BattleScript_SyrupBombActivates[]; +extern const u8 BattleScript_EffectCombinedPledge_Water[]; +extern const u8 BattleScript_EffectCombinedPledge_Fire[]; +extern const u8 BattleScript_EffectCombinedPledge_Grass[]; +extern const u8 BattleScript_TheRainbowDisappeared[]; +extern const u8 BattleScript_HurtByTheSeaOfFire[]; +extern const u8 BattleScript_TheSeaOfFireDisappeared[]; +extern const u8 BattleScript_TheSwampDisappeared[]; // zmoves extern const u8 BattleScript_ZMoveActivateDamaging[]; diff --git a/include/battle_util.h b/include/battle_util.h index 91362bbeaf..20c458fc94 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -247,7 +247,7 @@ void RemoveConfusionStatus(u32 battler); u8 GetBattlerGender(u32 battler); bool32 AreBattlersOfOppositeGender(u32 battler1, u32 battler2); bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2); -u32 CalcSecondaryEffectChance(u32 battler, u8 secondaryEffectChance); +u32 CalcSecondaryEffectChance(u32 battler, u8 secondaryEffectChance, u16 moveEffect); u8 GetBattlerType(u32 battler, u8 typeIndex); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/config/battle.h b/include/config/battle.h index d1ba9b969a..cc82096e15 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -56,7 +56,7 @@ #define B_TAUNT_TURNS GEN_LATEST // In Gen5+, Taunt lasts 3 turns if the user acts before the target, or 4 turns if the target acted before the user. In Gen3, taunt lasts 2 turns and in Gen 4, 3-5 turns. #define B_SPORT_TURNS GEN_LATEST // In Gen6+, Water/Mud Sport last 5 turns, even if the user switches out. #define B_MEGA_EVO_TURN_ORDER GEN_LATEST // In Gen7, a Pokémon's Speed after Mega Evolution is used to determine turn order, not its Speed before. -#define B_RECALC_TURN_AFTER_ACTIONS GEN_LATEST // In Gen8, switching/using a move affects the current turn's order of actions. +#define B_RECALC_TURN_AFTER_ACTIONS GEN_LATEST // In Gen8, switching/using a move affects the current turn's order of actions, better known as dynamic speed. #define B_FAINT_SWITCH_IN GEN_LATEST // In Gen4+, sending out a new Pokémon after the previous one fainted happens at the end of the turn. Before, it would happen after each action. // Move data settings diff --git a/include/constants/battle.h b/include/constants/battle.h index c8b53f4ca8..ecc823ee8b 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -238,9 +238,13 @@ #define SIDE_STATUS_MAT_BLOCK (1 << 21) #define SIDE_STATUS_STEELSURGE (1 << 22) #define SIDE_STATUS_DAMAGE_NON_TYPES (1 << 23) +#define SIDE_STATUS_RAINBOW (1 << 24) +#define SIDE_STATUS_SEA_OF_FIRE (1 << 25) +#define SIDE_STATUS_SWAMP (1 << 26) #define SIDE_STATUS_HAZARDS_ANY (SIDE_STATUS_SPIKES | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STEELSURGE) #define SIDE_STATUS_SCREEN_ANY (SIDE_STATUS_REFLECT | SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL) +#define SIDE_STATUS_PLEDGE_ANY (SIDE_STATUS_RAINBOW | SIDE_STATUS_SEA_OF_FIRE | SIDE_STATUS_SWAMP) // Field affecting statuses. #define STATUS_FIELD_MAGIC_ROOM (1 << 0) diff --git a/include/constants/battle_anim.h b/include/constants/battle_anim.h index 0a01eb2996..76e68b2457 100644 --- a/include/constants/battle_anim.h +++ b/include/constants/battle_anim.h @@ -515,6 +515,7 @@ #define BG_STEEL_BEAM_OPPONENT 78 #define BG_STEEL_BEAM_PLAYER 79 #define BG_CHLOROBLAST 80 +#define BG_RAINBOW 81 // table ids for general animations (gBattleAnims_General) #define B_ANIM_STATS_CHANGE 0 @@ -559,6 +560,9 @@ #define B_ANIM_DYNAMAX_GROWTH 39 #define B_ANIM_MAX_SET_WEATHER 40 #define B_ANIM_SYRUP_BOMB_SPEED_DROP 41 +#define B_ANIM_RAINBOW 42 +#define B_ANIM_SEA_OF_FIRE 43 +#define B_ANIM_SWAMP 44 // special animations table (gBattleAnims_Special) #define B_ANIM_LVL_UP 0 diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 69e3dd935d..354b3d20b5 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -686,8 +686,17 @@ #define STRINGID_PKMNHURTBYROCKSTHROWN 684 #define STRINGID_MOVEBLOCKEDBYDYNAMAX 685 #define STRINGID_ZEROTOHEROTRANSFORMATION 686 +#define STRINGID_THETWOMOVESBECOMEONE 687 +#define STRINGID_ARAINBOWAPPEAREDONSIDE 688 +#define STRINGID_THERAINBOWDISAPPEARED 689 +#define STRINGID_WAITINGFORPARTNERSMOVE 690 +#define STRINGID_SEAOFFIREENVELOPEDSIDE 691 +#define STRINGID_HURTBYTHESEAOFFIRE 692 +#define STRINGID_THESEAOFFIREDISAPPEARED 693 +#define STRINGID_SWAMPENVELOPEDSIDE 694 +#define STRINGID_THESWAMPDISAPPEARED 695 -#define BATTLESTRINGS_COUNT 687 +#define BATTLESTRINGS_COUNT 696 // 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/graphics.h b/include/graphics.h index 91c6edce8d..0c86ca3a3c 100644 --- a/include/graphics.h +++ b/include/graphics.h @@ -11510,6 +11510,11 @@ extern const u16 gSlotMachineReelTimePikachu_Pal[]; extern const u32 gBattleAnimBgTilemap_Sandstorm[]; extern const u32 gBattleAnimBgImage_Sandstorm[]; +// Pledge Effect field status - Rainbow +extern const u32 gBattleAnimBgImage_Rainbow[]; +extern const u32 gBattleAnimBGPalette_Rainbow[]; +extern const u32 gBattleAnimBgTilemap_Rainbow[]; + // Pokedex Area Screen extern const u32 gPokedexAreaScreenAreaUnknown_Gfx[]; extern const u16 gPokedexAreaScreenAreaUnknown_Pal[]; diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 3be0c35410..ea789a79a6 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -578,6 +578,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi) AI_THINKING_STRUCT->aiLogicId = 0; AI_THINKING_STRUCT->movesetIndex = 0; flags = AI_THINKING_STRUCT->aiFlags; + while (flags != 0) { if (flags & 1) diff --git a/src/battle_anim.c b/src/battle_anim.c index c46c5cee4c..d2216e5155 100644 --- a/src/battle_anim.c +++ b/src/battle_anim.c @@ -278,6 +278,9 @@ void LaunchBattleAnimation(u32 animType, u32 animId) case B_ANIM_PRIMAL_REVERSION: case B_ANIM_ULTRA_BURST: case B_ANIM_GULP_MISSILE: + case B_ANIM_RAINBOW: + case B_ANIM_SEA_OF_FIRE: + case B_ANIM_SWAMP: sAnimHideHpBoxes = TRUE; break; default: diff --git a/src/battle_anim_fire.c b/src/battle_anim_fire.c index 9a68f929bb..3a770a7c8c 100644 --- a/src/battle_anim_fire.c +++ b/src/battle_anim_fire.c @@ -527,6 +527,18 @@ const struct SpriteTemplate gSpacialRendBladesTemplate2 = .callback = AnimFireSpread }; +// Sea of Fire +const struct SpriteTemplate gTwisterEmberSpriteTemplate = +{ + .tileTag = ANIM_TAG_SMALL_EMBER, + .paletteTag = ANIM_TAG_SMALL_EMBER, + .oam = &gOamData_AffineOff_ObjNormal_32x32, + .anims = gAnims_BasicFire, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = AnimMoveTwisterParticle, +}; + static void AnimLavaPlumeOrbitScatter(struct Sprite *sprite) { sprite->x = GetBattlerSpriteCoord(gBattleAnimAttacker, 2); diff --git a/src/battle_main.c b/src/battle_main.c index a9d1f91add..28484a7061 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4651,6 +4651,9 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect) if (gBattleMons[battler].status1 & STATUS1_PARALYSIS && ability != ABILITY_QUICK_FEET) speed /= B_PARALYSIS_SPEED >= GEN_7 ? 2 : 4; + if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SWAMP) + speed /= 4; + return speed; } diff --git a/src/battle_message.c b/src/battle_message.c index f2052e6919..6763fe32ba 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -823,9 +823,27 @@ static const u8 sText_TargetIsHurtBySaltCure[] = _("{B_DEF_NAME_WITH_PREFIX} is static const u8 sText_OpportunistCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} copied its\nopponent's stat changes!"); static const u8 sText_TargetCoveredInStickyCandySyrup[] = _("{B_DEF_NAME_WITH_PREFIX} got covered\nin sticky syrup!"); static const u8 sText_ZeroToHeroTransformation[] = _("{B_ATK_NAME_WITH_PREFIX} underwent a heroic\ntransformation!"); +static const u8 sText_TheTwoMovesBecomeOne[] = _("The two moves become one!\nIt's a combined move!{PAUSE 16}"); +static const u8 sText_ARainbowAppearedOnSide[] = _("A rainbow appeared in the sky\non {B_ATK_TEAM2} team's side!"); +static const u8 sText_TheRainbowDisappeared[] = _("The rainbow on {B_ATK_TEAM2}\nside disappeared!"); +static const u8 sText_WaitingForPartnersMove[] = _("{B_ATK_NAME_WITH_PREFIX} is waiting\nfor {B_ATK_PARTNER_NAME}'s move…{PAUSE 16}"); +static const u8 sText_SeaOfFireEnvelopedSide[] = _("A sea of fire enveloped\n{B_DEF_TEAM2} team!"); +static const u8 sText_HurtByTheSeaOfFire[] = _("{B_ATK_TEAM1} {B_ATK_NAME_WITH_PREFIX} was hurt\nby the sea of fire!"); +static const u8 sText_TheSeaOfFireDisappeared[] = _("The sea of fire around {B_ATK_TEAM2}\nteam disappeared!"); +static const u8 sText_SwampEnvelopedSide[] = _("A swamp enveloped\n{B_DEF_TEAM2} team!"); +static const u8 sText_TheSwampDisappeared[] = _("The swamp around {B_ATK_TEAM2}\nteam disappeared!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_THESWAMPDISAPPEARED - BATTLESTRINGS_TABLE_START] = sText_TheSwampDisappeared, + [STRINGID_SWAMPENVELOPEDSIDE - BATTLESTRINGS_TABLE_START] = sText_SwampEnvelopedSide, + [STRINGID_THESEAOFFIREDISAPPEARED - BATTLESTRINGS_TABLE_START] = sText_TheSeaOfFireDisappeared, + [STRINGID_HURTBYTHESEAOFFIRE - BATTLESTRINGS_TABLE_START] = sText_HurtByTheSeaOfFire, + [STRINGID_SEAOFFIREENVELOPEDSIDE - BATTLESTRINGS_TABLE_START] = sText_SeaOfFireEnvelopedSide, + [STRINGID_WAITINGFORPARTNERSMOVE - BATTLESTRINGS_TABLE_START] = sText_WaitingForPartnersMove, + [STRINGID_THERAINBOWDISAPPEARED - BATTLESTRINGS_TABLE_START] = sText_TheRainbowDisappeared, + [STRINGID_ARAINBOWAPPEAREDONSIDE - BATTLESTRINGS_TABLE_START] = sText_ARainbowAppearedOnSide, + [STRINGID_THETWOMOVESBECOMEONE - BATTLESTRINGS_TABLE_START] = sText_TheTwoMovesBecomeOne, [STRINGID_ZEROTOHEROTRANSFORMATION - BATTLESTRINGS_TABLE_START] = sText_ZeroToHeroTransformation, [STRINGID_MOVEBLOCKEDBYDYNAMAX - BATTLESTRINGS_TABLE_START] = sText_MoveBlockedByDynamax, [STRINGID_OPPORTUNISTCOPIED - BATTLESTRINGS_TABLE_START] = sText_OpportunistCopied, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 9268e99ca2..bf63d0c1b2 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2076,7 +2076,9 @@ END: } if (gSpecialStatuses[gBattlerAttacker].gemBoost && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) - && gBattleMons[gBattlerAttacker].item) + && gBattleMons[gBattlerAttacker].item + && gBattleMoves[gCurrentMove].effect != EFFECT_PLEDGE + && gCurrentMove != MOVE_STRUGGLE) { BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_GemActivates; @@ -3598,8 +3600,8 @@ void SetMoveEffect(bool32 primary, u32 certain) break; case MOVE_EFFECT_TRIPLE_ARROWS: { - u8 randomLowerDefenseChance = RandomPercentage(RNG_TRIPLE_ARROWS_DEFENSE_DOWN, CalcSecondaryEffectChance(gBattlerAttacker, 50)); - u8 randomFlinchChance = RandomPercentage(RNG_TRIPLE_ARROWS_FLINCH, CalcSecondaryEffectChance(gBattlerAttacker, 30)); + u8 randomLowerDefenseChance = RandomPercentage(RNG_TRIPLE_ARROWS_DEFENSE_DOWN, CalcSecondaryEffectChance(gBattlerAttacker, 50, EFFECT_DEFENSE_DOWN_HIT)); + u8 randomFlinchChance = RandomPercentage(RNG_TRIPLE_ARROWS_FLINCH, CalcSecondaryEffectChance(gBattlerAttacker, 30, EFFECT_FLINCH_HIT)); if (randomFlinchChance && battlerAbility != ABILITY_INNER_FOCUS && GetBattlerTurnOrderNum(gEffectBattler) > gCurrentTurnActionNumber) gBattleMons[gEffectBattler].status2 |= sStatusFlagsForMoveEffects[MOVE_EFFECT_FLINCH]; @@ -3639,7 +3641,7 @@ static void Cmd_seteffectwithchance(void) { CMD_ARGS(); - u32 percentChance = CalcSecondaryEffectChance(gBattlerAttacker, gBattleMoves[gCurrentMove].secondaryEffectChance); + u32 percentChance = CalcSecondaryEffectChance(gBattlerAttacker, gBattleMoves[gCurrentMove].secondaryEffectChance, gBattleMoves[gCurrentMove].effect); if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && gBattleScripting.moveEffect) @@ -8322,8 +8324,6 @@ static void CourtChangeSwapSideStatuses(void) struct SideTimer *sideTimerOpp = &gSideTimers[B_SIDE_OPPONENT]; u32 temp; - // TODO: add Pledge-related effects - // Swap timers and statuses COURTCHANGE_SWAP(SIDE_STATUS_REFLECT, reflectTimer, temp) COURTCHANGE_SWAP(SIDE_STATUS_LIGHTSCREEN, lightscreenTimer, temp) @@ -8339,6 +8339,10 @@ static void CourtChangeSwapSideStatuses(void) COURTCHANGE_SWAP(SIDE_STATUS_STICKY_WEB, stickyWebAmount, temp); COURTCHANGE_SWAP(SIDE_STATUS_STEELSURGE, steelsurgeAmount, temp); COURTCHANGE_SWAP(SIDE_STATUS_DAMAGE_NON_TYPES, damageNonTypesTimer, temp); + // Track Pledge effect side + COURTCHANGE_SWAP(SIDE_STATUS_RAINBOW, rainbowTimer, temp); + COURTCHANGE_SWAP(SIDE_STATUS_SEA_OF_FIRE, seaOfFireTimer, temp); + COURTCHANGE_SWAP(SIDE_STATUS_SWAMP, swampTimer, temp); // Change battler IDs of swapped effects. Needed for the correct string when they expire // E.g. "Foe's Reflect wore off!" @@ -16285,3 +16289,115 @@ void BS_TryRelicSong(void) else gBattlescriptCurrInstr = cmd->nextInstr; } + + +void BS_SetPledge(void) +{ + NATIVE_ARGS(const u8 *jumpInstr); + + u32 partner = BATTLE_PARTNER(gBattlerAttacker); + u32 partnerMove = gBattleMons[partner].moves[gBattleStruct->chosenMovePositions[partner]]; + u32 i = 0; + u32 k = 0; + + if (gBattleStruct->pledgeMove) + { + PrepareStringBattle(STRINGID_USEDMOVE, gBattlerAttacker); + gHitMarker |= HITMARKER_ATTACKSTRING_PRINTED; + + if ((gCurrentMove == MOVE_GRASS_PLEDGE && partnerMove == MOVE_WATER_PLEDGE) + || (gCurrentMove == MOVE_WATER_PLEDGE && partnerMove == MOVE_GRASS_PLEDGE)) + { + gCurrentMove = MOVE_GRASS_PLEDGE; + gBattlescriptCurrInstr = BattleScript_EffectCombinedPledge_Grass; + } + else if ((gCurrentMove == MOVE_FIRE_PLEDGE && partnerMove == MOVE_GRASS_PLEDGE) + || (gCurrentMove == MOVE_GRASS_PLEDGE && partnerMove == MOVE_FIRE_PLEDGE)) + { + gCurrentMove = MOVE_FIRE_PLEDGE; + gBattlescriptCurrInstr = BattleScript_EffectCombinedPledge_Fire; + } + else if ((gCurrentMove == MOVE_WATER_PLEDGE && partnerMove == MOVE_FIRE_PLEDGE) + || (gCurrentMove == MOVE_FIRE_PLEDGE && partnerMove == MOVE_WATER_PLEDGE)) + { + gCurrentMove = MOVE_WATER_PLEDGE; + gBattlescriptCurrInstr = BattleScript_EffectCombinedPledge_Water; + } + + gBattleCommunication[MSG_DISPLAY] = 0; + } + else if ((gChosenActionByBattler[partner] == B_ACTION_USE_MOVE) + && gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && IsBattlerAlive(partner) + && gCurrentMove != partnerMove + && gBattleMoves[partnerMove].effect == EFFECT_PLEDGE) + { + u32 currPledgeUser = 0; + u32 newTurnOrder[] = {0xFF, 0xFF}; + + for (i = 0; i < gBattlersCount; i++) + { + if (gBattlerByTurnOrder[i] == gBattlerAttacker) + { + currPledgeUser = i + 1; // Current battler going after attacker + break; + } + } + for (i = currPledgeUser; i < gBattlersCount; i++) + { + if (gBattlerByTurnOrder[i] != partner) + { + newTurnOrder[k] = gBattlerByTurnOrder[i]; + k++; + } + } + + gBattlerByTurnOrder[currPledgeUser] = partner; + currPledgeUser++; + + for (i = 0; newTurnOrder[i] != 0xFF && i < 2; i++) + { + gBattlerByTurnOrder[currPledgeUser] = newTurnOrder[i]; + currPledgeUser++; + } + + gBattleStruct->pledgeMove = TRUE; + gBattleScripting.battler = partner; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattlescriptCurrInstr = cmd->jumpInstr; + + } +} + +void BS_SetPledgeStatus(void) +{ + NATIVE_ARGS(u8 battler, u32 sideStatus); + + u32 battler = GetBattlerForBattleScript(cmd->battler); + u32 side = GetBattlerSide(battler); + + gBattleStruct->pledgeMove = FALSE; + if (!(gSideStatuses[side] & cmd->sideStatus)) + { + gSideStatuses[side] |= cmd->sideStatus; + + switch (cmd->sideStatus) + { + case SIDE_STATUS_RAINBOW: + gSideTimers[side].rainbowTimer = 4; + break; + case SIDE_STATUS_SEA_OF_FIRE: + gSideTimers[side].seaOfFireTimer = 4; + break; + case SIDE_STATUS_SWAMP: + gSideTimers[side].swampTimer = 4; + } + + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + gBattlescriptCurrInstr = BattleScript_MoveEnd; +} diff --git a/src/battle_util.c b/src/battle_util.c index fcd6de48b7..c4fb6da0a0 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -373,6 +373,7 @@ void HandleAction_UseMove(void) || (GetBattlerAbility(battler) == ABILITY_STORM_DRAIN && moveType == TYPE_WATER)) && GetBattlerTurnOrderNum(battler) < var && gBattleMoves[gCurrentMove].effect != EFFECT_SNIPE_SHOT + && gBattleMoves[gCurrentMove].effect != EFFECT_PLEDGE && (GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL || GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART)) { @@ -865,7 +866,7 @@ void HandleAction_ActionFinished(void) gBattleResources->battleScriptsStack->size = 0; gBattleStruct->dynamax.usingMaxMove[gBattlerAttacker] = 0; - if (B_RECALC_TURN_AFTER_ACTIONS >= GEN_8 && !afterYouActive) + if (B_RECALC_TURN_AFTER_ACTIONS >= GEN_8 && !afterYouActive && !gBattleStruct->pledgeMove) { // i starts at `gCurrentTurnActionNumber` because we don't want to recalculate turn order for mon that have already // taken action. It's been previously increased, which we want in order to not recalculate the turn of the mon that just finished its action @@ -1926,6 +1927,9 @@ enum ENDTURN_RETALIATE, ENDTURN_WEATHER_FORM, ENDTURN_STATUS_HEAL, + ENDTURN_RAINBOW, + ENDTURN_SEA_OF_FIRE, + ENDTURN_SWAMP, ENDTURN_FIELD_COUNT, }; @@ -2433,6 +2437,95 @@ u8 DoFieldEndTurnEffects(void) } gBattleStruct->turnCountersTracker++; break; + case ENDTURN_RAINBOW: + while (gBattleStruct->turnSideTracker < 2) + { + side = gBattleStruct->turnSideTracker; + if (gSideStatuses[side] & SIDE_STATUS_RAINBOW) + { + for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) + { + if (GetBattlerSide(gBattlerAttacker) == side) + break; + } + + if (gSideTimers[side].rainbowTimer > 0 && --gSideTimers[side].rainbowTimer == 0) + { + gSideStatuses[side] &= ~SIDE_STATUS_RAINBOW; + BattleScriptExecute(BattleScript_TheRainbowDisappeared); + effect++; + } + } + gBattleStruct->turnSideTracker++; + if (effect != 0) + break; + } + if (!effect) + { + gBattleStruct->turnCountersTracker++; + gBattleStruct->turnSideTracker = 0; + } + break; + case ENDTURN_SEA_OF_FIRE: + while (gBattleStruct->turnSideTracker < 2) + { + side = gBattleStruct->turnSideTracker; + + if (gSideStatuses[side] & SIDE_STATUS_SEA_OF_FIRE) + { + for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) + { + if (GetBattlerSide(gBattlerAttacker) == side) + break; + } + + if (gSideTimers[side].seaOfFireTimer > 0 && --gSideTimers[side].seaOfFireTimer == 0) + { + gSideStatuses[side] &= ~SIDE_STATUS_SEA_OF_FIRE; + BattleScriptExecute(BattleScript_TheSeaOfFireDisappeared); + effect++; + } + } + gBattleStruct->turnSideTracker++; + if (effect != 0) + break; + } + if (!effect) + { + gBattleStruct->turnCountersTracker++; + gBattleStruct->turnSideTracker = 0; + } + break; + case ENDTURN_SWAMP: + while (gBattleStruct->turnSideTracker < 2) + { + side = gBattleStruct->turnSideTracker; + + if (gSideStatuses[side] & SIDE_STATUS_SWAMP) + { + for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) + { + if (GetBattlerSide(gBattlerAttacker) == side) + break; + } + + if (gSideTimers[side].swampTimer > 0 && --gSideTimers[side].swampTimer == 0) + { + gSideStatuses[side] &= ~SIDE_STATUS_SWAMP; + BattleScriptExecute(BattleScript_TheSwampDisappeared); + effect++; + } + } + gBattleStruct->turnSideTracker++; + if (effect != 0) + break; + } + if (!effect) + { + gBattleStruct->turnCountersTracker++; + gBattleStruct->turnSideTracker = 0; + } + break; case ENDTURN_FIELD_COUNT: effect++; break; @@ -2483,6 +2576,7 @@ enum ENDTURN_SALT_CURE, ENDTURN_SYRUP_BOMB, ENDTURN_DYNAMAX, + ENDTURN_SEA_OF_FIRE_DAMAGE, ENDTURN_BATTLER_COUNT }; @@ -3069,6 +3163,17 @@ u8 DoBattlerEndTurnEffects(void) } gBattleStruct->turnEffectsTracker++; break; + case ENDTURN_SEA_OF_FIRE_DAMAGE: + if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SEA_OF_FIRE) + { + gBattleMoveDamage = gBattleMons[battler].maxHP / 8; + BtlController_EmitStatusAnimation(battler, BUFFER_A, FALSE, STATUS1_BURN); + MarkBattlerForControllerExec(battler); + BattleScriptExecute(BattleScript_HurtByTheSeaOfFire); + effect++; + } + gBattleStruct->turnEffectsTracker++; + break; case ENDTURN_BATTLER_COUNT: // done gBattleStruct->turnEffectsTracker = 0; gBattleStruct->turnEffectsBattlerId++; @@ -7610,6 +7715,8 @@ u8 ItemBattleEffects(u8 caseID, u32 battler, bool32 moveTurn) u16 ability = GetBattlerAbility(gBattlerAttacker); if (B_SERENE_GRACE_BOOST >= GEN_5 && ability == ABILITY_SERENE_GRACE) atkHoldEffectParam *= 2; + if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_RAINBOW && gCurrentMove != MOVE_SECRET_POWER) + atkHoldEffectParam *= 2; if (gBattleMoveDamage != 0 // Need to have done damage && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && TARGET_TURN_DAMAGED @@ -8525,7 +8632,8 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 switch (gBattleMoves[move].effect) { case EFFECT_PLEDGE: - // todo + if (gBattleStruct->pledgeMove) + basePower = 150; break; case EFFECT_FLING: basePower = GetFlingPowerFromItemId(gBattleMons[battlerAtk].item); @@ -9504,7 +9612,9 @@ static inline uq4_12_t GetParentalBondModifier(u32 battlerAtk) static inline uq4_12_t GetSameTypeAttackBonusModifier(u32 battlerAtk, u32 moveType, u32 move, u32 abilityAtk) { - if (!IS_BATTLER_OF_TYPE(battlerAtk, moveType) || move == MOVE_STRUGGLE || move == MOVE_NONE) + if (gBattleStruct->pledgeMove && IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerAtk), moveType)) + return (abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5); + else if (!IS_BATTLER_OF_TYPE(battlerAtk, moveType) || move == MOVE_STRUGGLE || move == MOVE_NONE) return UQ_4_12(1.0); return (abilityAtk == ABILITY_ADAPTABILITY) ? UQ_4_12(2.0) : UQ_4_12(1.5); } @@ -11122,9 +11232,17 @@ bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2) return (gender1 != MON_GENDERLESS && gender2 != MON_GENDERLESS && gender1 == gender2); } -u32 CalcSecondaryEffectChance(u32 battler, u8 secondaryEffectChance) +u32 CalcSecondaryEffectChance(u32 battler, u8 secondaryEffectChance, u16 moveEffect) { - if (GetBattlerAbility(battler) == ABILITY_SERENE_GRACE) + bool8 hasSereneGrace = (GetBattlerAbility(battler) == ABILITY_SERENE_GRACE); + bool8 hasRainbow = (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_RAINBOW) != 0; + + if (hasRainbow && hasSereneGrace && moveEffect == EFFECT_FLINCH_HIT) + return secondaryEffectChance *= 2; + + if (hasSereneGrace) + secondaryEffectChance *= 2; + if (hasRainbow && moveEffect != EFFECT_SECRET_POWER) secondaryEffectChance *= 2; return secondaryEffectChance; @@ -11166,4 +11284,3 @@ u8 GetBattlerType(u32 battler, u8 typeIndex) return types[typeIndex]; } - diff --git a/src/data/battle_anim.h b/src/data/battle_anim.h index 880b98efce..239e655918 100644 --- a/src/data/battle_anim.h +++ b/src/data/battle_anim.h @@ -1996,4 +1996,5 @@ const struct BattleAnimBackground gBattleAnimBackgroundTable[] = [BG_STEEL_BEAM_OPPONENT] = {gBattleAnimBgImage_Highspeed, gBattleAnimBgPalette_SteelBeam, gBattleAnimBgTilemap_HighspeedOpponent}, [BG_STEEL_BEAM_PLAYER] = {gBattleAnimBgImage_Highspeed, gBattleAnimBgPalette_SteelBeam, gBattleAnimBgTilemap_HighspeedPlayer}, [BG_CHLOROBLAST] = {gBattleAnimBgImage_HydroCannon, gBattleAnimBgPalette_Chloroblast, gBattleAnimBgTilemap_HydroCannon}, + [BG_RAINBOW] = {gBattleAnimBgImage_Rainbow, gBattleAnimBGPalette_Rainbow, gBattleAnimBgTilemap_Rainbow}, }; diff --git a/src/graphics.c b/src/graphics.c index 45c066ad26..c1049d63a1 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -1606,6 +1606,11 @@ const u32 gBattleAnimSpritePal_Slash2[] = INCBIN_U32("graphics/battle_anims/spri const u32 gBattleAnimSpriteGfx_WhiteShadow[] = INCBIN_U32("graphics/battle_anims/sprites/white_shadow.4bpp.lz"); const u32 gBattleAnimSpritePal_WhiteShadow[] = INCBIN_U32("graphics/battle_anims/sprites/white_shadow.gbapal.lz"); +// Pledge Effect field status - Rainbow +const u32 gBattleAnimBgImage_Rainbow[] = INCBIN_U32("graphics/battle_anims/backgrounds/rainbow.4bpp.lz"); +const u32 gBattleAnimBGPalette_Rainbow[] = INCBIN_U32("graphics/battle_anims/backgrounds/rainbow.gbapal.lz"); +const u32 gBattleAnimBgTilemap_Rainbow[] = INCBIN_U32("graphics/battle_anims/backgrounds/rainbow.bin.lz"); + const u32 gPartyMenuBg_Gfx[] = INCBIN_U32("graphics/party_menu/bg.4bpp.lz"); const u32 gPartyMenuBg_Pal[] = INCBIN_U32("graphics/party_menu/bg.gbapal.lz"); const u32 gPartyMenuBg_Tilemap[] = INCBIN_U32("graphics/party_menu/bg.bin.lz"); diff --git a/test/battle/ai.c b/test/battle/ai.c index b3f336d15e..42fa32760f 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -53,7 +53,6 @@ AI_SINGLE_BATTLE_TEST("AI prefers Water Gun over Bubble if it knows that foe has PARAMETRIZE { abilityAI = ABILITY_MOXIE; } PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; } // Mold Breaker ignores Contrary. - GIVEN { AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); PLAYER(SPECIES_SHUCKLE) { Ability(ABILITY_CONTRARY); } diff --git a/test/battle/damage_formula.c b/test/battle/damage_formula.c index a4b0a03f58..3aeeeb9f83 100644 --- a/test/battle/damage_formula.c +++ b/test/battle/damage_formula.c @@ -31,11 +31,11 @@ SINGLE_BATTLE_TEST("Damage calculation matches Gen5+") MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); } } - SCENE{ + SCENE { MESSAGE("Glaceon used Ice Fang!"); HP_BAR(opponent, captureDamage: &dmg); } - THEN{ + THEN { EXPECT_EQ(expectedDamage, dmg); } } @@ -68,11 +68,11 @@ SINGLE_BATTLE_TEST("Damage calculation matches Gen5+ (Muscle Band, crit)") MOVE(player, MOVE_ICE_FANG, WITH_RNG(RNG_DAMAGE_MODIFIER, i), criticalHit: TRUE); } } - SCENE{ + SCENE { MESSAGE("Glaceon used Ice Fang!"); HP_BAR(opponent, captureDamage: &dmg); } - THEN{ + THEN { EXPECT_EQ(expectedDamage, dmg); } } diff --git a/test/battle/move_effect/pledge.c b/test/battle/move_effect/pledge.c new file mode 100644 index 0000000000..a7843be619 --- /dev/null +++ b/test/battle/move_effect/pledge.c @@ -0,0 +1,352 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_WATER_PLEDGE].effect == EFFECT_PLEDGE); + ASSUME(gBattleMoves[MOVE_FIRE_PLEDGE].effect == EFFECT_PLEDGE); + ASSUME(gBattleMoves[MOVE_GRASS_PLEDGE].effect == EFFECT_PLEDGE); +} + +DOUBLE_BATTLE_TEST("Water and Fire Pledge create a rainbow on the user's side of the field for four turns") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_FIRE_PLEDGE, target: opponentRight); + } + TURN {} + TURN {} + TURN {} + } SCENE { + MESSAGE("Wobbuffet used Water Pledge!"); + MESSAGE("Wobbuffet is waiting for Wynaut's move…{PAUSE 16}"); + MESSAGE("Wynaut used Fire Pledge!"); + MESSAGE("The two moves become one! It's a combined move!{PAUSE 16}"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerRight); + HP_BAR(opponentRight); + MESSAGE("A rainbow appeared in the sky on your team's side!"); + MESSAGE("The rainbow on your side disappeared!"); + } +} + +DOUBLE_BATTLE_TEST("Rainbow doubles the chance of secondary move effects") +{ + PASSES_RANDOMLY(20, 100, RNG_SECONDARY_EFFECT); + GIVEN { + ASSUME(gBattleMoves[MOVE_EMBER].effect == EFFECT_BURN_HIT); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_FIRE_PLEDGE, target: opponentRight); + } + TURN { MOVE(playerLeft, MOVE_EMBER, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, playerLeft); + MESSAGE("Foe Wynaut was burned!"); + } +} + +DOUBLE_BATTLE_TEST("Rainbow flinch chance does not stack with Serene Grace") +{ + PASSES_RANDOMLY(60, 100, RNG_SECONDARY_EFFECT); + GIVEN { + ASSUME(gBattleMoves[MOVE_BITE].effect == EFFECT_FLINCH_HIT); + PLAYER(SPECIES_TOGEPI) { Speed(8); Ability(ABILITY_SERENE_GRACE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_WYNAUT) { Speed(3); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_FIRE_PLEDGE, target: opponentRight); + } + TURN { MOVE(playerLeft, MOVE_BITE, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, playerLeft); + MESSAGE("Foe Wynaut flinched!"); + } +} + +DOUBLE_BATTLE_TEST("Rainbow flinch chance does not stack with Serene Grace if mvoe Triple Arrows is used") +{ + PASSES_RANDOMLY(60, 100, RNG_TRIPLE_ARROWS_FLINCH); + GIVEN { + ASSUME(gBattleMoves[MOVE_TRIPLE_ARROWS].effect == EFFECT_TRIPLE_ARROWS); + PLAYER(SPECIES_TOGEPI) { Speed(8); Ability(ABILITY_SERENE_GRACE); } + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(4); } + OPPONENT(SPECIES_WYNAUT) { Speed(3); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_FIRE_PLEDGE, target: opponentRight); + } + TURN { MOVE(playerLeft, MOVE_TRIPLE_ARROWS, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TRIPLE_ARROWS, playerLeft); + MESSAGE("Foe Wynaut flinched!"); + } +} + +DOUBLE_BATTLE_TEST("Fire and Grass Pledge summons Sea Of Fire for four turns that damages the opponent") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIRE_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_GRASS_PLEDGE, target: opponentRight); + } + TURN {} + TURN {} + TURN {} + } SCENE { + MESSAGE("Wobbuffet used Fire Pledge!"); + MESSAGE("Wobbuffet is waiting for Wynaut's move…{PAUSE 16}"); + MESSAGE("Wynaut used Grass Pledge!"); + MESSAGE("The two moves become one! It's a combined move!{PAUSE 16}"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerRight); + HP_BAR(opponentRight); + MESSAGE("A sea of fire enveloped the opposing team!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SEA_OF_FIRE, opponentRight); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentLeft); + MESSAGE("The opposing Foe Wobbuffet was hurt by the sea of fire!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + MESSAGE("The opposing Foe Wynaut was hurt by the sea of fire!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentLeft); + MESSAGE("The opposing Foe Wobbuffet was hurt by the sea of fire!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + MESSAGE("The opposing Foe Wynaut was hurt by the sea of fire!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentLeft); + MESSAGE("The opposing Foe Wobbuffet was hurt by the sea of fire!"); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + MESSAGE("The opposing Foe Wynaut was hurt by the sea of fire!"); + MESSAGE("The sea of fire around the opposing team disappeared!"); + } +} + +DOUBLE_BATTLE_TEST("Sea Of Fire deals 1/8th damage per turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIRE_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_GRASS_PLEDGE, target: opponentRight); + } + } SCENE { + s32 maxHPopponentLeft = GetMonData(&OPPONENT_PARTY[0], MON_DATA_MAX_HP); + s32 maxHPopponentRight = GetMonData(&OPPONENT_PARTY[1], MON_DATA_MAX_HP); + HP_BAR(opponentLeft, damage: maxHPopponentLeft / 8); + HP_BAR(opponentRight, damage: maxHPopponentRight / 8); + } +} + +DOUBLE_BATTLE_TEST("Grass and Water Pledge create a swamp on the user's side of the field for four turns") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASS_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_WATER_PLEDGE, target: opponentRight); + } + TURN {} + TURN {} + TURN {} + } SCENE { + MESSAGE("Wobbuffet used Grass Pledge!"); + MESSAGE("Wobbuffet is waiting for Wynaut's move…{PAUSE 16}"); + MESSAGE("Wynaut used Water Pledge!"); + MESSAGE("The two moves become one! It's a combined move!{PAUSE 16}"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerRight); + HP_BAR(opponentRight); + MESSAGE("A swamp enveloped the opposing team!"); + MESSAGE("The swamp around the opposing team disappeared!"); + } +} + +DOUBLE_BATTLE_TEST("Swamp reduces the speed of the effected side by 1/4th") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + PLAYER(SPECIES_WYNAUT) { Speed(4); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(12); } + OPPONENT(SPECIES_WYNAUT) { Speed(8); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_GRASS_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_WATER_PLEDGE, target: opponentRight); + } + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASS_PLEDGE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponentRight); + } +} + +DOUBLE_BATTLE_TEST("The base power of a combined pledge move effect is 150") +{ + s16 hyperBeamDamage; + s16 combinedPledgeDamage; + + GIVEN { + ASSUME(gBattleMoves[MOVE_HYPER_BEAM].power == 150); + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(opponentRight, MOVE_HYPER_BEAM, target: playerRight); + MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_FIRE_PLEDGE, target: opponentRight); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_BEAM, opponentRight); + HP_BAR(playerRight, captureDamage: &hyperBeamDamage); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerRight); + HP_BAR(opponentRight, captureDamage: &combinedPledgeDamage); + } THEN { + EXPECT_EQ(hyperBeamDamage, combinedPledgeDamage); + } +} + +DOUBLE_BATTLE_TEST("Pledge moves can not be redirected by absorbing abilities") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LILEEP) { Ability(ABILITY_STORM_DRAIN); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_PLEDGE, target: opponentRight);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PLEDGE, playerLeft); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Pledge status timer does not reset if combined move is used again") +{ + u16 pledgeMove1, pledgeMove2; + + PARAMETRIZE { pledgeMove1 = MOVE_WATER_PLEDGE; pledgeMove2 = MOVE_FIRE_PLEDGE; } + PARAMETRIZE { pledgeMove1 = MOVE_FIRE_PLEDGE; pledgeMove2 = MOVE_GRASS_PLEDGE; } + PARAMETRIZE { pledgeMove1 = MOVE_GRASS_PLEDGE; pledgeMove2 = MOVE_WATER_PLEDGE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(playerLeft, pledgeMove1, target: opponentLeft); + MOVE(playerRight, pledgeMove2, target: opponentRight); + } + TURN { MOVE(playerLeft, pledgeMove1, target: opponentLeft); + MOVE(playerRight, pledgeMove2, target: opponentRight); + } + TURN {} + TURN {} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, pledgeMove1, playerRight); + ANIMATION(ANIM_TYPE_MOVE, pledgeMove1, playerRight); + if (pledgeMove1 == MOVE_WATER_PLEDGE && pledgeMove2 == MOVE_FIRE_PLEDGE) + { + NOT MESSAGE("A rainbow appeared in the sky on your team's side!"); + MESSAGE("The rainbow on your side disappeared!"); + } + if (pledgeMove1 == MOVE_FIRE_PLEDGE && pledgeMove2 == MOVE_GRASS_PLEDGE) + { + NOT MESSAGE("A sea of fire enveloped the opposing team!"); + MESSAGE("The sea of fire around the opposing team disappeared!"); + } + if (pledgeMove1 == MOVE_GRASS_PLEDGE && pledgeMove2 == MOVE_WATER_PLEDGE) + { + NOT MESSAGE("A swamp enveloped the opposing team!"); + MESSAGE("The swamp around the opposing team disappeared!"); + } + } +} + +DOUBLE_BATTLE_TEST("Pledge moves get same attack type bonus from partner", s16 damage) +{ + u32 species; + + PARAMETRIZE { species = SPECIES_WOBBUFFET; } + PARAMETRIZE { species = SPECIES_CHARMANDER; } + + GIVEN { + PLAYER(species) { Speed(4); } + PLAYER(SPECIES_WYNAUT) { Speed(3); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(8); } + OPPONENT(SPECIES_WYNAUT) { Speed(5); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_FIRE_PLEDGE, target: opponentLeft); + MOVE(playerRight, MOVE_GRASS_PLEDGE, target: opponentRight); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FIRE_PLEDGE, playerRight); + HP_BAR(opponentRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +DOUBLE_BATTLE_TEST("Damage calculation: Combined pledge move") +{ + s16 dmg; + s16 expectedDamage; + PARAMETRIZE { expectedDamage = 159; } + PARAMETRIZE { expectedDamage = 156; } + PARAMETRIZE { expectedDamage = 154; } + PARAMETRIZE { expectedDamage = 153; } + PARAMETRIZE { expectedDamage = 151; } + PARAMETRIZE { expectedDamage = 150; } + PARAMETRIZE { expectedDamage = 148; } + PARAMETRIZE { expectedDamage = 147; } + PARAMETRIZE { expectedDamage = 145; } + PARAMETRIZE { expectedDamage = 144; } + PARAMETRIZE { expectedDamage = 142; } + PARAMETRIZE { expectedDamage = 141; } + PARAMETRIZE { expectedDamage = 139; } + PARAMETRIZE { expectedDamage = 138; } + PARAMETRIZE { expectedDamage = 136; } + PARAMETRIZE { expectedDamage = 135; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(4); } + PLAYER(SPECIES_WOBBUFFET) { HP(521); SpDefense(152); Speed(3); } + OPPONENT(SPECIES_CHARIZARD) { Speed(8); } + OPPONENT(SPECIES_EEVEE) { SpAttack(126); Speed(5); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_FIRE_PLEDGE, target: playerLeft, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); + MOVE(opponentRight, MOVE_GRASS_PLEDGE, target: playerRight, WITH_RNG(RNG_DAMAGE_MODIFIER, i)); + } + } + SCENE { + HP_BAR(playerRight, captureDamage: &dmg); + } + THEN { + EXPECT_EQ(expectedDamage, dmg); + } +}