From 057928438a824705bd306b52bc3dd43d72989ac0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 16 Aug 2023 16:41:29 -0400 Subject: [PATCH] Document ChooseMoveAndTargetInBattlePalace --- include/battle.h | 7 ++- src/battle_gfx_sfx_util.c | 89 +++++++++++++++++++++++------------- src/battle_script_commands.c | 2 +- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/include/battle.h b/include/battle.h index 118d560984..bb0e42fb83 100644 --- a/include/battle.h +++ b/include/battle.h @@ -392,7 +392,7 @@ struct BattleStruct u8 expGetterBattlerId; u8 unused_5; u8 absentBattlerFlags; - u8 palaceFlags; // First 4 bits are "is < 50% HP and not asleep" for each battler, last 4 bits are selected moves to pass to AI + u8 palaceFlags; // First 4 bits are "is <= 50% HP and not asleep" for each battler, last 4 bits are selected moves to pass to AI u8 field_93; // related to choosing pokemon? u8 wallyBattleState; u8 wallyMovesState; @@ -440,6 +440,11 @@ struct BattleStruct u8 alreadyStatusedMoveAttempt; // As bits for battlers; For example when using Thunder Wave on an already paralyzed pokemon. }; +// The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider, +// and 1 flag per battler to indicate whether the battler is awake and at <= 50% HP (which affects move choice). +// The assert below is to ensure palaceFlags is large enough to store these flags without overlap. +STATIC_ASSERT(sizeof(((struct BattleStruct *)0)->palaceFlags) * 8 >= MAX_BATTLERS_COUNT + MAX_MON_MOVES, PalaceFlagsTooSmall) + #define F_DYNAMIC_TYPE_1 (1 << 6) #define F_DYNAMIC_TYPE_2 (1 << 7) #define DYNAMIC_TYPE_MASK (F_DYNAMIC_TYPE_1 - 1) diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index b610654cef..d98809be46 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -121,9 +121,9 @@ u16 ChooseMoveAndTargetInBattlePalace(void) #define selectedGroup percent #define selectedMoves var2 #define moveTarget var1 - #define validMoveFlags var1 - #define numValidMoveGroups var2 - #define validMoveGroup var2 + #define numMovesPerGroup var1 + #define numMultipleMoveGroups var2 + #define randSelectGroup var2 // If battler is < 50% HP and not asleep, use second set of move group likelihoods // otherwise use first set @@ -172,32 +172,45 @@ u16 ChooseMoveAndTargetInBattlePalace(void) { if (unusableMovesBits != ALL_MOVES_MASK) { - validMoveFlags = 0, numValidMoveGroups = 0; + numMovesPerGroup = 0, numMultipleMoveGroups = 0; for (i = 0; i < MAX_MON_MOVES; i++) { - // validMoveFlags is used here as a bitfield for which moves can be used for each move group type - // first 4 bits are for attack (1 for each move), then 4 bits for defense, and 4 for support + // Count the number of usable moves the battler has in each move group. + // The totals will be stored separately in 3 groups of 4 bits each in numMovesPerGroup. if (GetBattlePalaceMoveGroup(moveInfo->moves[i]) == PALACE_MOVE_GROUP_ATTACK && !(gBitTable[i] & unusableMovesBits)) - validMoveFlags += (1 << 0); + numMovesPerGroup += (1 << 0); if (GetBattlePalaceMoveGroup(moveInfo->moves[i]) == PALACE_MOVE_GROUP_DEFENSE && !(gBitTable[i] & unusableMovesBits)) - validMoveFlags += (1 << 4); + numMovesPerGroup += (1 << 4); if (GetBattlePalaceMoveGroup(moveInfo->moves[i]) == PALACE_MOVE_GROUP_SUPPORT && !(gBitTable[i] & unusableMovesBits)) - validMoveFlags += (1 << 8); + numMovesPerGroup += (1 << 8); } - // Count the move groups the pokemon has - if ((validMoveFlags & 0xF) > 1) - numValidMoveGroups++; - if ((validMoveFlags & 0xF0) > 0x1F) - numValidMoveGroups++; - if ((validMoveFlags & 0xF0) > 0x1FF) - numValidMoveGroups++; + // Count the number of move groups for which the battler has at least 2 usable moves. + // This is a roundabout way to determine if there is a move group that should be + // preferred, because it has multiple move options and the others do not. + // The condition intended to check the total for the Support group is accidentally + // checking the Defense total, and is never true. As a result the preferences for + // random move selection here will skew away from the Support move group. + if ((numMovesPerGroup & 0xF) >= 2) + numMultipleMoveGroups++; + if ((numMovesPerGroup & (0xF << 4)) >= (2 << 4)) + numMultipleMoveGroups++; +#ifdef BUGFIX + if ((numMovesPerGroup & (0xF << 8)) >= (2 << 8)) +#else + if ((numMovesPerGroup & (0xF << 4)) >= (2 << 8)) +#endif + numMultipleMoveGroups++; - // If more than 1 possible move group, or no possible move groups - // then choose move randomly - if (numValidMoveGroups > 1 || numValidMoveGroups == 0) + // By this point we already know the battler only has usable moves from at most 2 of the 3 move groups, + // because they had no usable moves from the move group that was selected based on Nature. + // + // The below condition is effectively 'numMultipleMoveGroups != 1'. + // There is no stand-out group with multiple moves to choose from, so we pick randomly. + // Note that because of the bug above the battler may actually have any number of Support moves. + if (numMultipleMoveGroups > 1 || numMultipleMoveGroups == 0) { do { @@ -206,27 +219,36 @@ u16 ChooseMoveAndTargetInBattlePalace(void) chosenMoveId = i; } while (chosenMoveId == -1); } - // Otherwise randomly choose move of only available move group else { - if ((validMoveFlags & 0xF) > 1) - validMoveGroup = PALACE_MOVE_GROUP_ATTACK; - if ((validMoveFlags & 0xF0) > 0x1F) - validMoveGroup = PALACE_MOVE_GROUP_DEFENSE; - if ((validMoveFlags & 0xF0) > 0x1FF) - validMoveGroup = PALACE_MOVE_GROUP_SUPPORT; + // The battler has just 1 move group with multiple move options to choose from. + // Choose a move randomly from this group. + + // Same bug as the previous set of conditions (the condition for Support is never true). + // This bug won't cause a softlock below, because if Support is the only group with multiple + // moves then it won't have been counted, and the 'numMultipleMoveGroups == 0' above will be true. + if ((numMovesPerGroup & 0xF) >= 2) + randSelectGroup = PALACE_MOVE_GROUP_ATTACK; + if ((numMovesPerGroup & (0xF << 4)) >= (2 << 4)) + randSelectGroup = PALACE_MOVE_GROUP_DEFENSE; +#ifdef BUGFIX + if ((numMovesPerGroup & (0xF << 8)) >= (2 << 8)) +#else + if ((numMovesPerGroup & (0xF << 4)) >= (2 << 8)) +#endif + randSelectGroup = PALACE_MOVE_GROUP_SUPPORT; do { i = Random() % MAX_MON_MOVES; - if (!(gBitTable[i] & unusableMovesBits) && validMoveGroup == GetBattlePalaceMoveGroup(moveInfo->moves[i])) + if (!(gBitTable[i] & unusableMovesBits) && randSelectGroup == GetBattlePalaceMoveGroup(moveInfo->moves[i])) chosenMoveId = i; } while (chosenMoveId == -1); } - // If a move was selected (and in this case was not from the Nature-chosen group) - // then there's a 50% chance it won't be used anyway - if (Random() % 100 > 49) + // Because the selected move was not from the Nature-chosen move group there's a 50% chance + // that it will be unable to use it. This could have been checked earlier to avoid the above work. + if (Random() % 100 >= 50) { gProtectStructs[gActiveBattler].palaceUnableToUseMove = TRUE; return 0; @@ -234,6 +256,7 @@ u16 ChooseMoveAndTargetInBattlePalace(void) } else { + // All the battler's moves were flagged as unusable. gProtectStructs[gActiveBattler].palaceUnableToUseMove = TRUE; return 0; } @@ -266,9 +289,9 @@ u16 ChooseMoveAndTargetInBattlePalace(void) #undef selectedGroup #undef selectedMoves #undef moveTarget -#undef validMoveFlags -#undef numValidMoveGroups -#undef validMoveGroup +#undef numMovesPerGroup +#undef numMultipleMoveGroups +#undef randSelectGroup static u8 GetBattlePalaceMoveGroup(u16 move) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index ab8e6ad8ed..63569d60c4 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -849,7 +849,7 @@ static const u8 sBallCatchBonuses[] = // In Battle Palace, moves are chosen based on the pokemons nature rather than by the player // Moves are grouped into "Attack", "Defense", or "Support" (see PALACE_MOVE_GROUP_*) // Each nature has a certain percent chance of selecting a move from a particular group -// and a separate percent chance for each group when below 50% HP +// and a separate percent chance for each group when at or below 50% HP // The table below doesn't list percentages for Support because you can subtract the other two // Support percentages are listed in comments off to the side instead #define PALACE_STYLE(atk, def, atkLow, defLow) {atk, atk + def, atkLow, atkLow + defLow}