merge with upcoming

This commit is contained in:
DizzyEggg 2023-08-30 13:23:55 +02:00
commit 493c330cbd
33 changed files with 689 additions and 81 deletions

View File

@ -1637,6 +1637,11 @@
.byte \case
.endm
.macro handleultraburst battler:req, case:req
various \battler, VARIOUS_HANDLE_ULTRA_BURST
.byte \case
.endm
.macro handleformchange battler:req, case:req
various \battler, VARIOUS_HANDLE_FORM_CHANGE
.byte \case

View File

@ -947,6 +947,7 @@ gBattleAnims_General::
.4byte General_ZMoveActivate @ B_ANIM_ZMOVE_ACTIVATE
.4byte General_AffectionHangedOn @ B_ANIM_AFFECTION_HANGED_ON
.4byte General_Snow @ B_ANIM_SNOW_CONTINUES
.4byte General_UltraBurst @ B_ANIM_ULTRA_BURST
.align 2
gBattleAnims_Special::
@ -27002,6 +27003,43 @@ General_PrimalReversion_Omega:
blendoff
end
General_UltraBurst::
loadspritegfx ANIM_TAG_ULTRA_BURST_SYMBOL
loadspritegfx ANIM_TAG_SPARK_2 @spark
loadspritegfx ANIM_TAG_LEAF @green
loadspritegfx ANIM_TAG_ELECTRIC_ORBS @charge particles
loadspritegfx ANIM_TAG_CIRCLE_OF_LIGHT @psycho boost
monbg ANIM_ATTACKER
setalpha 12, 8
createvisualtask AnimTask_BlendBattleAnimPal, 0xa, (F_PAL_BG | F_PAL_ADJACENT), 0x2, 0x0, 0xF, 0x0000
waitforvisualfinish
createvisualtask AnimTask_ElectricChargingParticles, 2, ANIM_ATTACKER, 60, 2, 12 @ charge particles to attacker
delay 0x1e
loopsewithpan SE_M_CHARGE, SOUND_PAN_ATTACKER, 0xe, 0xa
createsprite gSuperpowerOrbSpriteTemplate, ANIM_TARGET, 3, 0x0
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
call LightThatBurnsTheSkyGreenSparks
delay 20
createvisualtask AnimTask_BlendBattleAnimPalExclude, 5, 5, 2, 0, 16, RGB_WHITEALPHA
createvisualtask AnimTask_TransformMon, 2, 1, 0
createsprite gUltraBurstSymbolSpriteTemplate, ANIM_ATTACKER, 0x0, 0x0, 0x0, 0x0, 0x0
waitforvisualfinish
createvisualtask AnimTask_BlendBattleAnimPalExclude, 5, 5, 2, 16, 0, RGB_WHITEALPHA
createvisualtask AnimTask_HorizontalShake, 5, ANIM_TARGET, 5, 14
waitforvisualfinish
createvisualtask SoundTask_PlayNormalCry, 0
waitforvisualfinish
clearmonbg ANIM_ATK_PARTNER
blendoff
end
General_AffectionHangedOn::
loadspritegfx ANIM_TAG_RED_HEART
loopsewithpan SE_M_CHARM, SOUND_PAN_ATTACKER, 12, 3

View File

@ -7796,6 +7796,21 @@ BattleScript_PrimalReversionRet::
switchinabilities BS_ATTACKER
return
BattleScript_UltraBurst::
printstring STRINGID_EMPTYSTRING3
trytrainerslidezmovemsg BS_ATTACKER
printstring STRINGID_ULTRABURSTREACTING
waitmessage B_WAIT_TIME_LONG
setbyte gIsCriticalHit, 0
handleultraburst BS_ATTACKER, 0
playanimation BS_ATTACKER, B_ANIM_ULTRA_BURST
waitanimation
handleultraburst BS_ATTACKER, 1
printstring STRINGID_ULTRABURSTCOMPLETED
waitmessage B_WAIT_TIME_LONG
switchinabilities BS_ATTACKER
end3
BattleScript_AttackerFormChange::
pause 5
copybyte gBattlerAbility, gBattlerAttacker

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

View File

@ -292,6 +292,8 @@ struct AiLogicData
s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 moveLimitations[MAX_BATTLERS_COUNT];
bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler.
u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch.
};
struct AI_ThinkingStruct
@ -304,8 +306,7 @@ struct AI_ThinkingStruct
u32 aiFlags;
u8 aiAction;
u8 aiLogicId;
struct AI_SavedBattleMon saved[4];
bool8 switchMon; // Because all available moves have no/little effect.
struct AI_SavedBattleMon saved[MAX_BATTLERS_COUNT];
};
#define AI_MOVE_HISTORY_COUNT 3
@ -488,6 +489,15 @@ struct MegaEvolutionData
u8 triggerSpriteId;
};
struct UltraBurstData
{
u8 toBurst; // As flags using gBitTable.
bool8 alreadyBursted[4]; // Array id is used for mon position.
u8 battlerId;
bool8 playerSelect;
u8 triggerSpriteId;
};
struct Illusion
{
u8 on;
@ -616,6 +626,7 @@ struct BattleStruct
u8 abilityPopUpSpriteIds[MAX_BATTLERS_COUNT][2]; // two per battler
bool8 throwingPokeBall;
struct MegaEvolutionData mega;
struct UltraBurstData burst;
struct ZMoveData zmove;
const u8 *trainerSlideMsg;
bool8 trainerSlideLowHpMsgDone;

View File

@ -99,6 +99,7 @@ enum {
// Special return values in gBattleBufferB from Battle Controller functions.
#define RET_VALUE_LEVELED_UP 11
#define RET_MEGA_EVOLUTION 0x80
#define RET_ULTRA_BURST 0x70
struct UnusedControllerStruct
{
@ -129,6 +130,7 @@ struct ChooseMoveStruct
u8 monType2;
u8 monType3;
struct MegaEvolutionData mega;
struct UltraBurstData burst;
struct ZMoveData zmove;
};

View File

@ -53,11 +53,13 @@ enum
#define TAG_ALPHA_INDICATOR_TILE 0xD779
#define TAG_OMEGA_INDICATOR_TILE 0xD77A
#define TAG_ZMOVE_TRIGGER_TILE 0xD77B
#define TAG_BURST_TRIGGER_TILE 0xD77C
#define TAG_MEGA_TRIGGER_PAL 0xD777
#define TAG_MEGA_INDICATOR_PAL 0xD778
#define TAG_ALPHA_OMEGA_INDICATOR_PAL 0xD779 // Alpha and Omega indicators use the same palette as each of them only uses 4 different colors.
#define TAG_ZMOVE_TRIGGER_PAL 0xD77B
#define TAG_BURST_TRIGGER_PAL 0xD77C
enum
{
@ -91,6 +93,11 @@ void CreateMegaTriggerSprite(u8 battlerId, u8 palId);
bool32 IsMegaTriggerSpriteActive(void);
void HideMegaTriggerSprite(void);
void DestroyMegaTriggerSprite(void);
void ChangeBurstTriggerSprite(u8 spriteId, u8 animId);
void CreateBurstTriggerSprite(u8 battlerId, u8 palId);
bool32 IsBurstTriggerSpriteActive(void);
void HideBurstTriggerSprite(void);
void DestroyBurstTriggerSprite(void);
void MegaIndicator_LoadSpritesGfx(void);
u8 CreatePartyStatusSummarySprites(u8 battler, struct HpAndStatus *partyInfo, bool8 skipPlayer, bool8 isBattleStart);
void Task_HidePartyStatusSummary(u8 taskId);

View File

@ -472,6 +472,7 @@ extern const u8 BattleScript_SpikesActivates[];
extern const u8 BattleScript_BerserkGeneRet[];
extern const u8 BattleScript_TargetFormChangeWithStringNoPopup[];
extern const u8 BattleScript_DefDown[];
extern const u8 BattleScript_UltraBurst[];
// zmoves
extern const u8 BattleScript_ZMoveActivateDamaging[];

View File

@ -177,8 +177,10 @@ uq4_12_t GetTypeModifier(u8 atkType, u8 defType);
s32 GetStealthHazardDamage(u8 hazardType, u8 battler);
s32 GetStealthHazardDamageByTypesAndHP(u8 hazardType, u8 type1, u8 type2, u32 maxHp);
bool32 CanMegaEvolve(u8 battler);
bool32 CanUltraBurst(u8 battler);
bool32 IsBattlerMegaEvolved(u8 battler);
bool32 IsBattlerPrimalReverted(u8 battler);
bool32 IsBattlerUltraBursted(u8 battler);
u16 GetBattleFormChangeTargetSpecies(u8 battler, u16 method);
bool32 TryBattleFormChange(u8 battler, u16 method);
bool32 DoBattlersShareType(u32 battler1, u32 battler2);

View File

@ -495,4 +495,9 @@
#define PARENTAL_BOND_2ND_HIT 1
#define PARENTAL_BOND_OFF 0
// Constants for if HandleScriptMegaPrimalBurst should handle Mega Evolution, Primal Reversion, or Ultra Burst.
#define HANDLE_TYPE_MEGA_EVOLUTION 0
#define HANDLE_TYPE_PRIMAL_REVERSION 1
#define HANDLE_TYPE_ULTRA_BURST 2
#endif // GUARD_CONSTANTS_BATTLE_H

View File

@ -548,6 +548,7 @@
#define B_ANIM_ZMOVE_ACTIVATE 34 // Using Z Moves
#define B_ANIM_AFFECTION_HANGED_ON 35
#define B_ANIM_SNOW_CONTINUES 36
#define B_ANIM_ULTRA_BURST 37
// special animations table (gBattleAnims_Special)
#define B_ANIM_LVL_UP 0

View File

@ -257,6 +257,7 @@
#define VARIOUS_TRY_REVIVAL_BLESSING 165
#define VARIOUS_TRY_TRAINER_SLIDE_MSG_Z_MOVE 166
#define VARIOUS_TRY_TRAINER_SLIDE_MSG_MEGA_EVOLUTION 167
#define VARIOUS_HANDLE_ULTRA_BURST 168
// Cmd_manipulatedamage
#define DMG_CHANGE_SIGN 0

View File

@ -665,8 +665,10 @@
#define STRINGID_SNOWSTOPPED 663
#define STRINGID_SNOWWARNINGSNOW 664
#define STRINGID_PKMNITEMMELTED 665
#define STRINGID_ULTRABURSTREACTING 666
#define STRINGID_ULTRABURSTCOMPLETED 667
#define BATTLESTRINGS_COUNT 666
#define BATTLESTRINGS_COUNT 668
// This is the string id that gBattleStringsTable starts with.
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,

View File

@ -96,4 +96,9 @@
// param1: ability to check.
#define FORM_CHANGE_BATTLE_TURN_END 15
// Form change that activates when the mon has the defined item.
// If it's on the player's side, it also requires for the player to trigger it by pressing START before selecting a move.
// param1: item to hold.
#define FORM_CHANGE_BATTLE_ULTRA_BURST 16
#endif // GUARD_CONSTANTS_FORM_CHANGE_TYPES_H

View File

@ -325,6 +325,7 @@
#define SPECIES_FLAG_HISUIAN_FORM (1 << 7)
#define SPECIES_FLAG_ALL_PERFECT_IVS (1 << 8)
#define SPECIES_FLAG_CANNOT_BE_TRADED (1 << 9)
#define SPECIES_FLAG_ULTRA_BURST (1 << 10)
#define LEGENDARY_PERFECT_IV_COUNT 3

View File

@ -796,6 +796,8 @@ struct MoveContext
u16 explicitSecondaryEffect:1;
u16 megaEvolve:1;
u16 explicitMegaEvolve:1;
u16 ultraBurst:1;
u16 explicitUltraBurst:1;
// TODO: u8 zMove:1;
u16 allowed:1;
u16 explicitAllowed:1;

View File

@ -403,6 +403,78 @@ void GetAiLogicData(void)
}
}
static bool32 AI_SwitchMonIfSuitable(u32 battler)
{
u32 monToSwitchId = GetMostSuitableMonToSwitchInto(battler);
if (monToSwitchId != PARTY_SIZE)
{
AI_DATA->shouldSwitchMon |= gBitTable[battler];
AI_DATA->monToSwitchId[battler] = monToSwitchId;
return TRUE;
}
return FALSE;
}
static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
{
u32 i, j;
// If can switch.
if (CountUsablePartyMons(battler) > 0
&& !IsBattlerTrapped(battler, TRUE)
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
&& AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS))
{
// Consider switching if all moves are worthless to use.
if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak.
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) // Mon has more than 50% of its HP
{
s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
if (doubleBattle)
{
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (i != battler && IsBattlerAlive(i))
{
for (j = 0; j < MAX_MON_MOVES; j++)
{
if (gBattleStruct->aiFinalScore[battler][i][j] > cap)
break;
}
if (j != MAX_MON_MOVES)
break;
}
}
if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler))
return TRUE;
}
else
{
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (AI_THINKING_STRUCT->score[i] > cap)
break;
}
if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler))
return TRUE;
}
}
// Consider switching if your mon with truant is bodied by Protect spam.
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
if (GetBattlerAbility(battler) == ABILITY_TRUANT
&& IsTruantMonVulnerable(battler, gBattlerTarget)
&& gDisableStructs[battler].truantCounter
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2
&& AI_SwitchMonIfSuitable(battler))
{
return TRUE;
}
}
return FALSE;
}
static u8 ChooseMoveOrAction_Singles(void)
{
u8 currentMoveArray[MAX_MON_MOVES];
@ -434,46 +506,9 @@ static u8 ChooseMoveOrAction_Singles(void)
if (AI_THINKING_STRUCT->aiAction & AI_ACTION_WATCH)
return AI_CHOICE_WATCH;
// If can switch.
if (CountUsablePartyMons(sBattler_AI) > 0
&& !IsAbilityPreventingEscape(sBattler_AI)
&& !(gBattleMons[sBattler_AI].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION))
&& !(gStatuses3[sBattler_AI] & STATUS3_ROOTED)
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
&& AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS))
{
// Consider switching if all moves are worthless to use.
if (GetTotalBaseStat(gBattleMons[sBattler_AI].species) >= 310 // Mon is not weak.
&& gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2)
{
s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (AI_THINKING_STRUCT->score[i] > cap)
break;
}
if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto(sBattler_AI) != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
return AI_CHOICE_SWITCH;
}
}
// Consider switching if your mon with truant is bodied by Protect spam.
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
if (GetBattlerAbility(sBattler_AI) == ABILITY_TRUANT
&& IsTruantMonVulnerable(sBattler_AI, gBattlerTarget)
&& gDisableStructs[sBattler_AI].truantCounter
&& gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2)
{
if (GetMostSuitableMonToSwitchInto(sBattler_AI) != PARTY_SIZE)
{
AI_THINKING_STRUCT->switchMon = TRUE;
return AI_CHOICE_SWITCH;
}
}
}
// Switch mon if there are no good moves to use.
if (AI_ShouldSwitchIfBadMoves(sBattler_AI, FALSE))
return AI_CHOICE_SWITCH;
numOfBestMoves = 1;
currentMoveArray[0] = AI_THINKING_STRUCT->score[0];
@ -588,7 +623,6 @@ static u8 ChooseMoveOrAction_Doubles(void)
if (i == BATTLE_PARTNER(sBattler_AI) && bestMovePointsForTarget[i] < 100)
{
bestMovePointsForTarget[i] = -1;
mostViableMovesScores[0] = mostViableMovesScores[0]; // Needed to match.
}
}
@ -598,6 +632,10 @@ static u8 ChooseMoveOrAction_Doubles(void)
}
}
// Switch mon if all of the moves are bad to use against any of the target.
if (AI_ShouldSwitchIfBadMoves(sBattler_AI, TRUE))
return AI_CHOICE_SWITCH;
mostMovePoints = bestMovePointsForTarget[0];
mostViableTargetsArray[0] = 0;
mostViableTargetsNo = 1;

View File

@ -60,10 +60,10 @@ void GetAIPartyIndexes(u32 battler, s32 *firstId, s32 *lastId)
static bool8 ShouldSwitchIfAllBadMoves(u32 battler)
{
if (gBattleResources->ai->switchMon)
if (AI_DATA->shouldSwitchMon & gBitTable[battler])
{
gBattleResources->ai->switchMon = 0;
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
AI_DATA->shouldSwitchMon &= ~(gBitTable[battler]);
gBattleStruct->AI_monToSwitchIntoId[battler] = AI_DATA->monToSwitchId[battler];
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
}

View File

@ -275,6 +275,7 @@ void LaunchBattleAnimation(u32 animType, u32 animId)
case B_ANIM_WISH_HEAL:
case B_ANIM_MEGA_EVOLUTION:
case B_ANIM_PRIMAL_REVERSION:
case B_ANIM_ULTRA_BURST:
case B_ANIM_GULP_MISSILE:
sAnimHideHpBoxes = TRUE;
break;

View File

@ -4761,6 +4761,18 @@ const struct SpriteTemplate gSpriteTemplate_BitterMaliceRing = {
.callback = AnimParticleInVortex
};
//ultra burst
const struct SpriteTemplate gUltraBurstSymbolSpriteTemplate =
{
.tileTag = ANIM_TAG_ULTRA_BURST_SYMBOL,
.paletteTag = ANIM_TAG_ULTRA_BURST_SYMBOL,
.oam = &gOamData_AffineDouble_ObjBlend_32x32,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gAffineAnims_LusterPurgeCircle,
.callback = AnimSpriteOnMonPos
};
// Z MOVES
//activate
const struct SpriteTemplate gZMoveSymbolSpriteTemplate =

View File

@ -561,6 +561,8 @@ static void OpponentHandleChooseMove(u32 battler)
QueueZMove(battler, chosenMove);
if (CanMegaEvolve(battler)) // If opponent can mega evolve, do it.
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
else if (CanUltraBurst(battler))
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_ULTRA_BURST) | (gBattlerTarget << 8));
else
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
}

View File

@ -436,6 +436,8 @@ static void HandleInputChooseTarget(u32 battler)
gSprites[gBattlerSpriteIds[gMultiUsePlayerCursor]].callback = SpriteCB_HideAsMoveTarget;
if (gBattleStruct->mega.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8));
else if (gBattleStruct->burst.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8));
else
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8));
EndBounceEffect(gMultiUsePlayerCursor, BOUNCE_HEALTHBOX);
@ -594,9 +596,11 @@ static void HandleInputShowEntireFieldTargets(u32 battler)
HideAllTargets();
if (gBattleStruct->mega.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8));
else if (gBattleStruct->burst.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8));
else
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8));
HideMegaTriggerSprite();
HideTriggerSprites();
PlayerBufferExecCompleted(battler);
}
else if (JOY_NEW(B_BUTTON) || gPlayerDpadHoldFrames > 59)
@ -622,6 +626,8 @@ static void HandleInputShowTargets(u32 battler)
HideShownTargets(battler);
if (gBattleStruct->mega.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8));
else if (gBattleStruct->burst.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8));
else
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8));
HideTriggerSprites();
@ -737,6 +743,8 @@ static void HandleInputChooseMove(u32 battler)
default:
if (gBattleStruct->mega.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_MEGA_EVOLUTION | (gMultiUsePlayerCursor << 8));
else if (gBattleStruct->burst.playerSelect)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8));
else
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8));
HideTriggerSprites();
@ -773,6 +781,7 @@ static void HandleInputChooseMove(u32 battler)
else
{
gBattleStruct->mega.playerSelect = FALSE;
gBattleStruct->burst.playerSelect = FALSE;
gBattleStruct->zmove.viable = FALSE;
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF);
HideTriggerSprites();
@ -857,6 +866,12 @@ static void HandleInputChooseMove(u32 battler)
ChangeMegaTriggerSprite(gBattleStruct->mega.triggerSpriteId, gBattleStruct->mega.playerSelect);
PlaySE(SE_SELECT);
}
else if (CanUltraBurst(battler))
{
gBattleStruct->burst.playerSelect ^= 1;
ChangeBurstTriggerSprite(gBattleStruct->burst.triggerSpriteId, gBattleStruct->burst.playerSelect);
PlaySE(SE_SELECT);
}
else if (gBattleStruct->zmove.viable)
{
// show z move name / info
@ -873,6 +888,7 @@ static void HandleInputChooseMove(u32 battler)
static void ReloadMoveNames(u32 battler)
{
gBattleStruct->mega.playerSelect = FALSE;
gBattleStruct->burst.playerSelect = FALSE;
gBattleStruct->zmove.viewing = FALSE;
MoveSelectionDestroyCursorAt(battler);
MoveSelectionDisplayMoveNames(battler);
@ -1946,10 +1962,15 @@ static void PlayerHandleChooseMove(u32 battler)
InitMoveSelectionsVarsAndStrings(battler);
gBattleStruct->mega.playerSelect = FALSE;
gBattleStruct->burst.playerSelect = FALSE;
if (!IsMegaTriggerSpriteActive())
gBattleStruct->mega.triggerSpriteId = 0xFF;
if (CanMegaEvolve(battler))
CreateMegaTriggerSprite(battler, 0);
if (!IsBurstTriggerSpriteActive())
gBattleStruct->burst.triggerSpriteId = 0xFF;
if (CanUltraBurst(battler))
CreateBurstTriggerSprite(battler, 0);
if (!IsZMoveTriggerSpriteActive())
gBattleStruct->zmove.triggerSpriteId = 0xFF;

View File

@ -352,23 +352,32 @@ static void PlayerPartnerHandleChooseMove(u32 battler)
chosenMoveId = gBattleStruct->aiMoveOrAction[battler];
gBattlerTarget = gBattleStruct->aiChosenTarget[battler];
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = battler;
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
if (chosenMoveId == AI_CHOICE_SWITCH)
{
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF);
}
if (ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId]))
QueueZMove(battler, moveInfo->moves[chosenMoveId]);
// If partner can mega evolve, do it.
if (CanMegaEvolve(battler))
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
else
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
{
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = battler;
if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
{
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & gBitTable[gBattlerTarget])
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
if (ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId]))
QueueZMove(battler, moveInfo->moves[chosenMoveId]);
// If partner can mega evolve, do it.
if (CanMegaEvolve(battler))
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8));
else if (CanUltraBurst(battler))
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_ULTRA_BURST) | (gBattlerTarget << 8));
else
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
}
PlayerPartnerBufferExecCompleted(battler);
}
@ -382,7 +391,7 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler)
chosenMonId = gSelectedMonPartyId = GetFirstFaintedPartyIndex(battler);
}
// Switching out
else
else if (gBattleStruct->monToSwitchIntoId[battler] == PARTY_SIZE)
{
chosenMonId = GetMostSuitableMonToSwitchInto(battler);
if (chosenMonId == PARTY_SIZE) // just switch to the next mon
@ -402,6 +411,12 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler)
}
*(gBattleStruct->monToSwitchIntoId + battler) = chosenMonId;
}
else // Mon to switch out has been already chosen.
{
chosenMonId = gBattleStruct->monToSwitchIntoId[battler];
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
}
BtlController_EmitChosenMonReturnValue(battler, BUFFER_B, chosenMonId, NULL);
PlayerPartnerBufferExecCompleted(battler);
}

View File

@ -194,6 +194,7 @@ static void SpriteCB_StatusSummaryBalls_Exit(struct Sprite *);
static void SpriteCB_StatusSummaryBalls_OnSwitchout(struct Sprite *);
static void SpriteCb_MegaTrigger(struct Sprite *);
static void SpriteCb_BurstTrigger(struct Sprite *);
static void MegaIndicator_SetVisibilities(u32 healthboxId, bool32 invisible);
static void MegaIndicator_UpdateLevel(u32 healthboxId, u32 level);
static void MegaIndicator_CreateSprite(u32 battlerId, u32 healthboxSpriteId);
@ -676,6 +677,64 @@ static const struct SpriteTemplate sSpriteTemplate_MegaTrigger =
.callback = SpriteCb_MegaTrigger
};
static const u8 ALIGNED(4) sBurstTriggerGfx[] = INCBIN_U8("graphics/battle_interface/burst_trigger.4bpp");
static const u16 sBurstTriggerPal[] = INCBIN_U16("graphics/battle_interface/burst_trigger.gbapal");
static const struct SpriteSheet sSpriteSheet_BurstTrigger =
{
sBurstTriggerGfx, sizeof(sBurstTriggerGfx), TAG_BURST_TRIGGER_TILE
};
static const struct SpritePalette sSpritePalette_BurstTrigger =
{
sBurstTriggerPal, TAG_BURST_TRIGGER_PAL
};
static const struct OamData sOamData_BurstTrigger =
{
.y = 0,
.affineMode = 0,
.objMode = 0,
.mosaic = 0,
.bpp = 0,
.shape = ST_OAM_SQUARE,
.x = 0,
.matrixNum = 0,
.size = 2,
.tileNum = 0,
.priority = 1,
.paletteNum = 0,
.affineParam = 0,
};
static const union AnimCmd sSpriteAnim_BurstTriggerOff[] =
{
ANIMCMD_FRAME(0, 0),
ANIMCMD_END
};
static const union AnimCmd sSpriteAnim_BurstTriggerOn[] =
{
ANIMCMD_FRAME(16, 0),
ANIMCMD_END
};
static const union AnimCmd *const sSpriteAnimTable_BurstTrigger[] =
{
sSpriteAnim_BurstTriggerOff,
sSpriteAnim_BurstTriggerOn,
};
static const struct SpriteTemplate sSpriteTemplate_BurstTrigger =
{
.tileTag = TAG_BURST_TRIGGER_TILE,
.paletteTag = TAG_BURST_TRIGGER_PAL,
.oam = &sOamData_BurstTrigger,
.anims = sSpriteAnimTable_BurstTrigger,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCb_BurstTrigger
};
// Because the healthbox is too large to fit into one sprite, it is divided into two sprites.
// healthboxLeft or healthboxMain is the left part that is used as the 'main' sprite.
// healthboxRight or healthboxOther is the right part of the healthbox.
@ -1418,6 +1477,7 @@ void HideMegaTriggerSprite(void)
void HideTriggerSprites(void)
{
HideMegaTriggerSprite();
HideBurstTriggerSprite();
HideZMoveTriggerSprite();
}
@ -1433,6 +1493,128 @@ void DestroyMegaTriggerSprite(void)
#undef tBattler
#undef tHide
// Ultra Burst Trigger icon functions.
void ChangeBurstTriggerSprite(u8 spriteId, u8 animId)
{
StartSpriteAnim(&gSprites[spriteId], animId);
}
#define SINGLES_BURST_TRIGGER_POS_X_OPTIMAL (30)
#define SINGLES_BURST_TRIGGER_POS_X_PRIORITY (31)
#define SINGLES_BURST_TRIGGER_POS_X_SLIDE (15)
#define SINGLES_BURST_TRIGGER_POS_Y_DIFF (-11)
#define DOUBLES_BURST_TRIGGER_POS_X_OPTIMAL (30)
#define DOUBLES_BURST_TRIGGER_POS_X_PRIORITY (31)
#define DOUBLES_BURST_TRIGGER_POS_X_SLIDE (15)
#define DOUBLES_BURST_TRIGGER_POS_Y_DIFF (-4)
#define tBattler data[0]
#define tHide data[1]
void CreateBurstTriggerSprite(u8 battlerId, u8 palId)
{
LoadSpritePalette(&sSpritePalette_BurstTrigger);
if (GetSpriteTileStartByTag(TAG_BURST_TRIGGER_TILE) == 0xFFFF)
LoadSpriteSheet(&sSpriteSheet_BurstTrigger);
if (gBattleStruct->burst.triggerSpriteId == 0xFF)
{
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
gBattleStruct->burst.triggerSpriteId = CreateSprite(&sSpriteTemplate_BurstTrigger,
gSprites[gHealthboxSpriteIds[battlerId]].x - DOUBLES_BURST_TRIGGER_POS_X_SLIDE,
gSprites[gHealthboxSpriteIds[battlerId]].y - DOUBLES_BURST_TRIGGER_POS_Y_DIFF, 0);
else
gBattleStruct->burst.triggerSpriteId = CreateSprite(&sSpriteTemplate_BurstTrigger,
gSprites[gHealthboxSpriteIds[battlerId]].x - SINGLES_BURST_TRIGGER_POS_X_SLIDE,
gSprites[gHealthboxSpriteIds[battlerId]].y - SINGLES_BURST_TRIGGER_POS_Y_DIFF, 0);
}
gSprites[gBattleStruct->burst.triggerSpriteId].tBattler = battlerId;
gSprites[gBattleStruct->burst.triggerSpriteId].tHide = FALSE;
ChangeBurstTriggerSprite(gBattleStruct->burst.triggerSpriteId, palId);
}
static void SpriteCb_BurstTrigger(struct Sprite *sprite)
{
s32 xSlide, xPriority, xOptimal;
s32 yDiff;
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
{
xSlide = DOUBLES_BURST_TRIGGER_POS_X_SLIDE;
xPriority = DOUBLES_BURST_TRIGGER_POS_X_PRIORITY;
xOptimal = DOUBLES_BURST_TRIGGER_POS_X_OPTIMAL;
yDiff = DOUBLES_BURST_TRIGGER_POS_Y_DIFF;
}
else
{
xSlide = SINGLES_BURST_TRIGGER_POS_X_SLIDE;
xPriority = SINGLES_BURST_TRIGGER_POS_X_PRIORITY;
xOptimal = SINGLES_BURST_TRIGGER_POS_X_OPTIMAL;
yDiff = SINGLES_BURST_TRIGGER_POS_Y_DIFF;
}
if (sprite->tHide)
{
if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide)
sprite->x++;
if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority)
sprite->oam.priority = 2;
else
sprite->oam.priority = 1;
sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff;
sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff;
if (sprite->x == gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xSlide)
DestroyBurstTriggerSprite();
}
else
{
if (sprite->x != gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xOptimal)
sprite->x--;
if (sprite->x >= gSprites[gHealthboxSpriteIds[sprite->tBattler]].x - xPriority)
sprite->oam.priority = 2;
else
sprite->oam.priority = 1;
sprite->y = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y - yDiff;
sprite->y2 = gSprites[gHealthboxSpriteIds[sprite->tBattler]].y2 - yDiff;
}
}
bool32 IsBurstTriggerSpriteActive(void)
{
if (GetSpriteTileStartByTag(TAG_BURST_TRIGGER_TILE) == 0xFFFF)
return FALSE;
else if (IndexOfSpritePaletteTag(TAG_BURST_TRIGGER_PAL) != 0xFF)
return TRUE;
else
return FALSE;
}
void HideBurstTriggerSprite(void)
{
if (gBattleStruct->burst.triggerSpriteId >= MAX_SPRITES)
return;
ChangeBurstTriggerSprite(gBattleStruct->burst.triggerSpriteId, 0);
gSprites[gBattleStruct->burst.triggerSpriteId].tHide = TRUE;
}
void DestroyBurstTriggerSprite(void)
{
FreeSpritePaletteByTag(TAG_BURST_TRIGGER_PAL);
FreeSpriteTilesByTag(TAG_BURST_TRIGGER_TILE);
if (gBattleStruct->burst.triggerSpriteId != 0xFF)
DestroySprite(&gSprites[gBattleStruct->burst.triggerSpriteId]);
gBattleStruct->burst.triggerSpriteId = 0xFF;
}
#undef tBattler
#undef tHide
// Code for Mega Evolution (And Alpha/Omega) Trigger icon visible on the battler's healthbox.
enum
{

View File

@ -3111,6 +3111,7 @@ static void BattleStartClearSetData(void)
gBattleStruct->arenaLostOpponentMons = 0;
gBattleStruct->mega.triggerSpriteId = 0xFF;
gBattleStruct->burst.triggerSpriteId = 0xFF;
for (i = 0; i < ARRAY_COUNT(gSideTimers); i++)
{
@ -4288,6 +4289,7 @@ static void HandleTurnActionSelectionState(void)
}
gBattleStruct->mega.toEvolve &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]);
gBattleStruct->burst.toBurst &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]);
gBattleStruct->zmove.toBeUsed[BATTLE_PARTNER(GetBattlerPosition(battler))] = MOVE_NONE;
BtlController_EmitEndBounceEffect(battler, BUFFER_A);
MarkBattlerForControllerExec(battler);
@ -4374,11 +4376,14 @@ static void HandleTurnActionSelectionState(void)
RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][2]);
RecordedBattle_SetBattlerAction(battler, gBattleResources->bufferB[battler][3]);
}
*(gBattleStruct->chosenMovePositions + battler) = gBattleResources->bufferB[battler][2] & ~RET_MEGA_EVOLUTION;
gChosenMoveByBattler[battler] = gBattleMons[battler].moves[*(gBattleStruct->chosenMovePositions + battler)];
*(gBattleStruct->moveTarget + battler) = gBattleResources->bufferB[battler][3];
gBattleStruct->chosenMovePositions[battler] = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST);
gChosenMoveByBattler[battler] = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]];
gBattleStruct->moveTarget[battler] = gBattleResources->bufferB[battler][3];
if (gBattleResources->bufferB[battler][2] & RET_MEGA_EVOLUTION)
gBattleStruct->mega.toEvolve |= gBitTable[battler];
else if (gBattleResources->bufferB[battler][2] & RET_ULTRA_BURST)
gBattleStruct->burst.toBurst |= gBitTable[battler];
gBattleCommunication[battler]++;
}
break;
@ -4996,9 +5001,9 @@ static void PopulateArrayWithBattlers(u8 *battlers)
static bool32 TryDoMegaEvosBeforeMoves(void)
{
if (!(gHitMarker & HITMARKER_RUN) && gBattleStruct->mega.toEvolve)
if (!(gHitMarker & HITMARKER_RUN) && (gBattleStruct->mega.toEvolve || gBattleStruct->burst.toBurst))
{
u32 i;
u32 i, battler;
struct Pokemon *party;
struct Pokemon *mon;
u8 megaOrder[MAX_BATTLERS_COUNT];
@ -5021,6 +5026,18 @@ static bool32 TryDoMegaEvosBeforeMoves(void)
BattleScriptExecute(BattleScript_MegaEvolution);
return TRUE;
}
if (gBattleStruct->burst.toBurst & gBitTable[megaOrder[i]]
&& !(gProtectStructs[megaOrder[i]].noValidMoves))
{
battler = gBattlerAttacker = megaOrder[i];
gBattleStruct->burst.toBurst &= ~(gBitTable[battler]);
gLastUsedItem = gBattleMons[battler].item;
party = GetBattlerParty(battler);
mon = &party[gBattlerPartyIndexes[battler]];
BattleScriptExecute(BattleScript_UltraBurst);
return TRUE;
}
}
}

View File

@ -800,6 +800,9 @@ static const u8 sText_ItemRestoredSpeciesPP[] = _("{B_BUFF1} had its\nPP restore
static const u8 sText_AtkTrappedDef[] = _("{B_ATK_NAME_WITH_PREFIX} trapped\nthe {B_DEF_NAME_WITH_PREFIX}!");
static const u8 sText_MirrorHerbCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} used its {B_LAST_ITEM}\nto mirror its opponent's stat changes!");
static const u8 sText_PkmnItemMelted[] = _("{B_ATK_NAME_WITH_PREFIX} corroded\n{B_DEF_NAME_WITH_PREFIX}'s {B_LAST_ITEM}!");
static const u8 sText_UltraBurstReacting[] = _("Bright light is about to\nburst out of {B_ATK_NAME_WITH_PREFIX}!");
static const u8 sText_UltraBurstCompleted[] = _("{B_ATK_NAME_WITH_PREFIX} regained its\ntrue power through Ultra Burst!");
const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
{
@ -1456,6 +1459,8 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
[STRINGID_PKMNFROSTBITEHEALED - BATTLESTRINGS_TABLE_START] = sText_PkmnFrostbiteHealed,
[STRINGID_PKMNFROSTBITEHEALED2 - BATTLESTRINGS_TABLE_START] = sText_PkmnFrostbiteHealed2,
[STRINGID_PKMNFROSTBITEHEALEDBY - BATTLESTRINGS_TABLE_START] = sText_PkmnFrostbiteHealedBy,
[STRINGID_ULTRABURSTREACTING - BATTLESTRINGS_TABLE_START] = sText_UltraBurstReacting,
[STRINGID_ULTRABURSTCOMPLETED - BATTLESTRINGS_TABLE_START] = sText_UltraBurstCompleted,
};
const u16 gTrainerUsedItemStringIds[] =

View File

@ -4964,7 +4964,8 @@ static void PlayAnimation(u32 battler, u8 animId, const u16 *argPtr, const u8 *n
|| animId == B_ANIM_ILLUSION_OFF
|| animId == B_ANIM_FORM_CHANGE
|| animId == B_ANIM_SUBSTITUTE_FADE
|| animId == B_ANIM_PRIMAL_REVERSION)
|| animId == B_ANIM_PRIMAL_REVERSION
|| animId == B_ANIM_ULTRA_BURST)
{
BtlController_EmitBattleAnimation(battler, BUFFER_A, animId, *argPtr);
MarkBattlerForControllerExec(battler);
@ -8455,7 +8456,7 @@ static bool32 CourtChangeSwapSideStatuses(void)
SWAP(sideTimerPlayer->stickyWebBattlerSide, sideTimerOpp->stickyWebBattlerSide, temp);
}
static void HandleScriptMegaPrimal(u32 caseId, u32 battler, bool32 isMega)
static void HandleScriptMegaPrimalBurst(u32 caseId, u32 battler, u32 type)
{
struct Pokemon *party = GetBattlerParty(battler);
struct Pokemon *mon = &party[gBattlerPartyIndexes[battler]];
@ -8465,13 +8466,15 @@ static void HandleScriptMegaPrimal(u32 caseId, u32 battler, bool32 isMega)
// Change species.
if (caseId == 0)
{
if (isMega)
if (type == HANDLE_TYPE_MEGA_EVOLUTION)
{
if (!TryBattleFormChange(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM))
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE);
}
else
else if (type == HANDLE_TYPE_PRIMAL_REVERSION)
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_PRIMAL_REVERSION);
else
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_ULTRA_BURST);
PREPARE_SPECIES_BUFFER(gBattleTextBuff1, gBattleMons[battler].species);
@ -8484,8 +8487,10 @@ static void HandleScriptMegaPrimal(u32 caseId, u32 battler, bool32 isMega)
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL);
if (side == B_SIDE_OPPONENT)
SetBattlerShadowSpriteCallback(battler, gBattleMons[battler].species);
if (isMega)
if (type == HANDLE_TYPE_MEGA_EVOLUTION)
gBattleStruct->mega.alreadyEvolved[position] = TRUE;
if (type == HANDLE_TYPE_ULTRA_BURST)
gBattleStruct->burst.alreadyBursted[position] = TRUE;
}
}
@ -9515,14 +9520,21 @@ static void Cmd_various(void)
case VARIOUS_HANDLE_MEGA_EVO:
{
VARIOUS_ARGS(u8 case_);
HandleScriptMegaPrimal(cmd->case_, battler, TRUE);
HandleScriptMegaPrimalBurst(cmd->case_, battler, HANDLE_TYPE_MEGA_EVOLUTION);
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
case VARIOUS_HANDLE_PRIMAL_REVERSION:
{
VARIOUS_ARGS(u8 case_);
HandleScriptMegaPrimal(cmd->case_, battler, FALSE);
HandleScriptMegaPrimalBurst(cmd->case_, battler, HANDLE_TYPE_PRIMAL_REVERSION);
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
case VARIOUS_HANDLE_ULTRA_BURST:
{
VARIOUS_ARGS(u8 case_);
HandleScriptMegaPrimalBurst(cmd->case_, battler, HANDLE_TYPE_ULTRA_BURST);
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}

View File

@ -1608,7 +1608,7 @@ static bool32 IsBelchPreventingMove(u32 battler, u32 move)
u32 TrySetCantSelectMoveBattleScript(u32 battler)
{
u32 limitations = 0;
u8 moveId = gBattleResources->bufferB[battler][2] & ~RET_MEGA_EVOLUTION;
u8 moveId = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST);
u32 move = gBattleMons[battler].moves[moveId];
u32 holdEffect = GetBattlerHoldEffect(battler, TRUE);
u16 *choicedMove = &gBattleStruct->choicedMove[battler];
@ -10083,6 +10083,7 @@ bool32 DoesSpeciesUseHoldItemToChangeForm(u16 species, u16 heldItemId)
{
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
case FORM_CHANGE_BATTLE_ULTRA_BURST:
case FORM_CHANGE_ITEM_HOLD:
if (formChanges[i].param1 == heldItemId)
return TRUE;
@ -10157,6 +10158,61 @@ bool32 CanMegaEvolve(u8 battler)
return FALSE;
}
bool32 CanUltraBurst(u8 battler)
{
u32 itemId, holdEffect, species;
struct Pokemon *mon;
u8 battlerPosition = GetBattlerPosition(battler);
u8 partnerPosition = GetBattlerPosition(BATTLE_PARTNER(battler));
// Check if Player has a Z Ring
if ((GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT))
&& !CheckBagHasItem(ITEM_Z_POWER_RING, 1))
return FALSE;
// Check if trainer already ultra bursted a pokemon.
if (gBattleStruct->burst.alreadyBursted[battlerPosition])
return FALSE;
// Cannot use z move and ultra burst on same turn
if (gBattleStruct->zmove.toBeUsed[battler])
return FALSE;
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE
&& IsPartnerMonFromSameTrainer(battler)
&& (gBattleStruct->burst.alreadyBursted[partnerPosition] || (gBattleStruct->burst.toBurst & gBitTable[BATTLE_PARTNER(battler)])))
return FALSE;
// Check if mon is currently held by Sky Drop
if (gStatuses3[battler] & STATUS3_SKY_DROPPED)
return FALSE;
// Gets mon data.
if (GetBattlerSide(battler) == B_SIDE_OPPONENT)
mon = &gEnemyParty[gBattlerPartyIndexes[battler]];
else
mon = &gPlayerParty[gBattlerPartyIndexes[battler]];
species = GetMonData(mon, MON_DATA_SPECIES);
itemId = GetMonData(mon, MON_DATA_HELD_ITEM);
// Check if there is an entry in the evolution table for Ultra Burst.
if (GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_ULTRA_BURST) != SPECIES_NONE)
{
if (itemId == ITEM_ENIGMA_BERRY_E_READER)
holdEffect = gEnigmaBerries[battler].holdEffect;
else
holdEffect = ItemId_GetHoldEffect(itemId);
// Can Ultra Burst via Z Crystal.
if (holdEffect == HOLD_EFFECT_Z_CRYSTAL)
return TRUE;
}
// No checks passed, the mon CAN'T ultra burst.
return FALSE;
}
bool32 IsBattlerMegaEvolved(u8 battler)
{
// While Transform does copy stats and visuals, it shouldn't be counted as true Mega Evolution.
@ -10173,6 +10229,14 @@ bool32 IsBattlerPrimalReverted(u8 battler)
return (gSpeciesInfo[gBattleMons[battler].species].flags & SPECIES_FLAG_PRIMAL_REVERSION);
}
bool32 IsBattlerUltraBursted(u8 battler)
{
// While Transform does copy stats and visuals, it shouldn't be counted as true Ultra Burst.
if (gBattleMons[battler].status2 & STATUS2_TRANSFORMED)
return FALSE;
return (gSpeciesInfo[gBattleMons[battler].species].flags & SPECIES_FLAG_ULTRA_BURST);
}
// Returns SPECIES_NONE if no form change is possible
u16 GetBattleFormChangeTargetSpecies(u8 battler, u16 method)
{
@ -10196,6 +10260,7 @@ u16 GetBattleFormChangeTargetSpecies(u8 battler, u16 method)
{
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
case FORM_CHANGE_BATTLE_ULTRA_BURST:
if (heldItem == formChanges[i].param1)
targetSpecies = formChanges[i].targetSpecies;
break;
@ -10266,8 +10331,8 @@ bool32 CanBattlerFormChange(u8 battler, u16 method)
if (gBattleMons[battler].status2 & STATUS2_TRANSFORMED
&& B_TRANSFORM_FORM_CHANGES >= GEN_5)
return FALSE;
// Mega Evolved Pokémon should always revert to normal upon fainting or ending the battle.
if (IsBattlerMegaEvolved(battler) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE))
// Mega Evolved and Ultra Bursted Pokémon should always revert to normal upon fainting or ending the battle.
if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE))
return TRUE;
else if (IsBattlerPrimalReverted(battler) && (method == FORM_CHANGE_END_BATTLE))
return TRUE;
@ -10303,8 +10368,8 @@ bool32 TryBattleFormChange(u8 battler, u16 method)
{
bool8 restoreSpecies = FALSE;
// Mega Evolved Pokémon should always revert to normal upon fainting or ending the battle, so no need to add it to the form change tables.
if (IsBattlerMegaEvolved(battler) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE))
// Mega Evolved and Ultra Bursted Pokémon should always revert to normal upon fainting or ending the battle, so no need to add it to the form change tables.
if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE))
restoreSpecies = TRUE;
// Unlike Megas, Primal Reversion isn't canceled on fainting.

View File

@ -210,6 +210,8 @@ const struct FormChange *const gFormChangeTablePointers[NUM_SPECIES] =
[SPECIES_MINIOR_CORE_VIOLET] = sMiniorVioletFormChangeTable,
[SPECIES_MINIOR_METEOR_YELLOW] = sMiniorYellowFormChangeTable,
[SPECIES_MINIOR_CORE_YELLOW] = sMiniorYellowFormChangeTable,
[SPECIES_NECROZMA_DUSK_MANE] = sNecrozmaDuskManeFormChangeTable,
[SPECIES_NECROZMA_DAWN_WINGS] = sNecrozmaDawnWingsFormChangeTable,
#endif
#if P_GEN_8_POKEMON == TRUE
[SPECIES_CRAMORANT] = sCramorantFormChangeTable,

View File

@ -548,6 +548,14 @@ static const struct FormChange sMiniorYellowFormChangeTable[] = {
{FORM_CHANGE_END_BATTLE, SPECIES_MINIOR_CORE_YELLOW},
{FORM_CHANGE_TERMINATOR},
};
static const struct FormChange sNecrozmaDuskManeFormChangeTable[] = {
{FORM_CHANGE_BATTLE_ULTRA_BURST, SPECIES_NECROZMA_ULTRA, ITEM_ULTRANECROZIUM_Z},
{FORM_CHANGE_TERMINATOR},
};
static const struct FormChange sNecrozmaDawnWingsFormChangeTable[] = {
{FORM_CHANGE_BATTLE_ULTRA_BURST, SPECIES_NECROZMA_ULTRA, ITEM_ULTRANECROZIUM_Z},
{FORM_CHANGE_TERMINATOR},
};
#endif
#if P_GEN_8_POKEMON == TRUE

View File

@ -24484,7 +24484,7 @@ const struct SpeciesInfo gSpeciesInfo[] =
.abilities = {ABILITY_NEUROFORCE, ABILITY_NONE},
.bodyColor = BODY_COLOR_YELLOW,
.noFlip = TRUE,
.flags = SPECIES_FLAG_LEGENDARY,
.flags = SPECIES_FLAG_LEGENDARY | SPECIES_FLAG_ULTRA_BURST,
},
[SPECIES_MAGEARNA_ORIGINAL_COLOR] = MAGEARNA_SPECIES_INFO(BODY_COLOR_RED),

View File

@ -0,0 +1,127 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Dusk Mane Necrozma can Ultra Burst holding Ultranecrozium Z")
{
GIVEN {
ASSUME(P_GEN_7_POKEMON == TRUE);
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, ultraBurst: TRUE); }
} SCENE {
MESSAGE("Bright light is about to burst out of Necrozma!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player);
MESSAGE("Necrozma regained its true power through Ultra Burst!");
} THEN {
EXPECT_EQ(player->species, SPECIES_NECROZMA_ULTRA);
}
}
DOUBLE_BATTLE_TEST("Ultra Burst's order is determined by Speed - opponent faster")
{
GIVEN {
ASSUME(P_GEN_7_POKEMON == TRUE);
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(1); }
PLAYER(SPECIES_WOBBUFFET) { Speed(3); }
OPPONENT(SPECIES_NECROZMA_DAWN_WINGS) { Item(ITEM_ULTRANECROZIUM_Z); Speed(3); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(4); }
} WHEN {
TURN { MOVE(opponentLeft, MOVE_CELEBRATE, ultraBurst: TRUE); MOVE(playerLeft, MOVE_CELEBRATE, ultraBurst: TRUE); }
} SCENE {
MESSAGE("Bright light is about to burst out of Foe Necrozma!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, opponentLeft);
MESSAGE("Foe Necrozma regained its true power through Ultra Burst!");
MESSAGE("Bright light is about to burst out of Necrozma!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, playerLeft);
MESSAGE("Necrozma regained its true power through Ultra Burst!");
}
}
DOUBLE_BATTLE_TEST("Ultra Burst's order is determined by Speed - player faster")
{
GIVEN {
ASSUME(P_GEN_7_POKEMON == TRUE);
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(5); }
PLAYER(SPECIES_WOBBUFFET) { Speed(3); }
OPPONENT(SPECIES_NECROZMA_DAWN_WINGS) { Item(ITEM_ULTRANECROZIUM_Z); Speed(2); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(4); }
} WHEN {
TURN { MOVE(opponentLeft, MOVE_CELEBRATE, ultraBurst: TRUE); MOVE(playerLeft, MOVE_CELEBRATE, ultraBurst: TRUE); }
} SCENE {
MESSAGE("Bright light is about to burst out of Necrozma!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, playerLeft);
MESSAGE("Necrozma regained its true power through Ultra Burst!");
MESSAGE("Bright light is about to burst out of Foe Necrozma!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, opponentLeft);
MESSAGE("Foe Necrozma regained its true power through Ultra Burst!");
}
}
SINGLE_BATTLE_TEST("Ultra Burst affects turn order")
{
GIVEN {
ASSUME(P_GEN_7_POKEMON == TRUE);
ASSUME(B_MEGA_EVO_TURN_ORDER);
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(105); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(106); }
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, ultraBurst: TRUE); }
} SCENE {
MESSAGE("Necrozma used Celebrate!");
MESSAGE("Foe Wobbuffet used Celebrate!");
} THEN {
ASSUME(player->speed == 263);
}
}
DOUBLE_BATTLE_TEST("Ultra Burst happens after switching, but before Focus Punch-like Moves")
{
GIVEN {
ASSUME(P_GEN_7_POKEMON == TRUE);
ASSUME(gBattleMoves[MOVE_FOCUS_PUNCH].effect == EFFECT_FOCUS_PUNCH);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); }
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { SWITCH(opponentRight, 2); MOVE(playerRight, MOVE_FOCUS_PUNCH, ultraBurst: TRUE, target: opponentLeft); MOVE(playerLeft, MOVE_FOCUS_PUNCH, target: opponentLeft); }
TURN {}
} SCENE {
MESSAGE("2 withdrew Wobbuffet!");
MESSAGE("2 sent out Wobbuffet!");
MESSAGE("Bright light is about to burst out of Necrozma!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, playerRight);
MESSAGE("Necrozma regained its true power through Ultra Burst!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FOCUS_PUNCH_SETUP, playerRight);
MESSAGE("Necrozma is tightening its focus!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FOCUS_PUNCH_SETUP, playerLeft);
MESSAGE("Wobbuffet is tightening its focus!");
}
}
SINGLE_BATTLE_TEST("Ultra Burst and Mega Evolution can happen on the same turn")
{
GIVEN {
ASSUME(P_GEN_7_POKEMON == TRUE);
PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(3); }
OPPONENT(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(2); }
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE, ultraBurst: TRUE); MOVE(opponent, MOVE_CELEBRATE, megaEvolve: TRUE); }
} SCENE {
MESSAGE("Bright light is about to burst out of Necrozma!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ULTRA_BURST, player);
MESSAGE("Necrozma regained its true power through Ultra Burst!");
MESSAGE("Foe Gardevoir's Gardevoirite is reacting to 's Mega Ring!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, opponent);
MESSAGE("Foe Gardevoir has Mega Evolved into Mega Gardevoir!");
} THEN {
EXPECT_EQ(player->species, SPECIES_NECROZMA_ULTRA);
EXPECT_EQ(opponent->species, SPECIES_GARDEVOIR_MEGA);
}
}

View File

@ -1483,6 +1483,9 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
if (ctx.explicitMegaEvolve && ctx.megaEvolve)
moveSlot |= RET_MEGA_EVOLUTION;
if (ctx.explicitUltraBurst && ctx.ultraBurst)
moveSlot |= RET_ULTRA_BURST;
if (ctx.explicitTarget)
{
target = ctx.target - gBattleMons;