diff --git a/include/battle.h b/include/battle.h index 9844eb2d63..86205ad103 100644 --- a/include/battle.h +++ b/include/battle.h @@ -431,19 +431,20 @@ struct ZMoveData { /*0x00*/ u8 viable:1; // current move can become a z move u8 viewing:1; //if player is viewing the z move name instead of regular moves - u8 split:2; u8 active:1; //is z move being used this turn u8 zStatusActive:1; u8 healReplacement:1; //TODO: z-parting shot + u8 activeSplit:1; //active z move split u8 zUnused:2; - /*0x02*/ u16 currZMove; //z move of move cursor is on - /*0x06*/ u8 triggerSpriteId; + /*0x01*/ u8 triggerSpriteId; + /*0x02*/ u8 possibleZMoves[MAX_BATTLERS_COUNT]; + /*0x02*/ u16 chosenZMove; //z move of move cursor is on u8 effect; u8 used[MAX_BATTLERS_COUNT]; //one per bank for multi-battles u16 toBeUsed[MAX_BATTLERS_COUNT]; //TODO z moves per battler to be used u16 baseMoves[MAX_BATTLERS_COUNT]; u8 splits[MAX_BATTLERS_COUNT]; -}; /* size = 8 */ +}; struct BattleStruct { diff --git a/include/battle_ai_script_commands.h b/include/battle_ai_script_commands.h index f8c27bd87e..31313fc3d5 100644 --- a/include/battle_ai_script_commands.h +++ b/include/battle_ai_script_commands.h @@ -7,7 +7,7 @@ #define AI_CHOICE_WATCH 5 #define AI_CHOICE_SWITCH 7 -s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef); +s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, bool32 considerZPower); s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon *mon); u16 AI_GetTypeEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef); void BattleAI_SetupItems(void); diff --git a/include/battle_controllers.h b/include/battle_controllers.h index e0eeca6be3..b0fbf485a8 100644 --- a/include/battle_controllers.h +++ b/include/battle_controllers.h @@ -113,6 +113,7 @@ struct ChooseMoveStruct u8 monType2; u8 monType3; struct MegaEvolutionData mega; + struct ZMoveData zmove; }; enum diff --git a/include/battle_z_move.h b/include/battle_z_move.h index 70347439a7..ae2b38834e 100644 --- a/include/battle_z_move.h +++ b/include/battle_z_move.h @@ -15,7 +15,7 @@ struct SignatureZMove void QueueZMove(u8 battlerId, u16 baseMove); bool32 IsViableZMove(u8 battlerId, u16 move); -bool32 TryChangeZIndicator(u8 battlerId, u16 move); +bool32 TryChangeZIndicator(u8 battlerId, u8 moveIndex); void CreateZMoveTriggerSprite(u8, bool8); void HideZMoveTriggerSprite(void); bool32 IsZMoveTriggerSpriteActive(void); @@ -23,6 +23,8 @@ void DestroyZMoveTriggerSprite(void); bool32 MoveSelectionDisplayZMove(u16 zmove); const u8* GetZMoveName(u16 move); void SetZEffect(void); -bool32 ShouldAIUseZMove(u8 activeId, u8 targetId, u16 *baseMove, u8 *chosenMoveId); +bool32 ShouldAIUseZMove(u8 activeId, u8 targetId, u16 chosenMove); +bool32 IsZMoveUsable(u8 battlerId, u16 moveIndex); +void GetUsableZMoves(u8 battlerId, u16 *moves); #endif // GUARD_BATTLE_Z_MOVE_H \ No newline at end of file diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index 0f30471993..a5c14023f2 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -145,9 +145,6 @@ #define B_INCINERATE_GEMS GEN_6 // In Gen6+, Incinerate can destroy Gems. #define B_MINIMIZE_DMG_ACC GEN_6 // In Gen6+, moves that causes double damage to minimized Pokémon will also skip accuracy checks. -// AI Settings -#define B_AI_PREFER_STATUS_Z_MOVES FALSE // If TRUE, the AI will prefer z-status moves over damaging z moves - // Ability settings #define B_ABILITY_WEATHER GEN_6 // In Gen5+, weather caused by abilities lasts the same amount of turns as induced from a move. Before, they lasted till the battle's end or weather change by a move. #define B_GALE_WINGS GEN_6 // In Gen7+ requires full HP to trigger. diff --git a/src/battle_ai_script_commands.c b/src/battle_ai_script_commands.c index 9c39c935ce..d7e5e94c35 100644 --- a/src/battle_ai_script_commands.c +++ b/src/battle_ai_script_commands.c @@ -5,6 +5,7 @@ #include "battle_ai_script_commands.h" #include "battle_factory.h" #include "battle_setup.h" +#include "battle_z_move.h" #include "data.h" #include "item.h" #include "pokemon.h" @@ -422,9 +423,10 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves) { dmg = 0; move = gBattleMons[sBattler_AI].moves[i]; + if (gBattleMoves[move].power != 0 && !(moveLimitations & gBitTable[i])) { - dmg = AI_CalcDamage(move, sBattler_AI, gBattlerTarget) * (100 - (Random() % 10)) / 100; + dmg = AI_CalcDamage(move, sBattler_AI, gBattlerTarget, TRUE) * (100 - (Random() % 10)) / 100; if (dmg == 0) dmg = 1; } @@ -904,10 +906,16 @@ static bool32 AI_GetIfCrit(u32 move, u8 battlerAtk, u8 battlerDef) return isCrit; } -s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef) +s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef, bool32 considerZPower) { s32 dmg, moveType; - + + if (considerZPower && IsViableZMove(battlerAtk, move)) + { + gBattleStruct->zmove.baseMoves[battlerAtk] = move; + gBattleStruct->zmove.active = TRUE; //temporarily enable z moves for damage calcs + } + SaveBattlerData(battlerAtk); SaveBattlerData(battlerDef); @@ -922,6 +930,8 @@ s32 AI_CalcDamage(u16 move, u8 battlerAtk, u8 battlerDef) RestoreBattlerData(battlerAtk); RestoreBattlerData(battlerDef); + gBattleStruct->zmove.active = FALSE; + gBattleStruct->zmove.baseMoves[battlerAtk] = MOVE_NONE; return dmg; } @@ -935,7 +945,7 @@ s32 AI_CalcPartyMonDamage(u16 move, u8 battlerAtk, u8 battlerDef, struct Pokemon battleMons[i] = gBattleMons[i]; PokemonToBattleMon(mon, &gBattleMons[battlerAtk]); - dmg = AI_CalcDamage(move, battlerAtk, battlerDef); + dmg = AI_CalcDamage(move, battlerAtk, battlerDef, TRUE); for (i = 0; i < MAX_BATTLERS_COUNT; i++) gBattleMons[i] = battleMons[i]; @@ -2674,7 +2684,7 @@ static void Cmd_if_ai_can_go_down(void) for (i = 0; i < MAX_MON_MOVES; i++) { if (moves[i] != MOVE_NONE && moves[i] != 0xFFFF && !(unusable & gBitTable[i]) - && AI_CalcDamage(moves[i], gBattlerTarget, sBattler_AI) >= gBattleMons[sBattler_AI].hp) + && AI_CalcDamage(moves[i], gBattlerTarget, sBattler_AI, TRUE) >= gBattleMons[sBattler_AI].hp) { gAIScriptPtr = T1_READ_PTR(gAIScriptPtr + 1); return; diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 36f05b6a84..9e6a61d402 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -1576,26 +1576,18 @@ static void OpponentHandleChooseMove(void) default: { u16 chosenMove = moveInfo->moves[chosenMoveId]; - - if (ShouldAIUseZMove(gActiveBattler, gBattlerTarget, moveInfo->moves, &chosenMoveId)) + + if (gBattleMoves[chosenMove].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) + gBattlerTarget = gActiveBattler; + if (gBattleMoves[chosenMove].target & MOVE_TARGET_BOTH) { - QueueZMove(gActiveBattler, moveInfo->moves[chosenMoveId]); - chosenMove = moveInfo->moves[chosenMoveId]; gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); } - else - { - if (gBattleMoves[chosenMove].target & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) - gBattlerTarget = gActiveBattler; - if (gBattleMoves[chosenMove].target & MOVE_TARGET_BOTH) - { - gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); - } - } + + if (ShouldAIUseZMove(gActiveBattler, gBattlerTarget, chosenMove)) + QueueZMove(gActiveBattler, moveInfo->moves[chosenMoveId]); if (CanMegaEvolve(gActiveBattler)) // If opponent can mega evolve, do it. BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 034c45eb67..ca67171801 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -653,9 +653,9 @@ static void HandleInputChooseMove(void) PlayerBufferExecCompleted(); } } - else if (JOY_NEW(DPAD_LEFT)) + else if (JOY_NEW(DPAD_LEFT) && !gBattleStruct->zmove.viewing) { - if (!gBattleStruct->zmove.viewing && gMoveSelectionCursor[gActiveBattler] & 1) + if (gMoveSelectionCursor[gActiveBattler] & 1) { MoveSelectionDestroyCursorAt(gMoveSelectionCursor[gActiveBattler]); gMoveSelectionCursor[gActiveBattler] ^= 1; @@ -663,12 +663,12 @@ static void HandleInputChooseMove(void) MoveSelectionCreateCursorAt(gMoveSelectionCursor[gActiveBattler], 0); MoveSelectionDisplayPpNumber(); MoveSelectionDisplayMoveType(); - TryChangeZIndicator(gActiveBattler, moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]); + TryChangeZIndicator(gActiveBattler, gMoveSelectionCursor[gActiveBattler]); } } - else if (JOY_NEW(DPAD_RIGHT)) + else if (JOY_NEW(DPAD_RIGHT) && !gBattleStruct->zmove.viewing) { - if (!gBattleStruct->zmove.viewing && !(gMoveSelectionCursor[gActiveBattler] & 1) + if (!(gMoveSelectionCursor[gActiveBattler] & 1) && (gMoveSelectionCursor[gActiveBattler] ^ 1) < gNumberOfMovesToChoose) { MoveSelectionDestroyCursorAt(gMoveSelectionCursor[gActiveBattler]); @@ -677,12 +677,12 @@ static void HandleInputChooseMove(void) MoveSelectionCreateCursorAt(gMoveSelectionCursor[gActiveBattler], 0); MoveSelectionDisplayPpNumber(); MoveSelectionDisplayMoveType(); - TryChangeZIndicator(gActiveBattler, moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]); + TryChangeZIndicator(gActiveBattler, gMoveSelectionCursor[gActiveBattler]); } } - else if (JOY_NEW(DPAD_UP)) + else if (JOY_NEW(DPAD_UP) && !gBattleStruct->zmove.viewing) { - if (!gBattleStruct->zmove.viewing && gMoveSelectionCursor[gActiveBattler] & 2) + if (gMoveSelectionCursor[gActiveBattler] & 2) { MoveSelectionDestroyCursorAt(gMoveSelectionCursor[gActiveBattler]); gMoveSelectionCursor[gActiveBattler] ^= 2; @@ -690,12 +690,12 @@ static void HandleInputChooseMove(void) MoveSelectionCreateCursorAt(gMoveSelectionCursor[gActiveBattler], 0); MoveSelectionDisplayPpNumber(); MoveSelectionDisplayMoveType(); - TryChangeZIndicator(gActiveBattler, moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]); + TryChangeZIndicator(gActiveBattler, gMoveSelectionCursor[gActiveBattler]); } } - else if (JOY_NEW(DPAD_DOWN)) + else if (JOY_NEW(DPAD_DOWN) && !gBattleStruct->zmove.viewing) { - if (!gBattleStruct->zmove.viewing && !(gMoveSelectionCursor[gActiveBattler] & 2) + if (!(gMoveSelectionCursor[gActiveBattler] & 2) && (gMoveSelectionCursor[gActiveBattler] ^ 2) < gNumberOfMovesToChoose) { MoveSelectionDestroyCursorAt(gMoveSelectionCursor[gActiveBattler]); @@ -704,7 +704,7 @@ static void HandleInputChooseMove(void) MoveSelectionCreateCursorAt(gMoveSelectionCursor[gActiveBattler], 0); MoveSelectionDisplayPpNumber(); MoveSelectionDisplayMoveType(); - TryChangeZIndicator(gActiveBattler, moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]); + TryChangeZIndicator(gActiveBattler, gMoveSelectionCursor[gActiveBattler]); } } else if (JOY_NEW(SELECT_BUTTON) && !gBattleStruct->zmove.viewing) @@ -723,7 +723,7 @@ static void HandleInputChooseMove(void) gBattlerControllerFuncs[gActiveBattler] = HandleMoveSwitching; } } - else if (gMain.newKeys & START_BUTTON) + else if (JOY_NEW(START_BUTTON)) { if (CanMegaEvolve(gActiveBattler)) { @@ -731,13 +731,13 @@ static void HandleInputChooseMove(void) ChangeMegaTriggerSprite(gBattleStruct->mega.triggerSpriteId, gBattleStruct->mega.playerSelect); PlaySE(SE_SELECT); } - else if (gBattleStruct->zmove.currZMove != MOVE_NONE) + else if (gBattleStruct->zmove.viable) { // show z move name / info //TODO: brighten z move symbol PlaySE(SE_SELECT); if (!gBattleStruct->zmove.viewing) - MoveSelectionDisplayZMove(gBattleStruct->zmove.currZMove); + MoveSelectionDisplayZMove(gBattleStruct->zmove.chosenZMove); else ReloadMoveNames(); } @@ -2766,7 +2766,9 @@ static void PlayerHandleChooseMove(void) CreateMegaTriggerSprite(gActiveBattler, 0); if (!IsZMoveTriggerSpriteActive()) gBattleStruct->zmove.triggerSpriteId = 0xFF; - gBattleStruct->zmove.viable = IsViableZMove(gActiveBattler, moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]); //is current move a z move + + GetUsableZMoves(gActiveBattler, moveInfo->moves); + gBattleStruct->zmove.viable = IsZMoveUsable(gActiveBattler, gMoveSelectionCursor[gActiveBattler]); CreateZMoveTriggerSprite(gActiveBattler, gBattleStruct->zmove.viable); gBattlerControllerFuncs[gActiveBattler] = HandleChooseMoveAfterDma3; } diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 9743e92e00..14dced42f4 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -7,6 +7,7 @@ #include "battle_interface.h" #include "battle_setup.h" #include "battle_tower.h" +#include "battle_z_move.h" #include "bg.h" #include "data.h" #include "item_use.h" @@ -1516,7 +1517,7 @@ static void PlayerPartnerHandleChooseMove(void) BattleAI_SetupAIData(0xF); chosenMoveId = BattleAI_ChooseMoveOrAction(); - + if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) gBattlerTarget = gActiveBattler; if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH) @@ -1525,6 +1526,9 @@ static void PlayerPartnerHandleChooseMove(void) if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); } + + if (ShouldAIUseZMove(gActiveBattler, gBattlerTarget, moveInfo->moves[chosenMoveId])) + QueueZMove(gActiveBattler, moveInfo->moves[chosenMoveId]); BtlController_EmitTwoReturnValues(1, 10, chosenMoveId | (gBattlerTarget << 8)); PlayerPartnerBufferExecCompleted(); diff --git a/src/battle_main.c b/src/battle_main.c index e5ba05bbc7..6b1e26a976 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3801,6 +3801,7 @@ static void HandleTurnActionSelectionState(void) { struct ChooseMoveStruct moveInfo; + moveInfo.zmove = gBattleStruct->zmove; moveInfo.mega = gBattleStruct->mega; moveInfo.species = gBattleMons[gActiveBattler].species; moveInfo.monType1 = gBattleMons[gActiveBattler].type1; @@ -3913,6 +3914,7 @@ static void HandleTurnActionSelectionState(void) } gBattleStruct->mega.toEvolve &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(gActiveBattler))]); + gBattleStruct->zmove.toBeUsed[BATTLE_PARTNER(GetBattlerPosition(gActiveBattler))] = MOVE_NONE; BtlController_EmitEndBounceEffect(0); MarkBattlerForControllerExec(gActiveBattler); return; diff --git a/src/battle_util.c b/src/battle_util.c index 3f856e23b2..9a5ca9dcf2 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1400,7 +1400,7 @@ u8 TrySetCantSelectMoveBattleScript(void) } } - if (gDisableStructs[gActiveBattler].tauntTimer != 0 && gBattleMoves[move].power == 0) + if (!gBattleStruct->zmove.active && gDisableStructs[gActiveBattler].tauntTimer != 0 && gBattleMoves[move].power == 0) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1415,7 +1415,7 @@ u8 TrySetCantSelectMoveBattleScript(void) } } - if (gDisableStructs[gActiveBattler].throatChopTimer != 0 && gBattleMoves[move].flags & FLAG_SOUND) + if (!gBattleStruct->zmove.active && gDisableStructs[gActiveBattler].throatChopTimer != 0 && gBattleMoves[move].flags & FLAG_SOUND) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1430,7 +1430,7 @@ u8 TrySetCantSelectMoveBattleScript(void) } } - if (GetImprisonedMovesCount(gActiveBattler, move)) + if (!gBattleStruct->zmove.active && GetImprisonedMovesCount(gActiveBattler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1445,7 +1445,7 @@ u8 TrySetCantSelectMoveBattleScript(void) } } - if (IsGravityPreventingMove(move)) + if (!gBattleStruct->zmove.active && IsGravityPreventingMove(move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1460,7 +1460,7 @@ u8 TrySetCantSelectMoveBattleScript(void) } } - if (IsHealBlockPreventingMove(gActiveBattler, move)) + if (!gBattleStruct->zmove.active && IsHealBlockPreventingMove(gActiveBattler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1475,7 +1475,7 @@ u8 TrySetCantSelectMoveBattleScript(void) } } - if (IsBelchPreventingMove(gActiveBattler, move)) + if (!gBattleStruct->zmove.active && IsBelchPreventingMove(gActiveBattler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1543,7 +1543,7 @@ u8 CheckMoveLimitations(u8 battlerId, u8 unusableMoves, u8 check) s32 i; gPotentialItemEffectBattler = battlerId; - + for (i = 0; i < MAX_MON_MOVES; i++) { if (gBattleMons[battlerId].moves[i] == 0 && check & MOVE_LIMITATION_ZEROMOVE) @@ -3191,9 +3191,12 @@ u8 AtkCanceller_UnableToUseMove(void) { //attacker has a queued z move gBattleStruct->zmove.active = TRUE; + gBattleStruct->zmove.activeSplit = gBattleStruct->zmove.splits[gBattlerAttacker]; RecordItemEffectBattle(gBattlerAttacker, HOLD_EFFECT_Z_CRYSTAL); - gBattleStruct->zmove.used[gBattlerAttacker] = TRUE; - //TODO - partner battles + gBattleStruct->zmove.used[gBattlerAttacker] = TRUE; + if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && IsPartnerMonFromSameTrainer(gBattlerAttacker)) + gBattleStruct->zmove.used[BATTLE_PARTNER(gBattlerAttacker)] = TRUE; //if 1v1 double, set partner used flag as well + gBattleScripting.battler = gBattlerAttacker; if (IS_MOVE_STATUS(gBattleStruct->zmove.splits[gBattlerAttacker])) { @@ -7811,9 +7814,8 @@ bool8 ShouldGetStatBadgeBoost(u16 badgeFlag, u8 battlerId) u8 GetBattleMoveSplit(u32 moveId) { - //TODO - light that burns the sky, photon geyser if (gBattleStruct->zmove.active && !IS_MOVE_STATUS(moveId)) - return gBattleStruct->zmove.split; + return gBattleStruct->zmove.activeSplit; else if (IS_MOVE_STATUS(moveId) || B_PHYSICAL_SPECIAL_SPLIT >= GEN_4) return gBattleMoves[moveId].split; else if (gBattleMoves[moveId].type < TYPE_MYSTERY) diff --git a/src/battle_z_move.c b/src/battle_z_move.c index e8dc7210be..3bb67f6840 100644 --- a/src/battle_z_move.c +++ b/src/battle_z_move.c @@ -140,9 +140,10 @@ bool8 IsZMove(u16 move) void QueueZMove(u8 battlerId, u16 baseMove) { - gBattleStruct->zmove.toBeUsed[battlerId] = gBattleStruct->zmove.currZMove; + gBattleStruct->zmove.toBeUsed[battlerId] = gBattleStruct->zmove.chosenZMove; gBattleStruct->zmove.baseMoves[battlerId] = baseMove; - gBattleStruct->zmove.splits[battlerId] = GetBattleMoveSplit(baseMove); + //TODO - light that burns the sky + gBattleStruct->zmove.splits[battlerId] = gBattleMoves[baseMove].split; } bool32 IsViableZMove(u8 battlerId, u16 move) @@ -155,19 +156,19 @@ bool32 IsViableZMove(u8 battlerId, u16 move) u16 holdEffect; u16 species; - gBattleStruct->zmove.currZMove = MOVE_NONE; //init - if (gBattleStruct->zmove.used[battlerId]) return FALSE; - // Gets mon data. - if (GetBattlerSide(battlerId) == B_SIDE_OPPONENT) + // Gets mon data + species = gBattleMons[battlerId].species; + item = gBattleMons[battlerId].item; + /*if (GetBattlerSide(battlerId) == B_SIDE_OPPONENT) mon = &gEnemyParty[gBattlerPartyIndexes[battlerId]]; else mon = &gPlayerParty[gBattlerPartyIndexes[battlerId]]; species = GetMonData(mon, MON_DATA_SPECIES); - item = GetMonData(mon, MON_DATA_HELD_ITEM); + item = GetMonData(mon, MON_DATA_HELD_ITEM);*/ if (gBattleTypeFlags & (BATTLE_TYPE_SAFARI | BATTLE_TYPE_WALLY_TUTORIAL | BATTLE_TYPE_FRONTIER)) return FALSE; @@ -202,16 +203,16 @@ bool32 IsViableZMove(u8 battlerId, u16 move) u16 zMove = GetSignatureZMove(move, gBattleMons[battlerId].species, item); if (zMove != MOVE_NONE) { - gBattleStruct->zmove.currZMove = zMove; //signature z move exists + gBattleStruct->zmove.chosenZMove = zMove; //signature z move exists return TRUE; } if (move != MOVE_NONE && zMove != MOVE_Z_STATUS && gBattleMoves[move].type == ItemId_GetSecondaryId(item)) { if (IS_MOVE_STATUS(gBattleMoves[move].split)) - gBattleStruct->zmove.currZMove = MOVE_Z_STATUS; + gBattleStruct->zmove.chosenZMove = move; else - gBattleStruct->zmove.currZMove = GetTypeBasedZMove(move, battlerId); + gBattleStruct->zmove.chosenZMove = GetTypeBasedZMove(move, battlerId); return TRUE; } @@ -220,9 +221,30 @@ bool32 IsViableZMove(u8 battlerId, u16 move) return FALSE; } -bool32 TryChangeZIndicator(u8 battlerId, u16 move) +void GetUsableZMoves(u8 battlerId, u16 *moves) { - bool32 viableZMove = IsViableZMove(battlerId, move); + u32 i; + gBattleStruct->zmove.possibleZMoves[battlerId] = 0; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && IsViableZMove(battlerId, moves[i])) + gBattleStruct->zmove.possibleZMoves[battlerId] |= (1 << i); + } +} + +bool32 IsZMoveUsable(u8 battlerId, u16 moveIndex) +{ + if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && IsPartnerMonFromSameTrainer(battlerId) && gBattleStruct->zmove.toBeUsed[BATTLE_PARTNER(battlerId)] != MOVE_NONE) + return FALSE; //player's other mon has a z move queued up already + if (gBattleStruct->zmove.possibleZMoves[battlerId] & (1 << moveIndex)) + return TRUE; + return FALSE; +} + +bool32 TryChangeZIndicator(u8 battlerId, u8 moveIndex) +{ + //bool32 viableZMove = IsViableZMove(battlerId, move); + bool32 viableZMove = IsZMoveUsable(battlerId, moveIndex); if (gBattleStruct->zmove.viable && !viableZMove) HideZMoveTriggerSprite(); //was a viable z move, now is not -> slide out @@ -268,7 +290,6 @@ void CreateZMoveTriggerSprite(u8 battlerId, bool8 viable) gSprites[gBattleStruct->zmove.triggerSpriteId].tBattler = battlerId; gSprites[gBattleStruct->zmove.triggerSpriteId].tHide = (viable == TRUE) ? FALSE : TRUE; - ChangeMegaTriggerSprite(gBattleStruct->zmove.triggerSpriteId, 0); } static void SpriteCB_ZMoveTrigger(struct Sprite *sprite) @@ -351,6 +372,7 @@ void DestroyZMoveTriggerSprite(void) FreeSpriteTilesByTag(TAG_ZMOVE_TRIGGER_TILE); if (gBattleStruct->zmove.triggerSpriteId != 0xFF) DestroySprite(&gSprites[gBattleStruct->zmove.triggerSpriteId]); + gBattleStruct->zmove.triggerSpriteId = 0xFF; } @@ -370,13 +392,9 @@ static u16 GetSignatureZMove(u16 move, u16 species, u16 item) static u16 GetTypeBasedZMove(u16 move, u8 battler) { - u8 moveType; - //handle dynamic move types - SetTypeBeforeUsingMove(battler, move); - GET_MOVE_TYPE(move, moveType); + u8 moveType = gBattleMoves[move].type; - // get z move from split - // TODO: light that burns the sky gets split from relative stats + // get z move from type if (moveType < TYPE_FIRE) return MOVE_BREAKNECK_BLITZ + moveType; else if (moveType >= TYPE_FAIRY) @@ -683,259 +701,35 @@ static bool32 AreStatsMaxed(u8 battlerId, u8 n) return TRUE; } -#define STAT_CAN_RISE(bank, stat) ((gBattleMons[bank].statStages[stat-1] < 12 && GetBattlerAbility(bank) != ABILITY_CONTRARY) || (GetBattlerAbility(bank) == ABILITY_CONTRARY && gBattleMons[bank].statStages[stat-1] > 0)) -#define STAT_CAN_FALL(bank, stat) ((gBattleMons[bank].statStages[stat-1] > 0 && GetBattlerAbility(bank) != ABILITY_CONTRARY) || (GetBattlerAbility(bank) == ABILITY_CONTRARY && gBattleMons[bank].statStages[stat-1] < 12)) //TODO - this could be a lot better -bool32 ShouldAIUseZMove(u8 battlerAtk, u8 battlerDef, u16 *baseMoves, u8 *chosenMoveId) +bool32 ShouldAIUseZMove(u8 battlerAtk, u8 battlerDef, u16 chosenMove) { - u32 i; - u16 possibleZMoves[MAX_MON_MOVES]; - u8 zMoveIndex = 0xFF; - u16 zMove = MOVE_NONE; - u8 scores[MAX_MON_MOVES] = {0}; - + // simple logic. just upgrades chosen move to z move if possible, unless regular move would kill opponent if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && battlerDef == BATTLE_PARTNER(battlerAtk)) return FALSE; //don't use z move on partner - if (gBattleStruct->zmove.used[battlerAtk]) return FALSE; //cant use z move twice - - // check possible z moves and select the 'best' one - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (baseMoves[i] != MOVE_NONE && IsViableZMove(battlerAtk, baseMoves[i])) //updates gBattleStruct->zmove.currZMove - { - if (zMove != MOVE_NONE) - { - scores[i] = GetZMoveScore(battlerAtk, battlerDef, baseMoves[i], zMove); - // another z move option already exists. compare them - #if B_AI_PREFER_STATUS_Z_MOVES == TRUE - if (scores[i] > scores[zMoveIndex] || (IS_MOVE_STATUS(gBattleStruct->zmove.currZMove) && !IS_MOVE_STATUS(zMove) && scores[i] != 0)) - { - zMove = gBattleStruct->zmove.currZMove; - zMoveIndex = i; - } - #else - if (scores[i] > scores[zMoveIndex] || (!IS_MOVE_STATUS(gBattleStruct->zmove.currZMove) && IS_MOVE_STATUS(zMove) && scores[i] != 0)) - { - zMove = gBattleStruct->zmove.currZMove; - zMoveIndex = i; - } - #endif - } - else - { - zMove = gBattleStruct->zmove.currZMove; - zMoveIndex = i; - } - } - } - if (zMoveIndex == 0xFF || scores[zMoveIndex] == 0) + if (IsViableZMove(battlerAtk, chosenMove)) { - return FALSE; //no available z moves - } - else - { - *chosenMoveId = zMoveIndex; - gBattleStruct->zmove.baseMoves[battlerAtk] = baseMoves[*chosenMoveId]; - gBattleStruct->zmove.currZMove = zMove; //z move the AI is looking at + #ifdef POKEMON_EXPANSION + if (defAbility == ABILITY_DISGUISE && defSpecies == SPECIES_MIMIKYU) + return 0; //Don't waste a Z-Move busting Mimikyu's disguise + if (defAbility == ABILITY_ICEFACE && defSpecies == SPECIES_EISCUE && IS_MOVE_PHYSICAL(chosenMove)) + return 0; //Don't waste a Z-Move busting Eiscue's Ice Face + #endif + + if (IS_MOVE_STATUS(chosenMove) && !IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove)) + return FALSE; + else if (!IS_MOVE_STATUS(chosenMove) && IS_MOVE_STATUS(gBattleStruct->zmove.chosenZMove)) + return FALSE; + + if (!IS_MOVE_STATUS(chosenMove) && AI_CalcDamage(chosenMove, battlerAtk, battlerDef, FALSE) >= gBattleMons[battlerDef].hp) + return FALSE; //don't waste damaging z move if regular is expected to faint target + return TRUE; } return FALSE; } - -static u8 GetZMoveScore(u8 battlerAtk, u8 battlerDef, u16 baseMove, u16 zMove) -{ - u8 score = 0; - u8 expectedDamage = 0; - u32 i; - - if (zMove != MOVE_Z_STATUS) - { - u8 defAbility = GetBattlerAbility(battlerDef); - u16 defSpecies = gBattleMons[battlerDef].species; - - /*if (baseMove == MOVE_FAKE_OUT && gDisableStructs[battlerAtk].isFirstTurn) - return FALSE; //Prefer actual Fake Out over Breakneck Blitz*/ - - /*if (MoveBlockedBySubstitute(zMove, battlerAtk, battlerDef) - || (defMovePrediction == MOVE_SUBSTITUTE - && !MoveWouldHitFirst(zMove, battlerAtk, battlerDef) - && !MoveIgnoresSubstitutes(zMove, ABILITY(battlerAtk)))) - return FALSE; //Don't use a Z-Move on a Substitute or if the enemy is going to go first and use Substitute*/ - - #ifdef POKEMON_EXPANSION - if (defAbility == ABILITY_DISGUISE && defSpecies == SPECIES_MIMIKYU) - return 0; //Don't waste a Z-Move busting Mimikyu's disguise - if (defAbility == ABILITY_ICEFACE && defSpecies == SPECIES_EISCUE && IS_MOVE_PHYSICAL(baseMove)) - return 0; //Don't waste a Z-Move busting Eiscue's Ice Face - #endif - - /*if (defMovePrediction == MOVE_PROTECT || defMovePrediction == MOVE_KINGSSHIELD || defMovePrediction == MOVE_SPIKYSHIELD || defMovePrediction == MOVE_OBSTRUCT - || (IsDynamaxed(battlerDef) && SPLIT(defMovePrediction) == SPLIT_STATUS)) - return FALSE; //Don't waste a Z-Move on a Protect*/ - - /*if (IsRaidBattle() && gNewBS->dynamaxData.raidShieldsUp && SIDE(battlerAtk) == B_SIDE_PLAYER && SIDE(battlerDef) == B_SIDE_OPPONENT) //Partner AI on Raid Pokemon with shields up - { - if (gNewBS->dynamaxData.shieldCount - gNewBS->dynamaxData.shieldsDestroyed <= 2 //Less than 3 shields left - && gNewBS->dynamaxData.stormLevel < 3) //The Raid boss hasn't almost won - return FALSE; //Don't waste a Z-Move breaking a shield - - u16 bankAtkPartner = BATTLE_PARTNER(battlerAtk); - u16 partnerMove = GetAIChosenMove(bankAtkPartner, battlerDef); - - if (SPLIT(partnerMove) == SPLIT_STATUS - || MoveWouldHitFirst(partnerMove, bankAtkPartner, battlerAtk) - || (gChosenMovesByBanks[bankAtkPartner] != MOVE_NONE && gBattleStruct->moveTarget[bankAtkPartner] != battlerDef)) //Not targeting raid Pokemon - return FALSE; //Don't waste a Z-Move if partner can't destroy shield first - }*/ - - //These moves should always be turned into Z-Moves, regardless if they KO or not - switch (gBattleMoves[baseMove].effect) - { - case EFFECT_RECHARGE: - case EFFECT_SEMI_INVULNERABLE: - case EFFECT_SKULL_BASH: - case EFFECT_SOLARBEAM: - case EFFECT_LAST_RESORT: - //todo: sky drop - return 255; - } - if (baseMove == MOVE_SKY_ATTACK) - return 255; - - gBattleStruct->zmove.active = TRUE; //for damage calc only - expectedDamage = AI_CalcDamage(baseMove, battlerAtk, battlerDef); - gBattleStruct->zmove.active = FALSE; - if (expectedDamage >= gBattleMons[battlerDef].hp) - return 0; //base move knocks out already, no need for z move - - return (expectedDamage > 255) ? 255 : expectedDamage; - } - else //Status Move - { - u8 zEffect = gBattleMoves[baseMove].zMoveEffect; - - switch (zEffect) - { - case Z_EFFECT_NONE: - return 0; - case Z_EFFECT_RESET_STATS: - for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) - { - if (i == STAT_ATK && !HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL)) //Only reset lowered Attack if useful - continue; - else if (i == STAT_ATK && !HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL)) //Only reset lowered Special Attack if useful - continue; - - if (STAT_STAGE(battlerAtk, i) < 6) - return 50; //Want to reset any negative stats - } - break; - case Z_EFFECT_ALL_STATS_UP_1: - if (!AreStatsMaxed(battlerAtk, STAT_EVASION)) //all battle stats maxed - return 80; - break; - case Z_EFFECT_BOOST_CRITS: - if (!(gBattleMons[battlerAtk].status2 & STATUS2_FOCUS_ENERGY)) - return 30; //kinda meh? - break; - case Z_EFFECT_FOLLOW_ME: - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - return 30; //kinda meh? - break; - case Z_EFFECT_ATK_UP_1: - if (STAT_CAN_RISE(battlerAtk, STAT_ATK) && HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL)) - return 60; - break; - case Z_EFFECT_ATK_UP_2: - if (STAT_CAN_RISE(battlerAtk, STAT_ATK) && HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL)) - return 70; - break; - case Z_EFFECT_ATK_UP_3: - if (STAT_CAN_RISE(battlerAtk, STAT_ATK) && HasMoveWithSplit(battlerAtk, SPLIT_PHYSICAL)) - return 80; - break; - case Z_EFFECT_DEF_UP_1: - if (STAT_CAN_RISE(battlerAtk, STAT_DEF)) - return 50; - break; - case Z_EFFECT_DEF_UP_2: - if (STAT_CAN_RISE(battlerAtk, STAT_DEF)) - return 60; - break; - case Z_EFFECT_DEF_UP_3: - if (STAT_CAN_RISE(battlerAtk, STAT_DEF)) - return 70; - break; - case Z_EFFECT_SPATK_UP_1: - if (STAT_CAN_RISE(battlerAtk, STAT_SPATK) && HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL)) - return 50; - break; - case Z_EFFECT_SPATK_UP_2: - if (STAT_CAN_RISE(battlerAtk, STAT_SPATK) && HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL)) - return 60; - break; - case Z_EFFECT_SPATK_UP_3: - if (STAT_CAN_RISE(battlerAtk, STAT_SPATK) && HasMoveWithSplit(battlerAtk, SPLIT_SPECIAL)) - return 70; - break; - case Z_EFFECT_SPDEF_UP_1: - if (STAT_CAN_RISE(battlerAtk, STAT_SPDEF)) - return 50; - break; - case Z_EFFECT_SPDEF_UP_2: - if (STAT_CAN_RISE(battlerAtk, STAT_SPDEF)) - return 60; - break; - case Z_EFFECT_SPDEF_UP_3: - if (STAT_CAN_RISE(battlerAtk, STAT_SPDEF)) - return 70; - break; - case Z_EFFECT_SPD_UP_1: - if (STAT_CAN_RISE(battlerAtk, STAT_SPEED)) - return 50; - break; - case Z_EFFECT_SPD_UP_2: - if (STAT_CAN_RISE(battlerAtk, STAT_SPEED)) - return 60; - break; - case Z_EFFECT_SPD_UP_3: - if (STAT_CAN_RISE(battlerAtk, STAT_SPEED)) - return 70; - break; - case Z_EFFECT_ACC_UP_1: - if (STAT_CAN_RISE(battlerAtk, STAT_ACC)) - return 20; //TODO: only if knows low-accuracy move - break; - case Z_EFFECT_ACC_UP_2: - if (STAT_CAN_RISE(battlerAtk, STAT_ACC)) - return 40; //TODO: only if knows low-accuracy move - break; - case Z_EFFECT_ACC_UP_3: - //if (STAT_CAN_RISE(battlerAtk, STAT_ACC) && MoveInMovesetWithAccuracyLessThan(battlerAtk, battlerDef, 90, FALSE)) - if (STAT_CAN_RISE(battlerAtk, STAT_ACC)) - return 60; //TODO: only if knows low-accuracy move - break; - case Z_EFFECT_EVSN_UP_1: - if (STAT_CAN_RISE(battlerAtk, STAT_EVASION)) - return 40; - break; - case Z_EFFECT_EVSN_UP_2: - if (STAT_CAN_RISE(battlerAtk, STAT_EVASION)) - return 60; - break; - case Z_EFFECT_EVSN_UP_3: - if (STAT_CAN_RISE(battlerAtk, STAT_EVASION)) - return 80; - break; - default: //Recover HP - return 70; - } - } - - return score; -}