Damage roll selection in AI_CalcDamage (#4615)
* Damage roll selection and AI_FLAG_CONSERVATIVE * Clarify enum names * Simplify AverageRollDmg line * Change u8s to u32s * Turns out Boomburst does a lot of damage lol * Spacing
This commit is contained in:
parent
d999092689
commit
be2517415b
6 changed files with 53 additions and 18 deletions
|
@ -5,6 +5,13 @@
|
||||||
|
|
||||||
#define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER))
|
#define AI_STRIKES_FIRST(battlerAi, battlerDef, move)((AI_WhoStrikesFirst(battlerAi, battlerDef, move) == AI_IS_FASTER))
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
DMG_ROLL_LOWEST,
|
||||||
|
DMG_ROLL_AVERAGE,
|
||||||
|
DMG_ROLL_HIGHEST,
|
||||||
|
};
|
||||||
|
|
||||||
bool32 AI_RandLessThan(u32 val);
|
bool32 AI_RandLessThan(u32 val);
|
||||||
bool32 IsAiVsAiBattle(void);
|
bool32 IsAiVsAiBattle(void);
|
||||||
bool32 BattlerHasAi(u32 battlerId);
|
bool32 BattlerHasAi(u32 battlerId);
|
||||||
|
@ -82,8 +89,8 @@ bool32 ShouldLowerEvasion(u32 battlerAtk, u32 battlerDef, u32 defAbility);
|
||||||
bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect);
|
bool32 IsAffectedByPowder(u32 battler, u32 ability, u32 holdEffect);
|
||||||
bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category);
|
bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category);
|
||||||
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo);
|
s32 AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo);
|
||||||
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower);
|
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 dmgRoll);
|
||||||
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather);
|
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, u32 dmgRoll);
|
||||||
bool32 AI_IsDamagedByRecoil(u32 battler);
|
bool32 AI_IsDamagedByRecoil(u32 battler);
|
||||||
u32 GetNoOfHitsToKO(u32 dmg, s32 hp);
|
u32 GetNoOfHitsToKO(u32 dmg, s32 hp);
|
||||||
u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef);
|
u32 GetNoOfHitsToKOBattlerDmg(u32 dmg, u32 battlerDef);
|
||||||
|
@ -187,7 +194,7 @@ void IncreaseSleepScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
|
||||||
void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
|
void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
|
||||||
void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
|
void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score);
|
||||||
|
|
||||||
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool8 isPartyMonAttacker);
|
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, u32 dmgRoll);
|
||||||
s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle);
|
s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle);
|
||||||
s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle);
|
s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle);
|
||||||
bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef);
|
bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef);
|
||||||
|
|
|
@ -46,8 +46,9 @@
|
||||||
#define AI_FLAG_ACE_POKEMON (1 << 16) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining.
|
#define AI_FLAG_ACE_POKEMON (1 << 16) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining.
|
||||||
#define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items
|
#define AI_FLAG_OMNISCIENT (1 << 17) // AI has full knowledge of player moves, abilities, hold items
|
||||||
#define AI_FLAG_SMART_MON_CHOICES (1 << 18) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING.
|
#define AI_FLAG_SMART_MON_CHOICES (1 << 18) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING.
|
||||||
|
#define AI_FLAG_CONSERVATIVE (1 << 19) // AI assumes all moves will low roll damage
|
||||||
|
|
||||||
#define AI_FLAG_COUNT 19
|
#define AI_FLAG_COUNT 20
|
||||||
|
|
||||||
// 'other' ai logic flags
|
// 'other' ai logic flags
|
||||||
#define AI_FLAG_ROAMING (1 << 29)
|
#define AI_FLAG_ROAMING (1 << 29)
|
||||||
|
|
|
@ -459,7 +459,12 @@ static void SetBattlerAiMovesData(struct AiLogicData *aiData, u32 battlerAtk, u3
|
||||||
//&& gMovesInfo[move].power != 0 /* we want to get effectiveness and accuracy of status moves */
|
//&& gMovesInfo[move].power != 0 /* we want to get effectiveness and accuracy of status moves */
|
||||||
&& !(aiData->moveLimitations[battlerAtk] & gBitTable[i]))
|
&& !(aiData->moveLimitations[battlerAtk] & gBitTable[i]))
|
||||||
{
|
{
|
||||||
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather);
|
if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_RISKY)
|
||||||
|
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, DMG_ROLL_HIGHEST);
|
||||||
|
else if (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)
|
||||||
|
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, DMG_ROLL_LOWEST);
|
||||||
|
else
|
||||||
|
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, TRUE, weather, DMG_ROLL_AVERAGE);
|
||||||
aiData->moveAccuracy[battlerAtk][battlerDef][i] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move);
|
aiData->moveAccuracy[battlerAtk][battlerDef][i] = Ai_SetMoveAccuracy(aiData, battlerAtk, battlerDef, move);
|
||||||
}
|
}
|
||||||
aiData->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
|
aiData->simulatedDmg[battlerAtk][battlerDef][i] = dmg;
|
||||||
|
|
|
@ -147,7 +147,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
|
||||||
playerMove = gBattleMons[opposingBattler].moves[i];
|
playerMove = gBattleMons[opposingBattler].moves[i];
|
||||||
if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0)
|
if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0)
|
||||||
{
|
{
|
||||||
damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather);
|
damageTaken = AI_CalcDamage(playerMove, opposingBattler, battler, &effectiveness, FALSE, weather, DMG_ROLL_HIGHEST);
|
||||||
if (damageTaken > maxDamageTaken)
|
if (damageTaken > maxDamageTaken)
|
||||||
maxDamageTaken = damageTaken;
|
maxDamageTaken = damageTaken;
|
||||||
}
|
}
|
||||||
|
@ -1234,7 +1234,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
|
||||||
if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
|
if (aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
|
||||||
{
|
{
|
||||||
aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j);
|
aiMove = GetMonData(&party[i], MON_DATA_MOVE1 + j);
|
||||||
dmg = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE);
|
dmg = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_AVERAGE);
|
||||||
if (bestDmg < dmg)
|
if (bestDmg < dmg)
|
||||||
{
|
{
|
||||||
bestDmg = dmg;
|
bestDmg = dmg;
|
||||||
|
@ -1683,7 +1683,7 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle
|
||||||
playerMove = gBattleMons[opposingBattler].moves[i];
|
playerMove = gBattleMons[opposingBattler].moves[i];
|
||||||
if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0)
|
if (playerMove != MOVE_NONE && gMovesInfo[playerMove].power != 0)
|
||||||
{
|
{
|
||||||
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, FALSE);
|
damageTaken = AI_CalcPartyMonDamage(playerMove, opposingBattler, battler, battleMon, FALSE, DMG_ROLL_HIGHEST);
|
||||||
if (damageTaken > maxDamageTaken)
|
if (damageTaken > maxDamageTaken)
|
||||||
maxDamageTaken = damageTaken;
|
maxDamageTaken = damageTaken;
|
||||||
}
|
}
|
||||||
|
@ -1775,7 +1775,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
|
||||||
|
|
||||||
// Only do damage calc if switching after KO, don't need it otherwise and saves ~0.02s per turn
|
// Only do damage calc if switching after KO, don't need it otherwise and saves ~0.02s per turn
|
||||||
if (isSwitchAfterKO && aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
|
if (isSwitchAfterKO && aiMove != MOVE_NONE && gMovesInfo[aiMove].power != 0)
|
||||||
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE);
|
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE, DMG_ROLL_AVERAGE);
|
||||||
|
|
||||||
// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
|
// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
|
||||||
if (aiMove == MOVE_BATON_PASS && ((hitsToKO > hitsToKOThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKO > hitsToKOThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed)))
|
if (aiMove == MOVE_BATON_PASS && ((hitsToKO > hitsToKOThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKO > hitsToKOThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed)))
|
||||||
|
|
|
@ -354,14 +354,14 @@ bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, u32 category)
|
||||||
}
|
}
|
||||||
|
|
||||||
// To save computation time this function has 2 variants. One saves, sets and restores battlers, while the other doesn't.
|
// To save computation time this function has 2 variants. One saves, sets and restores battlers, while the other doesn't.
|
||||||
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower)
|
s32 AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 dmgRoll)
|
||||||
{
|
{
|
||||||
s32 dmg = 0;
|
s32 dmg = 0;
|
||||||
SaveBattlerData(battlerAtk);
|
SaveBattlerData(battlerAtk);
|
||||||
SaveBattlerData(battlerDef);
|
SaveBattlerData(battlerDef);
|
||||||
SetBattlerData(battlerAtk);
|
SetBattlerData(battlerAtk);
|
||||||
SetBattlerData(battlerDef);
|
SetBattlerData(battlerDef);
|
||||||
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(AI_DATA));
|
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, typeEffectiveness, considerZPower, AI_GetWeather(AI_DATA), dmgRoll);
|
||||||
RestoreBattlerData(battlerAtk);
|
RestoreBattlerData(battlerAtk);
|
||||||
RestoreBattlerData(battlerDef);
|
RestoreBattlerData(battlerDef);
|
||||||
return dmg;
|
return dmg;
|
||||||
|
@ -374,6 +374,18 @@ static inline s32 LowestRollDmg(s32 dmg)
|
||||||
return dmg;
|
return dmg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline s32 HighestRollDmg(s32 dmg)
|
||||||
|
{
|
||||||
|
return dmg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline s32 AverageRollDmg(s32 dmg)
|
||||||
|
{
|
||||||
|
dmg = ((HighestRollDmg(dmg) + LowestRollDmg(dmg)) * 100) / 2;
|
||||||
|
dmg /= 100;
|
||||||
|
return dmg;
|
||||||
|
}
|
||||||
|
|
||||||
bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
|
bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
|
||||||
{
|
{
|
||||||
s32 moveType;
|
s32 moveType;
|
||||||
|
@ -459,7 +471,7 @@ bool32 IsDamageMoveUnusable(u32 move, u32 battlerAtk, u32 battlerDef)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather)
|
s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectiveness, bool32 considerZPower, u32 weather, u32 dmgRoll)
|
||||||
{
|
{
|
||||||
s32 dmg, moveType;
|
s32 dmg, moveType;
|
||||||
uq4_12_t effectivenessMultiplier;
|
uq4_12_t effectivenessMultiplier;
|
||||||
|
@ -534,11 +546,21 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes
|
||||||
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
|
aiData->abilities[battlerAtk], aiData->abilities[battlerDef]);
|
||||||
u32 critChance = GetCritHitChance(critChanceIndex);
|
u32 critChance = GetCritHitChance(critChanceIndex);
|
||||||
// With critChance getting closer to 1, dmg gets closer to critDmg.
|
// With critChance getting closer to 1, dmg gets closer to critDmg.
|
||||||
dmg = LowestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance));
|
if (dmgRoll == DMG_ROLL_AVERAGE)
|
||||||
|
dmg = AverageRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance));
|
||||||
|
else if (dmgRoll == DMG_ROLL_HIGHEST)
|
||||||
|
dmg = HighestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance));
|
||||||
|
else
|
||||||
|
dmg = LowestRollDmg((critDmg + normalDmg * (critChance - 1)) / (critChance)); // Default to lowest roll
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dmg = LowestRollDmg(normalDmg);
|
if (dmgRoll == DMG_ROLL_AVERAGE)
|
||||||
|
dmg = AverageRollDmg(normalDmg);
|
||||||
|
else if (dmgRoll == DMG_ROLL_HIGHEST)
|
||||||
|
dmg = HighestRollDmg(normalDmg);
|
||||||
|
else
|
||||||
|
dmg = LowestRollDmg(normalDmg); // Default to lowest roll
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gBattleStruct->zmove.active)
|
if (!gBattleStruct->zmove.active)
|
||||||
|
@ -3286,7 +3308,7 @@ void FreeRestoreBattleMons(struct BattlePokemon *savedBattleMons)
|
||||||
}
|
}
|
||||||
|
|
||||||
// party logic
|
// party logic
|
||||||
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool8 isPartyMonAttacker)
|
s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool32 isPartyMonAttacker, u32 dmgRoll)
|
||||||
{
|
{
|
||||||
s32 dmg;
|
s32 dmg;
|
||||||
u8 effectiveness;
|
u8 effectiveness;
|
||||||
|
@ -3307,7 +3329,7 @@ s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct Battl
|
||||||
AI_THINKING_STRUCT->saved[battlerAtk].saved = FALSE;
|
AI_THINKING_STRUCT->saved[battlerAtk].saved = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(AI_DATA));
|
dmg = AI_CalcDamage(move, battlerAtk, battlerDef, &effectiveness, FALSE, AI_GetWeather(AI_DATA), dmgRoll);
|
||||||
// restores original gBattleMon struct
|
// restores original gBattleMon struct
|
||||||
FreeRestoreBattleMons(savedBattleMons);
|
FreeRestoreBattleMons(savedBattleMons);
|
||||||
return dmg;
|
return dmg;
|
||||||
|
@ -3751,7 +3773,7 @@ bool32 ShouldUseZMove(u32 battlerAtk, u32 battlerDef, u32 chosenMove)
|
||||||
else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove))
|
else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE) >= gBattleMons[battlerDef].hp)
|
if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamageSaveBattlers(chosenMove, battlerAtk, battlerDef, &effectiveness, FALSE, DMG_ROLL_AVERAGE) >= gBattleMons[battlerDef].hp)
|
||||||
return FALSE; // don't waste damaging z move if can otherwise faint target
|
return FALSE; // don't waste damaging z move if can otherwise faint target
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
|
@ -679,7 +679,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize
|
||||||
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES);
|
||||||
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); }
|
PLAYER(SPECIES_SWELLOW) { Level(30); Moves(MOVE_WING_ATTACK, MOVE_BOOMBURST); Speed(5); }
|
||||||
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(4); } // Forces switchout
|
OPPONENT(SPECIES_PONYTA) { Level(1); Moves(MOVE_NONE); Speed(4); } // Forces switchout
|
||||||
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); } // Mid battle, AI sends out Aron
|
OPPONENT(SPECIES_ARON) { Level(30); Moves(MOVE_HEADBUTT); Speed(4); SpDefense(41); } // Mid battle, AI sends out Aron
|
||||||
OPPONENT(SPECIES_ELECTRODE) { Level(30); Moves(MOVE_CHARGE_BEAM); Speed(6); }
|
OPPONENT(SPECIES_ELECTRODE) { Level(30); Moves(MOVE_CHARGE_BEAM); Speed(6); }
|
||||||
} WHEN {
|
} WHEN {
|
||||||
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 1); }
|
TURN { MOVE(player, MOVE_WING_ATTACK); EXPECT_SWITCH(opponent, 1); }
|
||||||
|
|
Loading…
Reference in a new issue