Save party mons ai

This commit is contained in:
DizzyEggg 2022-08-23 01:07:25 +02:00
parent eb0cc91dce
commit 327782646b
10 changed files with 377 additions and 179 deletions

View File

@ -107,7 +107,7 @@ LIBPATH := -L ../../tools/agbcc/lib
LIB := $(LIBPATH) -lgcc -lc -L../../libagbsyscall -lagbsyscall
else
CC1 = $(shell $(PATH_MODERNCC) --print-prog-name=cc1) -quiet
override CFLAGS += -mthumb -mthumb-interwork -O2 -mabi=apcs-gnu -mtune=arm7tdmi -march=armv4t -fno-toplevel-reorder -Wno-pointer-to-int-cast
override CFLAGS += -mthumb -mthumb-interwork -O0 -mabi=apcs-gnu -mtune=arm7tdmi -march=armv4t -fno-toplevel-reorder -Wno-pointer-to-int-cast
ROM := $(MODERN_ROM_NAME)
OBJ_DIR := $(MODERN_OBJ_DIR_NAME)
LIBPATH := -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libgcc.a))" -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libnosys.a))" -L "$(dir $(shell $(PATH_MODERNCC) -mthumb -print-file-name=libc.a))"

BIN
NO$GBA.EXE - Shortcut.lnk Normal file

Binary file not shown.

View File

@ -245,6 +245,26 @@ struct AI_SavedBattleMon
u16 species;
};
struct AiPartyMon
{
u16 species;
u16 item;
u16 heldEffect;
u16 ability;
u16 gender;
u16 level;
u16 moves[MAX_MON_MOVES];
bool8 isFainted;
u8 switchInCount; // Counts how many times this Pokemon has been sent out or switched into in a battle.
bool8 wasSentInBattle;
};
struct AIPartyData // Opposing battlers - party mons.
{
struct AiPartyMon mons[2][PARTY_SIZE]; // 2 parties(player, opponent). Used to save information on opposing party.
u8 count[2];
};
struct AiLogicData
{
u16 abilities[MAX_BATTLERS_COUNT];
@ -313,6 +333,7 @@ struct BattleResources
struct StatsArray* beforeLvlUp;
struct AI_ThinkingStruct *ai;
struct AiLogicData *aiData;
struct AIPartyData *aiParty;
struct BattleHistory *battleHistory;
u8 bufferA[MAX_BATTLERS_COUNT][0x200];
u8 bufferB[MAX_BATTLERS_COUNT][0x200];
@ -320,6 +341,7 @@ struct BattleResources
#define AI_THINKING_STRUCT ((struct AI_ThinkingStruct *)(gBattleResources->ai))
#define AI_DATA ((struct AiLogicData *)(gBattleResources->aiData))
#define AI_PARTY ((struct AIPartyData *)(gBattleResources->aiParty))
#define BATTLE_HISTORY ((struct BattleHistory *)(gBattleResources->battleHistory))
struct BattleResults
@ -670,7 +692,7 @@ struct BattleStruct
#define SET_STATCHANGER(statId, stage, goesDown)(gBattleScripting.statChanger = (statId) + ((stage) << 3) + (goesDown << 7))
#define SET_STATCHANGER2(dst, statId, stage, goesDown)(dst = (statId) + ((stage) << 3) + (goesDown << 7))
// NOTE: The members of this struct have hard-coded offsets
// NOTE: The members of this struct have hard-coded offsets
// in include/constants/battle_script_commands.h
struct BattleScripting
{

View File

@ -24,6 +24,8 @@ void BattleAI_SetupItems(void);
void BattleAI_SetupFlags(void);
void BattleAI_SetupAIData(u8 defaultScoreMoves);
u8 BattleAI_ChooseMoveOrAction(void);
void Ai_InitPartyStruct(void);
void Ai_UpdateSwitchInData(u32 battler);
void GetAiLogicData(void);
extern u8 sBattler_AI;

View File

@ -130,10 +130,10 @@ static u32 GetWildAiFlags(void)
{
u8 avgLevel = GetMonData(&gEnemyParty[0], MON_DATA_LEVEL);
u32 flags;
if (IsDoubleBattle())
avgLevel = (GetMonData(&gEnemyParty[0], MON_DATA_LEVEL) + GetMonData(&gEnemyParty[1], MON_DATA_LEVEL)) / 2;
flags |= AI_FLAG_CHECK_BAD_MOVE;
if (avgLevel >= 20)
flags |= AI_FLAG_CHECK_VIABILITY;
@ -141,10 +141,10 @@ static u32 GetWildAiFlags(void)
flags |= AI_FLAG_PREFER_STRONGEST_MOVE;
if (avgLevel >= 80)
flags |= AI_FLAG_HP_AWARE;
if (B_VAR_WILD_AI_FLAGS != 0 && VarGet(B_VAR_WILD_AI_FLAGS) != 0)
flags |= VarGet(B_VAR_WILD_AI_FLAGS);
return flags;
}
@ -166,7 +166,7 @@ void BattleAI_SetupFlags(void)
AI_THINKING_STRUCT->aiFlags = gTrainers[gTrainerBattleOpponent_A].aiFlags | gTrainers[gTrainerBattleOpponent_B].aiFlags;
else
AI_THINKING_STRUCT->aiFlags = gTrainers[gTrainerBattleOpponent_A].aiFlags;
// check smart wild AI
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_TRAINER)) && IsWildMonSmart())
AI_THINKING_STRUCT->aiFlags |= GetWildAiFlags();
@ -220,11 +220,11 @@ u8 BattleAI_ChooseMoveOrAction(void)
ret = ChooseMoveOrAction_Singles();
else
ret = ChooseMoveOrAction_Doubles();
// Clear protect structures, some flags may be set during AI calcs
// e.g. pranksterElevated from GetMovePriority
memset(&gProtectStructs, 0, MAX_BATTLERS_COUNT * sizeof(struct ProtectStruct));
gCurrentMove = savedCurrentMove;
return ret;
}
@ -237,6 +237,66 @@ u8 ComputeBattleAiScores(u8 battler)
return BattleAI_ChooseMoveOrAction();
}
static void CopyBattlerDataToAIParty(u32 bPosition, u32 side)
{
u32 battler = GetBattlerAtPosition(bPosition);
struct AiPartyMon *aiMon = &AI_PARTY->mons[side][gBattlerPartyIndexes[battler]];
struct BattlePokemon *bMon = &gBattleMons[battler];
aiMon->species = bMon->species;
aiMon->level = bMon->level;
aiMon->gender = GetGenderFromSpeciesAndPersonality(bMon->species, bMon->personality);
aiMon->isFainted = FALSE;
aiMon->wasSentInBattle = TRUE;
aiMon->switchInCount++;
}
void Ai_InitPartyStruct(void)
{
AI_PARTY->count[B_SIDE_PLAYER] = gPlayerPartyCount;
AI_PARTY->count[B_SIDE_OPPONENT] = gEnemyPartyCount;
// Save first 2 or 4(in doubles) mons
CopyBattlerDataToAIParty(B_POSITION_PLAYER_LEFT, B_SIDE_PLAYER);
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
CopyBattlerDataToAIParty(B_POSITION_PLAYER_RIGHT, B_SIDE_PLAYER);
// If player's partner is AI, save opponent mons
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
{
CopyBattlerDataToAIParty(B_POSITION_OPPONENT_LEFT, B_SIDE_OPPONENT);
CopyBattlerDataToAIParty(B_POSITION_OPPONENT_RIGHT, B_SIDE_OPPONENT);
}
}
void Ai_UpdateSwitchInData(u32 battler)
{
u32 i;
u32 side = GetBattlerSide(battler);
struct AiPartyMon *aiMon = &AI_PARTY->mons[side][gBattlerPartyIndexes[battler]];
// See if the switched-in mon has been already in battle
if (aiMon->wasSentInBattle)
{
if (aiMon->ability)
BATTLE_HISTORY->abilities[battler] = aiMon->ability;
if (aiMon->heldEffect)
BATTLE_HISTORY->itemEffects[battler] = aiMon->heldEffect;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (aiMon->moves[i])
BATTLE_HISTORY->usedMoves[battler][i] = aiMon->moves[i];
}
}
else // If not, copy the newly switched-in mon in battle and clear battle history.
{
ClearBattlerMoveHistory(battler);
ClearBattlerAbilityHistory(battler);
ClearBattlerItemEffectHistory(battler);
CopyBattlerDataToAIParty(GetBattlerPosition(battler), side);
}
}
static void SetBattlerAiData(u8 battlerId)
{
AI_DATA->abilities[battlerId] = AI_GetAbility(battlerId);
@ -253,13 +313,13 @@ void GetAiLogicData(void)
u32 battlerAtk, battlerDef, i, move;
u8 effectiveness;
s32 dmg;
memset(AI_DATA, 0, sizeof(struct AiLogicData));
if (!(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER))
&& !IsWildMonSmart())
return;
// get/assume all battler data
for (i = 0; i < gBattlersCount; i++)
{
@ -267,7 +327,7 @@ void GetAiLogicData(void)
SetBattlerAiData(i);
}
}
// simulate AI damage
for (battlerAtk = 0; battlerAtk < gBattlersCount; battlerAtk++)
{
@ -275,26 +335,26 @@ void GetAiLogicData(void)
|| !IsBattlerAIControlled(battlerAtk)) {
continue;
}
for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if (battlerAtk == battlerDef)
continue;
RecordKnownMove(battlerDef, gLastMoves[battlerDef]);
for (i = 0; i < MAX_MON_MOVES; i++)
{
dmg = 0;
effectiveness = AI_EFFECTIVENESS_x0;
move = gBattleMons[battlerAtk].moves[i];
if (move != 0
&& move != 0xFFFF
//&& gBattleMoves[move].power != 0 /* we want to get effectiveness of status moves */
&& !(AI_DATA->moveLimitations[battlerAtk] & gBitTable[i])) {
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE);
}
AI_DATA->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
AI_DATA->effectiveness[battlerAtk][battlerDef][i] = effectiveness;
}
@ -334,7 +394,7 @@ static u8 ChooseMoveOrAction_Singles(void)
return AI_CHOICE_WATCH;
gActiveBattler = sBattler_AI;
// If can switch.
if (CountUsablePartyMons(sBattler_AI) > 0
&& !IsAbilityPreventingEscape(sBattler_AI)
@ -375,7 +435,7 @@ static u8 ChooseMoveOrAction_Singles(void)
}
}
}
numOfBestMoves = 1;
currentMoveArray[0] = AI_THINKING_STRUCT->score[0];
consideredMoveArray[0] = 0;
@ -427,7 +487,7 @@ static u8 ChooseMoveOrAction_Doubles(void)
BattleAI_SetupAIData(gBattleStruct->palaceFlags >> 4);
else
BattleAI_SetupAIData(0xF);
gBattlerTarget = i;
if ((i & BIT_SIDE) != (sBattler_AI & BIT_SIDE))
RecordLastUsedMoveByTarget();
@ -467,7 +527,7 @@ static u8 ChooseMoveOrAction_Doubles(void)
{
if (!CanTargetBattler(sBattler_AI, i, gBattleMons[sBattler_AI].moves[j]))
continue;
if (mostViableMovesScores[0] == AI_THINKING_STRUCT->score[j])
{
mostViableMovesScores[mostViableMovesNo] = AI_THINKING_STRUCT->score[j];
@ -586,7 +646,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
u32 i;
u16 predictedMove = AI_DATA->predictedMoves[battlerDef];
SetTypeBeforeUsingMove(move, battlerAtk);
GET_MOVE_TYPE(move, moveType);
@ -594,7 +654,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
return score;
GET_MOVE_TYPE(move, moveType);
// check non-user target
if (!(moveTarget & MOVE_TARGET_USER))
{
@ -604,7 +664,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
RETURN_SCORE_MINUS(20);
}
// check ground immunities
if (moveType == TYPE_GROUND
&& !IsBattlerGrounded(battlerDef)
@ -616,11 +676,11 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
RETURN_SCORE_MINUS(20);
}
// check off screen
if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move
// check if negates type
switch (effectiveness)
{
@ -632,7 +692,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
RETURN_SCORE_MINUS(10);
break;
}
// target ability checks
if (!DoesBattlerIgnoreAbilityChecks(AI_DATA->abilities[battlerAtk], move))
{
@ -758,7 +818,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
RETURN_SCORE_MINUS(10);
break;
} // def ability checks
// target partner ability checks & not attacking partner
if (isDoubleBattle)
{
@ -796,35 +856,35 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
} // def partner ability checks
} // ignore def ability check
// gen7+ dark type mons immune to priority->elevated moves from prankster
#if B_PRANKSTER_DARK_TYPES >= GEN_7
if (AI_DATA->abilities[battlerAtk] == ABILITY_PRANKSTER && IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK) && IS_MOVE_STATUS(move)
&& !(moveTarget & (MOVE_TARGET_OPPONENTS_FIELD | MOVE_TARGET_USER)))
RETURN_SCORE_MINUS(10);
#endif
// terrain & effect checks
if (AI_IsTerrainAffected(battlerDef, STATUS_FIELD_ELECTRIC_TERRAIN))
{
if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN)
RETURN_SCORE_MINUS(20);
}
if (AI_IsTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN))
{
if (IsNonVolatileStatusMoveEffect(moveEffect) || IsConfusionMoveEffect(moveEffect))
RETURN_SCORE_MINUS(20);
}
if (AI_IsTerrainAffected(battlerAtk, STATUS_FIELD_PSYCHIC_TERRAIN) && atkPriority > 0)
{
RETURN_SCORE_MINUS(20);
}
} // end check MOVE_TARGET_USER
// the following checks apply to any target (including user)
// throat chop check
if (gDisableStructs[battlerAtk].throatChopTimer && TestMoveFlags(move, FLAG_SOUND))
return 0; // Can't even select move at all
@ -860,7 +920,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
}
}
// check move effects
switch (moveEffect)
{
@ -874,7 +934,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_EXPLOSION:
if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_WILL_SUICIDE))
score -= 2;
if (effectiveness == AI_EFFECTIVENESS_x0)
{
score -= 10;
@ -920,7 +980,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (!BattlerStatCanRise(battlerAtk, AI_DATA->abilities[battlerAtk], STAT_SPATK) || !HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL))
score -= 10;
break;
case EFFECT_SPECIAL_DEFENSE_UP:
case EFFECT_SPECIAL_DEFENSE_UP:
case EFFECT_SPECIAL_DEFENSE_UP_2:
if (!BattlerStatCanRise(battlerAtk, AI_DATA->abilities[battlerAtk], STAT_SPDEF))
score -= 10;
@ -1230,7 +1290,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_LOW_KICK:
// AI_CBM_HighRiskForDamage
if (AI_DATA->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < AI_EFFECTIVENESS_x2)
score -= 10;
score -= 10;
break;
case EFFECT_COUNTER:
case EFFECT_MIRROR_COAT:
@ -1240,7 +1300,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
|| DoesSubstituteBlockMove(battlerAtk, BATTLE_PARTNER(battlerDef), predictedMove))
score -= 10;
break;
case EFFECT_ROAR:
if (CountUsablePartyMons(battlerDef) == 0)
score -= 10;
@ -1392,7 +1452,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_SPIKES:
if (gSideTimers[GetBattlerSide(battlerDef)].spikesAmount >= 3)
score -= 10;
else if (PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)
else if (PartnerMoveIsSameNoTarget(BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)
&& gSideTimers[GetBattlerSide(battlerDef)].spikesAmount == 2)
score -= 10; // only one mon needs to set up the last layer of Spikes
break;
@ -1570,7 +1630,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score -= 10;
break;
}
if (B_MENTAL_HERB >= GEN_5 && AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_MENTAL_HERB)
score -= 6;
break;
@ -1802,7 +1862,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gBattleMons[battlerAtk].hp > (gBattleMons[battlerAtk].hp + gBattleMons[battlerDef].hp) / 2)
score -= 10;
break;
case EFFECT_CONVERSION_2:
//TODO
break;
@ -1862,7 +1922,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
break;
} // move check
if (decreased)
break;
if (IsBattlerIncapacitated(battlerDef, AI_DATA->abilities[battlerDef]))
@ -1904,7 +1964,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
IncreaseAllyProtectionViability(&viability, 0xFF);
}*/
}
break;
break;
case EFFECT_MIRACLE_EYE:
if (gStatuses3[battlerDef] & STATUS3_MIRACLE_EYED)
score -= 10;
@ -1952,7 +2012,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
|| ((AI_DATA->abilities[battlerDef] == ABILITY_CONTRARY) && !IsTargetingPartner(battlerAtk, battlerDef))) // don't want to raise target stats unless its your partner
score -= 10;
break;
case EFFECT_PSYCH_UP: // haze stats check
{
for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++)
@ -2116,7 +2176,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
u32 atkNegativeStages = CountNegativeStatStages(battlerAtk);
u32 defPositiveStages = CountPositiveStatStages(battlerDef);
u32 defNegativeStages = CountNegativeStatStages(battlerDef);
if (atkPositiveStages >= defPositiveStages && atkNegativeStages <= defNegativeStages)
score -= 10;
break;
@ -2513,21 +2573,21 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score -= 10;
break;*/
} // move effect checks
if (score < 0)
score = 0;
return score;
}
static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
{
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (gBattleMoves[move].power == 0)
return score; // can't make anything faint with no power
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION)
{
// this move can faint the target
@ -2541,10 +2601,10 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// this move isn't expected to faint the target
if (TestMoveFlags(move, FLAG_HIGH_CRIT))
score += 2; // crit makes it more likely to make them faint
if (GetMoveDamageResult(move) == MOVE_POWER_OTHER)
score--;
switch (AI_DATA->effectiveness[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex])
{
case AI_EFFECTIVENESS_x8:
@ -2561,7 +2621,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
}
}
//AI_TryToFaint_CheckIfDanger
if (!WillAIStrikeFirst() && CanTargetFaintAi(battlerDef, battlerAtk))
{ // AI_TryToFaint_Danger
@ -2570,7 +2630,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
else
score++;
}
return score;
}
@ -2626,8 +2686,8 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
}
} // check partner move effect
// consider our move effect relative to partner state
switch (effect)
{
@ -2648,8 +2708,8 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
break;
} // our effect relative to partner
// consider global move effects
switch (effect)
{
@ -2679,8 +2739,8 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
break;
} // global move effect check
// check specific target
if (IsTargetingPartner(battlerAtk, battlerDef))
{
@ -2787,11 +2847,11 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
RETURN_SCORE_PLUS(1);
}
break;
break;
}
} // ability checks
} // move power check
// attacker move effects specifically targeting partner
if (!partnerProtecting)
{
@ -2904,12 +2964,12 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
} // attacker move effects
} // check partner protecting
score -= 30; // otherwise, don't target partner
}
else // checking opponent
{
// these checks mostly handled in AI_CheckBadMove and AI_CheckViability
// these checks mostly handled in AI_CheckBadMove and AI_CheckViability
switch (effect)
{
case EFFECT_SKILL_SWAP:
@ -2934,10 +2994,10 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score -= 3;
break;
}
// lightning rod, flash fire against enemy handled in AI_CheckBadMove
}
return score;
}
@ -2974,11 +3034,11 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
u16 predictedMove = AI_DATA->predictedMoves[battlerDef];
bool32 isDoubleBattle = IsValidDoubleBattle(battlerAtk);
u32 i;
// Targeting partner, check benefits of doing that instead
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
// check always hits
if (!IS_MOVE_STATUS(move) && gBattleMoves[move].accuracy == 0)
{
@ -2987,11 +3047,11 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (AI_RandLessThan(100) && (gBattleMons[battlerDef].statStages[STAT_EVASION] >= 8 || gBattleMons[battlerAtk].statStages[STAT_ACC] <= 4))
score++;
}
// check high crit
if (TestMoveFlags(move, FLAG_HIGH_CRIT) && effectiveness >= AI_EFFECTIVENESS_x2 && AI_RandLessThan(128))
score++;
// check already dead
if (!IsBattlerIncapacitated(battlerDef, AI_DATA->abilities[battlerDef])
&& CanTargetFaintAi(battlerAtk, battlerDef)
@ -3002,7 +3062,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
else
score--;
}
// check damage
if (gBattleMoves[move].power != 0 && GetMoveDamageResult(move) == MOVE_POWER_WEAK)
score--;
@ -3010,11 +3070,11 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// check status move preference
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_PREFER_STATUS_MOVES && IS_MOVE_STATUS(move) && effectiveness != AI_EFFECTIVENESS_x0)
score++;
// check thawing moves
if ((gBattleMons[battlerAtk].status1 & STATUS1_FREEZE) && TestMoveFlags(move, FLAG_THAW_USER))
score += (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) ? 20 : 10;
// check burn
if (gBattleMons[battlerAtk].status1 & STATUS1_BURN)
{
@ -3033,7 +3093,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
}
}
// attacker ability checks
switch (AI_DATA->abilities[battlerAtk])
{
@ -3049,8 +3109,8 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 8; // prioritize killing target for stat boost
}
break;
} // ability checks
} // ability checks
// move effect checks
switch (moveEffect)
{
@ -3095,7 +3155,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
}
}
if (!AI_RandLessThan(100))
{
score--;
@ -3141,7 +3201,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
break;
}
}
if (!AI_RandLessThan(100))
{
score--;
@ -3164,7 +3224,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score -= 2;
else if (AI_DATA->hpPercents[battlerAtk] <= 70)
score -= 2;
else
else
score++;
break;
case EFFECT_EVASION_UP:
@ -3294,7 +3354,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_ATTACK_SPATK_UP: // work up
if (AI_DATA->hpPercents[battlerAtk] <= 40 || AI_DATA->abilities[battlerAtk] == ABILITY_CONTRARY)
break;
if (HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL))
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score);
else if (HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL))
@ -3350,7 +3410,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
default:
break;
}
if (ShouldRecover(battlerAtk, battlerDef, move, healPercent))
score += 2;
}
@ -3578,7 +3638,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (newHp > healthBenchmark && ShouldAbsorb(battlerAtk, battlerDef, move, AI_DATA->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]))
score += 2;
}
break;
break;
case EFFECT_SLEEP_TALK:
case EFFECT_SNORE:
if (!IsWakeupTurn(battlerAtk) && gBattleMons[battlerAtk].status1 & STATUS1_SLEEP)
@ -3613,13 +3673,13 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_THIEF:
{
bool32 canSteal = FALSE;
#if defined B_TRAINERS_KNOCK_OFF_ITEMS && B_TRAINERS_KNOCK_OFF_ITEMS == TRUE
canSteal = TRUE;
#endif
if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER || GetBattlerSide(battlerAtk) == B_SIDE_PLAYER)
canSteal = TRUE;
if (canSteal && AI_DATA->items[battlerAtk] == ITEM_NONE
&& AI_DATA->items[battlerDef] != ITEM_NONE
&& CanBattlerGetOrLoseItem(battlerDef, AI_DATA->items[battlerDef])
@ -3763,8 +3823,8 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (AI_DATA->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE || CountUsablePartyMons(battlerDef) == 0)
break;
if (gDisableStructs[battlerAtk].isFirstTurn)
score += 2;
//TODO - track entire opponent party data to determine hazard effectiveness
score += 2;
//TODO - track entire opponent party data to determine hazard effectiveness
break;
case EFFECT_FORESIGHT:
if (AI_DATA->abilities[battlerAtk] == ABILITY_SCRAPPY)
@ -3793,7 +3853,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (HasMoveEffect(battlerDef, EFFECT_MORNING_SUN)
|| HasMoveEffect(battlerDef, EFFECT_SYNTHESIS)
|| HasMoveEffect(battlerDef, EFFECT_MOONLIGHT))
score += 2;
score += 2;
}
break;
case EFFECT_HAIL:
@ -3802,7 +3862,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if ((HasMoveEffect(battlerAtk, EFFECT_AURORA_VEIL) || HasMoveEffect(BATTLE_PARTNER(battlerAtk), EFFECT_AURORA_VEIL))
&& ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL))
score += 3;
score++;
if (AI_DATA->holdEffects[battlerAtk] == HOLD_EFFECT_ICY_ROCK)
score++;
@ -3861,7 +3921,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_SPECTRAL_THIEF:
// Want to copy positive stat changes
for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++)
{
{
if (gBattleMons[battlerDef].statStages[i] > gBattleMons[battlerAtk].statStages[i])
{
switch (i)
@ -3920,7 +3980,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (HasMoveEffect(battlerAtk, EFFECT_SWALLOW)
|| HasMoveEffect(battlerAtk, EFFECT_SPIT_UP))
score += 2;
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_DEF, &score);
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPDEF, &score);
break;
@ -3937,20 +3997,20 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
|| HasMoveEffect(battlerAtk, EFFECT_PSYCH_UP)
|| HasMoveEffect(battlerAtk, EFFECT_SPECTRAL_THIEF))
score++;
if (AI_DATA->abilities[battlerDef] == ABILITY_CONTRARY)
score += 2;
IncreaseConfusionScore(battlerAtk, battlerDef, move, &score);
break;
case EFFECT_FLATTER:
if (HasMoveEffect(battlerAtk, EFFECT_PSYCH_UP)
|| HasMoveEffect(battlerAtk, EFFECT_SPECTRAL_THIEF))
score += 2;
if (AI_DATA->abilities[battlerDef] == ABILITY_CONTRARY)
score += 2;
IncreaseConfusionScore(battlerAtk, battlerDef, move, &score);
break;
case EFFECT_FURY_CUTTER:
@ -3991,7 +4051,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score += 3;
break;
}
switch (move)
{
case MOVE_DEFOG:
@ -4007,7 +4067,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
&& AI_WhoStrikesFirst(battlerAtk, BATTLE_PARTNER(battlerAtk), move) == AI_IS_SLOWER) // Partner going first
break; // Don't use Defog if partner is going to set up hazards
}
// check defog lowering evasion
if (ShouldLowerEvasion(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef]))
{
@ -4179,10 +4239,10 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
u16 item = GetUsedHeldItem(battlerAtk);
u16 toHeal = (ItemId_GetHoldEffectParam(item) == 10) ? 10 : gBattleMons[battlerAtk].maxHP / ItemId_GetHoldEffectParam(item);
if (IsStatBoostingBerry(item) && AI_DATA->hpPercents[battlerAtk] > 60)
score++;
else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0)
else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0)
&& ((GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0))
|| !CanTargetFaintAiWithMod(battlerDef, battlerAtk, toHeal, 0)))
score++; // Recycle healing berry if we can't otherwise faint the target and the target wont kill us after we activate the berry
@ -4229,7 +4289,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
if (AI_DATA->abilities[battlerDef] != AI_DATA->abilities[battlerAtk] && !(gStatuses3[battlerDef] & STATUS3_GASTRO_ACID))
score += 2;
}
}
break;
case EFFECT_IMPRISON:
if (predictedMove != MOVE_NONE && HasMove(battlerAtk, predictedMove))
@ -4300,7 +4360,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
case EFFECT_SHELL_SMASH:
if (AI_DATA->holdEffects[battlerAtk] == HOLD_EFFECT_RESTORE_STATS)
score += 1;
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPEED, &score);
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_SPATK, &score);
IncreaseStatUpScore(battlerAtk, battlerDef, STAT_ATK, &score);
@ -4407,7 +4467,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gStatuses3[battlerAtk] & STATUS3_YAWN && IsBattlerGrounded(battlerAtk))
score += 10;
//fallthrough
case EFFECT_GRASSY_TERRAIN:
case EFFECT_GRASSY_TERRAIN:
case EFFECT_PSYCHIC_TERRAIN:
score += 2;
if (AI_DATA->holdEffects[battlerAtk] == HOLD_EFFECT_TERRAIN_EXTENDER)
@ -4680,7 +4740,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
//case EFFECT_SKY_DROP
//break;
} // move effect checks
return score;
}
@ -4690,15 +4750,15 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (IsTargetingPartner(battlerAtk, battlerDef)
|| gBattleResults.battleTurnCounter != 0)
return score;
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING
&& AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_SLOWER
&& CanTargetFaintAi(battlerDef, battlerAtk)
&& GetMovePriority(battlerAtk, move) == 0)
{
RETURN_SCORE_MINUS(20); // No point in setting up if you will faint. Should just switch if possible..
}
// check effects to prioritize first turn
switch (gBattleMoves[move].effect)
{
@ -4787,7 +4847,7 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
default:
break;
}
return score;
}
@ -4796,10 +4856,10 @@ static s16 AI_Risky(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (TestMoveFlags(move, FLAG_HIGH_CRIT))
score += 2;
switch (gBattleMoves[move].effect)
{
case EFFECT_SLEEP:
@ -4826,7 +4886,7 @@ static s16 AI_Risky(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
default:
break;
}
return score;
}
@ -4835,10 +4895,10 @@ static s16 AI_PreferStrongestMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 sc
{
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (GetMoveDamageResult(move) == MOVE_POWER_BEST)
score += 2;
return score;
}
@ -4846,14 +4906,14 @@ static s16 AI_PreferStrongestMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 sc
static s16 AI_PreferBatonPass(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
u32 i;
if (IsTargetingPartner(battlerAtk, battlerDef)
|| CountUsablePartyMons(battlerAtk) == 0
|| GetMoveDamageResult(move) != MOVE_POWER_OTHER
|| !HasMoveEffect(battlerAtk, EFFECT_BATON_PASS)
|| IsBattlerTrapped(battlerAtk, TRUE))
return score;
if (IsStatRaisingEffect(gBattleMoves[move].effect))
{
if (gBattleResults.battleTurnCounter == 0)
@ -4861,9 +4921,9 @@ static s16 AI_PreferBatonPass(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
else if (AI_DATA->hpPercents[battlerAtk] < 60)
score -= 10;
else
score++;
score++;
}
// other specific checks
switch (gBattleMoves[move].effect)
{
@ -4889,12 +4949,12 @@ static s16 AI_PreferBatonPass(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
if (gStatuses3[battlerAtk] & (STATUS3_ROOTED | STATUS3_AQUA_RING))
score += 2;
if (gStatuses3[battlerAtk] & STATUS3_LEECHSEED)
score -= 3;
score -= 3;
break;
default:
break;
}
return score;
}
@ -4914,11 +4974,11 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
if (gStatuses3[battlerDef] & STATUS3_HEAL_BLOCK)
return 0;
if (CanTargetFaintAi(FOE(battlerAtk), BATTLE_PARTNER(battlerAtk))
|| (CanTargetFaintAi(BATTLE_PARTNER(FOE(battlerAtk)), BATTLE_PARTNER(battlerAtk))))
score--;
if (AI_DATA->hpPercents[battlerDef] <= 50)
score++;
}
@ -4957,7 +5017,7 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// med hp
if (IsStatRaisingEffect(effect) || IsStatLoweringEffect(effect))
score -= 2;
switch (effect)
{
case EFFECT_EXPLOSION:
@ -4980,7 +5040,7 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
// low hp
if (IsStatRaisingEffect(effect) || IsStatLoweringEffect(effect))
score -= 2;
// check other discouraged low hp effects
switch (effect)
{
@ -5013,7 +5073,7 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
}
}
}
// consider target HP
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0))
{
@ -5085,7 +5145,7 @@ static s16 AI_HPAware(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
score -= 2; // don't use status moves if target is at low health
}
}
return score;
}
@ -5104,7 +5164,7 @@ static s16 AI_Roaming(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
if (IsBattlerTrapped(battlerAtk, FALSE))
return score;
AI_Flee();
return score;
}

View File

@ -499,6 +499,7 @@ void RecordKnownMove(u8 battlerId, u32 move)
if (BATTLE_HISTORY->usedMoves[battlerId][i] == MOVE_NONE)
{
BATTLE_HISTORY->usedMoves[battlerId][i] = move;
AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].moves[i] = move;
break;
}
}
@ -507,6 +508,7 @@ void RecordKnownMove(u8 battlerId, u32 move)
void RecordAbilityBattle(u8 battlerId, u16 abilityId)
{
BATTLE_HISTORY->abilities[battlerId] = abilityId;
AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].ability = abilityId;
}
void ClearBattlerAbilityHistory(u8 battlerId)
@ -517,6 +519,7 @@ void ClearBattlerAbilityHistory(u8 battlerId)
void RecordItemEffectBattle(u8 battlerId, u8 itemEffect)
{
BATTLE_HISTORY->itemEffects[battlerId] = itemEffect;
AI_PARTY->mons[GetBattlerSide(battlerId)][gBattlerPartyIndexes[battlerId]].heldEffect = itemEffect;
}
void ClearBattlerItemEffectHistory(u8 battlerId)
@ -787,7 +790,7 @@ s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 *typeEffectiveness,
dmg *= 2;
else if (move == MOVE_SURGING_STRIKES || (move == MOVE_WATER_SHURIKEN && gBattleMons[battlerAtk].species == SPECIES_GRENINJA_ASH))
dmg *= 3;
if (dmg == 0)
dmg = 1;
}
@ -798,7 +801,7 @@ s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, u8 *typeEffectiveness,
RestoreBattlerData(battlerAtk);
RestoreBattlerData(battlerDef);
// convert multiper to AI_EFFECTIVENESS_xX
*typeEffectiveness = AI_GetEffectiveness(effectivenessMultiplier);
@ -1153,11 +1156,11 @@ bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability)
s32 AI_GetAbility(u32 battlerId)
{
u32 knownAbility = GetBattlerAbility(battlerId);
// The AI knows its own ability.
if (IsBattlerAIControlled(battlerId))
return knownAbility;
// Check neutralizing gas, gastro acid
if (knownAbility == ABILITY_NONE)
return knownAbility;
@ -1177,10 +1180,10 @@ s32 AI_GetAbility(u32 battlerId)
{
abilityGuess = gBaseStats[gBattleMons[battlerId].species].abilities[Random() % NUM_ABILITY_SLOTS];
}
return abilityGuess;
}
return ABILITY_NONE; // Unknown.
}
@ -2668,7 +2671,7 @@ static bool32 AI_CanPoisonType(u8 battlerAttacker, u8 battlerTarget)
static bool32 AI_CanBePoisoned(u8 battlerAtk, u8 battlerDef)
{
u16 ability = AI_DATA->abilities[battlerDef];
if (!(AI_CanPoisonType(battlerAtk, battlerDef))
|| gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_SAFEGUARD
|| gBattleMons[battlerDef].status1 & STATUS1_ANY
@ -3019,7 +3022,7 @@ bool32 IsValidDoubleBattle(u8 battlerAtk)
u16 GetAllyChosenMove(u8 battlerId)
{
u8 partnerBattler = BATTLE_PARTNER(battlerId);
if (!IsBattlerAlive(partnerBattler) || !IsBattlerAIControlled(partnerBattler))
return MOVE_NONE;
else if (partnerBattler > battlerId) // Battler with the lower id chooses the move first.
@ -3425,7 +3428,7 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score)
if (AI_DATA->hpPercents[battlerAtk] < 80 && AI_RandLessThan(128))
return;
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return; // Damaging moves would get a score boost from AI_TryToFaint or PreferStrongestMove so we don't consider them here
@ -3544,7 +3547,7 @@ void IncreaseParalyzeScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
{
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanParalyze(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove))
{
u8 atkSpeed = GetBattlerTotalSpeedStat(battlerAtk);
@ -3565,7 +3568,7 @@ void IncreaseSleepScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
{
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanPutToSleep(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], move, AI_DATA->partnerMove))
*score += 2;
else
@ -3583,7 +3586,7 @@ void IncreaseConfusionScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score)
{
if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0))
return;
if (AI_CanConfuse(battlerAtk, battlerDef, AI_DATA->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, AI_DATA->partnerMove)
&& AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CURE_CONFUSION
&& AI_DATA->holdEffects[battlerDef] != HOLD_EFFECT_CURE_STATUS)
@ -3614,28 +3617,28 @@ bool32 ShouldUseZMove(u8 battlerAtk, u8 battlerDef, u16 chosenMove)
return FALSE; //don't use z move on partner
if (gBattleStruct->zmove.used[battlerAtk])
return FALSE; //cant use z move twice
if (IsViableZMove(battlerAtk, chosenMove))
{
u8 effectiveness;
#ifdef POKEMON_EXPANSION
if (gBattleMons[battlerDef].ability == ABILITY_DISGUISE && gBattleMons[battlerDef].species == SPECIES_MIMIKYU)
return FALSE; // Don't waste a Z-Move busting disguise
if (gBattleMons[battlerDef].ability == ABILITY_ICE_FACE && gBattleMons[battlerDef].species == SPECIES_EISCUE && IS_MOVE_PHYSICAL(chosenMove))
return FALSE; // Don't waste a Z-Move busting Ice Face
#endif
if (IS_MOVE_STATUS(chosenMove) && !IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove))
return FALSE;
else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove))
return FALSE;
if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamage(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE) >= gBattleMons[battlerDef].hp)
return FALSE; // don't waste damaging z move if can otherwise faint target
return TRUE;
}
return FALSE;
}

View File

@ -43,8 +43,8 @@ struct BattleDebugModifyArrows
u16 minValue;
u16 maxValue;
int currValue;
u8 currentDigit;
u8 maxDigits;
u8 currentDigit:4;
u8 maxDigits:4;
u8 charDigits[MAX_MODIFY_DIGITS];
void *modifiedValPtr;
u8 typeOfVal;
@ -52,7 +52,9 @@ struct BattleDebugModifyArrows
struct BattleDebugMenu
{
u8 battlerId;
u8 battlerId:2;
u8 aiBattlerId:2;
u8 battlerWindowId;
u8 mainListWindowId;
@ -72,11 +74,16 @@ struct BattleDebugMenu
const struct BitfieldInfo *bitfield;
bool8 battlerWasChanged[MAX_BATTLERS_COUNT];
u8 aiBattlerId;
u8 aiViewState;
u8 aiIconSpriteIds[MAX_BATTLERS_COUNT];
u8 aiMonSpriteId;
u8 aiMovesWindowId;
union
{
u8 aiIconSpriteIds[MAX_BATTLERS_COUNT];
u8 aiPartyIcons[PARTY_SIZE];
} spriteIds;
};
struct __attribute__((__packed__)) BitfieldInfo
@ -102,6 +109,7 @@ enum
LIST_ITEM_AI,
LIST_ITEM_AI_MOVES_PTS,
LIST_ITEM_AI_INFO,
LIST_ITEM_AI_PARTY,
LIST_ITEM_VARIOUS,
LIST_ITEM_COUNT
};
@ -234,6 +242,7 @@ static const u8 sText_Unknown[] = _("Unknown");
static const u8 sText_InLove[] = _("In Love");
static const u8 sText_AIMovePts[] = _("AI Pts/Dmg");
static const u8 sText_AiKnowledge[] = _("AI Info");
static const u8 sText_AiParty[] = _("AI Party");
static const u8 sText_EffectOverride[] = _("Effect Override");
static const u8 sText_EmptyString[] = _("");
@ -340,6 +349,7 @@ static const struct ListMenuItem sMainListItems[] =
{sText_AI, LIST_ITEM_AI},
{sText_AIMovePts, LIST_ITEM_AI_MOVES_PTS},
{sText_AiKnowledge, LIST_ITEM_AI_INFO},
{sText_AiParty, LIST_ITEM_AI_PARTY},
{sText_Various, LIST_ITEM_VARIOUS},
};
@ -610,6 +620,7 @@ static void UpdateMonData(struct BattleDebugMenu *data);
static u8 *GetSideStatusValue(struct BattleDebugMenu *data, bool32 changeStatus, bool32 statusTrue);
static bool32 TryMoveDigit(struct BattleDebugModifyArrows *modArrows, bool32 moveUp);
static void SwitchToDebugView(u8 taskId);
static void SwitchToDebugViewFromAiParty(u8 taskId);
// code
static struct BattleDebugMenu *GetStructPtr(u8 taskId)
@ -725,9 +736,9 @@ static void PutMovesPointsText(struct BattleDebugMenu *data)
AddTextPrinterParameterized(data->aiMovesWindowId, 1, text, 0, i * 15, 0, NULL);
for (count = 0, j = 0; j < MAX_BATTLERS_COUNT; j++)
{
if (data->aiIconSpriteIds[j] == 0xFF)
if (data->spriteIds.aiIconSpriteIds[j] == 0xFF)
continue;
battlerDef = gSprites[data->aiIconSpriteIds[j]].data[0];
battlerDef = gSprites[data->spriteIds.aiIconSpriteIds[j]].data[0];
ConvertIntToDecimalStringN(text,
gBattleStruct->aiFinalScore[data->aiBattlerId][battlerDef][i],
STR_CONV_MODE_RIGHT_ALIGN, 3);
@ -772,20 +783,20 @@ static void Task_ShowAiPoints(u8 taskId)
if (i != data->aiBattlerId && IsBattlerAlive(i))
{
#ifndef POKEMON_EXPANSION
data->aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
data->spriteIds.aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
SpriteCallbackDummy,
95 + (count * 60), 17, 0, 0, FALSE);
#else
data->aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
data->spriteIds.aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
SpriteCallbackDummy,
95 + (count * 60), 17, 0, 0);
#endif
gSprites[data->aiIconSpriteIds[i]].data[0] = i; // battler id
gSprites[data->spriteIds.aiIconSpriteIds[i]].data[0] = i; // battler id
count++;
}
else
{
data->aiIconSpriteIds[i] = 0xFF;
data->spriteIds.aiIconSpriteIds[i] = 0xFF;
}
}
#ifndef POKEMON_EXPANSION
@ -831,25 +842,26 @@ static void SwitchToAiPointsView(u8 taskId)
GetStructPtr(taskId)->aiViewState = 0;
}
static const u8 *const sAiInfoItemNames[] =
static const u8 *const sAiInfoItemNames[] =
{
sText_Ability,
sText_HeldItem,
sText_HoldEffect,
};
static void PutAiInfoText(struct BattleDebugMenu *data)
{
u32 i, j, count;
u8 *text = malloc(0x50);
FillWindowPixelBuffer(data->aiMovesWindowId, 0x11);
// item names
for (i = 0; i < ARRAY_COUNT(sAiInfoItemNames); i++)
{
AddTextPrinterParameterized(data->aiMovesWindowId, 1, sAiInfoItemNames[i], 3, i * 15, 0, NULL);
}
// items info
for (i = 0; i < gBattlersCount; i++)
{
@ -869,6 +881,31 @@ static void PutAiInfoText(struct BattleDebugMenu *data)
free(text);
}
static void PutAiPartyText(struct BattleDebugMenu *data)
{
u32 i, j, count, maxWidth;
u8 *text = malloc(0x50), *txtPtr;
struct AiPartyMon *aiMons = AI_PARTY->mons[GET_BATTLER_SIDE(data->aiBattlerId)];
FillWindowPixelBuffer(data->aiMovesWindowId, 0x11);
count = AI_PARTY->count[GET_BATTLER_SIDE(data->aiBattlerId)];
for (i = 0; i < count; i++)
{
txtPtr = StringCopyN(text, gAbilityNames[aiMons[i].ability], 7); // The screen is too small to fit the whole string, so we need to drop the last letters.
*txtPtr = EOS;
AddTextPrinterParameterized5(data->aiMovesWindowId, FONT_SMALL_NARROW, text, i * 41, 0, 0, NULL, 0, 0);
for (j = 0; j < MAX_MON_MOVES; j++)
{
txtPtr = StringCopyN(text, gMoveNames[aiMons[i].moves[j]], 8);
*txtPtr = EOS;
AddTextPrinterParameterized5(data->aiMovesWindowId, FONT_SMALL_NARROW, text, i * 41, 20 + j * 15, 0, NULL, 0, 0);
}
}
CopyWindowToVram(data->aiMovesWindowId, 3);
free(text);
}
static void Task_ShowAiKnowledge(u8 taskId)
{
u32 i, count;
@ -895,20 +932,20 @@ static void Task_ShowAiKnowledge(u8 taskId)
if (GET_BATTLER_SIDE(i) == B_SIDE_PLAYER && IsBattlerAlive(i))
{
#ifndef POKEMON_EXPANSION
data->aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
data->spriteIds.aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
SpriteCallbackDummy,
95 + (count * 80), 17, 0, 0, FALSE);
#else
data->aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
data->spriteIds.aiIconSpriteIds[i] = CreateMonIcon(gBattleMons[i].species,
SpriteCallbackDummy,
95 + (count * 80), 17, 0, 0);
#endif
gSprites[data->aiIconSpriteIds[i]].data[0] = i; // battler id
gSprites[data->spriteIds.aiIconSpriteIds[i]].data[0] = i; // battler id
count++;
}
else
{
data->aiIconSpriteIds[i] = 0xFF;
data->spriteIds.aiIconSpriteIds[i] = 0xFF;
}
}
#ifndef POKEMON_EXPANSION
@ -947,12 +984,82 @@ static void Task_ShowAiKnowledge(u8 taskId)
}
}
static void Task_ShowAiParty(u8 taskId)
{
u32 i;
struct WindowTemplate winTemplate;
struct AiPartyMon *aiMons;
struct BattleDebugMenu *data = GetStructPtr(taskId);
switch (data->aiViewState)
{
case 0:
HideBg(0);
ShowBg(1);
LoadMonIconPalettes();
data->aiBattlerId = data->battlerId;
aiMons = AI_PARTY->mons[GET_BATTLER_SIDE(data->aiBattlerId)];
for (i = 0; i < AI_PARTY->count[GET_BATTLER_SIDE(data->aiBattlerId)]; i++)
{
u16 species = SPECIES_OLD_UNOWN_B; // Question mark
if (aiMons[i].wasSentInBattle && aiMons[i].species)
species = aiMons[i].species;
data->spriteIds.aiPartyIcons[i] = CreateMonIcon(species, SpriteCallbackDummy, (i * 41) - 5 + 20, 7, 0, 0, FALSE);
}
for (; i < PARTY_SIZE; i++)
data->spriteIds.aiPartyIcons[i] = 0xFF;
data->aiViewState++;
break;
// Put text
case 1:
winTemplate = CreateWindowTemplate(1, 0, 4, 30, 14, 15, 0x200);
data->aiMovesWindowId = AddWindow(&winTemplate);
PutWindowTilemap(data->aiMovesWindowId);
PutAiPartyText(data);
data->aiViewState++;
break;
// Input
case 2:
if (gMain.newKeys & (SELECT_BUTTON | B_BUTTON))
{
SwitchToDebugViewFromAiParty(taskId);
HideBg(1);
ShowBg(0);
return;
}
break;
}
}
static void SwitchToAiInfoView(u8 taskId)
{
gTasks[taskId].func = Task_ShowAiKnowledge;
GetStructPtr(taskId)->aiViewState = 0;
}
static void SwitchToAiPartyView(u8 taskId)
{
gTasks[taskId].func = Task_ShowAiParty;
GetStructPtr(taskId)->aiViewState = 0;
}
static void SwitchToDebugViewFromAiParty(u8 taskId)
{
u32 i;
struct BattleDebugMenu *data = GetStructPtr(taskId);
FreeMonIconPalettes();
for (i = 0; i < PARTY_SIZE; i++)
{
if (data->spriteIds.aiPartyIcons[i] != 0xFF)
FreeAndDestroyMonIconSprite(&gSprites[data->spriteIds.aiPartyIcons[i]]);
}
RemoveWindow(data->aiMovesWindowId);
gTasks[taskId].func = Task_DebugMenuProcessInput;
}
static void SwitchToDebugView(u8 taskId)
{
u32 i;
@ -961,8 +1068,8 @@ static void SwitchToDebugView(u8 taskId)
FreeMonIconPalettes();
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (data->aiIconSpriteIds[i] != 0xFF)
FreeAndDestroyMonIconSprite(&gSprites[data->aiIconSpriteIds[i]]);
if (data->spriteIds.aiIconSpriteIds[i] != 0xFF)
FreeAndDestroyMonIconSprite(&gSprites[data->spriteIds.aiIconSpriteIds[i]]);
}
FreeAndDestroyMonPicSprite(data->aiMonSpriteId);
RemoveWindow(data->aiMovesWindowId);
@ -1019,6 +1126,11 @@ static void Task_DebugMenuProcessInput(u8 taskId)
SwitchToAiInfoView(taskId);
return;
}
else if (listItemId == LIST_ITEM_AI_PARTY && gMain.newKeys & A_BUTTON)
{
SwitchToAiPartyView(taskId);
return;
}
data->currentMainListItemId = listItemId;
// Create the secondary menu list.
@ -2040,7 +2152,7 @@ static const u8 sText_HoldEffectRoomService[] = _("Room Service");
static const u8 sText_HoldEffectBlunderPolicy[] = _("Blunder Policy");
static const u8 sText_HoldEffectHeavyDutyBoots[] = _("Heavy Duty Boots");
static const u8 sText_HoldEffectThroatSpray[] = _("Throat Spray");
static const u8 *const sHoldEffectNames[] =
static const u8 *const sHoldEffectNames[] =
{
[HOLD_EFFECT_NONE] = sText_HoldEffectNone,
[HOLD_EFFECT_RESTORE_HP] = sText_HoldEffectRestoreHp,

View File

@ -947,7 +947,7 @@ static void CB2_HandleStartBattle(void)
// Recv Pokémon 5-6
ResetBlockReceivedFlags();
memcpy(&gEnemyParty[4], gBlockRecvBuffer[enemyMultiplayerId], sizeof(struct Pokemon) * 2);
TryCorrectShedinjaLanguage(&gEnemyParty[0]);
TryCorrectShedinjaLanguage(&gEnemyParty[1]);
TryCorrectShedinjaLanguage(&gEnemyParty[2]);
@ -2856,7 +2856,7 @@ static void SpriteCB_TrainerThrowObject_Main(struct Sprite *sprite)
sprite->callback = SpriteCB_Idle;
}
// Sprite callback for a trainer back pic to throw an object
// Sprite callback for a trainer back pic to throw an object
// (Wally throwing a ball, throwing Pokéblocks/balls in the Safari Zone)
void SpriteCB_TrainerThrowObject(struct Sprite *sprite)
{
@ -2991,10 +2991,10 @@ static void BattleStartClearSetData(void)
gBattleStruct->arenaLostOpponentMons = 0;
gBattleStruct->mega.triggerSpriteId = 0xFF;
gBattleStruct->stickyWebUser = 0xFF;
gBattleStruct->appearedInBattle = 0;
for (i = 0; i < PARTY_SIZE; i++)
{
gBattleStruct->usedHeldItems[i][0] = 0;
@ -3094,7 +3094,7 @@ void SwitchInClearSetData(void)
gBattleStruct->lastTakenMoveFrom[gActiveBattler][3] = 0;
gBattleStruct->lastMoveFailed &= ~(gBitTable[gActiveBattler]);
gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]);
if (gActiveBattler == gBattleStruct->stickyWebUser)
gBattleStruct->stickyWebUser = 0xFF; // Switched into sticky web user slot so reset it
@ -3110,14 +3110,12 @@ void SwitchInClearSetData(void)
gBattleResources->flags->flags[gActiveBattler] = 0;
gCurrentMove = 0;
gBattleStruct->arenaTurnCounter = 0xFF;
// Reset damage to prevent things like red card activating if the switched-in mon is holding it
gSpecialStatuses[gActiveBattler].physicalDmg = 0;
gSpecialStatuses[gActiveBattler].specialDmg = 0;
ClearBattlerMoveHistory(gActiveBattler);
ClearBattlerAbilityHistory(gActiveBattler);
ClearBattlerItemEffectHistory(gActiveBattler);
Ai_UpdateSwitchInData(gActiveBattler);
}
void FaintClearSetData(void)
@ -3194,7 +3192,7 @@ void FaintClearSetData(void)
gBattleStruct->lastTakenMoveFrom[gActiveBattler][3] = 0;
gBattleStruct->palaceFlags &= ~(gBitTable[gActiveBattler]);
if (gActiveBattler == gBattleStruct->stickyWebUser)
gBattleStruct->stickyWebUser = 0xFF; // User of sticky web fainted, so reset the stored battler ID
@ -3572,7 +3570,8 @@ static void DoBattleIntro(void)
gBattleStruct->switchInAbilitiesCounter = 0;
gBattleStruct->switchInItemsCounter = 0;
gBattleStruct->overworldWeatherDone = FALSE;
GetAiLogicData(); // get assumed abilities, hold effects, etc of all battlers
Ai_InitPartyStruct(); // Save mons party counts, and first 2/4 mons on the battlefield.
gBattleMainFunc = TryDoEventsBeforeFirstTurn;
}
break;
@ -3706,8 +3705,6 @@ static void TryDoEventsBeforeFirstTurn(void)
gMoveResultFlags = 0;
gRandomTurnNumber = Random();
GetAiLogicData(); // get assumed abilities, hold effects, etc of all battlers
if (gBattleTypeFlags & BATTLE_TYPE_ARENA)
{
@ -3914,7 +3911,7 @@ static void HandleTurnActionSelectionState(void)
case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn.
RecordedBattle_CopyBattlerMoves();
gBattleCommunication[gActiveBattler] = STATE_BEFORE_ACTION_CHOSEN;
// Do AI score computations here so we can use them in AI_TrySwitchOrUseItem
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && IsBattlerAIControlled(gActiveBattler)) {
gBattleStruct->aiMoveOrAction[gActiveBattler] = ComputeBattleAiScores(gActiveBattler);
@ -4574,7 +4571,7 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves)
// QUICK CLAW / CUSTAP - always first
// LAGGING TAIL - always last
// STALL - always last
if (gProtectStructs[battler1].quickDraw && !gProtectStructs[battler2].quickDraw)
strikesFirst = 0;
else if (!gProtectStructs[battler1].quickDraw && gProtectStructs[battler2].quickDraw)

View File

@ -27,6 +27,7 @@ void AllocateBattleResources(void)
gBattleResources->beforeLvlUp = AllocZeroed(sizeof(*gBattleResources->beforeLvlUp));
gBattleResources->ai = AllocZeroed(sizeof(*gBattleResources->ai));
gBattleResources->aiData = AllocZeroed(sizeof(*gBattleResources->aiData));
gBattleResources->aiParty = AllocZeroed(sizeof(*gBattleResources->aiParty));
gBattleResources->battleHistory = AllocZeroed(sizeof(*gBattleResources->battleHistory));
gLinkBattleSendBuffer = AllocZeroed(BATTLE_BUFFER_LINK_SIZE);
@ -59,6 +60,7 @@ void FreeBattleResources(void)
FREE_AND_SET_NULL(gBattleResources->beforeLvlUp);
FREE_AND_SET_NULL(gBattleResources->ai);
FREE_AND_SET_NULL(gBattleResources->aiData);
FREE_AND_SET_NULL(gBattleResources->aiParty);
FREE_AND_SET_NULL(gBattleResources->battleHistory);
FREE_AND_SET_NULL(gBattleResources);

BIN
vbalink.ini Normal file

Binary file not shown.