03.08.2024 Master merge (#5083)

This commit is contained in:
Bassoonian 2024-08-03 19:48:56 +01:00 committed by GitHub
commit 052258addd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 136 additions and 12 deletions

View file

@ -43,7 +43,7 @@
* Migration script available in `migration_scripts/egg_move_refactor.py` by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/5040 * Migration script available in `migration_scripts/egg_move_refactor.py` by @AlexOn1ine in https://github.com/rh-hideout/pokeemerald-expansion/pull/5040
* Added documentation to `STATIC_ASSERTS` used by the `BoxPokemon` after 1.8.0 by @pkmnsnfrn in https://github.com/rh-hideout/pokeemerald-expansion/pull/4294 * Added documentation to `STATIC_ASSERTS` used by the `BoxPokemon` after 1.8.0 by @pkmnsnfrn in https://github.com/rh-hideout/pokeemerald-expansion/pull/4294
* #### Competitive-formatted parties by @mrgriffin in https://github.com/rh-hideout/pokeemerald-expansion/pull/3545 * #### Competitive-formatted parties by @mrgriffin in https://github.com/rh-hideout/pokeemerald-expansion/pull/3545
* Can be disabled by setting `COMPETITIVE_PARTY_SYNTAX` to `FALSE` in `include/config/general.h`. * Can be disabled by setting `COMPETITIVE_PARTY_SYNTAX` to `FALSE` in `include/config/general.h`. If migrating from 1.8, remove the first and last lines from `src/data/trainers.h` (`const struct Trainer gTrainers[] = {` and `};` respectively).
* Introduces `trainerproc`, a tool which converts Competitive-formatted parties into Trainer Control-formatted parties. * Introduces `trainerproc`, a tool which converts Competitive-formatted parties into Trainer Control-formatted parties.
* If you made custom changes to the following files and want to use this new format, ***Do not accept the incoming changes for them.*** Instead, use the migration script present in `migration_scripts/convert_parties.py`: * If you made custom changes to the following files and want to use this new format, ***Do not accept the incoming changes for them.*** Instead, use the migration script present in `migration_scripts/convert_parties.py`:
- `src/data/trainers.h` - `src/data/trainers.h`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1,019 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

After

Width:  |  Height:  |  Size: 949 B

View file

@ -795,6 +795,7 @@ struct BattleStruct
u8 quickClawRandom[MAX_BATTLERS_COUNT]; u8 quickClawRandom[MAX_BATTLERS_COUNT];
u8 quickDrawRandom[MAX_BATTLERS_COUNT]; u8 quickDrawRandom[MAX_BATTLERS_COUNT];
u8 shellSideArmCategory[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT]; u8 shellSideArmCategory[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT];
u8 speedTieBreaks; // MAX_BATTLERS_COUNT! values.
u8 boosterEnergyActivates; u8 boosterEnergyActivates;
u8 distortedTypeMatchups; u8 distortedTypeMatchups;
u8 categoryOverride; // for Z-Moves and Max Moves u8 categoryOverride; // for Z-Moves and Max Moves

View file

@ -75,6 +75,7 @@ s8 GetChosenMovePriority(u32 battlerId);
s8 GetMovePriority(u32 battlerId, u16 move); s8 GetMovePriority(u32 battlerId, u16 move);
s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2, s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMoves, u32 ability1, u32 ability2,
u32 holdEffectBattler1, u32 holdEffectBattler2, u32 speedBattler1, u32 speedBattler2, s32 priority1, s32 priority2); 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); s32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves);
void RunBattleScriptCommands_PopCallbacksStack(void); void RunBattleScriptCommands_PopCallbacksStack(void);
void RunBattleScriptCommands(void); void RunBattleScriptCommands(void);

View file

@ -2722,7 +2722,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(SLOW_KILL); ADJUST_SCORE(SLOW_KILL);
} }
else if (CanTargetFaintAi(battlerDef, battlerAtk) else if (CanTargetFaintAi(battlerDef, battlerAtk)
&& GetWhichBattlerFaster(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER && GetWhichBattlerFasterOrTies(battlerAtk, battlerDef, TRUE) != AI_IS_FASTER
&& GetMovePriority(battlerAtk, move) > 0) && GetMovePriority(battlerAtk, move) > 0)
{ {
ADJUST_SCORE(LAST_CHANCE); 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) if (IsStatBoostingBerry(item) && aiData->hpPercents[battlerAtk] > 60)
ADJUST_SCORE(WEAK_EFFECT); ADJUST_SCORE(WEAK_EFFECT);
else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0) 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))) || !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 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
} }

View file

@ -120,6 +120,7 @@ static void SpriteCB_UnusedBattleInit(struct Sprite *sprite);
static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite); static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite);
static u32 Crc32B (const u8 *data, u32 size); static u32 Crc32B (const u8 *data, u32 size);
static u32 GeneratePartyHash(const struct Trainer *trainer, u32 i); 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_X = 0;
EWRAM_DATA u16 gBattle_BG0_Y = 0; EWRAM_DATA u16 gBattle_BG0_Y = 0;
@ -3807,6 +3808,8 @@ static void TryDoEventsBeforeFirstTurn(void)
} }
#endif // TESTING #endif // TESTING
gBattleStruct->speedTieBreaks = RandomUniform(RNG_SPEED_TIE, 0, Factorial(MAX_BATTLERS_COUNT) - 1);
for (i = 0; i < gBattlersCount; i++) for (i = 0; i < gBattlersCount; i++)
gBattlerByTurnOrder[i] = i; gBattlerByTurnOrder[i] = i;
for (i = 0; i < gBattlersCount - 1; i++) for (i = 0; i < gBattlersCount - 1; i++)
@ -3977,6 +3980,8 @@ void BattleTurnPassed(void)
{ {
s32 i; s32 i;
gBattleStruct->speedTieBreaks = RandomUniform(RNG_SPEED_TIE, 0, Factorial(MAX_BATTLERS_COUNT) - 1);
TurnValuesCleanUp(TRUE); TurnValuesCleanUp(TRUE);
if (gBattleOutcome == 0) if (gBattleOutcome == 0)
{ {
@ -4865,9 +4870,10 @@ s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMov
strikesFirst = 1; strikesFirst = 1;
else 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) else if (speedBattler1 < speedBattler2)
{ {
@ -4898,7 +4904,7 @@ s32 GetWhichBattlerFasterArgs(u32 battler1, u32 battler2, bool32 ignoreChosenMov
return strikesFirst; return strikesFirst;
} }
s32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves) s32 GetWhichBattlerFasterOrTies(u32 battler1, u32 battler2, bool32 ignoreChosenMoves)
{ {
s32 priority1 = 0, priority2 = 0; s32 priority1 = 0, priority2 = 0;
u32 ability1 = GetBattlerAbility(battler1); u32 ability1 = GetBattlerAbility(battler1);
@ -4916,8 +4922,60 @@ s32 GetWhichBattlerFaster(u32 battler1, u32 battler2, bool32 ignoreChosenMoves)
priority2 = GetChosenMovePriority(battler2); priority2 = GetChosenMovePriority(battler2);
} }
return GetWhichBattlerFasterArgs(battler1, battler2, ignoreChosenMoves, ability1, ability2, return GetWhichBattlerFasterArgs(
holdEffectBattler1, holdEffectBattler2, speedBattler1, speedBattler2, priority1, priority2); 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) static void SetActionsAndBattlersTurnOrder(void)
@ -5890,3 +5948,11 @@ bool32 IsWildMonSmart(void)
return FALSE; return FALSE;
#endif #endif
} }
static s32 Factorial(s32 n)
{
s32 f = 1, i;
for (i = 2; i <= n; i++)
f *= i;
return f;
}

View file

@ -34,7 +34,10 @@ SINGLE_BATTLE_TEST("Comatose may be suppressed if pokemon transformed into a pok
PARAMETRIZE { move = MOVE_THUNDER_WAVE; } PARAMETRIZE { move = MOVE_THUNDER_WAVE; }
GIVEN { 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); } OPPONENT(SPECIES_DITTO) { Speed(20); }
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_GASTRO_ACID); MOVE(opponent, MOVE_TRANSFORM); } TURN { MOVE(player, MOVE_GASTRO_ACID); MOVE(opponent, MOVE_TRANSFORM); }

View file

@ -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, RNG_SPEED_TIE);
PASSES_RANDOMLY(1, 2);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); } PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(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") SINGLE_BATTLE_TEST("Critical hits occur at a 1/24 rate")
{ {
ASSUME(B_CRIT_CHANCE >= GEN_7); ASSUME(B_CRIT_CHANCE >= GEN_7);