pokeemmo/src/battle_controller_player_partner.c
khbsd a4ecd7338b
fix: seen flags for first mon in enemy party (#7791)
Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
2025-09-28 09:53:55 +02:00

346 lines
14 KiB
C

#include "global.h"
#include "battle.h"
#include "battle_ai_main.h"
#include "battle_ai_util.h"
#include "battle_anim.h"
#include "battle_controllers.h"
#include "battle_message.h"
#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"
#include "link.h"
#include "main.h"
#include "m4a.h"
#include "palette.h"
#include "party_menu.h"
#include "pokeball.h"
#include "pokemon.h"
#include "reshow_battle_screen.h"
#include "sound.h"
#include "string_util.h"
#include "task.h"
#include "text.h"
#include "util.h"
#include "window.h"
#include "constants/battle_anim.h"
#include "constants/battle_partner.h"
#include "constants/songs.h"
#include "constants/party_menu.h"
#include "constants/trainers.h"
static void PlayerPartnerHandleDrawTrainerPic(u32 battler);
static void PlayerPartnerHandleTrainerSlideBack(u32 battler);
static void PlayerPartnerHandleChooseAction(u32 battler);
static void PlayerPartnerHandleChooseMove(u32 battler);
static void PlayerPartnerHandleChoosePokemon(u32 battler);
static void PlayerPartnerHandleIntroTrainerBallThrow(u32 battler);
static void PlayerPartnerHandleDrawPartyStatusSummary(u32 battler);
static void PlayerPartnerHandleEndLinkBattle(u32 battler);
static void PlayerPartnerBufferRunCommand(u32 battler);
static void (*const sPlayerPartnerBufferCommands[CONTROLLER_CMDS_COUNT])(u32 battler) =
{
[CONTROLLER_GETMONDATA] = BtlController_HandleGetMonData,
[CONTROLLER_GETRAWMONDATA] = BtlController_Empty,
[CONTROLLER_SETMONDATA] = BtlController_HandleSetMonData,
[CONTROLLER_SETRAWMONDATA] = BtlController_HandleSetRawMonData,
[CONTROLLER_LOADMONSPRITE] = BtlController_HandleLoadMonSprite,
[CONTROLLER_SWITCHINANIM] = BtlController_HandleSwitchInAnim,
[CONTROLLER_RETURNMONTOBALL] = BtlController_HandleReturnMonToBall,
[CONTROLLER_DRAWTRAINERPIC] = PlayerPartnerHandleDrawTrainerPic,
[CONTROLLER_TRAINERSLIDE] = BtlController_Empty,
[CONTROLLER_TRAINERSLIDEBACK] = PlayerPartnerHandleTrainerSlideBack,
[CONTROLLER_FAINTANIMATION] = BtlController_HandleFaintAnimation,
[CONTROLLER_PALETTEFADE] = BtlController_Empty,
[CONTROLLER_SUCCESSBALLTHROWANIM] = BtlController_Empty,
[CONTROLLER_BALLTHROWANIM] = BtlController_Empty,
[CONTROLLER_PAUSE] = BtlController_Empty,
[CONTROLLER_MOVEANIMATION] = BtlController_HandleMoveAnimation,
[CONTROLLER_PRINTSTRING] = BtlController_HandlePrintString,
[CONTROLLER_PRINTSTRINGPLAYERONLY] = BtlController_Empty,
[CONTROLLER_CHOOSEACTION] = PlayerPartnerHandleChooseAction,
[CONTROLLER_YESNOBOX] = BtlController_Empty,
[CONTROLLER_CHOOSEMOVE] = PlayerPartnerHandleChooseMove,
[CONTROLLER_OPENBAG] = BtlController_Empty,
[CONTROLLER_CHOOSEPOKEMON] = PlayerPartnerHandleChoosePokemon,
[CONTROLLER_23] = BtlController_Empty,
[CONTROLLER_HEALTHBARUPDATE] = BtlController_HandleHealthBarUpdate,
[CONTROLLER_EXPUPDATE] = PlayerHandleExpUpdate, // Partner's player gets experience the same way as the player.
[CONTROLLER_STATUSICONUPDATE] = BtlController_HandleStatusIconUpdate,
[CONTROLLER_STATUSANIMATION] = BtlController_HandleStatusAnimation,
[CONTROLLER_STATUSXOR] = BtlController_Empty,
[CONTROLLER_DATATRANSFER] = BtlController_Empty,
[CONTROLLER_DMA3TRANSFER] = BtlController_Empty,
[CONTROLLER_PLAYBGM] = BtlController_Empty,
[CONTROLLER_32] = BtlController_Empty,
[CONTROLLER_TWORETURNVALUES] = BtlController_Empty,
[CONTROLLER_CHOSENMONRETURNVALUE] = BtlController_Empty,
[CONTROLLER_ONERETURNVALUE] = BtlController_Empty,
[CONTROLLER_ONERETURNVALUE_DUPLICATE] = BtlController_Empty,
[CONTROLLER_HITANIMATION] = BtlController_HandleHitAnimation,
[CONTROLLER_CANTSWITCH] = BtlController_Empty,
[CONTROLLER_PLAYSE] = BtlController_HandlePlaySE,
[CONTROLLER_PLAYFANFAREORBGM] = BtlController_HandlePlayFanfareOrBGM,
[CONTROLLER_FAINTINGCRY] = BtlController_HandleFaintingCry,
[CONTROLLER_INTROSLIDE] = BtlController_HandleIntroSlide,
[CONTROLLER_INTROTRAINERBALLTHROW] = PlayerPartnerHandleIntroTrainerBallThrow,
[CONTROLLER_DRAWPARTYSTATUSSUMMARY] = PlayerPartnerHandleDrawPartyStatusSummary,
[CONTROLLER_HIDEPARTYSTATUSSUMMARY] = BtlController_HandleHidePartyStatusSummary,
[CONTROLLER_ENDBOUNCE] = BtlController_Empty,
[CONTROLLER_SPRITEINVISIBILITY] = BtlController_HandleSpriteInvisibility,
[CONTROLLER_BATTLEANIMATION] = BtlController_HandleBattleAnimation,
[CONTROLLER_LINKSTANDBYMSG] = BtlController_Empty,
[CONTROLLER_RESETACTIONMOVESELECTION] = BtlController_Empty,
[CONTROLLER_ENDLINKBATTLE] = PlayerPartnerHandleEndLinkBattle,
[CONTROLLER_DEBUGMENU] = BtlController_Empty,
[CONTROLLER_TERMINATOR_NOP] = BtlController_TerminatorNop
};
void SetControllerToPlayerPartner(u32 battler)
{
gBattlerControllerEndFuncs[battler] = PlayerPartnerBufferExecCompleted;
gBattlerControllerFuncs[battler] = PlayerPartnerBufferRunCommand;
}
static void PlayerPartnerBufferRunCommand(u32 battler)
{
if (IsBattleControllerActiveOnLocal(battler))
{
if (gBattleResources->bufferA[battler][0] < ARRAY_COUNT(sPlayerPartnerBufferCommands))
sPlayerPartnerBufferCommands[gBattleResources->bufferA[battler][0]](battler);
else
BtlController_Complete(battler);
}
}
static void Intro_WaitForHealthbox(u32 battler)
{
bool32 finished = FALSE;
if (!IsDoubleBattle() || (IsDoubleBattle() && (gBattleTypeFlags & BATTLE_TYPE_MULTI)))
{
if (gSprites[gHealthboxSpriteIds[battler]].callback == SpriteCallbackDummy)
finished = TRUE;
}
else
{
if (gSprites[gHealthboxSpriteIds[battler]].callback == SpriteCallbackDummy
&& gSprites[gHealthboxSpriteIds[BATTLE_PARTNER(battler)]].callback == SpriteCallbackDummy)
{
finished = TRUE;
}
}
if (IsCryPlayingOrClearCrySongs())
finished = FALSE;
if (finished)
{
gBattleSpritesDataPtr->healthBoxesData[battler].introEndDelay = 3;
gBattlerControllerFuncs[battler] = BtlController_Intro_DelayAndEnd;
}
}
// Also used by the link partner.
void Controller_PlayerPartnerShowIntroHealthbox(u32 battler)
{
if (!gBattleSpritesDataPtr->healthBoxesData[battler].ballAnimActive
&& !gBattleSpritesDataPtr->healthBoxesData[BATTLE_PARTNER(battler)].ballAnimActive
&& gSprites[gBattleControllerData[battler]].callback == SpriteCallbackDummy
&& gSprites[gBattlerSpriteIds[battler]].callback == SpriteCallbackDummy
&& ++gBattleSpritesDataPtr->healthBoxesData[battler].introEndDelay != 1)
{
gBattleSpritesDataPtr->healthBoxesData[battler].introEndDelay = 0;
TryShinyAnimation(battler, GetBattlerMon(battler));
if (IsDoubleBattle() && !(gBattleTypeFlags & BATTLE_TYPE_MULTI))
{
DestroySprite(&gSprites[gBattleControllerData[BATTLE_PARTNER(battler)]]);
UpdateHealthboxAttribute(gHealthboxSpriteIds[BATTLE_PARTNER(battler)], GetBattlerMon(BATTLE_PARTNER(battler)), HEALTHBOX_ALL);
StartHealthboxSlideIn(BATTLE_PARTNER(battler));
SetHealthboxSpriteVisible(gHealthboxSpriteIds[BATTLE_PARTNER(battler)]);
}
DestroySprite(&gSprites[gBattleControllerData[battler]]);
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], GetBattlerMon(battler), HEALTHBOX_ALL);
StartHealthboxSlideIn(battler);
SetHealthboxSpriteVisible(gHealthboxSpriteIds[battler]);
gBattleSpritesDataPtr->animationData->introAnimActive = FALSE;
gBattlerControllerFuncs[battler] = Intro_WaitForHealthbox;
}
}
void PlayerPartnerBufferExecCompleted(u32 battler)
{
gBattlerControllerFuncs[battler] = PlayerPartnerBufferRunCommand;
if (gBattleTypeFlags & BATTLE_TYPE_LINK)
{
u8 playerId = GetMultiplayerId();
PrepareBufferDataTransferLink(battler, B_COMM_CONTROLLER_IS_DONE, 4, &playerId);
gBattleResources->bufferA[battler][0] = CONTROLLER_TERMINATOR_NOP;
}
else
{
MarkBattleControllerIdleOnLocal(battler);
}
}
// some explanation here
// in emerald it's possible to have a tag battle in the battle frontier facilities with AI
// which use the front sprite for both the player and the partner as opposed to any other battles (including the one with Steven) that use the back pic as well as animate it
static void PlayerPartnerHandleDrawTrainerPic(u32 battler)
{
bool32 isFrontPic;
s16 xPos, yPos;
u32 trainerPicId;
enum DifficultyLevel difficulty = GetBattlePartnerDifficultyLevel(gPartnerTrainerId);
if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE))
{
trainerPicId = gBattlePartners[difficulty][gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerBackPic;
xPos = 90;
yPos = (8 - gTrainerBacksprites[trainerPicId].coordinates.size) * 4 + 80;
}
else if (IsAiVsAiBattle())
{
trainerPicId = GetTrainerPicFromId(gPartnerTrainerId);
xPos = 60;
yPos = 80;
}
else
{
trainerPicId = GetFrontierTrainerFrontSpriteId(gPartnerTrainerId);
xPos = 32;
yPos = 80;
}
// Use back pic only if the partner Steven or is custom.
if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE))
isFrontPic = FALSE;
else
isFrontPic = TRUE;
BtlController_HandleDrawTrainerPic(battler, trainerPicId, isFrontPic, xPos, yPos, -1);
}
static void PlayerPartnerHandleTrainerSlideBack(u32 battler)
{
BtlController_HandleTrainerSlideBack(battler, 35, FALSE);
}
static void PlayerPartnerHandleChooseAction(u32 battler)
{
AI_TrySwitchOrUseItem(battler);
BtlController_Complete(battler);
}
static void PlayerPartnerHandleChooseMove(u32 battler)
{
u32 chosenMoveIndex;
struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct *)(&gBattleResources->bufferA[battler][4]);
chosenMoveIndex = gAiBattleData->chosenMoveIndex[battler];
gBattlerTarget = gAiBattleData->chosenTarget[battler];
u32 moveTarget = GetBattlerMoveTargetType(battler, moveInfo->moves[chosenMoveIndex]);
if (moveTarget & MOVE_TARGET_USER)
gBattlerTarget = battler;
else if (moveTarget & MOVE_TARGET_BOTH)
{
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & (1u << gBattlerTarget))
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
// If partner can and should use a gimmick (considering trainer data), do it
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE && IsAIUsingGimmick(battler))
{
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_EXEC_SCRIPT, (chosenMoveIndex) | (RET_GIMMICK) | (gBattlerTarget << 8));
}
else
{
BtlController_EmitTwoReturnValues(battler, B_COMM_TO_ENGINE, B_ACTION_EXEC_SCRIPT, (chosenMoveIndex) | (gBattlerTarget << 8));
}
BtlController_Complete(battler);
}
static void PlayerPartnerHandleChoosePokemon(u32 battler)
{
s32 chosenMonId;
// Choosing Revival Blessing target
if (gBattleResources->bufferA[battler][1] == PARTY_ACTION_CHOOSE_FAINTED_MON)
{
chosenMonId = gSelectedMonPartyId = GetFirstFaintedPartyIndex(battler);
}
// Switching out
else if (gBattleStruct->monToSwitchIntoId[battler] >= PARTY_SIZE || !IsValidForBattle(&gPlayerParty[gBattleStruct->monToSwitchIntoId[battler]]))
{
chosenMonId = GetMostSuitableMonToSwitchInto(battler, SWITCH_AFTER_KO);
if (chosenMonId == PARTY_SIZE || !IsValidForBattle(&gPlayerParty[chosenMonId])) // just switch to the next mon
{
s32 firstId = (IsAiVsAiBattle()) ? 0 : (PARTY_SIZE / 2);
u32 battler1 = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
u32 battler2 = IsDoubleBattle() ? GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT) : battler1;
for (chosenMonId = firstId; chosenMonId < PARTY_SIZE; chosenMonId++)
{
if (GetMonData(&gPlayerParty[chosenMonId], MON_DATA_HP) != 0
&& chosenMonId != gBattlerPartyIndexes[battler1]
&& chosenMonId != gBattlerPartyIndexes[battler2])
{
break;
}
}
}
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
GetBattlerPartyState(battler)->sentOut = TRUE;
}
else // Mon to switch out has been already chosen.
{
chosenMonId = gBattleStruct->monToSwitchIntoId[battler];
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
GetBattlerPartyState(battler)->sentOut = TRUE;
}
BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, chosenMonId, NULL);
BtlController_Complete(battler);
}
static void PlayerPartnerHandleIntroTrainerBallThrow(u32 battler)
{
const u16 *trainerPal;
enum DifficultyLevel difficulty = GetBattlePartnerDifficultyLevel(gPartnerTrainerId);
if (gPartnerTrainerId > TRAINER_PARTNER(PARTNER_NONE))
trainerPal = gTrainerBacksprites[gBattlePartners[difficulty][gPartnerTrainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerBackPic].palette.data;
else if (IsAiVsAiBattle())
trainerPal = gTrainerSprites[GetTrainerBackPicFromId(gPartnerTrainerId)].palette.data;
else
trainerPal = gTrainerSprites[GetFrontierTrainerFrontSpriteId(gPartnerTrainerId)].palette.data; // 2 vs 2 multi battle in Battle Frontier, load front sprite and pal.
BtlController_HandleIntroTrainerBallThrow(battler, 0xD6F9, trainerPal, 24, Controller_PlayerPartnerShowIntroHealthbox);
}
static void PlayerPartnerHandleDrawPartyStatusSummary(u32 battler)
{
BtlController_HandleDrawPartyStatusSummary(battler, B_SIDE_PLAYER, TRUE);
}
static void PlayerPartnerHandleEndLinkBattle(u32 battler)
{
gBattleOutcome = gBattleResources->bufferA[battler][1];
FadeOutMapMusic(5);
BeginFastPaletteFade(3);
BtlController_Complete(battler);
gBattlerControllerFuncs[battler] = SetBattleEndCallbacks;
}