Fix speed ties (#4780)
* Fix speed ties * fixup! Fix speed ties * fixup! Fix speed ties * fixup! fixup! Fix speed ties * fixup! Fix speed ties * Workaround for Comatose-Ditto interaction
This commit is contained in:
parent
ed20ff5e8b
commit
9d97537ee2
6 changed files with 135 additions and 11 deletions
|
@ -795,6 +795,7 @@ struct BattleStruct
|
|||
u8 quickClawRandom[MAX_BATTLERS_COUNT];
|
||||
u8 quickDrawRandom[MAX_BATTLERS_COUNT];
|
||||
u8 shellSideArmCategory[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT];
|
||||
u8 speedTieBreaks; // MAX_BATTLERS_COUNT! values.
|
||||
u8 boosterEnergyActivates;
|
||||
u8 distortedTypeMatchups;
|
||||
u8 categoryOverride; // for Z-Moves and Max Moves
|
||||
|
|
|
@ -75,6 +75,7 @@ s8 GetChosenMovePriority(u32 battlerId);
|
|||
s8 GetMovePriority(u32 battlerId, u16 move);
|
||||
s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2,
|
||||
u32 holdEffectBattler1, u32 holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2);
|
||||
s32 GetWhichBattlerFasterOrTies(u32 battler1, u32 battler2, bool32 ignoreChosenMoves);
|
||||
s32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves);
|
||||
void RunBattleScriptCommands_PopCallbacksStack(void);
|
||||
void RunBattleScriptCommands(void);
|
||||
|
|
|
@ -2722,7 +2722,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|
|||
ADJUST_SCORE(SLOW_KILL);
|
||||
}
|
||||
else if (CanTargetFaintAi(battlerDef, battlerAtk)
|
||||
&& GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER
|
||||
&& GetWhichBattlerFasterOrTies(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER
|
||||
&& GetMovePriority(battlerAtk, move) > 0)
|
||||
{
|
||||
ADJUST_SCORE(LAST_CHANCE);
|
||||
|
@ -4117,7 +4117,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
|
|||
if (IsStatBoostingBerry(item) && aiData->hpPercents[battlerAtk] > 60)
|
||||
ADJUST_SCORE(WEAK_EFFECT);
|
||||
else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0)
|
||||
&& ((GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) == 1 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0))
|
||||
&& ((GetWhichBattlerFasterOrTies(battlerAtk, battlerDef, TRUE) == 1 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0))
|
||||
|| !CanTargetFaintAiWithMod(battlerDef, battlerAtk, toHeal, 0)))
|
||||
ADJUST_SCORE(WEAK_EFFECT); // Recycle healing berry if we can't otherwise faint the target and the target wont kill us after we activate the berry
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ static void SpriteCB_UnusedBattleInit(struct Sprite *sprite);
|
|||
static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite);
|
||||
static u32 Crc32B (const u8 *data, u32 size);
|
||||
static u32 GeneratePartyHash(const struct Trainer *trainer, u32 i);
|
||||
static s32 Factorial(s32);
|
||||
|
||||
EWRAM_DATA u16 gBattle_BG0_X = 0;
|
||||
EWRAM_DATA u16 gBattle_BG0_Y = 0;
|
||||
|
@ -3807,6 +3808,8 @@ static void TryDoEventsBeforeFirstTurn(void)
|
|||
}
|
||||
#endif // TESTING
|
||||
|
||||
gBattleStruct->speedTieBreaks = RandomUniform(RNG_SPEED_TIE, 0, Factorial(MAX_BATTLERS_COUNT) - 1);
|
||||
|
||||
for (i = 0; i < gBattlersCount; i++)
|
||||
gBattlerByTurnOrder[i] = i;
|
||||
for (i = 0; i < gBattlersCount - 1; i++)
|
||||
|
@ -3977,6 +3980,8 @@ void BattleTurnPassed(void)
|
|||
{
|
||||
s32 i;
|
||||
|
||||
gBattleStruct->speedTieBreaks = RandomUniform(RNG_SPEED_TIE, 0, Factorial(MAX_BATTLERS_COUNT) - 1);
|
||||
|
||||
TurnValuesCleanUp(TRUE);
|
||||
if (gBattleOutcome == 0)
|
||||
{
|
||||
|
@ -4865,9 +4870,10 @@ s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMov
|
|||
strikesFirst = 1;
|
||||
else
|
||||
{
|
||||
if (speedBattler1 == speedBattler2 && Random() & 1)
|
||||
if (speedBattler1 == speedBattler2)
|
||||
{
|
||||
strikesFirst = 0; // same speeds, same priorities
|
||||
// same speeds, same priorities
|
||||
strikesFirst = 0;
|
||||
}
|
||||
else if (speedBattler1 < speedBattler2)
|
||||
{
|
||||
|
@ -4898,7 +4904,7 @@ s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMov
|
|||
return strikesFirst;
|
||||
}
|
||||
|
||||
s32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves)
|
||||
s32 GetWhichBattlerFasterOrTies(u32 battler1, u32 battler2, bool32 ignoreChosenMoves)
|
||||
{
|
||||
s32 priority1 = 0, priority2 = 0;
|
||||
u32 ability1 = GetBattlerAbility(battler1);
|
||||
|
@ -4916,8 +4922,60 @@ s32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves)
|
|||
priority2 = GetChosenMovePriority(battler2);
|
||||
}
|
||||
|
||||
return GetWhichBattlerFasterArgs(battler1, battler2, ignoreChosenMoves, ability1, ability2,
|
||||
holdEffectBattler1, holdEffectBattler2, speedBattler1, speedBattler2, priority1, priority2);
|
||||
return GetWhichBattlerFasterArgs(
|
||||
battler1, battler2,
|
||||
ignoreChosenMoves,
|
||||
ability1, ability2,
|
||||
holdEffectBattler1, holdEffectBattler2,
|
||||
speedBattler1, speedBattler2,
|
||||
priority1, priority2
|
||||
);
|
||||
}
|
||||
|
||||
// 24 == MAX_BATTLERS_COUNT!.
|
||||
// These are the possible orders if all the battlers speed tie. An order
|
||||
// is chosen at the start of the turn.
|
||||
static const u8 sBattlerOrders[24][4] =
|
||||
{
|
||||
{ 0, 1, 2, 3 },
|
||||
{ 0, 1, 3, 2 },
|
||||
{ 0, 2, 1, 3 },
|
||||
{ 0, 2, 3, 1 },
|
||||
{ 0, 3, 1, 2 },
|
||||
{ 0, 3, 2, 1 },
|
||||
{ 1, 0, 2, 3 },
|
||||
{ 1, 0, 3, 2 },
|
||||
{ 1, 2, 0, 3 },
|
||||
{ 1, 2, 3, 0 },
|
||||
{ 1, 3, 0, 2 },
|
||||
{ 1, 3, 2, 0 },
|
||||
{ 2, 0, 1, 3 },
|
||||
{ 2, 0, 3, 1 },
|
||||
{ 2, 1, 0, 3 },
|
||||
{ 2, 1, 3, 0 },
|
||||
{ 2, 3, 0, 1 },
|
||||
{ 2, 3, 1, 0 },
|
||||
{ 3, 0, 1, 2 },
|
||||
{ 3, 0, 2, 1 },
|
||||
{ 3, 1, 0, 2 },
|
||||
{ 3, 1, 2, 0 },
|
||||
{ 3, 2, 0, 1 },
|
||||
{ 3, 2, 1, 0 },
|
||||
};
|
||||
|
||||
s32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves)
|
||||
{
|
||||
s32 strikesFirst = GetWhichBattlerFasterOrTies(battler1, battler2, ignoreChosenMoves);
|
||||
if (strikesFirst == 0)
|
||||
{
|
||||
s32 order1 = sBattlerOrders[gBattleStruct->speedTieBreaks][battler1];
|
||||
s32 order2 = sBattlerOrders[gBattleStruct->speedTieBreaks][battler2];
|
||||
if (order1 < order2)
|
||||
strikesFirst = 1;
|
||||
else
|
||||
strikesFirst = -1;
|
||||
}
|
||||
return strikesFirst;
|
||||
}
|
||||
|
||||
static void SetActionsAndBattlersTurnOrder(void)
|
||||
|
@ -5890,3 +5948,11 @@ bool32 IsWildMonSmart(void)
|
|||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static s32 Factorial(s32 n)
|
||||
{
|
||||
s32 f = 1, i;
|
||||
for (i = 2; i <= n; i++)
|
||||
f *= i;
|
||||
return f;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,10 @@ SINGLE_BATTLE_TEST("Comatose may be suppressed if pokemon transformed into a pok
|
|||
PARAMETRIZE { move = MOVE_THUNDER_WAVE; }
|
||||
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); Speed(30); }
|
||||
// FIXME: Explicit moves currently required here because Ditto
|
||||
// expects to find Celebrate in slot 1 during the second turn
|
||||
// (after transforming).
|
||||
PLAYER(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); Speed(30); Moves(MOVE_CELEBRATE, MOVE_GASTRO_ACID, move); }
|
||||
OPPONENT(SPECIES_DITTO) { Speed(20); }
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_GASTRO_ACID); MOVE(opponent, MOVE_TRANSFORM); }
|
||||
|
|
|
@ -66,10 +66,9 @@ SINGLE_BATTLE_TEST("Turn order is determined by Speed if priority ties")
|
|||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and Speed tie")
|
||||
SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and Speed tie [singles]")
|
||||
{
|
||||
KNOWN_FAILING; // The algorithm is significantly biased.
|
||||
PASSES_RANDOMLY(1, 2);
|
||||
PASSES_RANDOMLY(1, 2, RNG_SPEED_TIE);
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Speed(1); }
|
||||
|
@ -81,6 +80,60 @@ SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and Speed tie"
|
|||
}
|
||||
}
|
||||
|
||||
DOUBLE_BATTLE_TEST("Turn order is determined randomly if priority and Speed tie [doubles]")
|
||||
{
|
||||
struct BattlePokemon *order[4] = { NULL, NULL, NULL, NULL };
|
||||
u32 a, b, c, d;
|
||||
|
||||
// TODO: Test all of these in a single PASSES_RANDOMLY pass rather
|
||||
// than 24 PARAMETRIZEd passes.
|
||||
PARAMETRIZE { a = 0; b = 1; c = 2; d = 3; }
|
||||
PARAMETRIZE { a = 0; b = 1; c = 3; d = 2; }
|
||||
PARAMETRIZE { a = 0; b = 2; c = 1; d = 3; }
|
||||
PARAMETRIZE { a = 0; b = 2; c = 3; d = 1; }
|
||||
PARAMETRIZE { a = 0; b = 3; c = 1; d = 2; }
|
||||
PARAMETRIZE { a = 0; b = 3; c = 2; d = 1; }
|
||||
PARAMETRIZE { a = 1; b = 0; c = 2; d = 3; }
|
||||
PARAMETRIZE { a = 1; b = 0; c = 3; d = 2; }
|
||||
PARAMETRIZE { a = 1; b = 2; c = 0; d = 3; }
|
||||
PARAMETRIZE { a = 1; b = 2; c = 3; d = 0; }
|
||||
PARAMETRIZE { a = 1; b = 3; c = 0; d = 2; }
|
||||
PARAMETRIZE { a = 1; b = 3; c = 2; d = 0; }
|
||||
PARAMETRIZE { a = 2; b = 0; c = 1; d = 3; }
|
||||
PARAMETRIZE { a = 2; b = 0; c = 3; d = 1; }
|
||||
PARAMETRIZE { a = 2; b = 1; c = 0; d = 3; }
|
||||
PARAMETRIZE { a = 2; b = 1; c = 3; d = 0; }
|
||||
PARAMETRIZE { a = 2; b = 3; c = 0; d = 1; }
|
||||
PARAMETRIZE { a = 2; b = 3; c = 1; d = 0; }
|
||||
PARAMETRIZE { a = 3; b = 0; c = 1; d = 2; }
|
||||
PARAMETRIZE { a = 3; b = 0; c = 2; d = 1; }
|
||||
PARAMETRIZE { a = 3; b = 1; c = 0; d = 2; }
|
||||
PARAMETRIZE { a = 3; b = 1; c = 2; d = 0; }
|
||||
PARAMETRIZE { a = 3; b = 2; c = 0; d = 1; }
|
||||
PARAMETRIZE { a = 3; b = 2; c = 1; d = 0; }
|
||||
|
||||
order[a] = playerLeft;
|
||||
order[b] = playerRight;
|
||||
order[c] = opponentLeft;
|
||||
order[d] = opponentRight;
|
||||
|
||||
PASSES_RANDOMLY(1, 24, RNG_SPEED_TIE);
|
||||
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
|
||||
PLAYER(SPECIES_WYNAUT) { Speed(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET) { Speed(1); }
|
||||
OPPONENT(SPECIES_WYNAUT) { Speed(1); }
|
||||
} WHEN {
|
||||
TURN { MOVE(playerLeft, MOVE_SPLASH); MOVE(playerRight, MOVE_SPLASH); MOVE(opponentLeft, MOVE_SPLASH); MOVE(opponentRight, MOVE_SPLASH); }
|
||||
} SCENE {
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[0]);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[1]);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[2]);
|
||||
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, order[3]);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Critical hits occur at a 1/24 rate")
|
||||
{
|
||||
ASSUME(B_CRIT_CHANCE >= GEN_7);
|
||||
|
|
Loading…
Reference in a new issue