improve switching with ai bad moves

This commit is contained in:
DizzyEggg 2023-08-08 12:55:47 +02:00
parent 17555feb20
commit f72ec368fc
5 changed files with 116 additions and 64 deletions

View File

@ -291,6 +291,8 @@ struct AiLogicData
s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 moveLimitations[MAX_BATTLERS_COUNT]; u8 moveLimitations[MAX_BATTLERS_COUNT];
bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler.
u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch.
}; };
struct AI_ThinkingStruct struct AI_ThinkingStruct
@ -303,8 +305,7 @@ struct AI_ThinkingStruct
u32 aiFlags; u32 aiFlags;
u8 aiAction; u8 aiAction;
u8 aiLogicId; u8 aiLogicId;
struct AI_SavedBattleMon saved[4]; struct AI_SavedBattleMon saved[MAX_BATTLERS_COUNT];
bool8 switchMon; // Because all available moves have no/little effect.
}; };
#define AI_MOVE_HISTORY_COUNT 3 #define AI_MOVE_HISTORY_COUNT 3

View File

@ -403,6 +403,78 @@ void GetAiLogicData(void)
} }
} }
static bool32 AI_SwitchMonIfSuitable(u32 battlerId)
{
u32 monToSwitchId = GetMostSuitableMonToSwitchInto();
if (monToSwitchId != PARTY_SIZE)
{
AI_DATA->shouldSwitchMon |= gBitTable[battlerId];
AI_DATA->monToSwitchId[battlerId] = monToSwitchId;
return TRUE;
}
return FALSE;
}
static bool32 AI_ShouldSwitchIfBadMoves(u32 battlerId, bool32 doubleBattle)
{
u32 i, j;
// If can switch.
if (CountUsablePartyMons(battlerId) > 0
&& !IsBattlerTrapped(battlerId, TRUE)
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
&& AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS))
{
// Consider switching if all moves are worthless to use.
if (GetTotalBaseStat(gBattleMons[battlerId].species) >= 310 // Mon is not weak.
&& gBattleMons[battlerId].hp >= gBattleMons[battlerId].maxHP / 2) // Mon has more than 50% of its HP
{
s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
if (doubleBattle)
{
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (i != battlerId && IsBattlerAlive(i))
{
for (j = 0; j < MAX_MON_MOVES; j++)
{
if (gBattleStruct->aiFinalScore[battlerId][i][j] > cap)
break;
}
if (j != MAX_MON_MOVES)
break;
}
}
if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battlerId))
return TRUE;
}
else
{
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (AI_THINKING_STRUCT->score[i] > cap)
break;
}
if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battlerId))
return TRUE;
}
}
// Consider switching if your mon with truant is bodied by Protect spam.
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
if (GetBattlerAbility(battlerId) == ABILITY_TRUANT
&& IsTruantMonVulnerable(battlerId, gBattlerTarget)
&& gDisableStructs[battlerId].truantCounter
&& gBattleMons[battlerId].hp >= gBattleMons[battlerId].maxHP / 2
&& AI_SwitchMonIfSuitable(battlerId))
{
return TRUE;
}
}
return FALSE;
}
static u8 ChooseMoveOrAction_Singles(void) static u8 ChooseMoveOrAction_Singles(void)
{ {
u8 currentMoveArray[MAX_MON_MOVES]; u8 currentMoveArray[MAX_MON_MOVES];
@ -436,46 +508,9 @@ static u8 ChooseMoveOrAction_Singles(void)
gActiveBattler = sBattler_AI; gActiveBattler = sBattler_AI;
// If can switch. // Swith mon if there are no good moves to use.
if (CountUsablePartyMons(sBattler_AI) > 0 if (AI_ShouldSwitchIfBadMoves(sBattler_AI, FALSE))
&& !IsAbilityPreventingEscape(sBattler_AI) return AI_CHOICE_SWITCH;
&& !(gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION))
&& !(gStatuses3[gActiveBattler] & STATUS3_ROOTED)
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
&& AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS))
{
// Consider switching if all moves are worthless to use.
if (GetTotalBaseStat(gBattleMons[sBattler_AI].species) >= 310 // Mon is not weak.
&& gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2)
{
s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (AI_THINKING_STRUCT->score[i] > cap)
break;
}
if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
return AI_CHOICE_SWITCH;
}
}
// Consider switching if your mon with truant is bodied by Protect spam.
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
if (GetBattlerAbility(sBattler_AI) == ABILITY_TRUANT
&& IsTruantMonVulnerable(sBattler_AI, gBattlerTarget)
&& gDisableStructs[sBattler_AI].truantCounter
&& gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2)
{
if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
return AI_CHOICE_SWITCH;
}
}
}
numOfBestMoves = 1; numOfBestMoves = 1;
currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; currentMoveArray[0] = AI_THINKING_STRUCT->score[0];
@ -590,7 +625,6 @@ static u8 ChooseMoveOrAction_Doubles(void)
if (i == BATTLE_PARTNER(sBattler_AI) && bestMovePointsForTarget[i] < 100) if (i == BATTLE_PARTNER(sBattler_AI) && bestMovePointsForTarget[i] < 100)
{ {
bestMovePointsForTarget[i] = -1; bestMovePointsForTarget[i] = -1;
mostViableMovesScores[0] = mostViableMovesScores[0]; // Needed to match.
} }
} }
@ -600,6 +634,10 @@ static u8 ChooseMoveOrAction_Doubles(void)
} }
} }
// Switch mon if all of the moves are bad to use against any of the target.
if (AI_ShouldSwitchIfBadMoves(sBattler_AI, TRUE))
return AI_CHOICE_SWITCH;
mostMovePoints = bestMovePointsForTarget[0]; mostMovePoints = bestMovePointsForTarget[0];
mostViableTargetsArray[0] = 0; mostViableTargetsArray[0] = 0;
mostViableTargetsNo = 1; mostViableTargetsNo = 1;

View File

@ -59,10 +59,10 @@ void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId)
static bool8 ShouldSwitchIfAllBadMoves(void) static bool8 ShouldSwitchIfAllBadMoves(void)
{ {
if (gBattleResources->ai->switchMon) if (AI_DATA->shouldSwitchMon & gBitTable[gActiveBattler])
{ {
gBattleResources->ai->switchMon = 0; AI_DATA->shouldSwitchMon &= ~(gBitTable[gActiveBattler]);
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; gBattleStruct->AI_monToSwitchIntoId[gActiveBattler] = AI_DATA->monToSwitchId[gActiveBattler];
BtlController_EmitTwoReturnValues(BUFFER_B, B_ACTION_SWITCH, 0); BtlController_EmitTwoReturnValues(BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE; return TRUE;
} }

View File

@ -1524,23 +1524,30 @@ static void PlayerPartnerHandleChooseMove(void)
chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler]; chosenMoveId = gBattleStruct->aiMoveOrAction[gActiveBattler];
gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler]; gBattlerTarget = gBattleStruct->aiChosenTarget[gActiveBattler];
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) if (chosenMoveId == AI_CHOICE_SWITCH)
gBattlerTarget = gActiveBattler;
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
{ {
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); BtlController_EmitTwoReturnValues(BUFFER_B, 10, 0xFFFF);
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
} }
if (ShouldUseZMove(gActiveBattler, gBattlerTarget, moveInfo->moves[chosenMoveId]))
QueueZMove(gActiveBattler, moveInfo->moves[chosenMoveId]);
// If partner can mega evolve, do it.
if (CanMegaEvolve(gActiveBattler))
BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
else else
BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); {
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = gActiveBattler;
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
{
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
if (ShouldUseZMove(gActiveBattler, gBattlerTarget, moveInfo->moves[chosenMoveId]))
QueueZMove(gActiveBattler, moveInfo->moves[chosenMoveId]);
// If partner can mega evolve, do it.
if (CanMegaEvolve(gActiveBattler))
BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
else
BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
}
PlayerPartnerBufferExecCompleted(); PlayerPartnerBufferExecCompleted();
} }
@ -1559,7 +1566,7 @@ static void PlayerPartnerHandleChoosePokemon(void)
chosenMonId = gSelectedMonPartyId = GetFirstFaintedPartyIndex(gActiveBattler); chosenMonId = gSelectedMonPartyId = GetFirstFaintedPartyIndex(gActiveBattler);
} }
// Switching out // Switching out
else else if (gBattleStruct->monToSwitchIntoId[gActiveBattler] == PARTY_SIZE)
{ {
chosenMonId = GetMostSuitableMonToSwitchInto(); chosenMonId = GetMostSuitableMonToSwitchInto();
if (chosenMonId == PARTY_SIZE) // just switch to the next mon if (chosenMonId == PARTY_SIZE) // just switch to the next mon
@ -1579,6 +1586,12 @@ static void PlayerPartnerHandleChoosePokemon(void)
} }
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = chosenMonId; *(gBattleStruct->monToSwitchIntoId + gActiveBattler) = chosenMonId;
} }
else // Mon to switch out has been already chosen.
{
chosenMonId = gBattleStruct->monToSwitchIntoId[gActiveBattler];
*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE;
*(gBattleStruct->monToSwitchIntoId + gActiveBattler) = chosenMonId;
}
BtlController_EmitChosenMonReturnValue(BUFFER_B, chosenMonId, NULL); BtlController_EmitChosenMonReturnValue(BUFFER_B, chosenMonId, NULL);
PlayerPartnerBufferExecCompleted(); PlayerPartnerBufferExecCompleted();
} }

View File

@ -565,8 +565,8 @@ static void CB2_InitBattleInternal(void)
gBattle_BG3_X = 0; gBattle_BG3_X = 0;
gBattle_BG3_Y = 0; gBattle_BG3_Y = 0;
#if DEBUG_OVERWORLD_MENU == FALSE #if DEBUG_OVERWORLD_MENU == FALSE
gBattleTerrain = BattleSetup_GetTerrainId(); gBattleTerrain = BattleSetup_GetTerrainId();
#else #else
if (!gIsDebugBattle) if (!gIsDebugBattle)
@ -594,7 +594,7 @@ static void CB2_InitBattleInternal(void)
else else
SetMainCallback2(CB2_HandleStartBattle); SetMainCallback2(CB2_HandleStartBattle);
#if DEBUG_OVERWORLD_MENU == FALSE #if DEBUG_OVERWORLD_MENU == FALSE
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED))) if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED)))
{ {
CreateNPCTrainerParty(&gEnemyParty[0], gTrainerBattleOpponent_A, TRUE); CreateNPCTrainerParty(&gEnemyParty[0], gTrainerBattleOpponent_A, TRUE);