From ec2aef2effa18df59fcc61afd07a0a6a2e46bedf Mon Sep 17 00:00:00 2001 From: grintoul1 <166724814+grintoul1@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:18:20 +0100 Subject: [PATCH] Battle Transition: mugshots for multibattles (#6567) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/data.h | 5 + include/pokemon.h | 4 +- src/battle_transition.c | 181 ++++++++++++++++++----- src/data/pokemon/trainer_class_lookups.h | 16 +- 4 files changed, 164 insertions(+), 42 deletions(-) diff --git a/include/data.h b/include/data.h index 8b1e9c22d1..8ffee05464 100644 --- a/include/data.h +++ b/include/data.h @@ -171,6 +171,7 @@ extern const union AnimCmd *const gAnims_MonPic[]; extern const union AnimCmd *const gAnims_Trainer[]; extern const struct TrainerSprite gTrainerSprites[]; extern const struct TrainerBacksprite gTrainerBacksprites[]; +extern const u16 gTrainerPicToTrainerBackPic[]; extern const struct Trainer gTrainers[DIFFICULTY_COUNT][TRAINERS_COUNT]; extern const struct Trainer gBattlePartners[DIFFICULTY_COUNT][PARTNER_COUNT]; @@ -239,6 +240,10 @@ static inline const u8 GetTrainerPicFromId(u16 trainerId) { u32 sanitizedTrainerId = SanitizeTrainerId(trainerId); enum DifficultyLevel difficulty = GetTrainerDifficultyLevel(sanitizedTrainerId); + enum DifficultyLevel partnerDifficulty = GetBattlePartnerDifficultyLevel(trainerId); + + if (trainerId > TRAINER_PARTNER(PARTNER_NONE)) + return gBattlePartners[partnerDifficulty][trainerId - TRAINER_PARTNER(PARTNER_NONE)].trainerPic; return gTrainers[difficulty][sanitizedTrainerId].trainerPic; } diff --git a/include/pokemon.h b/include/pokemon.h index c3232dcf6c..f55918fda6 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -613,8 +613,8 @@ extern bool32 consumeItem; extern u32 removeBagItem; extern u32 removeBagItemCount; -extern const u8 gFacilityClassToPicIndex[]; -extern const u8 gFacilityClassToTrainerClass[]; +extern const u16 gFacilityClassToPicIndex[]; +extern const u16 gFacilityClassToTrainerClass[]; extern const struct SpeciesInfo gSpeciesInfo[]; extern const u32 gExperienceTables[][MAX_LEVEL + 1]; extern const u8 gPPUpGetMask[]; diff --git a/src/battle_transition.c b/src/battle_transition.c index bc993cc61f..77cd376ae4 100644 --- a/src/battle_transition.c +++ b/src/battle_transition.c @@ -88,6 +88,7 @@ struct RectangularSpiralLine typedef bool8 (*TransitionStateFunc)(struct Task *task); typedef bool8 (*TransitionSpriteCallback)(struct Sprite *sprite); +typedef bool8 (*TransitionSpriteCallbackPartner)(struct Sprite *sprite); static bool8 Transition_StartIntro(struct Task *); static bool8 Transition_WaitForIntro(struct Task *); @@ -277,11 +278,13 @@ static bool8 IsIntroTaskDone(void); static bool16 UpdateRectangularSpiralLine(const s16 * const *, struct RectangularSpiralLine *); static void SpriteCB_FldEffPokeballTrail(struct Sprite *); static void SpriteCB_MugshotTrainerPic(struct Sprite *); +static void SpriteCB_MugshotTrainerPicPartner(struct Sprite *); static void SpriteCB_WhiteBarFade(struct Sprite *); static bool8 MugshotTrainerPic_Pause(struct Sprite *); static bool8 MugshotTrainerPic_Init(struct Sprite *); static bool8 MugshotTrainerPic_Slide(struct Sprite *); static bool8 MugshotTrainerPic_SlideSlow(struct Sprite *); +static bool8 MugshotTrainerPic_SlidePartner(struct Sprite *); static bool8 MugshotTrainerPic_SlideOffscreen(struct Sprite *); static s16 sDebug_RectangularSpiralData; @@ -544,6 +547,17 @@ static const TransitionSpriteCallback sMugshotTrainerPicFuncs[] = MugshotTrainerPic_Pause }; +static const TransitionSpriteCallbackPartner sMugshotTrainerPicFuncsPartner[] = +{ + MugshotTrainerPic_Pause, + MugshotTrainerPic_Init, + MugshotTrainerPic_SlidePartner, + MugshotTrainerPic_SlideSlow, + MugshotTrainerPic_Pause, + MugshotTrainerPic_SlideOffscreen, + MugshotTrainerPic_Pause +}; + // One element per slide direction. // Sign of acceleration is opposite speed, so slide decelerates. static const s16 sTrainerPicSlideSpeeds[2] = {12, -12}; @@ -2217,8 +2231,10 @@ static void VBlankCB_Wave(void) #define tBottomBannerX data[3] #define tTimer data[3] // Re-used #define tFadeSpread data[4] -#define tOpponentSpriteId data[13] +#define tOpponentSpriteAId data[11] +#define tOpponentSpriteBId data[12] #define tPlayerSpriteId data[14] +#define tPartnerSpriteId data[13] // Sprite data for trainer sprites in mugshots #define sState data[0] @@ -2359,14 +2375,18 @@ static bool8 Mugshot_StartOpponentSlide(struct Task *task) sTransitionData->BG0HOFS_Lower -= 8; sTransitionData->BG0HOFS_Upper += 8; - SetTrainerPicSlideDirection(task->tOpponentSpriteId, 0); + SetTrainerPicSlideDirection(task->tOpponentSpriteAId, 0); + SetTrainerPicSlideDirection(task->tOpponentSpriteBId, 0); SetTrainerPicSlideDirection(task->tPlayerSpriteId, 1); + SetTrainerPicSlideDirection(task->tPartnerSpriteId, 1); // Start opponent slide - IncrementTrainerPicState(task->tOpponentSpriteId); + IncrementTrainerPicState(task->tOpponentSpriteAId); PlaySE(SE_MUGSHOT); + IncrementTrainerPicState(task->tOpponentSpriteBId); + sTransitionData->VBlank_DMA++; return FALSE; } @@ -2377,9 +2397,10 @@ static bool8 Mugshot_WaitStartPlayerSlide(struct Task *task) sTransitionData->BG0HOFS_Upper += 8; // Start player's slide in once the opponent is finished - if (IsTrainerPicSlideDone(task->tOpponentSpriteId)) + if (IsTrainerPicSlideDone(task->tOpponentSpriteAId)) { task->tState++; + IncrementTrainerPicState(task->tPartnerSpriteId); IncrementTrainerPicState(task->tPlayerSpriteId); } return FALSE; @@ -2390,20 +2411,49 @@ static bool8 Mugshot_WaitPlayerSlide(struct Task *task) sTransitionData->BG0HOFS_Lower -= 8; sTransitionData->BG0HOFS_Upper += 8; - if (IsTrainerPicSlideDone(task->tPlayerSpriteId)) + if (gPartnerTrainerId != TRAINER_PARTNER(PARTNER_NONE) && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) { - sTransitionData->VBlank_DMA = FALSE; - SetVBlankCallback(NULL); - DmaStop(0); - memset(gScanlineEffectRegBuffers[0], 0, DISPLAY_HEIGHT * 2); - memset(gScanlineEffectRegBuffers[1], 0, DISPLAY_HEIGHT * 2); - SetGpuReg(REG_OFFSET_WIN0H, DISPLAY_WIDTH); - SetGpuReg(REG_OFFSET_BLDY, 0); - task->tState++; - task->tTimer = 0; - task->tFadeSpread = 0; - sTransitionData->BLDCNT = BLDCNT_TGT1_ALL | BLDCNT_EFFECT_LIGHTEN; - SetVBlankCallback(VBlankCB_MugshotsFadeOut); + if (IsTrainerPicSlideDone(task->tPartnerSpriteId)) + { + sTransitionData->VBlank_DMA = FALSE; + SetVBlankCallback(NULL); + DmaStop(0); + memset(gScanlineEffectRegBuffers[0], 0, DISPLAY_HEIGHT * 2); + memset(gScanlineEffectRegBuffers[1], 0, DISPLAY_HEIGHT * 2); + SetGpuReg(REG_OFFSET_WIN0H, DISPLAY_WIDTH); + SetGpuReg(REG_OFFSET_BLDY, 0); + task->tState++; + task->tTimer = 0; + task->tFadeSpread = 0; + sTransitionData->BLDCNT = BLDCNT_TGT1_ALL | BLDCNT_EFFECT_LIGHTEN; + SetVBlankCallback(VBlankCB_MugshotsFadeOut); + } + else + { + return FALSE; + } + } + else + { + if (IsTrainerPicSlideDone(task->tPlayerSpriteId)) + { + sTransitionData->VBlank_DMA = FALSE; + SetVBlankCallback(NULL); + DmaStop(0); + memset(gScanlineEffectRegBuffers[0], 0, DISPLAY_HEIGHT * 2); + memset(gScanlineEffectRegBuffers[1], 0, DISPLAY_HEIGHT * 2); + SetGpuReg(REG_OFFSET_WIN0H, DISPLAY_WIDTH); + SetGpuReg(REG_OFFSET_BLDY, 0); + task->tState++; + task->tTimer = 0; + task->tFadeSpread = 0; + sTransitionData->BLDCNT = BLDCNT_TGT1_ALL | BLDCNT_EFFECT_LIGHTEN; + SetVBlankCallback(VBlankCB_MugshotsFadeOut); + } + else + { + return FALSE; + } } return FALSE; } @@ -2518,48 +2568,83 @@ static void HBlankCB_Mugshots(void) static void Mugshots_CreateTrainerPics(struct Task *task) { - struct Sprite *opponentSprite, *playerSprite; + struct Sprite *opponentSpriteA, *opponentSpriteB=0, *playerSprite, *partnerSprite=0; - u8 trainerPicId = GetTrainerPicFromId(TRAINER_BATTLE_PARAM.opponentA); - s16 opponentRotationScales = 0; + u8 trainerAPicId = GetTrainerPicFromId(TRAINER_BATTLE_PARAM.opponentA); + u8 trainerBPicId = GetTrainerPicFromId(TRAINER_BATTLE_PARAM.opponentB); + u8 partnerPicId = gTrainerPicToTrainerBackPic[GetTrainerPicFromId(gPartnerTrainerId)]; + s16 opponentARotationScales = 0; + s16 opponentBRotationScales = 0; gReservedSpritePaletteCount = 10; - task->tOpponentSpriteId = CreateTrainerSprite(trainerPicId, - gTrainerSprites[trainerPicId].mugshotCoords.x - 32, - gTrainerSprites[trainerPicId].mugshotCoords.y + 42, + if (TRAINER_BATTLE_PARAM.opponentB != TRAINER_NONE && gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) + { + task->tOpponentSpriteBId = CreateTrainerSprite(trainerBPicId, + gTrainerSprites[trainerBPicId].mugshotCoords.x - 240, + gTrainerSprites[trainerBPicId].mugshotCoords.y + 42, + 0, NULL); + opponentSpriteB = &gSprites[task->tOpponentSpriteBId]; + opponentSpriteB->callback = SpriteCB_MugshotTrainerPicPartner; + opponentSpriteB->oam.affineMode = ST_OAM_AFFINE_DOUBLE; + opponentSpriteB->oam.matrixNum = AllocOamMatrix(); + opponentSpriteB->oam.shape = SPRITE_SHAPE(64x32); + opponentSpriteB->oam.size = SPRITE_SIZE(64x32); + CalcCenterToCornerVec(opponentSpriteB, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), ST_OAM_AFFINE_DOUBLE); + opponentBRotationScales = gTrainerSprites[trainerBPicId].mugshotRotation; + SetOamMatrixRotationScaling(opponentSpriteB->oam.matrixNum, opponentBRotationScales, opponentBRotationScales, 0); + } + + task->tOpponentSpriteAId = CreateTrainerSprite(trainerAPicId, + gTrainerSprites[trainerAPicId].mugshotCoords.x - 32, + gTrainerSprites[trainerAPicId].mugshotCoords.y + 42, 0, NULL); + gReservedSpritePaletteCount = 12; - - task->tPlayerSpriteId = CreateTrainerSprite(PlayerGenderToFrontTrainerPicId(gSaveBlock2Ptr->playerGender), - DISPLAY_WIDTH + 32, - 106, + if (gPartnerTrainerId != TRAINER_PARTNER(PARTNER_NONE) && gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) + { + task->tPartnerSpriteId = CreateTrainerSprite(partnerPicId, + DISPLAY_WIDTH + 240, + 106, 0, NULL); + partnerSprite = &gSprites[task->tPartnerSpriteId]; + partnerSprite->callback = SpriteCB_MugshotTrainerPicPartner; + partnerSprite->oam.affineMode = ST_OAM_AFFINE_DOUBLE; + partnerSprite->oam.matrixNum = AllocOamMatrix(); + partnerSprite->oam.shape = SPRITE_SHAPE(64x32); + partnerSprite->oam.size = SPRITE_SIZE(64x32); + CalcCenterToCornerVec(partnerSprite, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), ST_OAM_AFFINE_DOUBLE); + SetOamMatrixRotationScaling(partnerSprite->oam.matrixNum, -512, 512, 0); + } - opponentSprite = &gSprites[task->tOpponentSpriteId]; + task->tPlayerSpriteId = CreateTrainerSprite(PlayerGenderToFrontTrainerPicId(gSaveBlock2Ptr->playerGender), + DISPLAY_WIDTH + 32, + 106, + 0, NULL); + + opponentSpriteA = &gSprites[task->tOpponentSpriteAId]; playerSprite = &gSprites[task->tPlayerSpriteId]; - opponentSprite->callback = SpriteCB_MugshotTrainerPic; + opponentSpriteA->callback = SpriteCB_MugshotTrainerPic; playerSprite->callback = SpriteCB_MugshotTrainerPic; - opponentSprite->oam.affineMode = ST_OAM_AFFINE_DOUBLE; + opponentSpriteA->oam.affineMode = ST_OAM_AFFINE_DOUBLE; playerSprite->oam.affineMode = ST_OAM_AFFINE_DOUBLE; - opponentSprite->oam.matrixNum = AllocOamMatrix(); + opponentSpriteA->oam.matrixNum = AllocOamMatrix(); playerSprite->oam.matrixNum = AllocOamMatrix(); - opponentSprite->oam.shape = SPRITE_SHAPE(64x32); + opponentSpriteA->oam.shape = SPRITE_SHAPE(64x32); playerSprite->oam.shape = SPRITE_SHAPE(64x32); - opponentSprite->oam.size = SPRITE_SIZE(64x32); + opponentSpriteA->oam.size = SPRITE_SIZE(64x32); playerSprite->oam.size = SPRITE_SIZE(64x32); - CalcCenterToCornerVec(opponentSprite, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), ST_OAM_AFFINE_DOUBLE); + CalcCenterToCornerVec(opponentSpriteA, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), ST_OAM_AFFINE_DOUBLE); CalcCenterToCornerVec(playerSprite, SPRITE_SHAPE(64x32), SPRITE_SIZE(64x32), ST_OAM_AFFINE_DOUBLE); - opponentRotationScales = gTrainerSprites[trainerPicId].mugshotRotation; - - SetOamMatrixRotationScaling(opponentSprite->oam.matrixNum, opponentRotationScales, opponentRotationScales, 0); + opponentARotationScales = gTrainerSprites[trainerAPicId].mugshotRotation; + SetOamMatrixRotationScaling(opponentSpriteA->oam.matrixNum, opponentARotationScales, opponentARotationScales, 0); SetOamMatrixRotationScaling(playerSprite->oam.matrixNum, -512, 512, 0); } @@ -2568,6 +2653,12 @@ static void SpriteCB_MugshotTrainerPic(struct Sprite *sprite) while (sMugshotTrainerPicFuncs[sprite->sState](sprite)); } +static void SpriteCB_MugshotTrainerPicPartner(struct Sprite *sprite) +{ + while (sMugshotTrainerPicFuncsPartner[sprite->sState](sprite)); +} + + // Wait until IncrementTrainerPicState is called static bool8 MugshotTrainerPic_Pause(struct Sprite *sprite) { @@ -2600,6 +2691,18 @@ static bool8 MugshotTrainerPic_Slide(struct Sprite *sprite) return FALSE; } +static bool8 MugshotTrainerPic_SlidePartner(struct Sprite *sprite) +{ + sprite->x += sprite->sSlideSpeed; + + // Advance state when pic passes ~40% of screen + if (sprite->sSlideDir && sprite->x < DISPLAY_WIDTH - 60) + sprite->sState++; + else if (!sprite->sSlideDir && sprite->x > 60) + sprite->sState++; + return FALSE; +} + static bool8 MugshotTrainerPic_SlideSlow(struct Sprite *sprite) { // Add acceleration value to speed, then add speed. @@ -2654,8 +2757,10 @@ static s16 IsTrainerPicSlideDone(s16 spriteId) #undef tBottomBannerX #undef tTimer #undef tFadeSpread -#undef tOpponentSpriteId +#undef tOpponentSpriteAId +#undef tOpponentSpriteBId #undef tPlayerSpriteId +#undef tPartnerSpriteId //-------------------- // B_TRANSITION_SLICE diff --git a/src/data/pokemon/trainer_class_lookups.h b/src/data/pokemon/trainer_class_lookups.h index 909d3d7dcb..963537520c 100644 --- a/src/data/pokemon/trainer_class_lookups.h +++ b/src/data/pokemon/trainer_class_lookups.h @@ -1,4 +1,4 @@ -const u8 gFacilityClassToPicIndex[] = +const u16 gFacilityClassToPicIndex[] = { [FACILITY_CLASS_HIKER] = TRAINER_PIC_HIKER, [FACILITY_CLASS_AQUA_GRUNT_M] = TRAINER_PIC_AQUA_GRUNT_M, @@ -84,7 +84,19 @@ const u8 gFacilityClassToPicIndex[] = [FACILITY_CLASS_RS_MAY] = TRAINER_PIC_RS_MAY, }; -const u8 gFacilityClassToTrainerClass[] = +const u16 gTrainerPicToTrainerBackPic[] = +{ + [TRAINER_BACK_PIC_BRENDAN] = TRAINER_PIC_BRENDAN, + [TRAINER_BACK_PIC_MAY] = TRAINER_PIC_MAY, + [TRAINER_BACK_PIC_RED] = TRAINER_PIC_RED, + [TRAINER_BACK_PIC_LEAF] = TRAINER_PIC_LEAF, + [TRAINER_BACK_PIC_RUBY_SAPPHIRE_BRENDAN] = TRAINER_PIC_RS_BRENDAN, + [TRAINER_BACK_PIC_RUBY_SAPPHIRE_MAY] = TRAINER_PIC_RS_MAY, + [TRAINER_BACK_PIC_WALLY] = TRAINER_PIC_WALLY, + [TRAINER_BACK_PIC_STEVEN] = TRAINER_PIC_STEVEN, +}; + +const u16 gFacilityClassToTrainerClass[] = { [FACILITY_CLASS_HIKER] = TRAINER_CLASS_HIKER, [FACILITY_CLASS_AQUA_GRUNT_M] = TRAINER_CLASS_TEAM_AQUA,