diff --git a/include/battle.h b/include/battle.h index 70ce4485de..cfe9b2f682 100644 --- a/include/battle.h +++ b/include/battle.h @@ -68,20 +68,6 @@ #define BATTLE_BUFFER_LINK_SIZE 0x1000 -struct ResourceFlags -{ - u32 flags[MAX_BATTLERS_COUNT]; -}; - -#define RESOURCE_FLAG_FLASH_FIRE 0x1 -#define RESOURCE_FLAG_ROOST 0x2 -#define RESOURCE_FLAG_UNBURDEN 0x4 -#define RESOURCE_FLAG_UNUSED 0x8 -#define RESOURCE_FLAG_UNUSED_2 0x10 -#define RESOURCE_FLAG_EMERGENCY_EXIT 0x20 -#define RESOURCE_FLAG_NEUTRALIZING_GAS 0x40 -#define RESOURCE_FLAG_ICE_FACE 0x80 - struct DisableStruct { u32 transformedMonPersonality; @@ -118,6 +104,7 @@ struct DisableStruct u8 laserFocusTimer; u8 throatChopTimer; u8 wrapTurns; + u8 syrupBombTimer; u8 tormentTimer:4; // used for G-Max Meltdown u8 usedMoves:4; u8 truantCounter:1; @@ -130,16 +117,23 @@ struct DisableStruct u8 toxicSpikesDone:1; u8 stickyWebDone:1; u8 stealthRockDone:1; - u8 syrupBombTimer; - u8 syrupBombIsShiny:1; - u8 steelSurgeDone:1; u8 weatherAbilityDone:1; u8 terrainAbilityDone:1; + u8 syrupBombIsShiny:1; + u8 steelSurgeDone:1; u8 usedProteanLibero:1; + u8 flashFireBoosted:1; u16 overwrittenAbility; // abilities overwritten during battle (keep separate from battle history in case of switching) u8 boosterEnergyActivates:1; + u8 roostActive:1; + u8 unbrudenActive:1; + u8 startEmergencyExit:1; + u8 neutralizingGas:1; + u8 iceFaceActivationPrevention:1; // fixes hit escape move edge case + u8 padding:2; }; +// Fully Cleared each turn after end turn effects are done. A few things are cleared before end turn effects struct ProtectStruct { u32 protected:1; @@ -193,9 +187,9 @@ struct ProtectStruct u32 specialDmg; u8 physicalBattlerId; u8 specialBattlerId; - }; +// Cleared at the start of HandleAction_ActionFinished struct SpecialStatus { s32 physicalDmg; @@ -416,7 +410,6 @@ struct StatsArray struct BattleResources { struct SecretBase* secretBase; - struct ResourceFlags *flags; struct BattleScriptsStack* battleScriptsStack; struct BattleCallbacksStack* battleCallbackStack; struct StatsArray* beforeLvlUp; diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 1dc3867f21..0fefd71fbd 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -2826,7 +2826,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case ABILITY_FLASH_FIRE: if (moveType == TYPE_FIRE && HasMoveWithType(battlerAtkPartner, TYPE_FIRE) - && !(gBattleResources->flags->flags[battlerAtkPartner] & RESOURCE_FLAG_FLASH_FIRE)) + && !gDisableStructs[battlerAtkPartner].flashFireBoosted) { RETURN_SCORE_PLUS(WEAK_EFFECT); } diff --git a/src/battle_main.c b/src/battle_main.c index e0072829e0..5dba22a160 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3049,7 +3049,6 @@ static void BattleStartClearSetData(void) gLastHitBy[i] = 0xFF; gLockedMoves[i] = MOVE_NONE; gLastPrintedMoves[i] = MOVE_NONE; - gBattleResources->flags->flags[i] = 0; gPalaceSelectionBattleScripts[i] = 0; gBattleStruct->lastTakenMove[i] = MOVE_NONE; gBattleStruct->choicedMove[i] = MOVE_NONE; @@ -3263,7 +3262,6 @@ void SwitchInClearSetData(u32 battler) } gBattleStruct->choicedMove[battler] = MOVE_NONE; - gBattleResources->flags->flags[battler] = 0; gCurrentMove = MOVE_NONE; gBattleStruct->arenaTurnCounter = 0xFF; @@ -3404,8 +3402,6 @@ const u8* FaintClearSetData(u32 battler) gBattleStruct->lastTakenMoveFrom[i][battler] = 0; } - gBattleResources->flags->flags[battler] = 0; - gBattleMons[battler].types[0] = gSpeciesInfo[gBattleMons[battler].species].types[0]; gBattleMons[battler].types[1] = gSpeciesInfo[gBattleMons[battler].species].types[1]; gBattleMons[battler].types[2] = TYPE_MYSTERY; @@ -4818,7 +4814,7 @@ u32 GetBattlerTotalSpeedStatArgs(u32 battler, u32 ability, u32 holdEffect) // various effects if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND) speed *= 2; - if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_UNBURDEN) + if (gDisableStructs[battler].unbrudenActive) speed *= 2; // paralysis drop @@ -5972,11 +5968,11 @@ u32 GetDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler, u8 *ateBoost) u32 teraType; if (GetActiveGimmick(battler) == GIMMICK_TERA && ((teraType = GetMonData(mon, MON_DATA_TERA_TYPE)) != TYPE_STELLAR)) return teraType; - else if (type1 != TYPE_MYSTERY && !(gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST && type1 == TYPE_FLYING)) + else if (type1 != TYPE_MYSTERY && !(gDisableStructs[battler].roostActive && type1 == TYPE_FLYING)) return type1; - else if (type2 != TYPE_MYSTERY && !(gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST && type2 == TYPE_FLYING)) + else if (type2 != TYPE_MYSTERY && !(gDisableStructs[battler].roostActive && type2 == TYPE_FLYING)) return type2; - else if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST) + else if (gDisableStructs[battler].roostActive) return (B_ROOST_PURE_FLYING >= GEN_5 ? TYPE_NORMAL : TYPE_MYSTERY); else if (type3 != TYPE_MYSTERY) return type3; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 22252fdec3..529d00d507 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2137,7 +2137,7 @@ static void Cmd_adjustdamage(void) gBattleStruct->moveResultFlags[battlerDef] &= ~(MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE); gBattleStruct->moveDamage[battlerDef] = 0; RecordAbilityBattle(gBattlerTarget, ABILITY_ICE_FACE); - gBattleResources->flags->flags[battlerDef] |= RESOURCE_FLAG_ICE_FACE; + gDisableStructs[battlerDef].iceFaceActivationPrevention = TRUE; // Form change will be done after attack animation in Cmd_resultmessage. continue; } @@ -2807,9 +2807,9 @@ static void Cmd_resultmessage(void) // TODO: Convert this to a proper FORM_CHANGE type. // Do Ice Face form change which was set up in Cmd_adjustdamage. - if (gBattleResources->flags->flags[gBattlerTarget] & RESOURCE_FLAG_ICE_FACE) + if (gDisableStructs[gBattlerTarget].iceFaceActivationPrevention) { - gBattleResources->flags->flags[gBattlerTarget] &= ~(RESOURCE_FLAG_ICE_FACE); + gDisableStructs[gBattlerTarget].iceFaceActivationPrevention = FALSE; gBattleMons[gBattlerTarget].species = SPECIES_EISCUE_NOICE; gBattleScripting.battler = gBattlerTarget; // For STRINGID_PKMNTRANSFORMED BattleScriptPushCursor(); @@ -3062,7 +3062,7 @@ static void CheckSetUnburden(u8 battler) { if (GetBattlerAbility(battler) == ABILITY_UNBURDEN) { - gBattleResources->flags->flags[battler] |= RESOURCE_FLAG_UNBURDEN; + gDisableStructs[battler].unbrudenActive = TRUE; RecordAbilityBattle(battler, ABILITY_UNBURDEN); } } @@ -3084,7 +3084,8 @@ void StealTargetItem(u8 battlerStealer, u8 battlerItem) { RecordItemEffectBattle(battlerStealer, ItemId_GetHoldEffect(gLastUsedItem)); gBattleMons[battlerStealer].item = gLastUsedItem; - gBattleResources->flags->flags[battlerStealer] &= ~RESOURCE_FLAG_UNBURDEN; + + gDisableStructs[battlerStealer].unbrudenActive = FALSE; BtlController_EmitSetMonData(battlerStealer, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gLastUsedItem), &gLastUsedItem); // set attacker item MarkBattlerForControllerExec(battlerStealer); } @@ -5586,7 +5587,7 @@ static void Cmd_setroost(void) { CMD_ARGS(); - gBattleResources->flags->flags[gBattlerAttacker] |= RESOURCE_FLAG_ROOST; + gDisableStructs[gBattlerAttacker].roostActive = TRUE; gBattleStruct->roostTypes[gBattlerAttacker][0] = gBattleMons[gBattlerAttacker].types[0]; gBattleStruct->roostTypes[gBattlerAttacker][1] = gBattleMons[gBattlerAttacker].types[1]; @@ -6714,7 +6715,7 @@ static void Cmd_moveend(void) } else // Eject Pack { - if (!(gBattleResources->flags->flags[gBattlerTarget] & RESOURCE_FLAG_EMERGENCY_EXIT) + if (!gDisableStructs[gBattlerTarget].startEmergencyExit && !(gMovesInfo[gCurrentMove].effect == EFFECT_PARTING_SHOT && CanBattlerSwitch(gBattlerAttacker))) { effect = TRUE; @@ -6892,12 +6893,12 @@ static void Cmd_moveend(void) { if (gBattleStruct->redCardActivates) { - gBattleResources->flags->flags[i] &= ~RESOURCE_FLAG_EMERGENCY_EXIT; + gDisableStructs[i].startEmergencyExit = FALSE; continue; } - if (gBattleResources->flags->flags[i] & RESOURCE_FLAG_EMERGENCY_EXIT) + if (gDisableStructs[i].startEmergencyExit) { - gBattleResources->flags->flags[i] &= ~RESOURCE_FLAG_EMERGENCY_EXIT; + gDisableStructs[i].startEmergencyExit = FALSE; gSpecialStatuses[i].emergencyExited = TRUE; gBattlerTarget = gBattlerAbility = i; BattleScriptPushCursor(); @@ -8610,7 +8611,7 @@ static void BestowItem(u32 battlerAtk, u32 battlerDef) gBattleMons[battlerDef].item = gLastUsedItem; BtlController_EmitSetMonData(battlerDef, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battlerDef].item), &gBattleMons[battlerDef].item); MarkBattlerForControllerExec(battlerDef); - gBattleResources->flags->flags[battlerDef] &= ~RESOURCE_FLAG_UNBURDEN; + gDisableStructs[battlerDef].unbrudenActive = FALSE; } // Called by Cmd_removeitem. itemId represents the item that was removed, not being given. @@ -14665,8 +14666,8 @@ static void Cmd_tryswapitems(void) } else if (oldItemAtk == ITEM_NONE && *newItemAtk != ITEM_NONE) { - if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNBURDEN && gBattleResources->flags->flags[gBattlerAttacker] & RESOURCE_FLAG_UNBURDEN) - gBattleResources->flags->flags[gBattlerAttacker] &= ~RESOURCE_FLAG_UNBURDEN; + if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNBURDEN && gDisableStructs[gBattlerAttacker].unbrudenActive) + gDisableStructs[gBattlerAttacker].unbrudenActive = FALSE; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ITEM_SWAP_TAKEN; // nothing -> <- target's item } diff --git a/src/battle_util.c b/src/battle_util.c index 9d6ae548ec..d9c72e7d8e 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2782,8 +2782,8 @@ u8 DoBattlerEndTurnEffects(void) gBattleStruct->turnEffectsTracker++; break; case ENDTURN_ROOST: // Return flying type. - if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST) - gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_ROOST; + if (gDisableStructs[battler].roostActive) + gDisableStructs[battler].roostActive = FALSE; gBattleStruct->turnEffectsTracker++; break; case ENDTURN_ELECTRIFY: @@ -5590,14 +5590,14 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case MOVE_ABSORBED_BY_BOOST_FLASH_FIRE: gBattleStruct->pledgeMove = FALSE; - if (!(gBattleResources->flags->flags[battler] & RESOURCE_FLAG_FLASH_FIRE)) + if (!gDisableStructs[battler].flashFireBoosted) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_BOOST; if (gProtectStructs[gBattlerAttacker].notFirstStrike) gBattlescriptCurrInstr = BattleScript_FlashFireBoost; else gBattlescriptCurrInstr = BattleScript_FlashFireBoost_PPLoss; - gBattleResources->flags->flags[battler] |= RESOURCE_FLAG_FLASH_FIRE; + gDisableStructs[battler].flashFireBoosted = TRUE; } else { @@ -5704,7 +5704,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 // Not currently held by Sky Drop && !(gStatuses3[battler] & STATUS3_SKY_DROPPED)) { - gBattleResources->flags->flags[battler] |= RESOURCE_FLAG_EMERGENCY_EXIT; + gDisableStructs[battler].startEmergencyExit = TRUE; effect++; } break; @@ -6489,9 +6489,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards for (i = 0; i < gBattlersCount; i++) { - if (gBattleMons[i].ability == ABILITY_NEUTRALIZING_GAS && !(gBattleResources->flags->flags[i] & RESOURCE_FLAG_NEUTRALIZING_GAS)) + if (gBattleMons[i].ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[i].neutralizingGas) { - gBattleResources->flags->flags[i] |= RESOURCE_FLAG_NEUTRALIZING_GAS; + gDisableStructs[i].neutralizingGas = TRUE; gBattlerAbility = i; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); @@ -7223,9 +7223,8 @@ static u32 ItemHealHp(u32 battler, u32 itemId, enum ItemEffect caseID, bool32 pe BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_ItemHealHP_RemoveItemRet; } - if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_EMERGENCY_EXIT - && GetNonDynamaxHP(battler) >= GetNonDynamaxMaxHP(battler) / 2) - gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_EMERGENCY_EXIT; + if (gDisableStructs[battler].startEmergencyExit && GetNonDynamaxHP(battler) >= GetNonDynamaxMaxHP(battler) / 2) + gDisableStructs[battler].startEmergencyExit = FALSE; return ITEM_HP_CHANGE; } @@ -9768,7 +9767,7 @@ static inline u32 CalcAttackStat(struct DamageCalculationData *damageCalcData, u modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(0.5)); break; case ABILITY_FLASH_FIRE: - if (moveType == TYPE_FIRE && gBattleResources->flags->flags[battlerAtk] & RESOURCE_FLAG_FLASH_FIRE) + if (moveType == TYPE_FIRE && gDisableStructs[battlerAtk].flashFireBoosted) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); break; case ABILITY_SWARM: @@ -12065,7 +12064,7 @@ void GetBattlerTypes(u32 battler, bool32 ignoreTera, u32 types[static 3]) types[2] = gBattleMons[battler].types[2]; // Roost. - if (!isTera && (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST)) + if (!isTera && gDisableStructs[battler].roostActive) { if (types[0] == TYPE_FLYING && types[1] == TYPE_FLYING) types[0] = types[1] = B_ROOST_PURE_FLYING >= GEN_5 ? TYPE_NORMAL : TYPE_MYSTERY; diff --git a/src/battle_util2.c b/src/battle_util2.c index 326acf0e40..6c2d0ff0a9 100644 --- a/src/battle_util2.c +++ b/src/battle_util2.c @@ -25,7 +25,6 @@ void AllocateBattleResources(void) gBattleResources = AllocZeroed(sizeof(*gBattleResources)); gBattleResources->secretBase = AllocZeroed(sizeof(*gBattleResources->secretBase)); - gBattleResources->flags = AllocZeroed(sizeof(*gBattleResources->flags)); gBattleResources->battleScriptsStack = AllocZeroed(sizeof(*gBattleResources->battleScriptsStack)); gBattleResources->battleCallbackStack = AllocZeroed(sizeof(*gBattleResources->battleCallbackStack)); gBattleResources->beforeLvlUp = AllocZeroed(sizeof(*gBattleResources->beforeLvlUp)); @@ -58,7 +57,6 @@ void FreeBattleResources(void) FREE_AND_SET_NULL(gBattleStruct); FREE_AND_SET_NULL(gBattleResources->secretBase); - FREE_AND_SET_NULL(gBattleResources->flags); FREE_AND_SET_NULL(gBattleResources->battleScriptsStack); FREE_AND_SET_NULL(gBattleResources->battleCallbackStack); FREE_AND_SET_NULL(gBattleResources->beforeLvlUp); diff --git a/test/battle/ability/flash_fire.c b/test/battle/ability/flash_fire.c new file mode 100644 index 0000000000..c81967d1af --- /dev/null +++ b/test/battle/ability/flash_fire.c @@ -0,0 +1,28 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Flash Fire boosts fire type moves by 50% but no subsequent increase is applied") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_HEATRAN) { Ability(ABILITY_FLASH_FIRE); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EMBER); MOVE(opponent, MOVE_EMBER); } + TURN { MOVE(player, MOVE_EMBER); MOVE(opponent, MOVE_EMBER); } + TURN { MOVE(player, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ABILITY_POPUP(player, ABILITY_FLASH_FIRE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ABILITY_POPUP(player, ABILITY_FLASH_FIRE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.5), damage[1]); + EXPECT_EQ(damage[1], damage[2]); + } +}