diff --git a/include/battle.h b/include/battle.h index b492374704..fb55f9e193 100644 --- a/include/battle.h +++ b/include/battle.h @@ -615,7 +615,6 @@ struct BattleStruct u32 expValue; u8 expGettersOrder[PARTY_SIZE]; // First battlers which were sent out, then via exp-share u8 expGetterMonId; - u8 additionalEffectsCounter:2; u8 expOrderId:3; u8 expGetterBattlerId:2; u8 teamGotExpMsgPrinted:1; // The 'Rest of your team got msg' has been printed. @@ -737,6 +736,7 @@ struct BattleStruct u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler u8 blunderPolicy:1; // should blunder policy activate u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky + u8 additionalEffectsCounter:4; // A counter for the additionalEffects applied by the current move in Cmd_setadditionaleffects u8 ballSpriteIds[2]; // item gfx, window gfx u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. diff --git a/include/pokemon.h b/include/pokemon.h index f973425a39..8a2e629ae7 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -463,10 +463,9 @@ struct MoveInfo s32 priority:4; u32 recoil:7; - u32 strikeCount:4; // Max 15 hits. Defaults to 1 if not set. May apply its effect on each hit. + u32 strikeCount:4; // Max 15 hits. Defaults to 1 if not set. May apply its effect on each hit. u32 criticalHitStage:2; u32 alwaysCriticalHit:1; - u32 numAdditionalEffects:2; // limited to 3 - don't want to get too crazy // Flags u32 makesContact:1; @@ -514,7 +513,7 @@ struct MoveInfo u32 argument; // primary/secondary effects - const struct AdditionalEffect *additionalEffects; + uintptr_t additionalEffects; // contest parameters u8 contestEffect; @@ -524,7 +523,12 @@ struct MoveInfo }; #define EFFECTS_ARR(...) (const struct AdditionalEffect[]) {__VA_ARGS__} -#define ADDITIONAL_EFFECTS(...) EFFECTS_ARR( __VA_ARGS__ ), .numAdditionalEffects = ARRAY_COUNT(EFFECTS_ARR( __VA_ARGS__ )) +#define ADDITIONAL_EFFECTS(...) ((ARRAY_COUNT(EFFECTS_ARR( __VA_ARGS__ )) % 0xF) << 28) + (uintptr_t)(EFFECTS_ARR( __VA_ARGS__ )) + +// Retrieve a move's additional effects and the count thereof +#define GET_ADDITIONAL_EFFECTS_AND_COUNT(move, _count, _effects) u32 _count = GET_ADDITIONAL_EFFECTS_COUNT(move); const struct AdditionalEffect *_effects = GET_ADDITIONAL_EFFECTS(move) +#define GET_ADDITIONAL_EFFECTS(move) (void *)(gMovesInfo[move].additionalEffects & 0x8FFFFFF) +#define GET_ADDITIONAL_EFFECTS_COUNT(move) (gMovesInfo[move].additionalEffects >> 28) // Just a hack to make a move boosted by Sheer Force despite having no secondary effects affected #define SHEER_FORCE_HACK { .moveEffect = 0, .chance = 100, } diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index e24d59f6ac..ac7d2f93b2 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -3201,6 +3201,9 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) u32 movesetIndex = AI_THINKING_STRUCT->movesetIndex; u32 effectiveness = aiData->effectiveness[battlerAtk][battlerDef][movesetIndex]; + // additional effects + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, additionalEffectsCount, additionalEffects); + s32 score = 0; u32 predictedMove = aiData->predictedMoves[battlerDef]; u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); @@ -4427,19 +4430,19 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) } // move effect checks // check move additional effects that are likely to happen - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + for (i = 0; i < additionalEffectsCount; i++) { // Only consider effects with a guaranteed chance to happen - if (!MoveEffectIsGuaranteed(battlerAtk, aiData->abilities[battlerAtk], &gMovesInfo[move].additionalEffects[i])) + if (!MoveEffectIsGuaranteed(battlerAtk, aiData->abilities[battlerAtk], &additionalEffects[i])) continue; // Consider move effects that target self - if (gMovesInfo[move].additionalEffects[i].self) + if (additionalEffects[i].self) { - u32 oneStageStatId = STAT_CHANGE_ATK + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; - u32 twoStageStatId = STAT_CHANGE_ATK_2 + gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + u32 oneStageStatId = STAT_CHANGE_ATK + additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; + u32 twoStageStatId = STAT_CHANGE_ATK_2 + additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1; - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + switch (additionalEffects[i].moveEffect) { case MOVE_EFFECT_SPD_PLUS_2: case MOVE_EFFECT_SPD_PLUS_1: @@ -4490,7 +4493,7 @@ static u32 AI_CalcMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) } else // consider move effects that hinder the target { - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + switch (additionalEffects[i].moveEffect) { case MOVE_EFFECT_FLINCH: score += ShouldTryToFlinch(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move); @@ -4768,11 +4771,13 @@ static s32 AI_SetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_HIT: + { // TEMPORARY - should applied to all moves regardless of EFFECT // Consider move effects - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, additionalEffectsCount, additionalEffects); + for (i = 0; i < additionalEffectsCount; i++) { - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + switch (additionalEffects[i].moveEffect) { case MOVE_EFFECT_STEALTH_ROCK: case MOVE_EFFECT_SPIKES: @@ -4782,6 +4787,7 @@ static s32 AI_SetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score break; } } + } default: break; } @@ -4823,11 +4829,13 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_HIT: + { // TEMPORARY - should applied to all moves regardless of EFFECT // Consider move effects - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, additionalEffectsCount, additionalEffects); + for (i = 0; i < additionalEffectsCount; i++) { - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + switch (additionalEffects[i].moveEffect) { case MOVE_EFFECT_ALL_STATS_UP: if (Random() & 1) @@ -4837,6 +4845,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; } } + } default: break; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index f901d61b59..d04a952a93 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -517,6 +517,7 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 u32 i; u32 abilityDef = AI_DATA->abilities[battlerDef]; u32 abilityAtk = AI_DATA->abilities[battlerAtk]; + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, additionalEffectsCount, additionalEffects); switch (gMovesInfo[move].effect) { @@ -531,12 +532,12 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 } // check ADDITIONAL_EFFECTS - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + for (i = 0; i < additionalEffectsCount; i++) { // Consider move effects that target self - if (gMovesInfo[move].additionalEffects[i].self) + if (additionalEffects[i].self) { - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + switch (additionalEffects[i].moveEffect) { case MOVE_EFFECT_ATK_PLUS_1: if (BattlerStatCanRise(battlerAtk, abilityAtk, STAT_ATK)) @@ -566,7 +567,7 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 } else // consider move effects that hinder the target { - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + switch (additionalEffects[i].moveEffect) { case MOVE_EFFECT_POISON: case MOVE_EFFECT_TOXIC: @@ -600,7 +601,7 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 case MOVE_EFFECT_SP_DEF_MINUS_1: case MOVE_EFFECT_ACC_MINUS_1: case MOVE_EFFECT_EVS_MINUS_1: - if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK + (gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_1)) && noOfHitsToKo != 1) + if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK + (additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_1)) && noOfHitsToKo != 1) return TRUE; break; case MOVE_EFFECT_ATK_MINUS_2: @@ -610,7 +611,7 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 case MOVE_EFFECT_SP_DEF_MINUS_2: case MOVE_EFFECT_ACC_MINUS_2: case MOVE_EFFECT_EVS_MINUS_2: - if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK + (gMovesInfo[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_2)) && noOfHitsToKo != 1) + if (ShouldLowerStat(battlerDef, abilityDef, STAT_ATK + (additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_MINUS_2)) && noOfHitsToKo != 1) return TRUE; break; } @@ -640,9 +641,12 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s return TRUE; break; default: - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + { + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, additionalEffectsCount, additionalEffects); + + for (i = 0; i < additionalEffectsCount; i++) { - switch (gMovesInfo[move].additionalEffects[i].moveEffect) + switch (additionalEffects[i].moveEffect) { case MOVE_EFFECT_ATK_MINUS_1: case MOVE_EFFECT_DEF_MINUS_1: @@ -654,16 +658,17 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case MOVE_EFFECT_DEF_SPDEF_DOWN: case MOVE_EFFECT_SP_DEF_MINUS_1: case MOVE_EFFECT_SP_DEF_MINUS_2: - if ((gMovesInfo[move].additionalEffects[i].self && GetBattlerAbility(battlerAtk) != ABILITY_CONTRARY) + if ((additionalEffects[i].self && GetBattlerAbility(battlerAtk) != ABILITY_CONTRARY) || (noOfHitsToKo != 1 && abilityDef == ABILITY_CONTRARY && !IsMoldBreakerTypeAbility(abilityAtk))) return TRUE; break; case MOVE_EFFECT_RECHARGE: - return gMovesInfo[move].additionalEffects[i].self; + return additionalEffects[i].self; } } break; } + } return FALSE; } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 73bb926824..6247cbd740 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -3820,34 +3820,47 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattleScripting.moveEffect = 0; } +static bool32 CanApplyAdditionalEffect(const struct AdditionalEffect *additionalEffect) +{ + // Self-targeting move effects only apply after the last mon has been hit + u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); + if (additionalEffect->self + && (moveTarget == MOVE_TARGET_BOTH || moveTarget == MOVE_TARGET_FOES_AND_ALLY) + && GetNextTarget(moveTarget, TRUE) != MAX_BATTLERS_COUNT) + return FALSE; + + // Certain move effects only apply if the target raised stats this turn (e.g. Burning Jealousy) + if (additionalEffect->onlyIfTargetRaisedStats && !gProtectStructs[gBattlerTarget].statRaised) + return FALSE; + + // Certain additional effects only apply on a two-turn move's charge turn + if (additionalEffect->onChargeTurnOnly != gProtectStructs[gBattlerAttacker].chargingTurn) + return FALSE; + + return TRUE; +} + static void Cmd_setadditionaleffects(void) { CMD_ARGS(); if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) { - if (gMovesInfo[gCurrentMove].numAdditionalEffects > gBattleStruct->additionalEffectsCounter) + GET_ADDITIONAL_EFFECTS_AND_COUNT(gCurrentMove, additionalEffectsCount, additionalEffects); + if (additionalEffectsCount > gBattleStruct->additionalEffectsCounter) { - u32 percentChance; - u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); + u32 percentChance, i = gBattleStruct->additionalEffectsCounter; const u8 *currentPtr = gBattlescriptCurrInstr; - const struct AdditionalEffect *additionalEffect = &gMovesInfo[gCurrentMove].additionalEffects[gBattleStruct->additionalEffectsCounter]; - // Check additional effect flags - // self-targeting move effects cannot occur multiple times per turn - // only occur on the last setmoveeffect when there are multiple targets - if (!(gMovesInfo[gCurrentMove].additionalEffects[gBattleStruct->additionalEffectsCounter].self - && (moveTarget == MOVE_TARGET_BOTH || moveTarget == MOVE_TARGET_FOES_AND_ALLY) - && GetNextTarget(moveTarget, TRUE) != MAX_BATTLERS_COUNT) - && !(additionalEffect->onlyIfTargetRaisedStats && !gProtectStructs[gBattlerTarget].statRaised) - && additionalEffect->onChargeTurnOnly == gProtectStructs[gBattlerAttacker].chargingTurn) + // Various checks for if this move effect can be applied this turn + if (CanApplyAdditionalEffect(&additionalEffects[i])) { - percentChance = CalcSecondaryEffectChance(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), additionalEffect); + percentChance = CalcSecondaryEffectChance(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), &additionalEffects[i]); // Activate effect if it's primary (chance == 0) or if RNGesus says so - if ((percentChance == 0) || RandomPercentage(RNG_SECONDARY_EFFECT + gBattleStruct->additionalEffectsCounter, percentChance)) + if ((percentChance == 0) || RandomPercentage(RNG_SECONDARY_EFFECT + i, percentChance)) { - gBattleScripting.moveEffect = additionalEffect->moveEffect | (MOVE_EFFECT_AFFECTS_USER * (additionalEffect->self)); + gBattleScripting.moveEffect = additionalEffects[i].moveEffect | (MOVE_EFFECT_AFFECTS_USER * (additionalEffects[i].self)); SetMoveEffect( percentChance == 0, // a primary effect @@ -3862,7 +3875,7 @@ static void Cmd_setadditionaleffects(void) // Call setadditionaleffects again in the case of a move with multiple effects gBattleStruct->additionalEffectsCounter++; - if (gMovesInfo[gCurrentMove].numAdditionalEffects > gBattleStruct->additionalEffectsCounter) + if (additionalEffectsCount > gBattleStruct->additionalEffectsCounter) gBattleScripting.moveEffect = MOVE_EFFECT_CONTINUE; else gBattleScripting.moveEffect = gBattleStruct->additionalEffectsCounter = 0; @@ -15677,12 +15690,13 @@ static bool8 CanAbilityPreventStatLoss(u16 abilityDef, bool8 byIntimidate) static bool8 CanBurnHitThaw(u16 move) { u8 i; + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, additionalEffectsCount, additionalEffects); if (B_BURN_HIT_THAW >= GEN_6) { - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + for (i = 0; i < additionalEffectsCount; i++) { - if (gMovesInfo[move].additionalEffects[i].moveEffect == MOVE_EFFECT_BURN) + if (additionalEffects[i].moveEffect == MOVE_EFFECT_BURN) return TRUE; } } diff --git a/src/battle_util.c b/src/battle_util.c index 42c8386ab4..31fbb3bfad 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10691,9 +10691,11 @@ bool32 IsBattlerAffectedByHazards(u32 battler, bool32 toxicSpikes) bool32 MoveIsAffectedBySheerForce(u16 move) { u32 i; - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, additionalEffectsCount, additionalEffects); + + for (i = 0; i < additionalEffectsCount; i++) { - if (gMovesInfo[move].additionalEffects[i].chance > 0) + if (additionalEffects[i].chance > 0) return TRUE; } @@ -10975,10 +10977,11 @@ bool32 IsGen6ExpShareEnabled(void) bool32 MoveHasMoveEffect(u32 move, u32 moveEffect) { u32 i; - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, count, effects); + + for (i = 0; i < count; i++) { - if (gMovesInfo[move].additionalEffects[i].moveEffect == moveEffect - && gMovesInfo[move].additionalEffects[i].self == FALSE) + if (effects[i].moveEffect == moveEffect && effects[i].self == FALSE) return TRUE; } return FALSE; @@ -10987,10 +10990,11 @@ bool32 MoveHasMoveEffect(u32 move, u32 moveEffect) bool32 MoveHasMoveEffectWithChance(u32 move, u32 moveEffect, u32 chance) { u32 i; - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, count, effects); + + for (i = 0; i < count; i++) { - if (gMovesInfo[move].additionalEffects[i].moveEffect == moveEffect - && gMovesInfo[move].additionalEffects[i].chance == chance) + if (effects[i].moveEffect == moveEffect && effects[i].chance == chance) return TRUE; } return FALSE; @@ -10999,10 +11003,11 @@ bool32 MoveHasMoveEffectWithChance(u32 move, u32 moveEffect, u32 chance) bool32 MoveHasMoveEffectSelf(u32 move, u32 moveEffect) { u32 i; - for (i = 0; i < gMovesInfo[move].numAdditionalEffects; i++) + GET_ADDITIONAL_EFFECTS_AND_COUNT(move, count, effects); + + for (i = 0; i < count; i++) { - if (gMovesInfo[move].additionalEffects[i].moveEffect == moveEffect - && gMovesInfo[move].additionalEffects[i].self == TRUE) + if (effects[i].moveEffect == moveEffect && effects[i].self == TRUE) return TRUE; } return FALSE; diff --git a/test/battle/move.c b/test/battle/move.c index 4f51395ab1..02f76f731b 100644 --- a/test/battle/move.c +++ b/test/battle/move.c @@ -23,13 +23,12 @@ SINGLE_BATTLE_TEST("Accuracy controls the proportion of misses") SINGLE_BATTLE_TEST("AdditionalEffect.chance controls the proportion of secondary effects") { - u32 move; - PARAMETRIZE { move = MOVE_THUNDER_SHOCK; } - PARAMETRIZE { move = MOVE_DISCHARGE; } - PARAMETRIZE { move = MOVE_NUZZLE; } - ASSUME(MoveHasMoveEffect(move, MOVE_EFFECT_PARALYSIS) == TRUE); - ASSUME(0 < gMovesInfo[move].additionalEffects[0].chance && gMovesInfo[move].additionalEffects[0].chance <= 100); - PASSES_RANDOMLY(gMovesInfo[move].additionalEffects[0].chance, 100, RNG_SECONDARY_EFFECT); + u32 move, chance; + PARAMETRIZE { move = MOVE_THUNDER_SHOCK; chance = 10; } + PARAMETRIZE { move = MOVE_DISCHARGE; chance = 30; } + PARAMETRIZE { move = MOVE_NUZZLE; chance = 100; } + ASSUME(MoveHasMoveEffectWithChance(move, MOVE_EFFECT_PARALYSIS, chance) == TRUE); + PASSES_RANDOMLY(chance, 100, RNG_SECONDARY_EFFECT); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);