mirror of
https://github.com/rh-hideout/pokeemerald-expansion.git
synced 2026-04-26 10:25:49 -05:00
Add AI_FLAG_RANDOMIZE_SWITCHINS (#6222)
This commit is contained in:
parent
dea8974ddc
commit
00b3179f75
|
|
@ -204,3 +204,6 @@ AI will predict what move the player is going to use based on what move it would
|
|||
|
||||
## `AI_FLAG_PP_STALL_PREVENTION`
|
||||
This flag aims to prevent the player from PP stalling the AI by switching between immunities. The AI mon's move scores will slowly decay for absorbed moves over time, eventually making its moves unpredictable. More detailed control for this behaviour can be customized in the `ai.h` config file.
|
||||
|
||||
## `AI_FLAG_RANDOMIZE_SWITCHIN`
|
||||
AI will randomly choose between eligible switchin candidates rather than always picking the last one in the party. For example, if the AI has two mons that can revenge kill the player's mon after a KO, by default the AI will only track the most recent eligible candidate, and will always send in the last one in party order as a result. With this flag, it will instead track all of the eligible mons, and randomly choose between them when deciding which to send out.
|
||||
|
|
|
|||
0
include/battle.h
Executable file → Normal file
0
include/battle.h
Executable file → Normal file
|
|
@ -95,6 +95,9 @@
|
|||
// AI_FLAG_SMART_SWITCHING settings
|
||||
#define SMART_SWITCHING_OMNISCIENT FALSE // AI will use omniscience for switching calcs, regardless of omniscience setting otherwise
|
||||
|
||||
// AI_FLAG_RANDOMIZE_SWITCHIN settings
|
||||
#define RANDOMIZE_SWITCHIN_ANY_VALID TRUE // If AI has no good candidate mons, it will still choose randomly from all valid options rather than defaulting to the last one in party order
|
||||
|
||||
// Configurations specifically for AI_FLAG_DOUBLE_BATTLE.
|
||||
#define FRIENDLY_FIRE_RISKY_THRESHOLD 2 // AI_FLAG_RISKY acceptable number of hits to KO the partner via friendly fire
|
||||
#define FRIENDLY_FIRE_NORMAL_THRESHOLD 3 // typical acceptable number of hits to KO the partner via friendly fire
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@
|
|||
#define AI_FLAG_ASSUME_STATUS_MOVES AI_FLAG(29) // AI has a chance to know certain non-damaging moves, and also Fake Out and Super Fang. Restricted version of AI_FLAG_OMNISCIENT.
|
||||
#define AI_FLAG_ATTACKS_PARTNER AI_FLAG(30) // AI specific to double battles; AI can deliberately attack its 'partner.'
|
||||
#define AI_FLAG_KNOW_OPPONENT_PARTY AI_FLAG(31) // AI knows all the species in the player's party, but not moves/items/abilities unless they've been seen.
|
||||
#define AI_FLAG_RANDOMIZE_SWITCHIN AI_FLAG(32) // AI will randomly choose between eligible switchin candidates of a given category instead of picking the last one in the party. Does nothing without AI_FLAG_SMART_MON_CHOICES
|
||||
|
||||
// The following options are enough to have a basic/smart trainer. Any other addtion could make the trainer worse/better depending on the flag
|
||||
#define AI_FLAG_BASIC_TRAINER (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY)
|
||||
#define AI_FLAG_SMART_TRAINER (AI_FLAG_BASIC_TRAINER | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION | AI_FLAG_SMART_TERA)
|
||||
#define AI_FLAG_SMART_TRAINER (AI_FLAG_BASIC_TRAINER | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION | AI_FLAG_SMART_TERA | AI_FLAG_RANDOMIZE_SWITCHIN)
|
||||
#define AI_FLAG_PREDICTION (AI_FLAG_PREDICT_SWITCH | AI_FLAG_PREDICT_INCOMING_MON | AI_FLAG_PREDICT_MOVE)
|
||||
#define AI_FLAG_ASSUMPTIONS (AI_FLAG_ASSUME_STAB | AI_FLAG_ASSUME_STATUS_MOVES | AI_FLAG_WEIGH_ABILITY_PREDICTION)
|
||||
|
||||
|
|
|
|||
|
|
@ -204,6 +204,10 @@ enum RandomTag
|
|||
RNG_AI_BOOST_INTO_HAZE,
|
||||
RNG_AI_SHOULD_RECOVER,
|
||||
RNG_AI_PRIORITIZE_LAST_CHANCE,
|
||||
RNG_AI_RANDOM_SWITCHIN_POST_KO,
|
||||
RNG_AI_RANDOM_SWITCHIN_MID_BATTLE,
|
||||
RNG_AI_RANDOM_VALID_SWITCHIN_POST_KO,
|
||||
RNG_AI_RANDOM_VALID_SWITCHIN_MID_BATTLE,
|
||||
RNG_HEALER,
|
||||
RNG_DEXNAV_ENCOUNTER_LEVEL,
|
||||
RNG_AI_ASSUME_STATUS_SLEEP,
|
||||
|
|
@ -271,6 +275,9 @@ const void *RandomElementArrayDefault(enum RandomTag, const void *array, size_t
|
|||
|
||||
u8 RandomWeightedIndex(u8 *weights, u8 length);
|
||||
|
||||
u32 RandomBit(enum RandomTag tag, u32 bits);
|
||||
u32 RandomBitIndex(enum RandomTag tag, u32 bits);
|
||||
|
||||
#if TESTING
|
||||
u32 RandomUniformTrials(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32), void *caller);
|
||||
u32 RandomUniformDefaultValue(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32), void *caller);
|
||||
|
|
|
|||
|
|
@ -1592,17 +1592,14 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
|
|||
|
||||
static u32 GetFirstNonInvalidMon(u32 firstId, u32 lastId, u32 invalidMons)
|
||||
{
|
||||
u32 chosenMonId = PARTY_SIZE;
|
||||
for (u32 i = (lastId-1); i > firstId; i--)
|
||||
{
|
||||
if (!((1 << i) & invalidMons))
|
||||
{
|
||||
// first non invalid mon found
|
||||
chosenMonId = i;
|
||||
break;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return chosenMonId;
|
||||
return PARTY_SIZE;
|
||||
}
|
||||
|
||||
bool32 IsMonGrounded(enum HoldEffect heldItemEffect, enum Ability ability, enum Type type1, enum Type type2)
|
||||
|
|
@ -2058,24 +2055,58 @@ static u32 GetBattleMonTypeMatchup(struct BattlePokemon opposingBattleMon, struc
|
|||
return typeEffectiveness1 + typeEffectiveness2;
|
||||
}
|
||||
|
||||
static int GetRandomSwitchinWithBatonPass(int aliveCount, int bits, int firstId, int lastId, int currentMonId)
|
||||
static u32 GetSwitchinCandidate(u32 switchinCategory, u32 battler, int firstId, int lastId, enum SwitchType switchType)
|
||||
{
|
||||
// Breakout early if there aren't any Baton Pass mons to save computation time
|
||||
if (bits == 0)
|
||||
u32 i;
|
||||
|
||||
if (switchinCategory == 0)
|
||||
return PARTY_SIZE;
|
||||
|
||||
// GetBestMonBatonPass randomly chooses between all mons that met Baton Pass check
|
||||
if ((aliveCount == 2 || (aliveCount > 2 && Random() % 3 == 0)) && bits)
|
||||
// Randomize between eligible mons
|
||||
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_RANDOMIZE_SWITCHIN)
|
||||
{
|
||||
do
|
||||
{
|
||||
return (Random() % (lastId - firstId)) + firstId;
|
||||
} while (!(bits & (1 << (currentMonId))));
|
||||
// This split is necessary because the test system can't handle multiple calls with the same random tag in the same turn
|
||||
if (switchType == SWITCH_AFTER_KO)
|
||||
return RandomBitIndex(RNG_AI_RANDOM_SWITCHIN_POST_KO, switchinCategory); // Can't pass this anything with no set bits
|
||||
else
|
||||
return RandomBitIndex(RNG_AI_RANDOM_SWITCHIN_MID_BATTLE, switchinCategory); // Can't pass this anything with no set bits
|
||||
}
|
||||
|
||||
// Catch any other cases (such as only one mon alive and it has Baton Pass)
|
||||
else
|
||||
// Pick last eligible mon in party order
|
||||
for (i = lastId; i >= firstId; i--)
|
||||
{
|
||||
if (switchinCategory & (1 << i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return PARTY_SIZE;
|
||||
}
|
||||
|
||||
static u32 GetValidSwitchinCandidate(u32 validMonIds, u32 battler, u32 firstId, u32 lastId, enum SwitchType switchType)
|
||||
{
|
||||
u32 i;
|
||||
|
||||
if (validMonIds == 0)
|
||||
return PARTY_SIZE;
|
||||
|
||||
// Randomize between valid mons
|
||||
if ((gAiThinkingStruct->aiFlags[battler] & AI_FLAG_RANDOMIZE_SWITCHIN) && RANDOMIZE_SWITCHIN_ANY_VALID)
|
||||
{
|
||||
// This split is necessary because the test system can't handle multiple calls with the same random tag in the same turn
|
||||
if (switchType == SWITCH_AFTER_KO)
|
||||
return RandomBitIndex(RNG_AI_RANDOM_VALID_SWITCHIN_POST_KO, validMonIds); // Can't pass this anything with no set bits
|
||||
else
|
||||
return RandomBitIndex(RNG_AI_RANDOM_VALID_SWITCHIN_MID_BATTLE, validMonIds); // Can't pass this anything with no set bits
|
||||
}
|
||||
|
||||
// Pick last valid mon in party order
|
||||
for (i = (lastId-1); i > firstId; i--)
|
||||
{
|
||||
if (validMonIds & (1 << i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return PARTY_SIZE;
|
||||
}
|
||||
|
||||
static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattler, struct BattlePokemon battleMon, u32 *bestPlayerMove)
|
||||
|
|
@ -2220,15 +2251,15 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
{
|
||||
struct IncomingHealInfo healInfoData;
|
||||
const struct IncomingHealInfo *healInfo = &healInfoData;
|
||||
int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE, generic1v1MonId = PARTY_SIZE;
|
||||
int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = PARTY_SIZE, healingCandidateId = PARTY_SIZE;
|
||||
int i, j, aliveCount = 0, bits = 0, aceMonCount = 0;
|
||||
u32 revengeKillerIds = 0, slowRevengeKillerIds = 0, fastThreatenIds = 0, slowThreatenIds = 0, damageMonIds = 0, generic1v1MonIds = 0;
|
||||
u32 batonPassIds = 0, typeMatchupIds = 0, typeMatchupEffectiveIds = 0, defensiveMonIds = 0, trapperIds = 0, healingCandidateIds = 0;
|
||||
int i, j, aceMonId = PARTY_SIZE, aliveCount = 0, aceMonCount = 0;
|
||||
s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed
|
||||
s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0, bestHealGain = 0, monMaxDamage = 0;
|
||||
s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0, bestHealGain = 0;
|
||||
u32 aiMove, hitsToKOAI, hitsToKOPlayer, hitsToKOAIPriority, bestPlayerMove = MOVE_NONE, bestPlayerPriorityMove = MOVE_NONE, maxHitsToKO = 0;
|
||||
u32 bestResist = UQ_4_12(2.0), bestResistEffective = UQ_4_12(2.0), typeMatchup; // 2.0 is the default "Neutral" matchup from GetBattleMonTypeMatchup
|
||||
bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, isSwitchinFirstPriority, canSwitchinWin1v1;
|
||||
u32 invalidMons = 0;
|
||||
u32 validMonIds = 0;
|
||||
uq4_12_t effectiveness = UQ_4_12(1.0);
|
||||
|
||||
GetIncomingHealInfo(battler, &healInfoData);
|
||||
|
|
@ -2243,7 +2274,6 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
|| i == gBattleStruct->monToSwitchIntoId[battlerIn1]
|
||||
|| i == gBattleStruct->monToSwitchIntoId[battlerIn2])
|
||||
{
|
||||
invalidMons |= 1u << i;
|
||||
continue;
|
||||
}
|
||||
// Save Ace Pokemon for last
|
||||
|
|
@ -2251,11 +2281,13 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
{
|
||||
aceMonId = i;
|
||||
aceMonCount++;
|
||||
invalidMons |= 1u << i;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
aliveCount++;
|
||||
validMonIds |= (1u << i);
|
||||
}
|
||||
|
||||
InitializeSwitchinCandidate(&party[i]);
|
||||
|
||||
|
|
@ -2279,8 +2311,6 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
canSwitchinWin1v1 = FALSE;
|
||||
bool32 anyMoveCanWin1v1 = FALSE;
|
||||
|
||||
monMaxDamage = 0;
|
||||
|
||||
// Check through current mon's moves
|
||||
for (j = 0; j < MAX_MON_MOVES; j++)
|
||||
{
|
||||
|
|
@ -2302,7 +2332,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (GetMoveEffect(aiMove) == EFFECT_BATON_PASS)
|
||||
{
|
||||
if ((isSwitchinFirst && hitsToKOAI > 1) || hitsToKOAI > 2) // Need to take an extra hit if slower
|
||||
bits |= 1u << i;
|
||||
batonPassIds |= (1u << i);
|
||||
}
|
||||
|
||||
// Check that good type matchups get at least two turns and set best type matchup mon
|
||||
|
|
@ -2311,7 +2341,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (canSwitchinWin1v1)
|
||||
{
|
||||
bestResist = typeMatchup;
|
||||
typeMatchupId = i;
|
||||
typeMatchupIds |= (1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2320,11 +2350,11 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
{
|
||||
maxHitsToKO = hitsToKOAI;
|
||||
if (maxHitsToKO > defensiveMonHitKOThreshold)
|
||||
defensiveMonId = i;
|
||||
defensiveMonIds |= (1u << i);
|
||||
}
|
||||
|
||||
if (canSwitchinWin1v1)
|
||||
generic1v1MonId = i;
|
||||
generic1v1MonIds |= (1u << i);
|
||||
|
||||
// Check for mon with resistance and super effective move for best type matchup mon with effective move
|
||||
if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove))
|
||||
|
|
@ -2336,7 +2366,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (canSwitchinWin1v1)
|
||||
{
|
||||
bestResistEffective = typeMatchup;
|
||||
typeMatchupEffectiveId = i;
|
||||
typeMatchupEffectiveIds |= (1u << i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2347,15 +2377,13 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
&& damageDealt < playerMonHP)
|
||||
continue;
|
||||
|
||||
if (damageDealt > monMaxDamage)
|
||||
monMaxDamage = damageDealt;
|
||||
// Check that mon isn't one shot and set best damage mon
|
||||
if (damageDealt > maxDamageDealt)
|
||||
{
|
||||
if ((isFreeSwitch && hitsToKOAI > 1) || hitsToKOAI > 2) // This is a "default", we have uniquely low standards
|
||||
{
|
||||
maxDamageDealt = damageDealt;
|
||||
damageMonId = i;
|
||||
damageMonIds |= (1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2366,9 +2394,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (canSwitchinWin1v1)
|
||||
{
|
||||
if (isSwitchinFirst)
|
||||
revengeKillerId = i;
|
||||
revengeKillerIds |= (1u << i);
|
||||
else
|
||||
slowRevengeKillerId = i;
|
||||
slowRevengeKillerIds |= (1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2378,9 +2406,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (canSwitchinWin1v1)
|
||||
{
|
||||
if (isSwitchinFirst)
|
||||
fastThreatenId = i;
|
||||
fastThreatenIds |= (1u << i);
|
||||
else
|
||||
slowThreatenId = i;
|
||||
slowThreatenIds |= (1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2389,7 +2417,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
|| (CanAbilityTrapOpponent(gAiLogicData->abilities[opposingBattler], opposingBattler) && gAiLogicData->switchinCandidate.battleMon.ability == ABILITY_TRACE))
|
||||
&& CountUsablePartyMons(opposingBattler) > 0
|
||||
&& canSwitchinWin1v1)
|
||||
trapperId = i;
|
||||
trapperIds |= (1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2411,51 +2439,46 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
|||
if (anyMoveCanWin1v1 && healGain > (s32)(maxHP / 4) && healGain > bestHealGain)
|
||||
{
|
||||
bestHealGain = healGain;
|
||||
healingCandidateId = i;
|
||||
healingCandidateIds |= (1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
if (monMaxDamage == 0)
|
||||
invalidMons |= 1u << i;
|
||||
}
|
||||
|
||||
batonPassId = GetRandomSwitchinWithBatonPass(aliveCount, bits, firstId, lastId, i);
|
||||
|
||||
// Different switching priorities depending on switching mid battle vs switching after a KO or slow switch
|
||||
if (isFreeSwitch)
|
||||
{
|
||||
// Return Trapper > Revenge Killer > Type Matchup > Healing Candidate > Baton Pass > Best Damage
|
||||
if (trapperId != PARTY_SIZE) return trapperId;
|
||||
else if (revengeKillerId != PARTY_SIZE) return revengeKillerId;
|
||||
else if (slowRevengeKillerId != PARTY_SIZE) return slowRevengeKillerId;
|
||||
else if (fastThreatenId != PARTY_SIZE) return fastThreatenId;
|
||||
else if (slowThreatenId != PARTY_SIZE) return slowThreatenId;
|
||||
else if (typeMatchupEffectiveId != PARTY_SIZE) return typeMatchupEffectiveId;
|
||||
else if (typeMatchupId != PARTY_SIZE) return typeMatchupId;
|
||||
else if (healingCandidateId != PARTY_SIZE) return healingCandidateId;
|
||||
else if (batonPassId != PARTY_SIZE) return batonPassId;
|
||||
else if (generic1v1MonId != PARTY_SIZE) return generic1v1MonId;
|
||||
else if (damageMonId != PARTY_SIZE) return damageMonId;
|
||||
if (trapperIds != 0) return GetSwitchinCandidate(trapperIds, battler, firstId, lastId, switchType);
|
||||
else if (revengeKillerIds != 0) return GetSwitchinCandidate(revengeKillerIds, battler, firstId, lastId, switchType);
|
||||
else if (slowRevengeKillerIds != 0) return GetSwitchinCandidate(slowRevengeKillerIds, battler, firstId, lastId, switchType);
|
||||
else if (fastThreatenIds != 0) return GetSwitchinCandidate(fastThreatenIds, battler, firstId, lastId, switchType);
|
||||
else if (slowThreatenIds != 0) return GetSwitchinCandidate(slowThreatenIds, battler, firstId, lastId, switchType);
|
||||
else if (typeMatchupEffectiveIds != 0) return GetSwitchinCandidate(typeMatchupEffectiveIds, battler, firstId, lastId, switchType);
|
||||
else if (typeMatchupIds != 0) return GetSwitchinCandidate(typeMatchupIds, battler, firstId, lastId, switchType);
|
||||
else if (healingCandidateIds != 0) return GetSwitchinCandidate(healingCandidateIds, battler, firstId, lastId, switchType);
|
||||
else if (batonPassIds != 0) return GetSwitchinCandidate(batonPassIds, battler, firstId, lastId, switchType);
|
||||
else if (generic1v1MonIds != 0) return GetSwitchinCandidate(generic1v1MonIds, battler, firstId, lastId, switchType);
|
||||
else if (damageMonIds != 0) return GetSwitchinCandidate(damageMonIds, battler, firstId, lastId, switchType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return Trapper > Type Matchup > Best Defensive > Healing Candidate > Baton Pass
|
||||
if (trapperId != PARTY_SIZE) return trapperId;
|
||||
else if (typeMatchupEffectiveId != PARTY_SIZE) return typeMatchupEffectiveId;
|
||||
else if (typeMatchupId != PARTY_SIZE) return typeMatchupId;
|
||||
else if (defensiveMonId != PARTY_SIZE) return defensiveMonId;
|
||||
else if (healingCandidateId != PARTY_SIZE) return healingCandidateId;
|
||||
else if (batonPassId != PARTY_SIZE) return batonPassId;
|
||||
else if (generic1v1MonId != PARTY_SIZE) return generic1v1MonId;
|
||||
if (trapperIds != 0) return GetSwitchinCandidate(trapperIds, battler, firstId, lastId, switchType);
|
||||
else if (typeMatchupEffectiveIds != 0) return GetSwitchinCandidate(typeMatchupEffectiveIds, battler, firstId, lastId, switchType);
|
||||
else if (typeMatchupIds != 0) return GetSwitchinCandidate(typeMatchupIds, battler, firstId, lastId, switchType);
|
||||
else if (defensiveMonIds != 0) return GetSwitchinCandidate(defensiveMonIds, battler, firstId, lastId, switchType);
|
||||
else if (healingCandidateIds != 0) return GetSwitchinCandidate(healingCandidateIds, battler, firstId, lastId, switchType);
|
||||
else if (batonPassIds != 0) return GetSwitchinCandidate(batonPassIds, battler, firstId, lastId, switchType);
|
||||
else if (generic1v1MonIds != 0) return GetSwitchinCandidate(generic1v1MonIds, battler, firstId, lastId, switchType);
|
||||
}
|
||||
|
||||
// Not required to switch here and no good candidates, bail
|
||||
if (switchType == SWITCH_MID_BATTLE_OPTIONAL)
|
||||
return PARTY_SIZE;
|
||||
|
||||
// Fallback
|
||||
u32 bestMonId = GetFirstNonInvalidMon(firstId, lastId, invalidMons);
|
||||
if (bestMonId != PARTY_SIZE)
|
||||
return bestMonId;
|
||||
if (validMonIds != 0)
|
||||
return GetValidSwitchinCandidate(validMonIds, battler, firstId, lastId, switchType);
|
||||
|
||||
// If ace mon is the last available Pokemon and U-Turn/Volt Switch or Eject Pack/Button was used - switch to the mon.
|
||||
if (aceMonId != PARTY_SIZE && CountUsablePartyMons(battler) <= aceMonCount)
|
||||
|
|
|
|||
34
src/random.c
34
src/random.c
|
|
@ -224,3 +224,37 @@ u8 RandomWeightedIndex(u8 *weights, u8 length)
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns whole word with just the random bit set; don't call with no set bits
|
||||
u32 RandomBit(enum RandomTag tag, u32 bits)
|
||||
{
|
||||
u32 setBits[32];
|
||||
u32 n = 0;
|
||||
for (u32 mask = 1; mask != 0; mask <<= 1)
|
||||
{
|
||||
if (bits & mask)
|
||||
setBits[n++] = mask;
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
return 0; // This is a little awkward, there are no set bits!
|
||||
else
|
||||
return setBits[RandomUniform(tag, 0, n-1)];
|
||||
}
|
||||
|
||||
// Returns the index instead; don't call with no set bits
|
||||
u32 RandomBitIndex(enum RandomTag tag, u32 bits)
|
||||
{
|
||||
u8 setIndexes[32];
|
||||
u32 n = 0;
|
||||
for (u32 i = 0; i < 32; i++)
|
||||
{
|
||||
if (bits & (1 << i))
|
||||
setIndexes[n++] = i;
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
return 0; // This is a little awkward, there are no set bits!
|
||||
else
|
||||
return setIndexes[RandomUniform(tag, 0, n-1)];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ AI_SINGLE_BATTLE_TEST("When AI switches out due to having no move that affects t
|
|||
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_SWITCH(opponent, 2); EXPECT_SEND_OUT(opponent, 0);}
|
||||
TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_SWITCH(opponent, 2); EXPECT_SEND_OUT(opponent, 4);}
|
||||
TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_MOVE(opponent, MOVE_TACKLE); }
|
||||
}
|
||||
}
|
||||
|
|
@ -1781,3 +1781,49 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI considers both meeting and
|
|||
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EXPLOSION); hp > 80 ? EXPECT_SEND_OUT(opponent, 2) : EXPECT_SEND_OUT(opponent, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_RANDOMIZE_SWITCHIN: AI will randomly choose between eligible switchin candidates of the same category")
|
||||
{
|
||||
u32 trials; // Two trial counts to ensure randomization is scalable
|
||||
PARAMETRIZE { trials = 30; }
|
||||
PARAMETRIZE { trials = 50; }
|
||||
PASSES_RANDOMLY(10, trials, RNG_AI_RANDOM_SWITCHIN_POST_KO);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_RANDOMIZE_SWITCHIN);
|
||||
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_PROTECT, MOVE_TACKLE); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_EXPLOSION); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CLOSE_COMBAT); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CLOSE_COMBAT); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CLOSE_COMBAT); }
|
||||
if (trials == 50)
|
||||
{
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CLOSE_COMBAT); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CLOSE_COMBAT); }
|
||||
}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EXPLOSION); EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
||||
AI_SINGLE_BATTLE_TEST("AI_FLAG_RANDOMIZE_SWITCHIN: AI will randomly choose between all valid switchin candidates if no good options are available")
|
||||
{
|
||||
u32 trials; // Two trial counts to ensure randomization is scalable
|
||||
PARAMETRIZE { trials = 30; }
|
||||
PARAMETRIZE { trials = 50; }
|
||||
PASSES_RANDOMLY(10, trials, RNG_AI_RANDOM_VALID_SWITCHIN_POST_KO);
|
||||
GIVEN {
|
||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_RANDOMIZE_SWITCHIN);
|
||||
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_PROTECT, MOVE_BITE); }
|
||||
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_EXPLOSION); }
|
||||
OPPONENT(SPECIES_GASTLY) { Moves(MOVE_LICK); }
|
||||
OPPONENT(SPECIES_GASTLY) { Moves(MOVE_LICK); }
|
||||
OPPONENT(SPECIES_GASTLY) { Moves(MOVE_LICK); }
|
||||
if (trials == 50)
|
||||
{
|
||||
OPPONENT(SPECIES_GASTLY) { Moves(MOVE_LICK); }
|
||||
OPPONENT(SPECIES_GASTLY) { Moves(MOVE_LICK); }
|
||||
}
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_EXPLOSION); EXPECT_SEND_OUT(opponent, 2); }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user