Merge pull request #2660 from DizzyEggg/ace_voltswitch

Fix Volt Switch / Roar with ace pokemon
This commit is contained in:
ghoulslash 2023-02-23 09:20:18 -05:00 committed by GitHub
commit 827bf11307
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 33 deletions

View File

@ -3127,7 +3127,7 @@ BattleScript_EffectHitEscape:
jumpifbyte CMP_NOT_EQUAL gBattleOutcome 0, BattleScript_HitEscapeEnd jumpifbyte CMP_NOT_EQUAL gBattleOutcome 0, BattleScript_HitEscapeEnd
jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_HitEscapeEnd jumpifbattletype BATTLE_TYPE_ARENA, BattleScript_HitEscapeEnd
jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_HitEscapeEnd jumpifcantswitch SWITCH_IGNORE_ESCAPE_PREVENTION | BS_ATTACKER, BattleScript_HitEscapeEnd
jumpifemergencyexited BS_TARGET, BattleScript_HitEscapeEnd jumpifemergencyexited BS_TARGET, BattleScript_HitEscapeEnd
openpartyscreen BS_ATTACKER, BattleScript_HitEscapeEnd openpartyscreen BS_ATTACKER, BattleScript_HitEscapeEnd
switchoutabilities BS_ATTACKER switchoutabilities BS_ATTACKER
waitstate waitstate

View File

@ -645,6 +645,7 @@ struct BattleStruct
struct StolenItem itemStolen[PARTY_SIZE]; // Player's team that had items stolen (two bytes per party member) struct StolenItem itemStolen[PARTY_SIZE]; // Player's team that had items stolen (two bytes per party member)
u8 blunderPolicy:1; // should blunder policy activate u8 blunderPolicy:1; // should blunder policy activate
u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky
u8 forcedSwitch:4; // For each battler
u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler u8 switchInAbilityPostponed:4; // To not activate against an empty field, each bit for battler
u8 ballSpriteIds[2]; // item gfx, window gfx u8 ballSpriteIds[2]; // item gfx, window gfx
u8 stickyWebUser; u8 stickyWebUser;

View File

@ -27,6 +27,15 @@ static bool32 AiExpectsToFaintPlayer(void);
static bool32 AI_ShouldHeal(u32 healAmount); static bool32 AI_ShouldHeal(u32 healAmount);
static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount); static bool32 AI_OpponentCanFaintAiWithMod(u32 healAmount);
static bool32 IsAceMon(u32 battlerId, u32 monPartyId)
{
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON
&& !(gBattleStruct->forcedSwitch & gBitTable[battlerId])
&& monPartyId == CalculateEnemyPartyCount()-1)
return TRUE;
return FALSE;
}
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId) void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId)
{ {
if (BATTLE_TWO_VS_ONE_OPPONENT && (battlerId & BIT_SIDE) == B_SIDE_OPPONENT) if (BATTLE_TWO_VS_ONE_OPPONENT && (battlerId & BIT_SIDE) == B_SIDE_OPPONENT)
@ -109,8 +118,7 @@ static bool8 ShouldSwitchIfWonderGuard(void)
continue; continue;
if (i == gBattlerPartyIndexes[gActiveBattler]) if (i == gBattlerPartyIndexes[gActiveBattler])
continue; continue;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON if (IsAceMon(gActiveBattler, i))
&& i == (CalculateEnemyPartyCount()-1))
continue; continue;
for (opposingBattler = GetBattlerAtPosition(opposingPosition), j = 0; j < MAX_MON_MOVES; j++) for (opposingBattler = GetBattlerAtPosition(opposingPosition), j = 0; j < MAX_MON_MOVES; j++)
@ -202,8 +210,7 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void)
continue; continue;
if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2))
continue; continue;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON if (IsAceMon(gActiveBattler, i))
&& i == (CalculateEnemyPartyCount()-1))
continue; continue;
@ -267,7 +274,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
) )
switchMon = FALSE; switchMon = FALSE;
if (IsBattlerAlive(BATTLE_PARTNER(gActiveBattler)) if (IsBattlerAlive(BATTLE_PARTNER(gActiveBattler))
&& (gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_MISTY_TERRAIN && (gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_MISTY_TERRAIN
|| gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_ELECTRIC_TERRAIN) || gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_ELECTRIC_TERRAIN)
&& IsBattlerGrounded(gActiveBattler) && IsBattlerGrounded(gActiveBattler)
@ -277,15 +284,14 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
if (*(gBattleStruct->AI_monToSwitchIntoId + BATTLE_PARTNER(gActiveBattler)) != PARTY_SIZE) //Partner is switching if (*(gBattleStruct->AI_monToSwitchIntoId + BATTLE_PARTNER(gActiveBattler)) != PARTY_SIZE) //Partner is switching
{ {
GetAIPartyIndexes(gActiveBattler, &firstId, &lastId); GetAIPartyIndexes(gActiveBattler, &firstId, &lastId);
if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER) if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER)
party = gPlayerParty; party = gPlayerParty;
for (i = firstId; i < lastId; i++) for (i = firstId; i < lastId; i++)
{ {
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON if (IsAceMon(gActiveBattler, i))
&& i == (CalculateEnemyPartyCount()-1)) continue;
break;
//Look for mon in party that is able to be switched into and has ability that sets terrain //Look for mon in party that is able to be switched into and has ability that sets terrain
if (GetMonData(&party[i], MON_DATA_HP) != 0 if (GetMonData(&party[i], MON_DATA_HP) != 0
@ -305,7 +311,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
} }
} }
} }
//Check if Active Pokemon can KO opponent instead of switching //Check if Active Pokemon can KO opponent instead of switching
//Will still fall asleep, but take out opposing Pokemon first //Will still fall asleep, but take out opposing Pokemon first
if (AiExpectsToFaintPlayer()) if (AiExpectsToFaintPlayer())
@ -328,7 +334,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
&& AI_DATA->abilities[opposingBattler] != ABILITY_UNAWARE && AI_DATA->abilities[opposingBattler] != ABILITY_UNAWARE
&& AI_DATA->abilities[opposingBattler] != ABILITY_KEEN_EYE && AI_DATA->abilities[opposingBattler] != ABILITY_KEEN_EYE
&& !(gBattleMons[gActiveBattler].status2 & STATUS2_FORESIGHT) && !(gBattleMons[gActiveBattler].status2 & STATUS2_FORESIGHT)
&& !(gStatuses3[gActiveBattler] & STATUS3_MIRACLE_EYED)) && !(gStatuses3[gActiveBattler] & STATUS3_MIRACLE_EYED))
switchMon = FALSE; switchMon = FALSE;
} }
@ -343,7 +349,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
&& gBattleMons[gActiveBattler].hp >= (gBattleMons[gActiveBattler].maxHP / 3) && gBattleMons[gActiveBattler].hp >= (gBattleMons[gActiveBattler].maxHP / 3)
&& (Random() % (moduloChance*chanceReducer)) == 0) && (Random() % (moduloChance*chanceReducer)) == 0)
switchMon = TRUE; switchMon = TRUE;
//Cursed //Cursed
moduloChance = 2; //50% moduloChance = 2; //50%
if (gBattleMons[gActiveBattler].status2 & STATUS2_CURSED if (gBattleMons[gActiveBattler].status2 & STATUS2_CURSED
@ -370,7 +376,7 @@ static bool8 ShouldSwitchIfGameStatePrompt(void)
//Todo //Todo
//Pass Wish Heal //Pass Wish Heal
//Semi-Invulnerable //Semi-Invulnerable
if (gStatuses3[opposingBattler] & STATUS3_SEMI_INVULNERABLE) if (gStatuses3[opposingBattler] & STATUS3_SEMI_INVULNERABLE)
{ {
@ -416,7 +422,7 @@ static bool8 ShouldSwitchIfAbilityBenefit(void)
switch(AI_DATA->abilities[gActiveBattler]) { switch(AI_DATA->abilities[gActiveBattler]) {
case ABILITY_NATURAL_CURE: case ABILITY_NATURAL_CURE:
moduloChance = 4; //25% moduloChance = 4; //25%
//Attempt to cure bad ailment //Attempt to cure bad ailment
if (gBattleMons[gActiveBattler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) if (gBattleMons[gActiveBattler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON)
&& GetMostSuitableMonToSwitchInto() != PARTY_SIZE) && GetMostSuitableMonToSwitchInto() != PARTY_SIZE)
break; break;
@ -432,17 +438,17 @@ static bool8 ShouldSwitchIfAbilityBenefit(void)
case ABILITY_REGENERATOR: case ABILITY_REGENERATOR:
moduloChance = 2; //50% moduloChance = 2; //50%
//Don't switch if ailment //Don't switch if ailment
if (gBattleMons[gActiveBattler].status1 & STATUS1_ANY) if (gBattleMons[gActiveBattler].status1 & STATUS1_ANY)
return FALSE; return FALSE;
if ((gBattleMons[gActiveBattler].hp <= ((gBattleMons[gActiveBattler].maxHP * 2) / 3)) if ((gBattleMons[gActiveBattler].hp <= ((gBattleMons[gActiveBattler].maxHP * 2) / 3))
&& GetMostSuitableMonToSwitchInto() != PARTY_SIZE && GetMostSuitableMonToSwitchInto() != PARTY_SIZE
&& Random() % (moduloChance*chanceReducer) == 0) && Random() % (moduloChance*chanceReducer) == 0)
break; break;
return FALSE; return FALSE;
default: default:
return FALSE; return FALSE;
} }
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;
@ -576,8 +582,7 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent)
continue; continue;
if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2))
continue; continue;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON if (IsAceMon(gActiveBattler, i))
&& i == (CalculateEnemyPartyCount()-1))
continue; continue;
@ -619,6 +624,7 @@ bool32 ShouldSwitch(void)
struct Pokemon *party; struct Pokemon *party;
s32 i; s32 i;
s32 availableToSwitch; s32 availableToSwitch;
bool32 hasAceMon = FALSE;
if (gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION)) if (gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION))
return FALSE; return FALSE;
@ -668,15 +674,22 @@ bool32 ShouldSwitch(void)
continue; continue;
if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) if (i == *(gBattleStruct->monToSwitchIntoId + battlerIn2))
continue; continue;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON if (IsAceMon(gActiveBattler, i))
&& i == (CalculateEnemyPartyCount()-1)) {
hasAceMon = TRUE;
continue; continue;
}
availableToSwitch++; availableToSwitch++;
} }
if (availableToSwitch == 0) if (availableToSwitch == 0)
return FALSE; {
if (hasAceMon) // If the ace mon is the only available mon, use it
availableToSwitch++;
else
return FALSE;
}
//NOTE: The sequence of the below functions matter! Do not change unless you have carefully considered the outcome. //NOTE: The sequence of the below functions matter! Do not change unless you have carefully considered the outcome.
//Since the order is sequencial, and some of these functions prompt switch to specific party members. //Since the order is sequencial, and some of these functions prompt switch to specific party members.
@ -694,14 +707,14 @@ bool32 ShouldSwitch(void)
return TRUE; return TRUE;
if (ShouldSwitchIfAbilityBenefit()) if (ShouldSwitchIfAbilityBenefit())
return TRUE; return TRUE;
//Removing switch capabilites under specific conditions //Removing switch capabilites under specific conditions
//These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand. //These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand.
if (HasSuperEffectiveMoveAgainstOpponents(FALSE)) if (HasSuperEffectiveMoveAgainstOpponents(FALSE))
return FALSE; return FALSE;
if (AreStatsRaised()) if (AreStatsRaised())
return FALSE; return FALSE;
//Default Function //Default Function
//Can prompt switch if AI has a pokemon in party that resists current opponent & has super effective move //Can prompt switch if AI has a pokemon in party that resists current opponent & has super effective move
if (FindMonWithFlagsAndSuperEffective(MOVE_RESULT_DOESNT_AFFECT_FOE, 2) if (FindMonWithFlagsAndSuperEffective(MOVE_RESULT_DOESNT_AFFECT_FOE, 2)
@ -758,8 +771,7 @@ void AI_TrySwitchOrUseItem(void)
continue; continue;
if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn2))
continue; continue;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON if (IsAceMon(gActiveBattler, monToSwitchId))
&& monToSwitchId == (CalculateEnemyPartyCount()-1))
continue; continue;
break; break;
@ -916,7 +928,7 @@ u8 GetMostSuitableMonToSwitchInto(void)
s32 lastId = 0; // + 1 s32 lastId = 0; // + 1
struct Pokemon *party; struct Pokemon *party;
s32 i, j, aliveCount = 0; s32 i, j, aliveCount = 0;
u8 invalidMons = 0; u32 invalidMons = 0, aceMonId = PARTY_SIZE;
if (*(gBattleStruct->monToSwitchIntoId + gActiveBattler) != PARTY_SIZE) if (*(gBattleStruct->monToSwitchIntoId + gActiveBattler) != PARTY_SIZE)
return *(gBattleStruct->monToSwitchIntoId + gActiveBattler); return *(gBattleStruct->monToSwitchIntoId + gActiveBattler);
@ -958,12 +970,19 @@ u8 GetMostSuitableMonToSwitchInto(void)
|| gBattlerPartyIndexes[battlerIn2] == i || gBattlerPartyIndexes[battlerIn2] == i
|| i == *(gBattleStruct->monToSwitchIntoId + battlerIn1) || i == *(gBattleStruct->monToSwitchIntoId + battlerIn1)
|| i == *(gBattleStruct->monToSwitchIntoId + battlerIn2) || i == *(gBattleStruct->monToSwitchIntoId + battlerIn2)
|| (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(gActiveBattler, opposingBattler)) // While not really invalid per say, not really wise to switch into this mon. || (GetMonAbility(&party[i]) == ABILITY_TRUANT && IsTruantMonVulnerable(gActiveBattler, opposingBattler))) // While not really invalid per say, not really wise to switch into this mon.)
|| ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_ACE_POKEMON) {
&& i == (CalculateEnemyPartyCount() - 1))) //Save Ace Pokemon for last
invalidMons |= gBitTable[i]; invalidMons |= gBitTable[i];
}
else if (IsAceMon(gActiveBattler, i))// Save Ace Pokemon for last.
{
aceMonId = i;
invalidMons |= gBitTable[i];
}
else else
{
aliveCount++; aliveCount++;
}
} }
bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount); bestMonId = GetBestMonBatonPass(party, firstId, lastId, invalidMons, aliveCount);
@ -978,6 +997,11 @@ u8 GetMostSuitableMonToSwitchInto(void)
if (bestMonId != PARTY_SIZE) if (bestMonId != PARTY_SIZE)
return bestMonId; return bestMonId;
// If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon.
if (aceMonId != PARTY_SIZE
&& (gBattleMoves[gLastUsedMove].effect == EFFECT_HIT_ESCAPE || gBattleMoves[gLastUsedMove].effect == EFFECT_PARTING_SHOT))
return aceMonId;
return PARTY_SIZE; return PARTY_SIZE;
} }

View File

@ -7125,6 +7125,7 @@ static void Cmd_switchineffects(void)
gBattlerFainted++; gBattlerFainted++;
} }
} }
gBattleStruct->forcedSwitch &= ~(gBitTable[gActiveBattler]);
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
} }
} }
@ -12312,6 +12313,7 @@ static void Cmd_forcerandomswitch(void)
{ {
*(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget]; *(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget];
gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch; gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch;
gBattleStruct->forcedSwitch |= gBitTable[gBattlerTarget];
*(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[Random() % validMonsCount]; *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[Random() % validMonsCount];
if (!IsMultiBattle()) if (!IsMultiBattle())