diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index e66f4c2a8e..5e36241c9a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -20,6 +20,34 @@ .section script_data, "aw", %progbits +BattleScript_Terastallization:: + @ TODO: no string prints in S/V, but right now this helps with clarity + printstring STRINGID_PKMNTERASTALLIZEDINTO + @ TODO: replace this animation + playanimation BS_ATTACKER, B_ANIM_TOTEM_FLARE + waitanimation + end3 + +BattleScript_LowerAtkSpAtk:: + jumpifstat BS_EFFECT_BATTLER, CMP_GREATER_THAN, STAT_ATK, MIN_STAT_STAGE, BattleScript_LowerAtkSpAtkDoAnim + jumpifstat BS_EFFECT_BATTLER, CMP_EQUAL, STAT_SPATK, MIN_STAT_STAGE, BattleScript_LowerAtkSpAtkEnd +BattleScript_LowerAtkSpAtkDoAnim:: + setbyte sSTAT_ANIM_PLAYED, FALSE + playstatchangeanimation BS_EFFECT_BATTLER, BIT_ATK | BIT_SPATK, STAT_CHANGE_NEGATIVE + setstatchanger STAT_ATK, 1, TRUE + statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_LowerAtkSpAtkTrySpAtk + jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_DECREASE, BattleScript_LowerAtkSpAtkTrySpAtk + printfromtable gStatDownStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_LowerAtkSpAtkTrySpAtk:: + setstatchanger STAT_SPATK, 1, TRUE + statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_LowerAtkSpAtkEnd + jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_DECREASE, BattleScript_LowerAtkSpAtkEnd + printfromtable gStatDownStringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_LowerAtkSpAtkEnd: + return + BattleScript_EffectTidyUp:: attackcanceler attackstring diff --git a/graphics/battle_interface/bug_indicator.png b/graphics/battle_interface/bug_indicator.png new file mode 100644 index 0000000000..7ab7dc6dfa Binary files /dev/null and b/graphics/battle_interface/bug_indicator.png differ diff --git a/graphics/battle_interface/dark_indicator.png b/graphics/battle_interface/dark_indicator.png new file mode 100644 index 0000000000..149d2155a3 Binary files /dev/null and b/graphics/battle_interface/dark_indicator.png differ diff --git a/graphics/battle_interface/dragon_indicator.png b/graphics/battle_interface/dragon_indicator.png new file mode 100644 index 0000000000..4c1d9e1f78 Binary files /dev/null and b/graphics/battle_interface/dragon_indicator.png differ diff --git a/graphics/battle_interface/electric_indicator.png b/graphics/battle_interface/electric_indicator.png new file mode 100644 index 0000000000..518907dc05 Binary files /dev/null and b/graphics/battle_interface/electric_indicator.png differ diff --git a/graphics/battle_interface/fairy_indicator.png b/graphics/battle_interface/fairy_indicator.png new file mode 100644 index 0000000000..a41736eeec Binary files /dev/null and b/graphics/battle_interface/fairy_indicator.png differ diff --git a/graphics/battle_interface/fighting_indicator.png b/graphics/battle_interface/fighting_indicator.png new file mode 100644 index 0000000000..6102ab6846 Binary files /dev/null and b/graphics/battle_interface/fighting_indicator.png differ diff --git a/graphics/battle_interface/fire_indicator.png b/graphics/battle_interface/fire_indicator.png new file mode 100644 index 0000000000..4d3e59d543 Binary files /dev/null and b/graphics/battle_interface/fire_indicator.png differ diff --git a/graphics/battle_interface/flying_indicator.png b/graphics/battle_interface/flying_indicator.png new file mode 100644 index 0000000000..767954b706 Binary files /dev/null and b/graphics/battle_interface/flying_indicator.png differ diff --git a/graphics/battle_interface/ghost_indicator.png b/graphics/battle_interface/ghost_indicator.png new file mode 100644 index 0000000000..c1aeec6059 Binary files /dev/null and b/graphics/battle_interface/ghost_indicator.png differ diff --git a/graphics/battle_interface/grass_indicator.png b/graphics/battle_interface/grass_indicator.png new file mode 100644 index 0000000000..81fa2589d0 Binary files /dev/null and b/graphics/battle_interface/grass_indicator.png differ diff --git a/graphics/battle_interface/ground_indicator.png b/graphics/battle_interface/ground_indicator.png new file mode 100644 index 0000000000..7a306510fe Binary files /dev/null and b/graphics/battle_interface/ground_indicator.png differ diff --git a/graphics/battle_interface/ice_indicator.png b/graphics/battle_interface/ice_indicator.png new file mode 100644 index 0000000000..a40d96f018 Binary files /dev/null and b/graphics/battle_interface/ice_indicator.png differ diff --git a/graphics/battle_interface/normal_indicator.png b/graphics/battle_interface/normal_indicator.png new file mode 100644 index 0000000000..029827e296 Binary files /dev/null and b/graphics/battle_interface/normal_indicator.png differ diff --git a/graphics/battle_interface/poison_indicator.png b/graphics/battle_interface/poison_indicator.png new file mode 100644 index 0000000000..84806865cc Binary files /dev/null and b/graphics/battle_interface/poison_indicator.png differ diff --git a/graphics/battle_interface/psychic_indicator.png b/graphics/battle_interface/psychic_indicator.png new file mode 100644 index 0000000000..c2646d0a71 Binary files /dev/null and b/graphics/battle_interface/psychic_indicator.png differ diff --git a/graphics/battle_interface/rock_indicator.png b/graphics/battle_interface/rock_indicator.png new file mode 100644 index 0000000000..0329ec678c Binary files /dev/null and b/graphics/battle_interface/rock_indicator.png differ diff --git a/graphics/battle_interface/steel_indicator.png b/graphics/battle_interface/steel_indicator.png new file mode 100644 index 0000000000..e84d9fa1b0 Binary files /dev/null and b/graphics/battle_interface/steel_indicator.png differ diff --git a/graphics/battle_interface/stellar_indicator.png b/graphics/battle_interface/stellar_indicator.png new file mode 100644 index 0000000000..7551e2b230 Binary files /dev/null and b/graphics/battle_interface/stellar_indicator.png differ diff --git a/graphics/battle_interface/tera_indicator.pal b/graphics/battle_interface/tera_indicator.pal new file mode 100644 index 0000000000..d4ab140472 --- /dev/null +++ b/graphics/battle_interface/tera_indicator.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +98 83 124 +207 63 109 +83 105 175 +80 144 216 +216 208 179 +254 115 121 +148 155 163 +99 188 91 +147 192 47 +255 156 85 +199 182 140 +113 206 198 +117 206 192 +245 210 55 +255 255 255 diff --git a/graphics/battle_interface/tera_trigger.png b/graphics/battle_interface/tera_trigger.png new file mode 100644 index 0000000000..5498ea8806 Binary files /dev/null and b/graphics/battle_interface/tera_trigger.png differ diff --git a/graphics/battle_interface/water_indicator.png b/graphics/battle_interface/water_indicator.png new file mode 100644 index 0000000000..bdeb401f4a Binary files /dev/null and b/graphics/battle_interface/water_indicator.png differ diff --git a/graphics/types/stellar.png b/graphics/types/stellar.png new file mode 100644 index 0000000000..ee18b997ea Binary files /dev/null and b/graphics/types/stellar.png differ diff --git a/graphics_file_rules.mk b/graphics_file_rules.mk index 6a2728aff4..92b8cb0929 100644 --- a/graphics_file_rules.mk +++ b/graphics_file_rules.mk @@ -22,7 +22,7 @@ STARTERGFXDIR := graphics/starter_choose NAMINGGFXDIR := graphics/naming_screen SPINDAGFXDIR := graphics/pokemon/spinda/spots -types := normal fight flying poison ground rock bug ghost steel mystery fire water grass electric psychic ice dragon dark fairy +types := normal fight flying poison ground rock bug ghost steel mystery fire water grass electric psychic ice dragon dark fairy stellar contest_types := cool beauty cute smart tough ### Tilesets ### diff --git a/include/battle.h b/include/battle.h index 2032c5a3d4..c5d08bb584 100644 --- a/include/battle.h +++ b/include/battle.h @@ -15,6 +15,7 @@ #include "pokeball.h" #include "battle_debug.h" #include "battle_dynamax.h" +#include "battle_terastal.h" #include "random.h" // for rng_value_t // Helper for accessing command arguments and advancing gBattlescriptCurrInstr. @@ -363,6 +364,8 @@ struct AiLogicData bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once. u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c + bool8 shouldTerastal[MAX_BATTLERS_COUNT]; + bool8 shouldDynamax[MAX_BATTLERS_COUNT]; }; struct AI_ThinkingStruct @@ -611,6 +614,17 @@ struct DynamaxData u16 levelUpHP; }; +struct TeraData +{ + bool8 toTera; // flags using gBitTable + bool8 isTerastallized[NUM_BATTLE_SIDES]; // stored as a bitfield for each side's party members + bool8 alreadyTerastallized[MAX_BATTLERS_COUNT]; + bool8 playerSelect; + u32 stellarBoostFlags[NUM_BATTLE_SIDES]; // stored as a bitfield of flags for all types for each side + u8 triggerSpriteId; + u8 indicatorSpriteId[MAX_BATTLERS_COUNT]; +}; + struct LostItem { u16 originalItem:15; @@ -733,6 +747,7 @@ struct BattleStruct struct UltraBurstData burst; struct ZMoveData zmove; struct DynamaxData dynamax; + struct TeraData tera; const u8 *trainerSlideMsg; bool8 trainerSlideLowHpMsgDone; u8 introState; @@ -830,9 +845,9 @@ STATIC_ASSERT(sizeof(((struct BattleStruct *)0)->palaceFlags) * 8 >= MAX_BATTLER #define TARGET_TURN_DAMAGED ((gSpecialStatuses[gBattlerTarget].physicalDmg != 0 || gSpecialStatuses[gBattlerTarget].specialDmg != 0) || (gBattleStruct->enduredDamage & gBitTable[gBattlerTarget])) #define BATTLER_TURN_DAMAGED(battlerId) ((gSpecialStatuses[battlerId].physicalDmg != 0 || gSpecialStatuses[battlerId].specialDmg != 0) || (gBattleStruct->enduredDamage & gBitTable[battler])) -#define IS_BATTLER_OF_TYPE(battlerId, type)((GetBattlerType(battlerId, 0) == type || GetBattlerType(battlerId, 1) == type || (GetBattlerType(battlerId, 2) != TYPE_MYSTERY && GetBattlerType(battlerId, 2) == type))) - -#define IS_BATTLER_TYPELESS(battlerId)(GetBattlerType(battlerId, 0) == TYPE_MYSTERY && GetBattlerType(battlerId, 1) == TYPE_MYSTERY && GetBattlerType(battlerId, 2) == TYPE_MYSTERY) +#define IS_BATTLER_OF_TYPE(battlerId, type)((GetBattlerType(battlerId, 0, FALSE) == type || GetBattlerType(battlerId, 1, FALSE) == type || (GetBattlerType(battlerId, 2, FALSE) != TYPE_MYSTERY && GetBattlerType(battlerId, 2, FALSE) == type))) +#define IS_BATTLER_OF_BASE_TYPE(battlerId, type)((GetBattlerType(battlerId, 0, TRUE) == type || GetBattlerType(battlerId, 1, TRUE) == type || (GetBattlerType(battlerId, 2, TRUE) != TYPE_MYSTERY && GetBattlerType(battlerId, 2, TRUE) == type))) +#define IS_BATTLER_TYPELESS(battlerId)(GetBattlerType(battlerId, 0, FALSE) == TYPE_MYSTERY && GetBattlerType(battlerId, 1, FALSE) == TYPE_MYSTERY && GetBattlerType(battlerId, 2, FALSE) == TYPE_MYSTERY) #define SET_BATTLER_TYPE(battlerId, type) \ { \ diff --git a/include/battle_controllers.h b/include/battle_controllers.h index 5f3bd8316e..523434d57a 100644 --- a/include/battle_controllers.h +++ b/include/battle_controllers.h @@ -101,6 +101,7 @@ enum { #define RET_MEGA_EVOLUTION (1 << 7) #define RET_ULTRA_BURST (1 << 6) #define RET_DYNAMAX (1 << 5) +#define RET_TERASTAL (1 << 4) struct UnusedControllerStruct { diff --git a/include/battle_interface.h b/include/battle_interface.h index b26205d810..f32017745f 100644 --- a/include/battle_interface.h +++ b/include/battle_interface.h @@ -57,12 +57,36 @@ enum #define TAG_DYNAMAX_TRIGGER_TILE 0xD77D #define TAG_DYNAMAX_INDICATOR_TILE 0xD77E +#define TAG_NORMAL_INDICATOR_TILE 0xD77F +#define TAG_FIGHTING_INDICATOR_TILE 0xD780 +#define TAG_FLYING_INDICATOR_TILE 0xD781 +#define TAG_POISON_INDICATOR_TILE 0xD782 +#define TAG_GROUND_INDICATOR_TILE 0xD783 +#define TAG_ROCK_INDICATOR_TILE 0xD784 +#define TAG_BUG_INDICATOR_TILE 0xD785 +#define TAG_GHOST_INDICATOR_TILE 0xD786 +#define TAG_STEEL_INDICATOR_TILE 0xD787 +// empty spot for TYPE_MYSTERY +#define TAG_FIRE_INDICATOR_TILE 0xD789 +#define TAG_WATER_INDICATOR_TILE 0xD78A +#define TAG_GRASS_INDICATOR_TILE 0xD78B +#define TAG_ELECTRIC_INDICATOR_TILE 0xD78C +#define TAG_PSYCHIC_INDICATOR_TILE 0xD78D +#define TAG_ICE_INDICATOR_TILE 0xD78E +#define TAG_DRAGON_INDICATOR_TILE 0xD78F +#define TAG_DARK_INDICATOR_TILE 0xD790 +#define TAG_FAIRY_INDICATOR_TILE 0xD791 +#define TAG_STELLAR_INDICATOR_TILE 0xD792 +#define TAG_TERA_TRIGGER_TILE 0xD793 + #define TAG_MEGA_TRIGGER_PAL 0xD777 #define TAG_MEGA_INDICATOR_PAL 0xD778 #define TAG_MISC_INDICATOR_PAL 0xD779 // Alpha, Omega, and Dynamax 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 #define TAG_DYNAMAX_TRIGGER_PAL 0xD77D +#define TAG_TERA_INDICATOR_PAL 0xD77E +#define TAG_TERA_TRIGGER_PAL 0xD77F enum { diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 1a7654d37b..e4e9ca4307 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -499,6 +499,8 @@ extern const u8 BattleScript_TheSwampDisappeared[]; extern const u8 BattleScript_ItemRestoreHP_Party[]; extern const u8 BattleScript_EffectPsychicNoise[]; extern const u8 BattleScript_AromaVeilProtectsRet[]; +extern const u8 BattleScript_LowerAtkSpAtk[]; +extern const u8 BattleScript_Terastallization[]; extern const u8 BattleScript_BoosterEnergyEnd2[]; // zmoves diff --git a/include/battle_terastal.h b/include/battle_terastal.h new file mode 100644 index 0000000000..078bf39079 --- /dev/null +++ b/include/battle_terastal.h @@ -0,0 +1,30 @@ +#ifndef GUARD_BATTLE_TERASTAL_H +#define GUARD_BATTLE_TERASTAL_H + +void PrepareBattlerForTera(u32 battler); +bool32 CanTerastallize(u32 battler); +u32 GetBattlerTeraType(u32 battler); +bool32 IsTerastallized(u32 battler); +void ExpendTypeStellarBoost(u32 battler, u32 type); +bool32 IsTypeStellarBoosted(u32 battler, u32 type); +uq4_12_t GetTeraMultiplier(u32 battler, u32 type); + +u16 GetTeraTypeRGB(u32 type); + +void ChangeTeraTriggerSprite(u8 spriteId, u8 animId); +void CreateTeraTriggerSprite(u8 battler, u8 palId); +bool32 IsTeraTriggerSpriteActive(void); +void HideTeraTriggerSprite(void); +void DestroyTeraTriggerSprite(void); + +void TeraIndicator_LoadSpriteGfx(void); +bool32 TeraIndicator_ShouldBeInvisible(u32 battler); +u8 TeraIndicator_GetSpriteId(u32 healthboxSpriteId); +void TeraIndicator_SetVisibilities(u32 healthboxId, bool32 invisible); +void TeraIndicator_UpdateOamPriorities(u32 healthboxId, u32 oamPriority); +void TeraIndicator_UpdateLevel(u32 healthboxId, u32 level); +void TeraIndicator_CreateSprite(u32 battler, u32 healthboxSpriteId); +void TeraIndicator_DestroySprite(u32 healthboxSpriteId); +void TeraIndicator_UpdateType(u32 battler, u32 healthboxSpriteId); + +#endif diff --git a/include/battle_util.h b/include/battle_util.h index 1fb6d6a8f7..27ecb26e82 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -250,7 +250,7 @@ bool32 AreBattlersOfOppositeGender(u32 battler1, u32 battler2); bool32 AreBattlersOfSameGender(u32 battler1, u32 battler2); u32 CalcSecondaryEffectChance(u32 battler, u32 battlerAbility, const struct AdditionalEffect *additionalEffect); bool32 MoveEffectIsGuaranteed(u32 battler, u32 battlerAbility, const struct AdditionalEffect *additionalEffect); -u8 GetBattlerType(u32 battler, u8 typeIndex); +u8 GetBattlerType(u32 battler, u8 typeIndex, bool32 ignoreTera); bool8 CanMonParticipateInSkyBattle(struct Pokemon *mon); bool8 IsMonBannedFromSkyBattles(u16 species); void RemoveBattlerType(u32 battler, u8 type); diff --git a/include/config/battle.h b/include/config/battle.h index 40d7bd1147..c47f4fedcf 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -179,6 +179,8 @@ #define B_FLAG_NO_CATCHING 0 // If this flag is set, the ability to catch wild Pokémon is disabled. #define B_FLAG_AI_VS_AI_BATTLE 0 // If this flag is set, the player's mons will be controlled by the ai next battles. #define B_FLAG_DYNAMAX_BATTLE 0 // If this flag is set, the ability to Dynamax in battle is enabled for all trainers. +#define B_FLAG_TERA_ORB_CHARGED 0 // If this flag is set, the Tera Orb is charged. It is automatically set upon healing and cleared upon Terastallizing once configured. +#define B_FLAG_TERA_ORB_NO_COST 0 // If this flag is set, the Tera Orb does not use up its charge upon Terastallization. In S/V, this occurs after an event with Terapagos. // Var Settings // To use the following features in scripting, replace the 0s with the var ID you're assigning it to. diff --git a/include/constants/battle.h b/include/constants/battle.h index 0acb857ad0..30f304144f 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -402,8 +402,9 @@ #define MOVE_EFFECT_FLORAL_HEALING 77 #define MOVE_EFFECT_SECRET_POWER 78 #define MOVE_EFFECT_PSYCHIC_NOISE 79 +#define MOVE_EFFECT_TERA_BLAST 80 -#define NUM_MOVE_EFFECTS 80 +#define NUM_MOVE_EFFECTS 81 #define MOVE_EFFECT_AFFECTS_USER 0x2000 #define MOVE_EFFECT_CERTAIN 0x4000 diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index 0cf0862f15..b01685fbb7 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -350,6 +350,7 @@ enum { EFFECT_DRAGON_CHEER, EFFECT_LAST_RESPECTS, EFFECT_TIDY_UP, + EFFECT_TERA_BLAST, NUM_BATTLE_MOVE_EFFECTS, }; diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 86b48f9d4a..dfb5c5e2d4 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -707,12 +707,13 @@ #define STRINGID_BIZARREARENACREATED 705 #define STRINGID_BIZARREAREACREATED 706 #define STRINGID_TIDYINGUPCOMPLETE 707 -#define STRINGID_BOOSTERENERGYACTIVATES 708 -#define STRINGID_FOGCREPTUP 709 -#define STRINGID_FOGISDEEP 710 -#define STRINGID_FOGLIFTED 711 +#define STRINGID_PKMNTERASTALLIZEDINTO 708 +#define STRINGID_BOOSTERENERGYACTIVATES 709 +#define STRINGID_FOGCREPTUP 710 +#define STRINGID_FOGISDEEP 711 +#define STRINGID_FOGLIFTED 712 -#define BATTLESTRINGS_COUNT 712 +#define BATTLESTRINGS_COUNT 713 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/include/constants/pokemon.h b/include/constants/pokemon.h index 1adefd4938..343bf1a049 100644 --- a/include/constants/pokemon.h +++ b/include/constants/pokemon.h @@ -22,7 +22,8 @@ #define TYPE_DRAGON 16 #define TYPE_DARK 17 #define TYPE_FAIRY 18 -#define NUMBER_OF_MON_TYPES 19 +#define TYPE_STELLAR 19 +#define NUMBER_OF_MON_TYPES 20 // Pokémon egg groups #define EGG_GROUP_NONE 0 diff --git a/include/data.h b/include/data.h index b4490e8cc7..0549f29c4c 100644 --- a/include/data.h +++ b/include/data.h @@ -71,6 +71,7 @@ struct TrainerMon bool8 gender:2; bool8 isShiny:1; u8 dynamaxLevel:4; + u8 teraType:5; bool8 gigantamaxFactor:1; bool8 shouldDynamax:1; bool8 shouldTerastal:1; diff --git a/include/test/battle.h b/include/test/battle.h index 62b0680486..c4bd89f91b 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -935,6 +935,8 @@ struct MoveContext // TODO: u8 zMove:1; u16 dynamax:1; u16 explicitDynamax:1; + u16 tera:1; + u16 explicitTera:1; u16 allowed:1; u16 explicitAllowed:1; u16 notExpected:1; // Has effect only with EXPECT_MOVE diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 3d1c7e934b..da8b3764a3 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -9,6 +9,7 @@ #include "battle_factory.h" #include "battle_setup.h" #include "battle_z_move.h" +#include "battle_terastal.h" #include "data.h" #include "debug.h" #include "event_data.h" @@ -398,6 +399,23 @@ static void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) aiData->speedStats[battler] = GetBattlerTotalSpeedStatArgs(battler, ability, holdEffect); } +static void SetBattlerAiGimmickData(u32 battler, struct AiLogicData *aiData) +{ + bool32 isSecondTrainer = (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT) && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !BATTLE_TWO_VS_ONE_OPPONENT; + u16 trainerId = isSecondTrainer ? gTrainerBattleOpponent_B : gTrainerBattleOpponent_A; + const struct TrainerMon *party = GetTrainerPartyFromId(trainerId); + if (party != NULL) + { + aiData->shouldDynamax[battler] = CanDynamax(battler) && (party[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]].shouldDynamax); + aiData->shouldTerastal[battler] = CanTerastallize(battler) && (party[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]].shouldTerastal); + } + else + { + aiData->shouldDynamax[battler] = FALSE; + aiData->shouldTerastal[battler] = FALSE; + } +} + static u32 Ai_SetMoveAccuracy(struct AiLogicData *aiData, u32 battlerAtk, u32 battlerDef, u32 move) { u32 accuracy; @@ -467,6 +485,7 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) continue; SetBattlerAiData(battlerAtk, aiData); + SetBattlerAiGimmickData(battlerAtk, aiData); SetBattlerAiMovesData(aiData, battlerAtk, battlersCount); } } @@ -2367,20 +2386,20 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SOAK: if (PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) - || (GetBattlerType(battlerDef, 0) == TYPE_WATER - && GetBattlerType(battlerDef, 1) == TYPE_WATER - && GetBattlerType(battlerDef, 2) == TYPE_MYSTERY)) + || (GetBattlerType(battlerDef, 0, FALSE) == TYPE_WATER + && GetBattlerType(battlerDef, 1, FALSE) == TYPE_WATER + && GetBattlerType(battlerDef, 2, FALSE) == TYPE_MYSTERY)) ADJUST_SCORE(-10); // target is already water-only break; case EFFECT_THIRD_TYPE: switch (move) { case MOVE_TRICK_OR_TREAT: - if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GHOST) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) || IsTerastallized(battlerDef)) ADJUST_SCORE(-10); break; case MOVE_FORESTS_CURSE: - if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) || IsTerastallized(battlerDef)) ADJUST_SCORE(-10); break; } @@ -2514,9 +2533,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SYNCHRONOISE: //Check holding ring target or is of same type if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_RING_TARGET - || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 0)) - || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 1)) - || IS_BATTLER_OF_TYPE(battlerDef, GetBattlerType(battlerAtk, 2))) + || DoBattlersShareType(battlerAtk, battlerDef)) break; else ADJUST_SCORE(-10); @@ -2940,9 +2957,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SOAK: if (atkPartnerAbility == ABILITY_WONDER_GUARD - && (GetBattlerType(battlerAtkPartner, 0) != TYPE_WATER - || GetBattlerType(battlerAtkPartner, 1) != TYPE_WATER - || GetBattlerType(battlerAtkPartner, 2) != TYPE_WATER)) + && IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_WATER) + && !IsTerastallized(battlerAtkPartner)) { RETURN_SCORE_PLUS(WEAK_EFFECT); } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 40f3da2b5a..3458830a12 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -448,14 +448,16 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes s32 dmg, moveType; uq4_12_t effectivenessMultiplier; bool32 isDamageMoveUnusable = FALSE; + bool32 toggledDynamax = FALSE; + bool32 toggledTera = FALSE; struct AiLogicData *aiData = AI_DATA; SetBattlerData(battlerAtk); SetBattlerData(battlerDef); + // Temporarily enable Z-Moves for damage calcs if (considerZPower && IsViableZMove(battlerAtk, move)) { - //temporarily enable z moves for damage calcs gBattleStruct->zmove.baseMoves[battlerAtk] = move; gBattleStruct->zmove.active = TRUE; } @@ -465,6 +467,18 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes if (gMovesInfo[move].effect == EFFECT_NATURE_POWER) move = GetNaturePowerMove(); + // Temporarily enable other gimmicks for damage calcs if planned + if (AI_DATA->shouldDynamax[battlerAtk]) + { + toggledDynamax = TRUE; + gBattleStruct->dynamax.dynamaxed[battlerAtk] = TRUE; + } + if (AI_DATA->shouldTerastal[battlerAtk]) + { + toggledTera = TRUE; + gBattleStruct->tera.isTerastallized[GetBattlerSide(battlerAtk)] |= gBitTable[gBattlerPartyIndexes[battlerAtk]]; + } + gBattleStruct->dynamicMoveType = 0; @@ -582,6 +596,10 @@ s32 AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u8 *typeEffectivenes gBattleStruct->swapDamageCategory = FALSE; gBattleStruct->zmove.active = FALSE; gBattleStruct->zmove.baseMoves[battlerAtk] = MOVE_NONE; + if (toggledDynamax) + gBattleStruct->dynamax.dynamaxed[battlerAtk] = FALSE; + if (toggledTera) + gBattleStruct->tera.isTerastallized[GetBattlerSide(battlerAtk)] &= ~(gBitTable[gBattlerPartyIndexes[battlerAtk]]); return dmg; } diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 597d0b7c5f..1622b94e9d 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -544,13 +544,6 @@ static void OpponentHandleChooseMove(u32 battler) default: { u16 chosenMove = moveInfo->moves[chosenMoveId]; - bool32 isSecondTrainer = (GetBattlerPosition(battler) == B_POSITION_OPPONENT_RIGHT) && (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) && !BATTLE_TWO_VS_ONE_OPPONENT; - u16 trainerId = isSecondTrainer ? gTrainerBattleOpponent_B : gTrainerBattleOpponent_A; - const struct TrainerMon *party = GetTrainerPartyFromId(trainerId); - bool32 shouldDynamax = FALSE; - if (party != NULL) - shouldDynamax = party[isSecondTrainer ? gBattlerPartyIndexes[battler] - MULTI_PARTY_SIZE : gBattlerPartyIndexes[battler]].shouldDynamax; - if (GetBattlerMoveTargetType(battler, chosenMove) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) gBattlerTarget = battler; if (GetBattlerMoveTargetType(battler, chosenMove) & MOVE_TARGET_BOTH) @@ -568,8 +561,11 @@ static void OpponentHandleChooseMove(u32 battler) else if (CanUltraBurst(battler)) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_ULTRA_BURST) | (gBattlerTarget << 8)); // If opponent can Dynamax and is allowed in the partydata, do it. - else if (CanDynamax(battler) && shouldDynamax) + else if (CanDynamax(battler) && AI_DATA->shouldDynamax[battler]) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_DYNAMAX) | (gBattlerTarget << 8)); + // If opponent can Terastal and is allowed in the partydata, do it. + else if (CanTerastallize(battler) && AI_DATA->shouldTerastal[battler]) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_TERASTAL) | (gBattlerTarget << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); } diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index eeca1899b1..10a91a1485 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -454,6 +454,8 @@ static void HandleInputChooseTarget(u32 battler) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); else if (gBattleStruct->dynamax.playerSelect) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); + else if (gBattleStruct->tera.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); EndBounceEffect(gMultiUsePlayerCursor, BOUNCE_HEALTHBOX); @@ -616,6 +618,8 @@ static void HandleInputShowEntireFieldTargets(u32 battler) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); else if (gBattleStruct->dynamax.playerSelect) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); + else if (gBattleStruct->tera.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); HideTriggerSprites(); @@ -648,6 +652,8 @@ static void HandleInputShowTargets(u32 battler) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); else if (gBattleStruct->dynamax.playerSelect) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); + else if (gBattleStruct->tera.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); HideTriggerSprites(); @@ -772,6 +778,8 @@ static void HandleInputChooseMove(u32 battler) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_ULTRA_BURST | (gMultiUsePlayerCursor << 8)); else if (gBattleStruct->dynamax.playerSelect) BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_DYNAMAX | (gMultiUsePlayerCursor << 8)); + else if (gBattleStruct->tera.playerSelect) + BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | RET_TERASTAL | (gMultiUsePlayerCursor << 8)); else BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, gMoveSelectionCursor[battler] | (gMultiUsePlayerCursor << 8)); HideTriggerSprites(); @@ -810,6 +818,7 @@ static void HandleInputChooseMove(u32 battler) gBattleStruct->mega.playerSelect = FALSE; gBattleStruct->burst.playerSelect = FALSE; gBattleStruct->dynamax.playerSelect = FALSE; + gBattleStruct->tera.playerSelect = FALSE; gBattleStruct->zmove.viable = FALSE; BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF); HideTriggerSprites(); @@ -918,6 +927,12 @@ static void HandleInputChooseMove(u32 battler) ChangeDynamaxTriggerSprite(gBattleStruct->dynamax.triggerSpriteId, gBattleStruct->dynamax.playerSelect); PlaySE(SE_SELECT); } + else if (CanTerastallize(battler)) + { + gBattleStruct->tera.playerSelect ^= 1; + ChangeTeraTriggerSprite(gBattleStruct->tera.triggerSpriteId, gBattleStruct->tera.playerSelect); + PlaySE(SE_SELECT); + } } } @@ -926,6 +941,7 @@ static void ReloadMoveNames(u32 battler) gBattleStruct->mega.playerSelect = FALSE; gBattleStruct->burst.playerSelect = FALSE; gBattleStruct->dynamax.playerSelect = FALSE; + gBattleStruct->tera.playerSelect = FALSE; gBattleStruct->zmove.viewing = FALSE; MoveSelectionDestroyCursorAt(battler); MoveSelectionDisplayMoveNames(battler); @@ -2059,6 +2075,7 @@ static void PlayerHandleChooseMove(u32 battler) gBattleStruct->mega.playerSelect = FALSE; gBattleStruct->burst.playerSelect = FALSE; gBattleStruct->dynamax.playerSelect = FALSE; + gBattleStruct->tera.playerSelect = FALSE; if (!IsMegaTriggerSpriteActive()) gBattleStruct->mega.triggerSpriteId = 0xFF; if (CanMegaEvolve(battler)) @@ -2073,6 +2090,10 @@ static void PlayerHandleChooseMove(u32 battler) CreateDynamaxTriggerSprite(battler, 0); if (!IsZMoveTriggerSpriteActive()) gBattleStruct->zmove.triggerSpriteId = 0xFF; + if (!IsTeraTriggerSpriteActive()) + gBattleStruct->tera.triggerSpriteId = 0xFF; + if (CanTerastallize(battler)) + CreateTeraTriggerSprite(battler, 0); GetUsableZMoves(battler, moveInfo->moves); gBattleStruct->zmove.viable = IsZMoveUsable(battler, gMoveSelectionCursor[battler]); diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 09b85e7768..0a4eaa07dd 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -645,6 +645,13 @@ void BattleLoadMonSpriteGfx(struct Pokemon *mon, u32 battler) BlendPalette(paletteOffset, 16, 4, RGB(31, 0, 12)); CpuCopy32(gPlttBufferFaded + paletteOffset, gPlttBufferUnfaded + paletteOffset, PLTT_SIZEOF(16)); } + + // Terastallization's tint + if (IsTerastallized(battler)) + { + BlendPalette(paletteOffset, 16, 8, GetTeraTypeRGB(GetBattlerTeraType(battler))); + CpuCopy32(gPlttBufferFaded + paletteOffset, gPlttBufferUnfaded + paletteOffset, PLTT_SIZEOF(16)); + } } void BattleGfxSfxDummy2(u16 species) @@ -710,6 +717,7 @@ bool8 BattleLoadAllHealthBoxesGfx(u8 state) LoadSpritePalette(&sSpritePalettes_HealthBoxHealthBar[0]); LoadSpritePalette(&sSpritePalettes_HealthBoxHealthBar[1]); MegaIndicator_LoadSpritesGfx(); + TeraIndicator_LoadSpriteGfx(); } else if (!IsDoubleBattle()) { diff --git a/src/battle_interface.c b/src/battle_interface.c index 34cdec6e5c..6637fa4c4e 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -854,6 +854,9 @@ u8 CreateBattlerHealthboxSprites(u8 battlerId) // Create mega indicator sprite. MegaIndicator_CreateSprite(battlerId, healthboxLeftSpriteId); + // Create tera indicator sprites. + TeraIndicator_CreateSprite(battlerId, healthboxLeftSpriteId); + gBattleStruct->ballSpriteIds[0] = MAX_SPRITES; gBattleStruct->ballSpriteIds[1] = MAX_SPRITES; @@ -937,6 +940,7 @@ void SetHealthboxSpriteInvisible(u8 healthboxSpriteId) gSprites[gSprites[healthboxSpriteId].hMain_HealthBarSpriteId].invisible = TRUE; gSprites[gSprites[healthboxSpriteId].oam.affineParam].invisible = TRUE; MegaIndicator_SetVisibilities(healthboxSpriteId, TRUE); + TeraIndicator_SetVisibilities(healthboxSpriteId, TRUE); } void SetHealthboxSpriteVisible(u8 healthboxSpriteId) @@ -945,6 +949,7 @@ void SetHealthboxSpriteVisible(u8 healthboxSpriteId) gSprites[gSprites[healthboxSpriteId].hMain_HealthBarSpriteId].invisible = FALSE; gSprites[gSprites[healthboxSpriteId].oam.affineParam].invisible = FALSE; MegaIndicator_SetVisibilities(healthboxSpriteId, FALSE); + TeraIndicator_SetVisibilities(healthboxSpriteId, FALSE); } static void UpdateSpritePos(u8 spriteId, s16 x, s16 y) @@ -971,6 +976,7 @@ static void TryToggleHealboxVisibility(u32 priority, u32 healthboxLeftSpriteId, gSprites[healthboxRightSpriteId].invisible = invisible; gSprites[healthbarSpriteId].invisible = invisible; MegaIndicator_SetVisibilities(healthboxLeftSpriteId, invisible); + TeraIndicator_SetVisibilities(healthboxLeftSpriteId, invisible); } void UpdateOamPriorityInAllHealthboxes(u8 priority, bool32 hideHPBoxes) @@ -988,6 +994,7 @@ void UpdateOamPriorityInAllHealthboxes(u8 priority, bool32 hideHPBoxes) gSprites[healthbarSpriteId].oam.priority = priority; MegaIndicator_UpdateOamPriority(healthboxLeftSpriteId, priority); + TeraIndicator_UpdateOamPriorities(healthboxLeftSpriteId, priority); if (B_HIDE_HEALTHBOX_IN_ANIMS == TRUE && hideHPBoxes && IsBattlerAlive(i)) TryToggleHealboxVisibility(priority, healthboxLeftSpriteId, healthboxRightSpriteId, healthbarSpriteId); @@ -1050,6 +1057,13 @@ static void UpdateLvlInHealthbox(u8 healthboxSpriteId, u8 lvl) MegaIndicator_UpdateLevel(healthboxSpriteId, lvl); MegaIndicator_SetVisibilities(healthboxSpriteId, FALSE); } + else if (IsTerastallized(battler)) + { + objVram = ConvertIntToDecimalStringN(text, lvl, STR_CONV_MODE_LEFT_ALIGN, 3); + xPos = 5 * (3 - (objVram - (text + 2))) - 1; + TeraIndicator_UpdateLevel(healthboxSpriteId, lvl); + TeraIndicator_SetVisibilities(healthboxSpriteId, FALSE); + } else { text[0] = CHAR_EXTRA_SYMBOL; @@ -1473,6 +1487,7 @@ void HideTriggerSprites(void) HideBurstTriggerSprite(); HideZMoveTriggerSprite(); HideDynamaxTriggerSprite(); + HideTeraTriggerSprite(); } void DestroyMegaTriggerSprite(void) @@ -2532,6 +2547,10 @@ void UpdateHealthboxAttribute(u8 healthboxSpriteId, struct Pokemon *mon, u8 elem u32 battlerId = gSprites[healthboxSpriteId].hMain_Battler; s32 maxHp = GetMonData(mon, MON_DATA_MAX_HP); s32 currHp = GetMonData(mon, MON_DATA_HP); + + // This fixes a bug that should likely never happen involving switching between two Teras. + if (elementId == HEALTHBOX_ALL) + TeraIndicator_UpdateType(battlerId, healthboxSpriteId); if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) { diff --git a/src/battle_main.c b/src/battle_main.c index 6290d5305c..eba4916ef4 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -590,13 +590,15 @@ const struct TypeInfo gTypesInfo[NUMBER_OF_MON_TYPES] = //.teraShard = ITEM_FAIRY_TERA_SHARD, //.arceusForm = SPECIES_ARCEUS_FAIRY, }, - /* [TYPE_STELLAR] = { - .name = _("Stellar"), - .teraShard = ITEM_STELLAR_TERA_SHARD, + .name = _("Stellr"), + .generic = _("a STELLAR move"), + .palette = 15, + .zMove = MOVE_BREAKNECK_BLITZ, + .maxMove = MOVE_MAX_STRIKE, + // .teraShard = ITEM_STELLAR_TERA_SHARD, }, - */ }; // extra args are money and ball @@ -2325,6 +2327,11 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer u32 data = partyData[i].gigantamaxFactor; SetMonData(&party[i], MON_DATA_GIGANTAMAX_FACTOR, &data); } + if (partyData[i].teraType > 0) + { + u32 data = partyData[i].teraType; + SetMonData(&party[i], MON_DATA_TERA_TYPE, &data); + } CalculateMonStats(&party[i]); if (B_TRAINER_CLASS_POKE_BALLS >= GEN_7 && ball == -1) @@ -4608,6 +4615,7 @@ static void HandleTurnActionSelectionState(void) gBattleStruct->mega.toEvolve &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); gBattleStruct->burst.toBurst &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); gBattleStruct->dynamax.toDynamax &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); + gBattleStruct->tera.toTera &= ~(gBitTable[BATTLE_PARTNER(GetBattlerPosition(battler))]); gBattleStruct->dynamax.usingMaxMove[BATTLE_PARTNER(GetBattlerPosition(battler))] = FALSE; gBattleStruct->zmove.toBeUsed[BATTLE_PARTNER(GetBattlerPosition(battler))] = MOVE_NONE; BtlController_EmitEndBounceEffect(battler, BUFFER_A); @@ -4697,7 +4705,7 @@ static void HandleTurnActionSelectionState(void) } // Get the chosen move position (and thus the chosen move) and target from the returned buffer. - gBattleStruct->chosenMovePositions[battler] = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST | RET_DYNAMAX); + gBattleStruct->chosenMovePositions[battler] = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST | RET_DYNAMAX | RET_TERASTAL); gChosenMoveByBattler[battler] = gBattleMons[battler].moves[gBattleStruct->chosenMovePositions[battler]]; gBattleStruct->moveTarget[battler] = gBattleResources->bufferB[battler][3]; @@ -4708,6 +4716,8 @@ static void HandleTurnActionSelectionState(void) gBattleStruct->burst.toBurst |= gBitTable[battler]; else if (gBattleResources->bufferB[battler][2] & RET_DYNAMAX) gBattleStruct->dynamax.toDynamax |= gBitTable[battler]; + else if (gBattleResources->bufferB[battler][2] & RET_TERASTAL) + gBattleStruct->tera.toTera |= gBitTable[battler]; // Max Move check if (ShouldUseMaxMove(battler, gChosenMoveByBattler[battler])) @@ -5325,7 +5335,8 @@ static void PopulateArrayWithBattlers(u8 *battlers) static bool32 TryDoGimmicksBeforeMoves(void) { if (!(gHitMarker & HITMARKER_RUN) - && (gBattleStruct->mega.toEvolve || gBattleStruct->burst.toBurst || gBattleStruct->dynamax.toDynamax)) + && (gBattleStruct->mega.toEvolve || gBattleStruct->burst.toBurst + || gBattleStruct->dynamax.toDynamax || gBattleStruct->tera.toTera)) { u32 i, battler; u8 order[MAX_BATTLERS_COUNT]; @@ -5334,6 +5345,17 @@ static bool32 TryDoGimmicksBeforeMoves(void) SortBattlersBySpeed(order, FALSE); for (i = 0; i < gBattlersCount; i++) { + // Tera Check + if (gBattleStruct->tera.toTera & gBitTable[order[i]]) + { + gBattlerAttacker = order[i]; + gBattleScripting.battler = gBattlerAttacker; + gBattleStruct->tera.toTera &= ~(gBitTable[gBattlerAttacker]); + PrepareBattlerForTera(gBattlerAttacker); + PREPARE_TYPE_BUFFER(gBattleTextBuff1, GetBattlerTeraType(gBattlerAttacker)); + BattleScriptExecute(BattleScript_Terastallization); + return TRUE; + } // Dynamax Check if (gBattleStruct->dynamax.toDynamax & gBitTable[order[i]]) { @@ -5996,7 +6018,9 @@ void SetTypeBeforeUsingMove(u32 move, u32 battlerAtk) } else if (gMovesInfo[move].effect == EFFECT_REVELATION_DANCE) { - if (gBattleMons[battlerAtk].type1 != TYPE_MYSTERY) + if (IsTerastallized(battlerAtk) && GetBattlerTeraType(battlerAtk) != TYPE_STELLAR) + gBattleStruct->dynamicMoveType = GetBattlerTeraType(battlerAtk); + else if (gBattleMons[battlerAtk].type1 != TYPE_MYSTERY) gBattleStruct->dynamicMoveType = gBattleMons[battlerAtk].type1 | F_DYNAMIC_TYPE_SET; else if (gBattleMons[battlerAtk].type2 != TYPE_MYSTERY) gBattleStruct->dynamicMoveType = gBattleMons[battlerAtk].type2 | F_DYNAMIC_TYPE_SET; @@ -6038,6 +6062,10 @@ void SetTypeBeforeUsingMove(u32 move, u32 battlerAtk) gBattleStruct->dynamicMoveType = TYPE_NORMAL | F_DYNAMIC_TYPE_SET; } } + else if (gMovesInfo[move].effect == EFFECT_TERA_BLAST && IsTerastallized(battlerAtk)) + { + gBattleStruct->dynamicMoveType = GetBattlerTeraType(battlerAtk) | F_DYNAMIC_TYPE_SET; + } attackerAbility = GetBattlerAbility(battlerAtk); @@ -6046,6 +6074,7 @@ void SetTypeBeforeUsingMove(u32 move, u32 battlerAtk) && gMovesInfo[move].effect != EFFECT_WEATHER_BALL && gMovesInfo[move].effect != EFFECT_CHANGE_TYPE_ON_ITEM && gMovesInfo[move].effect != EFFECT_NATURAL_GIFT + && !(gMovesInfo[move].effect == EFFECT_TERA_BLAST && IsTerastallized(battlerAtk)) && ((attackerAbility == ABILITY_PIXILATE && (ateType = TYPE_FAIRY)) || (attackerAbility == ABILITY_REFRIGERATE && (ateType = TYPE_ICE)) || (attackerAbility == ABILITY_AERILATE && (ateType = TYPE_FLYING)) diff --git a/src/battle_message.c b/src/battle_message.c index 2efb99ff5f..5f7e6ca052 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -846,11 +846,13 @@ static const u8 sText_ElectroShotCharging[] = _("{B_ATK_NAME_WITH_PREFIX} absorb static const u8 sText_ItemWasUsedUp[] = _("The {B_LAST_ITEM}\nwas used up..."); static const u8 sText_AttackerLostItsType[] = _("{B_ATK_NAME_WITH_PREFIX} lost\nits {B_BUFF1} type!"); static const u8 sText_ShedItsTail[] = _("{B_ATK_NAME_WITH_PREFIX} shed its tail\nto create a decoy!"); +static const u8 sText_PkmnTerastallizedInto[] = _("{B_ATK_NAME_WITH_PREFIX} terastallized\ninto the {B_BUFF1} type!"); static const u8 sText_SupersweetAromaWafts[] = _("A supersweet aroma is wafting from\nthe syrup covering {B_ATK_NAME_WITH_PREFIX}!"); static const u8 sText_TidyingUpComplete[] = _("Tidying up complete!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_PKMNTERASTALLIZEDINTO - BATTLESTRINGS_TABLE_START] = sText_PkmnTerastallizedInto, [STRINGID_TIDYINGUPCOMPLETE - BATTLESTRINGS_TABLE_START] = sText_TidyingUpComplete, [STRINGID_SUPERSWEETAROMAWAFTS - BATTLESTRINGS_TABLE_START] = sText_SupersweetAromaWafts, [STRINGID_SHEDITSTAIL - BATTLESTRINGS_TABLE_START] = sText_ShedItsTail, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index feb0149c68..ddcbeee78c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1215,7 +1215,8 @@ bool32 ProteanTryChangeType(u32 battler, u32 ability, u32 move, u32 moveType) && !gDisableStructs[gBattlerAttacker].usedProteanLibero && (gBattleMons[battler].type1 != moveType || gBattleMons[battler].type2 != moveType || (gBattleMons[battler].type3 != moveType && gBattleMons[battler].type3 != TYPE_MYSTERY)) - && move != MOVE_STRUGGLE) + && move != MOVE_STRUGGLE + && !IsTerastallized(battler)) { SET_BATTLER_TYPE(battler, moveType); return TRUE; @@ -2085,12 +2086,12 @@ END: // of a move that is Super Effective against a Flying-type Pokémon. if (gBattleWeather & B_WEATHER_STRONG_WINDS) { - if ((GetBattlerType(gBattlerTarget, 0) == TYPE_FLYING - && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 0)) >= UQ_4_12(2.0)) - || (GetBattlerType(gBattlerTarget, 1) == TYPE_FLYING - && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 1)) >= UQ_4_12(2.0)) - || (GetBattlerType(gBattlerTarget, 2) == TYPE_FLYING - && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 2)) >= UQ_4_12(2.0))) + if ((GetBattlerType(gBattlerTarget, 0, FALSE) == TYPE_FLYING + && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 0, FALSE)) >= UQ_4_12(2.0)) + || (GetBattlerType(gBattlerTarget, 1, FALSE) == TYPE_FLYING + && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 1, FALSE)) >= UQ_4_12(2.0)) + || (GetBattlerType(gBattlerTarget, 2, FALSE) == TYPE_FLYING + && GetTypeModifier(moveType, GetBattlerType(gBattlerTarget, 2, FALSE)) >= UQ_4_12(2.0))) { gBattlerAbility = gBattlerTarget; BattleScriptPushCursor(); @@ -3806,6 +3807,15 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattlescriptCurrInstr = BattleScript_EffectPsychicNoise; } break; + case MOVE_EFFECT_TERA_BLAST: + if (IsTerastallized(gEffectBattler) + && GetBattlerTeraType(gEffectBattler) == TYPE_STELLAR + && !NoAliveMonsForEitherParty()) + { + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_LowerAtkSpAtk; + } + break; } } } @@ -9648,8 +9658,9 @@ static void Cmd_various(void) case VARIOUS_TRY_SOAK: { VARIOUS_ARGS(const u8 *failInstr); - if (GetBattlerType(gBattlerTarget, 0) == gMovesInfo[gCurrentMove].type - && GetBattlerType(gBattlerTarget, 1) == gMovesInfo[gCurrentMove].type) + if ((GetBattlerType(gBattlerTarget, 0, FALSE) == gMovesInfo[gCurrentMove].type + && GetBattlerType(gBattlerTarget, 1, FALSE) == gMovesInfo[gCurrentMove].type) + || IsTerastallized(gBattlerTarget)) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -9982,7 +9993,7 @@ static void Cmd_various(void) case VARIOUS_TRY_THIRD_TYPE: { VARIOUS_ARGS(const u8 *failInstr); - if (IS_BATTLER_OF_TYPE(battler, gMovesInfo[gCurrentMove].argument)) + if (IS_BATTLER_OF_TYPE(battler, gMovesInfo[gCurrentMove].argument) || IsTerastallized(battler)) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -12049,6 +12060,12 @@ static void Cmd_tryconversiontypechange(void) u8 moveChecked = 0; u8 moveType = 0; + if (IsTerastallized(gBattlerAttacker)) + { + gBattlescriptCurrInstr = cmd->failInstr; + return; + } + if (B_UPDATED_CONVERSION >= GEN_6) { // Changes user's type to its first move's type @@ -12825,6 +12842,14 @@ static void Cmd_settypetorandomresistance(void) { gBattlescriptCurrInstr = cmd->failInstr; } + else if (IsTerastallized(gBattlerAttacker)) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else if (gLastHitByType[gBattlerAttacker] == TYPE_STELLAR) + { + gBattlescriptCurrInstr = cmd->failInstr; + } else { u32 i, resistTypes = 0; @@ -14812,7 +14837,7 @@ static void Cmd_settypetoterrain(void) break; } - if (!IS_BATTLER_OF_TYPE(gBattlerAttacker, terrainType)) + if (!IS_BATTLER_OF_TYPE(gBattlerAttacker, terrainType) && !IsTerastallized(gBattlerAttacker)) { SET_BATTLER_TYPE(gBattlerAttacker, terrainType); PREPARE_TYPE_BUFFER(gBattleTextBuff1, terrainType); @@ -16378,14 +16403,18 @@ void BS_TryReflectType(void) { NATIVE_ARGS(const u8 *failInstr); u16 targetBaseSpecies = GET_BASE_SPECIES_ID(gBattleMons[gBattlerTarget].species); - u8 targetType1 = GetBattlerType(gBattlerTarget, 0); - u8 targetType2 = GetBattlerType(gBattlerTarget, 1); - u8 targetType3 = GetBattlerType(gBattlerTarget, 2); + u8 targetType1 = GetBattlerType(gBattlerTarget, 0, FALSE); + u8 targetType2 = GetBattlerType(gBattlerTarget, 1, FALSE); + u8 targetType3 = GetBattlerType(gBattlerTarget, 2, FALSE); if (targetBaseSpecies == SPECIES_ARCEUS || targetBaseSpecies == SPECIES_SILVALLY) { gBattlescriptCurrInstr = cmd->failInstr; } + else if (IsTerastallized(gBattlerAttacker)) + { + gBattlescriptCurrInstr = cmd->failInstr; + } else if (IS_BATTLER_TYPELESS(gBattlerTarget)) { gBattlescriptCurrInstr = cmd->failInstr; @@ -16746,7 +16775,8 @@ void BS_AllySwitchFailChance(void) void BS_SetPhotonGeyserCategory(void) { NATIVE_ARGS(); - gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); + if (!(gMovesInfo[gCurrentMove].effect == EFFECT_TERA_BLAST && !IsTerastallized(gBattlerAttacker))) + gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_terastal.c b/src/battle_terastal.c new file mode 100644 index 0000000000..8cc03aca57 --- /dev/null +++ b/src/battle_terastal.c @@ -0,0 +1,786 @@ +#include "global.h" +#include "battle.h" +#include "battle_anim.h" +#include "battle_controllers.h" +#include "battle_interface.h" +#include "battle_terastal.h" +#include "event_data.h" +#include "item.h" +#include "palette.h" +#include "pokemon.h" +#include "sprite.h" +#include "util.h" +#include "constants/abilities.h" +#include "constants/hold_effects.h" +#include "constants/rgb.h" + +// Sets flags and variables upon a battler's Terastallization. +void PrepareBattlerForTera(u32 battler) +{ + u32 side = GetBattlerSide(battler); + struct Pokemon *party = GetBattlerParty(battler); + u32 index = gBattlerPartyIndexes[battler]; + + // Update TeraData fields. + gBattleStruct->tera.isTerastallized[side] |= gBitTable[index]; + gBattleStruct->tera.alreadyTerastallized[battler] = TRUE; + + // Remove Tera Orb charge. + if (B_FLAG_TERA_ORB_CHARGED != 0 + && B_FLAG_TERA_ORB_NO_COST != 0 + && !FlagGet(B_FLAG_TERA_ORB_NO_COST) + && side == B_SIDE_PLAYER + && !(gBattleTypeFlags & BATTLE_TYPE_DOUBLE && !IsPartnerMonFromSameTrainer(battler))) + { + FlagClear(B_FLAG_TERA_ORB_CHARGED); + } + + // Show indicator and do palette blend. + UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], &party[index], HEALTHBOX_ALL); + BlendPalette(OBJ_PLTT_ID(battler), 16, 8, GetTeraTypeRGB(GetBattlerTeraType(battler))); + CpuCopy32(gPlttBufferFaded + OBJ_PLTT_ID(battler), gPlttBufferUnfaded + OBJ_PLTT_ID(battler), PLTT_SIZEOF(16)); +} + +// Returns whether a battler can Terastallize. +bool32 CanTerastallize(u32 battler) +{ + u32 holdEffect = GetBattlerHoldEffect(battler, FALSE); + + // Check if Player has Tera Orb and has charge. + if (B_FLAG_TERA_ORB_CHARGED != 0 + && (battler == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT)) + && !(CheckBagHasItem(ITEM_TERA_ORB, 1) + && FlagGet(B_FLAG_TERA_ORB_CHARGED))) + { + return FALSE; + } + + // Check if Trainer has already Terastallized. + if (gBattleStruct->tera.alreadyTerastallized[battler]) + { + return FALSE; + } + + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE + && IsPartnerMonFromSameTrainer(battler) + && (gBattleStruct->tera.alreadyTerastallized[BATTLE_PARTNER(battler)] + || (gBattleStruct->tera.toTera & gBitTable[BATTLE_PARTNER(battler)]))) + { + return FALSE; + } + + // Check if battler is holding a Z-Crystal or Mega Stone. + if (holdEffect == HOLD_EFFECT_Z_CRYSTAL || holdEffect == HOLD_EFFECT_MEGA_STONE) + { + return FALSE; + } + + // Every check passed! + return TRUE; +} + +// Returns a battler's Tera type. +u32 GetBattlerTeraType(u32 battler) +{ + struct Pokemon *mon = &GetBattlerParty(battler)[gBattlerPartyIndexes[battler]]; + return GetMonData(mon, MON_DATA_TERA_TYPE); +} + +// Returns whether a battler is terastallized. +bool32 IsTerastallized(u32 battler) +{ + return gBattleStruct->tera.isTerastallized[GetBattlerSide(battler)] & gBitTable[gBattlerPartyIndexes[battler]]; +} + + +// Uses up a type's Stellar boost. +void ExpendTypeStellarBoost(u32 battler, u32 type) +{ + if (type < 32) // avoid OOB access + gBattleStruct->tera.stellarBoostFlags[GetBattlerSide(battler)] |= gBitTable[type]; +} + +// Checks whether a type's Stellar boost has been expended. +bool32 IsTypeStellarBoosted(u32 battler, u32 type) +{ + if (type < 32) // avoid OOB access + return !(gBattleStruct->tera.stellarBoostFlags[GetBattlerSide(battler)] & gBitTable[type]); + else + return FALSE; +} + +// Returns the STAB power multiplier to use when Terastallized. +// Power multipliers from Smogon Research thread. +uq4_12_t GetTeraMultiplier(u32 battler, u32 type) +{ + u32 teraType = GetBattlerTeraType(battler); + bool32 hasAdaptability = (GetBattlerAbility(battler) == ABILITY_ADAPTABILITY); + + // Safety check. + if (!IsTerastallized(battler)) + return UQ_4_12(1.0); + + // Stellar-type checks. + if (teraType == TYPE_STELLAR) + { + bool32 shouldBoost = IsTypeStellarBoosted(battler, type); + if (IS_BATTLER_OF_BASE_TYPE(battler, type)) + { + if (shouldBoost) + return UQ_4_12(2.0); + else + return UQ_4_12(1.5); + } + else if (shouldBoost) + return UQ_4_12(1.2); + else + return UQ_4_12(1.0); + } + // Base and Tera type. + if (type == teraType && IS_BATTLER_OF_BASE_TYPE(battler, type)) + { + if (hasAdaptability) + return UQ_4_12(2.25); + else + return UQ_4_12(2.0); + } + // Base or Tera type only. + else if ((type == teraType && !IS_BATTLER_OF_BASE_TYPE(battler, type)) + || (type != teraType && IS_BATTLER_OF_BASE_TYPE(battler, type))) + { + if (hasAdaptability) + return UQ_4_12(2.0); + else + return UQ_4_12(1.5); + } + // Neither base or Tera type. + else + { + return UQ_4_12(1.0); + } +} + +// Most values pulled from the Tera type icon palette. +const u16 sTeraTypeRGBValues[NUMBER_OF_MON_TYPES] = { + [TYPE_NORMAL] = RGB_WHITE, // custom + [TYPE_FIGHTING] = RGB(26, 8, 14), + [TYPE_FLYING] = RGB(31, 26, 7), + [TYPE_POISON] = RGB(26, 10, 25), // custom + [TYPE_GROUND] = RGB(25, 23, 18), + [TYPE_ROCK] = RGB(18, 16, 8), // custom + [TYPE_BUG] = RGB(18, 24, 6), + [TYPE_GHOST] = RGB(12, 10, 16), + [TYPE_STEEL] = RGB(19, 19, 20), + [TYPE_MYSTERY] = RGB_WHITE, + [TYPE_FIRE] = RGB(31, 20, 11), + [TYPE_WATER] = RGB(10, 18, 27), + [TYPE_GRASS] = RGB(12, 24, 11), + [TYPE_ELECTRIC] = RGB(30, 26, 7), + [TYPE_PSYCHIC] = RGB(31, 14, 15), + [TYPE_ICE] = RGB(14, 26, 25), + [TYPE_DRAGON] = RGB(10, 18, 27), + [TYPE_DARK] = RGB(6, 5, 8), + [TYPE_FAIRY] = RGB(31, 15, 21), + [TYPE_STELLAR] = RGB(10, 18, 27), +}; + +u16 GetTeraTypeRGB(u32 type) +{ + return sTeraTypeRGBValues[type]; +} + +// TERASTAL TRIGGER: +static const u8 ALIGNED(4) sTeraTriggerGfx[] = INCBIN_U8("graphics/battle_interface/tera_trigger.4bpp"); +static const u16 sTeraTriggerPal[] = INCBIN_U16("graphics/battle_interface/tera_trigger.gbapal"); + +static const struct SpriteSheet sSpriteSheet_TeraTrigger = +{ + sTeraTriggerGfx, sizeof(sTeraTriggerGfx), TAG_TERA_TRIGGER_TILE +}; +static const struct SpritePalette sSpritePalette_TeraTrigger = +{ + sTeraTriggerPal, TAG_TERA_TRIGGER_PAL +}; + +static const struct OamData sOamData_TeraTrigger = +{ + .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_TeraTriggerOff[] = +{ + ANIMCMD_FRAME(0, 0), + ANIMCMD_END +}; + +static const union AnimCmd sSpriteAnim_TeraTriggerOn[] = +{ + ANIMCMD_FRAME(16, 0), + ANIMCMD_END +}; + +static const union AnimCmd *const sSpriteAnimTable_TeraTrigger[] = +{ + sSpriteAnim_TeraTriggerOff, + sSpriteAnim_TeraTriggerOn, +}; + +static void SpriteCb_TeraTrigger(struct Sprite *sprite); +static const struct SpriteTemplate sSpriteTemplate_TeraTrigger = +{ + .tileTag = TAG_TERA_TRIGGER_TILE, + .paletteTag = TAG_TERA_TRIGGER_PAL, + .oam = &sOamData_TeraTrigger, + .anims = sSpriteAnimTable_TeraTrigger, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraTrigger +}; + +// Tera Evolution Trigger icon functions. +void ChangeTeraTriggerSprite(u8 spriteId, u8 animId) +{ + StartSpriteAnim(&gSprites[spriteId], animId); +} + +#define SINGLES_TERA_TRIGGER_POS_X_OPTIMAL (30) +#define SINGLES_TERA_TRIGGER_POS_X_PRIORITY (31) +#define SINGLES_TERA_TRIGGER_POS_X_SLIDE (15) +#define SINGLES_TERA_TRIGGER_POS_Y_DIFF (-11) + +#define DOUBLES_TERA_TRIGGER_POS_X_OPTIMAL (30) +#define DOUBLES_TERA_TRIGGER_POS_X_PRIORITY (31) +#define DOUBLES_TERA_TRIGGER_POS_X_SLIDE (15) +#define DOUBLES_TERA_TRIGGER_POS_Y_DIFF (-4) + +#define tBattler data[0] +#define tHide data[1] + +void CreateTeraTriggerSprite(u8 battler, u8 palId) +{ + LoadSpritePalette(&sSpritePalette_TeraTrigger); + if (GetSpriteTileStartByTag(TAG_TERA_TRIGGER_TILE) == 0xFFFF) + { + LoadSpriteSheet(&sSpriteSheet_TeraTrigger); + } + if (gBattleStruct->tera.triggerSpriteId == 0xFF) + { + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + gBattleStruct->tera.triggerSpriteId = CreateSprite(&sSpriteTemplate_TeraTrigger, + gSprites[gHealthboxSpriteIds[battler]].x - DOUBLES_TERA_TRIGGER_POS_X_SLIDE, + gSprites[gHealthboxSpriteIds[battler]].y - DOUBLES_TERA_TRIGGER_POS_Y_DIFF, 0); + else + gBattleStruct->tera.triggerSpriteId = CreateSprite(&sSpriteTemplate_TeraTrigger, + gSprites[gHealthboxSpriteIds[battler]].x - SINGLES_TERA_TRIGGER_POS_X_SLIDE, + gSprites[gHealthboxSpriteIds[battler]].y - SINGLES_TERA_TRIGGER_POS_Y_DIFF, 0); + } + gSprites[gBattleStruct->tera.triggerSpriteId].tBattler = battler; + gSprites[gBattleStruct->tera.triggerSpriteId].tHide = FALSE; + + ChangeTeraTriggerSprite(gBattleStruct->tera.triggerSpriteId, palId); +} + +static void SpriteCb_TeraTrigger(struct Sprite *sprite) +{ + s32 xSlide, xPriority, xOptimal; + s32 yDiff; + + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + xSlide = DOUBLES_TERA_TRIGGER_POS_X_SLIDE; + xPriority = DOUBLES_TERA_TRIGGER_POS_X_PRIORITY; + xOptimal = DOUBLES_TERA_TRIGGER_POS_X_OPTIMAL; + yDiff = DOUBLES_TERA_TRIGGER_POS_Y_DIFF; + } + else + { + xSlide = SINGLES_TERA_TRIGGER_POS_X_SLIDE; + xPriority = SINGLES_TERA_TRIGGER_POS_X_PRIORITY; + xOptimal = SINGLES_TERA_TRIGGER_POS_X_OPTIMAL; + yDiff = SINGLES_TERA_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) + DestroyTeraTriggerSprite(); + } + 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 IsTeraTriggerSpriteActive(void) +{ + if (GetSpriteTileStartByTag(TAG_TERA_TRIGGER_TILE) == 0xFFFF) + return FALSE; + else if (IndexOfSpritePaletteTag(TAG_TERA_TRIGGER_PAL) != 0xFF) + return TRUE; + else + return FALSE; +} + +void HideTeraTriggerSprite(void) +{ + if (gBattleStruct->tera.triggerSpriteId != 0xFF) + { + ChangeTeraTriggerSprite(gBattleStruct->tera.triggerSpriteId, 0); + gSprites[gBattleStruct->tera.triggerSpriteId].tHide = TRUE; + } +} + +void DestroyTeraTriggerSprite(void) +{ + FreeSpritePaletteByTag(TAG_TERA_TRIGGER_PAL); + FreeSpriteTilesByTag(TAG_TERA_TRIGGER_TILE); + if (gBattleStruct->tera.triggerSpriteId != 0xFF) + DestroySprite(&gSprites[gBattleStruct->tera.triggerSpriteId]); + gBattleStruct->tera.triggerSpriteId = 0xFF; +} + +#undef tBattler +#undef tHide + +// TERA INDICATOR: +static const u16 sTeraIndicatorPal[] = INCBIN_U16("graphics/battle_interface/tera_indicator.gbapal"); +static const u8 ALIGNED(4) sNormalIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/normal_indicator.4bpp"); +static const u8 ALIGNED(4) sFightingIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fighting_indicator.4bpp"); +static const u8 ALIGNED(4) sFlyingIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/flying_indicator.4bpp"); +static const u8 ALIGNED(4) sPoisonIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/poison_indicator.4bpp"); +static const u8 ALIGNED(4) sGroundIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ground_indicator.4bpp"); +static const u8 ALIGNED(4) sRockIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/rock_indicator.4bpp"); +static const u8 ALIGNED(4) sBugIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/bug_indicator.4bpp"); +static const u8 ALIGNED(4) sGhostIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ghost_indicator.4bpp"); +static const u8 ALIGNED(4) sSteelIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/steel_indicator.4bpp"); +static const u8 ALIGNED(4) sFireIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fire_indicator.4bpp"); +static const u8 ALIGNED(4) sWaterIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/water_indicator.4bpp"); +static const u8 ALIGNED(4) sGrassIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/grass_indicator.4bpp"); +static const u8 ALIGNED(4) sElectricIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/electric_indicator.4bpp"); +static const u8 ALIGNED(4) sPsychicIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/psychic_indicator.4bpp"); +static const u8 ALIGNED(4) sIceIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/ice_indicator.4bpp"); +static const u8 ALIGNED(4) sDragonIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dragon_indicator.4bpp"); +static const u8 ALIGNED(4) sDarkIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/dark_indicator.4bpp"); +static const u8 ALIGNED(4) sFairyIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/fairy_indicator.4bpp"); +static const u8 ALIGNED(4) sStellarIndicatorGfx[] = INCBIN_U8("graphics/battle_interface/stellar_indicator.4bpp"); + +static void SpriteCb_TeraIndicator(struct Sprite *sprite); +static const s8 sIndicatorPositions[][2] = +{ + [B_POSITION_PLAYER_LEFT] = {53, -9}, + [B_POSITION_OPPONENT_LEFT] = {44, -9}, + [B_POSITION_PLAYER_RIGHT] = {52, -9}, + [B_POSITION_OPPONENT_RIGHT] = {44, -9}, +}; + +static const struct SpritePalette sSpritePalette_TeraIndicator = +{ + sTeraIndicatorPal, TAG_TERA_INDICATOR_PAL +}; + +static const struct OamData sOamData_TeraIndicator = +{ + .shape = SPRITE_SHAPE(16x16), + .size = SPRITE_SIZE(16x16), + .priority = 1, +}; + +static const struct SpriteTemplate sSpriteTemplate_NormalIndicator = +{ + .tileTag = TAG_NORMAL_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_FightingIndicator = +{ + .tileTag = TAG_FIGHTING_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_FlyingIndicator = +{ + .tileTag = TAG_FLYING_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_PoisonIndicator = +{ + .tileTag = TAG_POISON_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_GroundIndicator = +{ + .tileTag = TAG_GROUND_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_RockIndicator = +{ + .tileTag = TAG_ROCK_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_BugIndicator = +{ + .tileTag = TAG_BUG_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_GhostIndicator = +{ + .tileTag = TAG_GHOST_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_SteelIndicator = +{ + .tileTag = TAG_STEEL_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_FireIndicator = +{ + .tileTag = TAG_FIRE_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_WaterIndicator = +{ + .tileTag = TAG_WATER_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_GrassIndicator = +{ + .tileTag = TAG_GRASS_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_ElectricIndicator = +{ + .tileTag = TAG_ELECTRIC_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_PsychicIndicator = +{ + .tileTag = TAG_PSYCHIC_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_IceIndicator = +{ + .tileTag = TAG_ICE_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_DragonIndicator = +{ + .tileTag = TAG_DRAGON_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_DarkIndicator = +{ + .tileTag = TAG_DARK_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_FairyIndicator = +{ + .tileTag = TAG_FAIRY_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteTemplate sSpriteTemplate_StellarIndicator = +{ + .tileTag = TAG_STELLAR_INDICATOR_TILE, + .paletteTag = TAG_TERA_INDICATOR_PAL, + .oam = &sOamData_TeraIndicator, + .anims = gDummySpriteAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCb_TeraIndicator, +}; + +static const struct SpriteSheet sTeraIndicatorSpriteSheets[NUMBER_OF_MON_TYPES + 1] = +{ + {sNormalIndicatorGfx, sizeof(sNormalIndicatorGfx), TAG_NORMAL_INDICATOR_TILE}, + {sFightingIndicatorGfx, sizeof(sFightingIndicatorGfx), TAG_FIGHTING_INDICATOR_TILE}, + {sFlyingIndicatorGfx, sizeof(sFlyingIndicatorGfx), TAG_FLYING_INDICATOR_TILE}, + {sPoisonIndicatorGfx, sizeof(sPoisonIndicatorGfx), TAG_POISON_INDICATOR_TILE}, + {sGroundIndicatorGfx, sizeof(sGroundIndicatorGfx), TAG_GROUND_INDICATOR_TILE}, + {sRockIndicatorGfx, sizeof(sRockIndicatorGfx), TAG_ROCK_INDICATOR_TILE}, + {sBugIndicatorGfx, sizeof(sBugIndicatorGfx), TAG_BUG_INDICATOR_TILE}, + {sGhostIndicatorGfx, sizeof(sGhostIndicatorGfx), TAG_GHOST_INDICATOR_TILE}, + {sSteelIndicatorGfx, sizeof(sSteelIndicatorGfx), TAG_STEEL_INDICATOR_TILE}, + {sNormalIndicatorGfx, sizeof(sNormalIndicatorGfx), TAG_NORMAL_INDICATOR_TILE}, // TYPE_MYSTERY + {sFireIndicatorGfx, sizeof(sFireIndicatorGfx), TAG_FIRE_INDICATOR_TILE}, + {sWaterIndicatorGfx, sizeof(sWaterIndicatorGfx), TAG_WATER_INDICATOR_TILE}, + {sGrassIndicatorGfx, sizeof(sGrassIndicatorGfx), TAG_GRASS_INDICATOR_TILE}, + {sElectricIndicatorGfx, sizeof(sElectricIndicatorGfx), TAG_ELECTRIC_INDICATOR_TILE}, + {sPsychicIndicatorGfx, sizeof(sPsychicIndicatorGfx), TAG_PSYCHIC_INDICATOR_TILE}, + {sIceIndicatorGfx, sizeof(sIceIndicatorGfx), TAG_ICE_INDICATOR_TILE}, + {sDragonIndicatorGfx, sizeof(sDragonIndicatorGfx), TAG_DRAGON_INDICATOR_TILE}, + {sDarkIndicatorGfx, sizeof(sDarkIndicatorGfx), TAG_DARK_INDICATOR_TILE}, + {sFairyIndicatorGfx, sizeof(sFairyIndicatorGfx), TAG_FAIRY_INDICATOR_TILE}, + {sStellarIndicatorGfx, sizeof(sStellarIndicatorGfx), TAG_STELLAR_INDICATOR_TILE}, + {0} +}; + +static const struct SpriteTemplate * const sTeraIndicatorSpriteTemplates[NUMBER_OF_MON_TYPES] = +{ + [TYPE_NORMAL] = &sSpriteTemplate_NormalIndicator, + [TYPE_FIGHTING] = &sSpriteTemplate_FightingIndicator, + [TYPE_FLYING] = &sSpriteTemplate_FlyingIndicator, + [TYPE_POISON] = &sSpriteTemplate_PoisonIndicator, + [TYPE_GROUND] = &sSpriteTemplate_GroundIndicator, + [TYPE_ROCK] = &sSpriteTemplate_RockIndicator, + [TYPE_BUG] = &sSpriteTemplate_BugIndicator, + [TYPE_GHOST] = &sSpriteTemplate_GhostIndicator, + [TYPE_STEEL] = &sSpriteTemplate_SteelIndicator, + [TYPE_MYSTERY] = &sSpriteTemplate_NormalIndicator, // just in case + [TYPE_FIRE] = &sSpriteTemplate_FireIndicator, + [TYPE_WATER] = &sSpriteTemplate_WaterIndicator, + [TYPE_GRASS] = &sSpriteTemplate_GrassIndicator, + [TYPE_ELECTRIC] = &sSpriteTemplate_ElectricIndicator, + [TYPE_PSYCHIC] = &sSpriteTemplate_PsychicIndicator, + [TYPE_ICE] = &sSpriteTemplate_IceIndicator, + [TYPE_DRAGON] = &sSpriteTemplate_DragonIndicator, + [TYPE_DARK] = &sSpriteTemplate_DarkIndicator, + [TYPE_FAIRY] = &sSpriteTemplate_FairyIndicator, + [TYPE_STELLAR] = &sSpriteTemplate_StellarIndicator, +}; + +// for sprite data fields +#define tBattler data[0] +#define tType data[1] // Indicator type: tera +#define tPosX data[2] +#define tLevelXDelta data[3] // X position depends whether level has 3, 2 or 1 digit + +// data fields for healthboxMain +// oam.affineParam holds healthboxRight spriteId +#define hMain_TeraIndicatorId data[3] +#define hMain_HealthBarSpriteId data[5] +#define hMain_Battler data[6] +#define hMain_Data7 data[7] + +// data fields for healthboxRight +#define hOther_HealthBoxSpriteId data[5] + +// data fields for healthbar +#define hBar_HealthBoxSpriteId data[5] + +void TeraIndicator_LoadSpriteGfx(void) +{ + LoadSpriteSheets(sTeraIndicatorSpriteSheets); + LoadSpritePalette(&sSpritePalette_TeraIndicator); +} + +bool32 TeraIndicator_ShouldBeInvisible(u32 battler) +{ + return !IsTerastallized(battler); +} + +u8 TeraIndicator_GetSpriteId(u32 healthboxSpriteId) +{ + return gBattleStruct->tera.indicatorSpriteId[gSprites[healthboxSpriteId].hMain_Battler]; +} + +void TeraIndicator_SetVisibilities(u32 healthboxId, bool32 invisible) +{ + u8 spriteId = TeraIndicator_GetSpriteId(healthboxId); + u32 battler = gSprites[healthboxId].hMain_Battler; + + if (invisible == TRUE) + gSprites[spriteId].invisible = TRUE; + else // Try visible. + gSprites[spriteId].invisible = TeraIndicator_ShouldBeInvisible(battler); +} + +void TeraIndicator_UpdateOamPriorities(u32 healthboxId, u32 oamPriority) +{ + u8 spriteId = TeraIndicator_GetSpriteId(healthboxId); + gSprites[spriteId].oam.priority = oamPriority; +} + +void TeraIndicator_UpdateLevel(u32 healthboxId, u32 level) +{ + s16 xDelta = 0; + u8 spriteId = TeraIndicator_GetSpriteId(healthboxId); + + if (level >= 100) + xDelta -= 4; + else if (level < 10) + xDelta += 5; + + gSprites[spriteId].tLevelXDelta = xDelta; +} + +void TeraIndicator_CreateSprite(u32 battler, u32 healthboxSpriteId) +{ + u32 position; + u8 spriteId; + s16 xHealthbox = 0, y = 0; + s32 x = 0; + u32 type = GetBattlerTeraType(battler); + + position = GetBattlerPosition(battler); + GetBattlerHealthboxCoords(battler, &xHealthbox, &y); + + x = sIndicatorPositions[position][0]; + y += sIndicatorPositions[position][1]; + + spriteId = gBattleStruct->tera.indicatorSpriteId[battler] = CreateSpriteAtEnd(sTeraIndicatorSpriteTemplates[type], 0, y, 0); + gSprites[spriteId].tBattler = battler; + gSprites[spriteId].tPosX = x; + gSprites[spriteId].invisible = TRUE; +} + +void TeraIndicator_DestroySprite(u32 healthboxSpriteId) +{ + u8 spriteId = TeraIndicator_GetSpriteId(healthboxSpriteId); + DestroySprite(&gSprites[spriteId]); +} + +void TeraIndicator_UpdateType(u32 battler, u32 healthboxSpriteId) +{ + TeraIndicator_DestroySprite(healthboxSpriteId); + TeraIndicator_CreateSprite(battler, healthboxSpriteId); +} + +static void SpriteCb_TeraIndicator(struct Sprite *sprite) +{ + u32 battler = sprite->tBattler; + + sprite->x = gSprites[gHealthboxSpriteIds[battler]].x + sprite->tPosX + sprite->tLevelXDelta; + sprite->x2 = gSprites[gHealthboxSpriteIds[battler]].x2; + sprite->y2 = gSprites[gHealthboxSpriteIds[battler]].y2; +} + +#undef tBattler +#undef tType +#undef tPosX +#undef tLevelXDelta diff --git a/src/battle_util.c b/src/battle_util.c index b6fb998376..e11ddc27ea 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -707,7 +707,7 @@ void HandleAction_NothingIsFainted(void) void HandleAction_ActionFinished(void) { - u32 i, j; + u32 i, j, moveType; bool32 afterYouActive = gSpecialStatuses[gBattlerByTurnOrder[gCurrentTurnActionNumber + 1]].afterYou; *(gBattleStruct->monToSwitchIntoId + gBattlerByTurnOrder[gCurrentTurnActionNumber]) = gSelectedMonPartyId = PARTY_SIZE; gCurrentTurnActionNumber++; @@ -718,6 +718,16 @@ void HandleAction_ActionFinished(void) | HITMARKER_OBEYS | HITMARKER_WAKE_UP_CLEAR | HITMARKER_SYNCHRONISE_EFFECT | HITMARKER_CHARGING | HITMARKER_NEVER_SET | HITMARKER_IGNORE_DISGUISE); + // check if Stellar type boost should be used up + GET_MOVE_TYPE(gCurrentMove, moveType); + if (IsTerastallized(gBattlerAttacker) + && GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR + && gMovesInfo[gCurrentMove].category != DAMAGE_CATEGORY_STATUS + && IsTypeStellarBoosted(gBattlerAttacker, moveType)) + { + ExpendTypeStellarBoost(gBattlerAttacker, moveType); + } + gCurrentMove = 0; gBattleMoveDamage = 0; gMoveResultFlags = 0; @@ -898,34 +908,35 @@ static const uq4_12_t sPercentToModifier[] = static const uq4_12_t sTypeEffectivenessTable[NUMBER_OF_MON_TYPES][NUMBER_OF_MON_TYPES] = {// Defender --> - // Attacker Normal Fighting Flying Poison Ground Rock Bug Ghost Steel Mystery Fire Water Grass Electric Psychic Ice Dragon Dark Fairy - [TYPE_NORMAL] = {______, ______, ______, ______, ______, X(0.5), ______, X(0.0), X(0.5), ______, ______, ______, ______, ______, ______, ______, ______, ______, ______}, - [TYPE_FIGHTING] = {X(2.0), ______, X(0.5), X(0.5), ______, X(2.0), X(0.5), X(0.0), X(2.0), ______, ______, ______, ______, ______, X(0.5), X(2.0), ______, X(2.0), X(0.5)}, - [TYPE_FLYING] = {______, X(2.0), ______, ______, ______, X(0.5), X(2.0), ______, X(0.5), ______, ______, ______, X(2.0), X(0.5), ______, ______, ______, ______, ______}, - [TYPE_POISON] = {______, ______, ______, X(0.5), X(0.5), X(0.5), ______, X(0.5), X(0.0), ______, ______, ______, X(2.0), ______, ______, ______, ______, ______, X(2.0)}, - [TYPE_GROUND] = {______, ______, X(0.0), X(2.0), ______, X(2.0), X(0.5), ______, X(2.0), ______, X(2.0), ______, X(0.5), X(2.0), ______, ______, ______, ______, ______}, - [TYPE_ROCK] = {______, X(0.5), X(2.0), ______, X(0.5), ______, X(2.0), ______, X(0.5), ______, X(2.0), ______, ______, ______, ______, X(2.0), ______, ______, ______}, - [TYPE_BUG] = {______, X(0.5), X(0.5), X(0.5), ______, ______, ______, X(0.5), X(0.5), ______, X(0.5), ______, X(2.0), ______, X(2.0), ______, ______, X(2.0), X(0.5)}, + // Attacker Normal Fighting Flying Poison Ground Rock Bug Ghost Steel Mystery Fire Water Grass Electric Psychic Ice Dragon Dark Fairy Stellar + [TYPE_NORMAL] = {______, ______, ______, ______, ______, X(0.5), ______, X(0.0), X(0.5), ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______}, + [TYPE_FIGHTING] = {X(2.0), ______, X(0.5), X(0.5), ______, X(2.0), X(0.5), X(0.0), X(2.0), ______, ______, ______, ______, ______, X(0.5), X(2.0), ______, X(2.0), X(0.5), ______}, + [TYPE_FLYING] = {______, X(2.0), ______, ______, ______, X(0.5), X(2.0), ______, X(0.5), ______, ______, ______, X(2.0), X(0.5), ______, ______, ______, ______, ______, ______}, + [TYPE_POISON] = {______, ______, ______, X(0.5), X(0.5), X(0.5), ______, X(0.5), X(0.0), ______, ______, ______, X(2.0), ______, ______, ______, ______, ______, X(2.0), ______}, + [TYPE_GROUND] = {______, ______, X(0.0), X(2.0), ______, X(2.0), X(0.5), ______, X(2.0), ______, X(2.0), ______, X(0.5), X(2.0), ______, ______, ______, ______, ______, ______}, + [TYPE_ROCK] = {______, X(0.5), X(2.0), ______, X(0.5), ______, X(2.0), ______, X(0.5), ______, X(2.0), ______, ______, ______, ______, X(2.0), ______, ______, ______, ______}, + [TYPE_BUG] = {______, X(0.5), X(0.5), X(0.5), ______, ______, ______, X(0.5), X(0.5), ______, X(0.5), ______, X(2.0), ______, X(2.0), ______, ______, X(2.0), X(0.5), ______}, #if B_STEEL_RESISTANCES >= GEN_6 - [TYPE_GHOST] = {X(0.0), ______, ______, ______, ______, ______, ______, X(2.0), ______, ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), ______}, + [TYPE_GHOST] = {X(0.0), ______, ______, ______, ______, ______, ______, X(2.0), ______, ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), ______, ______}, #else - [TYPE_GHOST] = {X(0.0), ______, ______, ______, ______, ______, ______, X(2.0), X(0.5), ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), ______}, + [TYPE_GHOST] = {X(0.0), ______, ______, ______, ______, ______, ______, X(2.0), X(0.5), ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), ______, ______}, #endif - [TYPE_STEEL] = {______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), ______, X(0.5), X(0.5), ______, X(0.5), ______, X(2.0), ______, ______, X(2.0)}, - [TYPE_MYSTERY] = {______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______}, - [TYPE_FIRE] = {______, ______, ______, ______, ______, X(0.5), X(2.0), ______, X(2.0), ______, X(0.5), X(0.5), X(2.0), ______, ______, X(2.0), X(0.5), ______, ______}, - [TYPE_WATER] = {______, ______, ______, ______, X(2.0), X(2.0), ______, ______, ______, ______, X(2.0), X(0.5), X(0.5), ______, ______, ______, X(0.5), ______, ______}, - [TYPE_GRASS] = {______, ______, X(0.5), X(0.5), X(2.0), X(2.0), X(0.5), ______, X(0.5), ______, X(0.5), X(2.0), X(0.5), ______, ______, ______, X(0.5), ______, ______}, - [TYPE_ELECTRIC] = {______, ______, X(2.0), ______, X(0.0), ______, ______, ______, ______, ______, ______, X(2.0), X(0.5), X(0.5), ______, ______, X(0.5), ______, ______}, - [TYPE_PSYCHIC] = {______, X(2.0), ______, X(2.0), ______, ______, ______, ______, X(0.5), ______, ______, ______, ______, ______, X(0.5), ______, ______, X(0.0), ______}, - [TYPE_ICE] = {______, ______, X(2.0), ______, X(2.0), ______, ______, ______, X(0.5), ______, X(0.5), X(0.5), X(2.0), ______, ______, X(0.5), X(2.0), ______, ______}, - [TYPE_DRAGON] = {______, ______, ______, ______, ______, ______, ______, ______, X(0.5), ______, ______, ______, ______, ______, ______, ______, X(2.0), ______, X(0.0)}, + [TYPE_STEEL] = {______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), ______, X(0.5), X(0.5), ______, X(0.5), ______, X(2.0), ______, ______, X(2.0), ______}, + [TYPE_MYSTERY] = {______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______}, + [TYPE_FIRE] = {______, ______, ______, ______, ______, X(0.5), X(2.0), ______, X(2.0), ______, X(0.5), X(0.5), X(2.0), ______, ______, X(2.0), X(0.5), ______, ______, ______}, + [TYPE_WATER] = {______, ______, ______, ______, X(2.0), X(2.0), ______, ______, ______, ______, X(2.0), X(0.5), X(0.5), ______, ______, ______, X(0.5), ______, ______, ______}, + [TYPE_GRASS] = {______, ______, X(0.5), X(0.5), X(2.0), X(2.0), X(0.5), ______, X(0.5), ______, X(0.5), X(2.0), X(0.5), ______, ______, ______, X(0.5), ______, ______, ______}, + [TYPE_ELECTRIC] = {______, ______, X(2.0), ______, X(0.0), ______, ______, ______, ______, ______, ______, X(2.0), X(0.5), X(0.5), ______, ______, X(0.5), ______, ______, ______}, + [TYPE_PSYCHIC] = {______, X(2.0), ______, X(2.0), ______, ______, ______, ______, X(0.5), ______, ______, ______, ______, ______, X(0.5), ______, ______, X(0.0), ______, ______}, + [TYPE_ICE] = {______, ______, X(2.0), ______, X(2.0), ______, ______, ______, X(0.5), ______, X(0.5), X(0.5), X(2.0), ______, ______, X(0.5), X(2.0), ______, ______, ______}, + [TYPE_DRAGON] = {______, ______, ______, ______, ______, ______, ______, ______, X(0.5), ______, ______, ______, ______, ______, ______, ______, X(2.0), ______, X(0.0), ______}, #if B_STEEL_RESISTANCES >= GEN_6 - [TYPE_DARK] = {______, X(0.5), ______, ______, ______, ______, ______, X(2.0), ______, ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), X(0.5)}, + [TYPE_DARK] = {______, X(0.5), ______, ______, ______, ______, ______, X(2.0), ______, ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), X(0.5), ______}, #else - [TYPE_DARK] = {______, X(0.5), ______, ______, ______, ______, ______, X(2.0), X(0.5), ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), X(0.5)}, + [TYPE_DARK] = {______, X(0.5), ______, ______, ______, ______, ______, X(2.0), X(0.5), ______, ______, ______, ______, ______, X(2.0), ______, ______, X(0.5), X(0.5), ______}, #endif - [TYPE_FAIRY] = {______, X(2.0), ______, X(0.5), ______, ______, ______, ______, X(0.5), ______, X(0.5), ______, ______, ______, ______, ______, X(2.0), X(2.0), ______}, + [TYPE_FAIRY] = {______, X(2.0), ______, X(0.5), ______, ______, ______, ______, X(0.5), ______, X(0.5), ______, ______, ______, ______, ______, X(2.0), X(2.0), ______, ______}, + [TYPE_STELLAR] = {______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______, ______}, }; #undef ______ @@ -1276,7 +1287,7 @@ static bool32 IsBelchPreventingMove(u32 battler, u32 move) u32 TrySetCantSelectMoveBattleScript(u32 battler) { u32 limitations = 0; - u8 moveId = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST | RET_DYNAMAX); + u8 moveId = gBattleResources->bufferB[battler][2] & ~(RET_MEGA_EVOLUTION | RET_ULTRA_BURST | RET_DYNAMAX | RET_TERASTAL); u32 move = gBattleMons[battler].moves[moveId]; u32 holdEffect = GetBattlerHoldEffect(battler, TRUE); u16 *choicedMove = &gBattleStruct->choicedMove[battler]; @@ -5349,6 +5360,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && gMovesInfo[move].power != 0 && TARGET_TURN_DAMAGED && !IS_BATTLER_OF_TYPE(battler, moveType) + && moveType != TYPE_STELLAR && gBattleMons[battler].hp != 0) { SET_BATTLER_TYPE(battler, moveType); @@ -8842,6 +8854,10 @@ static inline u32 CalcMoveBasePower(u32 move, u32 battlerAtk, u32 battlerDef, u3 if (RandomPercentage(RNG_FICKLE_BEAM, 30)) basePower *= 2; break; + case EFFECT_TERA_BLAST: + if (IsTerastallized(battlerAtk) && GetBattlerTeraType(battlerAtk) == TYPE_STELLAR) + basePower = 100; + break; case EFFECT_LAST_RESPECTS: basePower += (basePower * min(100, GetBattlerSideFaintCounter(battlerAtk))); break; @@ -9210,6 +9226,19 @@ static inline u32 CalcMoveBasePowerAfterModifiers(u32 move, u32 battlerAtk, u32 modifier = uq4_12_multiply(modifier, UQ_4_12(1.1)); break; } + + // Terastallization boosts weak, non-priority, non-multi hit moves after modifiers to 60 BP. + if (IsTerastallized(battlerAtk) + && (moveType == GetBattlerTeraType(battlerAtk) + || (GetBattlerTeraType(battlerAtk) == TYPE_STELLAR && IsTypeStellarBoosted(battlerAtk, moveType))) + && uq4_12_multiply_by_int_half_down(modifier, basePower) < 60 + && gMovesInfo[move].strikeCount < 2 + && gMovesInfo[move].effect != EFFECT_MULTI_HIT + && gMovesInfo[move].priority == 0) + { + return 60; + } + return uq4_12_multiply_by_int_half_down(modifier, basePower); } @@ -9912,7 +9941,10 @@ static inline s32 DoMoveDamageCalcVars(u32 move, u32 battlerAtk, u32 battlerDef, dmg /= 100; } - DAMAGE_APPLY_MODIFIER(GetSameTypeAttackBonusModifier(battlerAtk, moveType, move, abilityAtk)); + if (IsTerastallized(battlerAtk)) + DAMAGE_APPLY_MODIFIER(GetTeraMultiplier(battlerAtk, moveType)); + else + DAMAGE_APPLY_MODIFIER(GetSameTypeAttackBonusModifier(battlerAtk, moveType, move, abilityAtk)); DAMAGE_APPLY_MODIFIER(typeEffectivenessModifier); DAMAGE_APPLY_MODIFIER(GetBurnOrFrostBiteModifier(battlerAtk, move, abilityAtk)); DAMAGE_APPLY_MODIFIER(GetZMaxMoveAgainstProtectionModifier(battlerDef, move)); @@ -10119,12 +10151,12 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(u32 move, u32 mov { u32 illusionSpecies; - MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 0), battlerAtk, recordAbilities); - if (GetBattlerType(battlerDef, 1) != GetBattlerType(battlerDef, 0)) - MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 1), battlerAtk, recordAbilities); - if (GetBattlerType(battlerDef, 2) != TYPE_MYSTERY && GetBattlerType(battlerDef, 2) != GetBattlerType(battlerDef, 1) - && GetBattlerType(battlerDef, 2) != GetBattlerType(battlerDef, 0)) - MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 2), battlerAtk, recordAbilities); + MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 0, FALSE), battlerAtk, recordAbilities); + if (GetBattlerType(battlerDef, 1, FALSE) != GetBattlerType(battlerDef, 0, FALSE)) + MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 1, FALSE), battlerAtk, recordAbilities); + if (GetBattlerType(battlerDef, 2, FALSE) != TYPE_MYSTERY && GetBattlerType(battlerDef, 2, FALSE) != GetBattlerType(battlerDef, 1, FALSE) + && GetBattlerType(battlerDef, 2, FALSE) != GetBattlerType(battlerDef, 0, FALSE)) + MulByTypeEffectiveness(&modifier, move, moveType, battlerDef, GetBattlerType(battlerDef, 2, FALSE), battlerAtk, recordAbilities); if (recordAbilities && (illusionSpecies = GetIllusionMonSpecies(battlerDef))) TryNoticeIllusionInTypeEffectiveness(move, moveType, battlerAtk, battlerDef, modifier, illusionSpecies); @@ -10185,12 +10217,16 @@ uq4_12_t CalcTypeEffectivenessMultiplier(u32 move, u32 moveType, u32 battlerAtk, { uq4_12_t modifier = UQ_4_12(1.0); - if (move != MOVE_STRUGGLE && moveType != TYPE_MYSTERY) + if (move != MOVE_STRUGGLE && moveType != TYPE_MYSTERY && moveType != TYPE_STELLAR) { modifier = CalcTypeEffectivenessMultiplierInternal(move, moveType, battlerAtk, battlerDef, recordAbilities, modifier, defAbility); if (gMovesInfo[move].effect == EFFECT_TWO_TYPED_MOVE) modifier = CalcTypeEffectivenessMultiplierInternal(move, gMovesInfo[move].argument, battlerAtk, battlerDef, recordAbilities, modifier, defAbility); } + else if (moveType == TYPE_STELLAR) + { + modifier = IsTerastallized(battlerDef) ? UQ_4_12(2.0) : UQ_4_12(1.0); + } if (recordAbilities) UpdateMoveResultFlags(modifier); @@ -10640,8 +10676,8 @@ bool32 TryBattleFormChange(u32 battler, u16 method) bool32 DoBattlersShareType(u32 battler1, u32 battler2) { s32 i; - u8 types1[3] = {GetBattlerType(battler1, 0), GetBattlerType(battler1, 1), GetBattlerType(battler1, 2)}; - u8 types2[3] = {GetBattlerType(battler2, 0), GetBattlerType(battler2, 1), GetBattlerType(battler2, 2)}; + u8 types1[3] = {GetBattlerType(battler1, 0, FALSE), GetBattlerType(battler1, 1, FALSE), GetBattlerType(battler1, 2, FALSE)}; + u8 types2[3] = {GetBattlerType(battler2, 0, FALSE), GetBattlerType(battler2, 1, FALSE), GetBattlerType(battler2, 2, FALSE)}; if (types1[2] == TYPE_MYSTERY) types1[2] = types1[0]; @@ -10823,7 +10859,7 @@ static bool32 IsUnnerveAbilityOnOpposingSide(u32 battler) return FALSE; } -// Photon geyser & light that burns the sky +// Photon Geyser, Light That Burns the Sky, Tera Blast u8 GetCategoryBasedOnStats(u32 battler) { u32 attack = gBattleMons[battler].attack; @@ -11381,17 +11417,23 @@ bool8 IsMonBannedFromSkyBattles(u16 species) } } -u8 GetBattlerType(u32 battler, u8 typeIndex) +u8 GetBattlerType(u32 battler, u8 typeIndex, bool32 ignoreTera) { + u32 teraType = GetBattlerTeraType(battler); u16 types[3] = {0}; types[0] = gBattleMons[battler].type1; types[1] = gBattleMons[battler].type2; types[2] = gBattleMons[battler].type3; + // Handle Terastallization + if (IsTerastallized(battler) && teraType != TYPE_STELLAR && !ignoreTera) + return GetBattlerTeraType(battler); + // Handle Roost's Flying-type suppression if (typeIndex == 0 || typeIndex == 1) { - if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST) + if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_ROOST + && !IsTerastallized(battler)) { if (types[0] == TYPE_FLYING && types[1] == TYPE_FLYING) return B_ROOST_PURE_FLYING >= GEN_5 ? TYPE_NORMAL : TYPE_MYSTERY; @@ -11406,6 +11448,8 @@ u8 GetBattlerType(u32 battler, u8 typeIndex) void RemoveBattlerType(u32 battler, u8 type) { u32 i; + if (IsTerastallized(battler)) // don't remove type if Terastallized + return; for (i = 0; i < 3; i++) { if (*(u8 *)(&gBattleMons[battler].type1 + i) == type) diff --git a/src/data/battle_move_effects.h b/src/data/battle_move_effects.h index 52efcb1212..de8d3f9ec8 100644 --- a/src/data/battle_move_effects.h +++ b/src/data/battle_move_effects.h @@ -2230,4 +2230,10 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] = .battleTvScore = 0, // TODO: Assign points .encourageEncore = TRUE, }, + + [EFFECT_TERA_BLAST] = + { + .battleScript = BattleScript_EffectPhotonGeyser, + .battleTvScore = 0, // TODO: Assign points + }, }; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 10e0c8ef28..911f2b6b82 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -18440,7 +18440,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .description = COMPOUND_STRING( "If the user's Terastallized,\n" "it hits with its Tera-type."), - .effect = EFFECT_PLACEHOLDER, // EFFECT_TERA_BLAST, + .effect = EFFECT_TERA_BLAST, .power = 80, .type = TYPE_NORMAL, .accuracy = 100, @@ -18449,6 +18449,10 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, .forcePressure = TRUE, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_TERA_BLAST, + .self = TRUE, + }), }, [MOVE_SILK_TRAP] = diff --git a/src/pokemon_summary_screen.c b/src/pokemon_summary_screen.c index f5c2e7f6b7..6e63ce5eab 100644 --- a/src/pokemon_summary_screen.c +++ b/src/pokemon_summary_screen.c @@ -891,6 +891,10 @@ static const union AnimCmd sSpriteAnim_TypeFairy[] = { ANIMCMD_FRAME(TYPE_FAIRY * 8, 0, FALSE, FALSE), ANIMCMD_END }; +static const union AnimCmd sSpriteAnim_TypeStellar[] = { + ANIMCMD_FRAME(TYPE_STELLAR * 8, 0, FALSE, FALSE), + ANIMCMD_END +}; static const union AnimCmd sSpriteAnim_CategoryCool[] = { ANIMCMD_FRAME((CONTEST_CATEGORY_COOL + NUMBER_OF_MON_TYPES) * 8, 0, FALSE, FALSE), ANIMCMD_END @@ -931,6 +935,7 @@ static const union AnimCmd *const sSpriteAnimTable_MoveTypes[NUMBER_OF_MON_TYPES sSpriteAnim_TypeDragon, sSpriteAnim_TypeDark, sSpriteAnim_TypeFairy, + sSpriteAnim_TypeStellar, sSpriteAnim_CategoryCool, sSpriteAnim_CategoryBeauty, sSpriteAnim_CategoryCute, diff --git a/src/script_pokemon_util.c b/src/script_pokemon_util.c index 31e40fa31a..763bb3f9e6 100644 --- a/src/script_pokemon_util.c +++ b/src/script_pokemon_util.c @@ -7,6 +7,7 @@ #include "decompress.h" #include "event_data.h" #include "international_string_util.h" +#include "item.h" #include "link.h" #include "link_rfu.h" #include "main.h" @@ -38,6 +39,10 @@ void HealPlayerParty(void) HealPokemon(&gPlayerParty[i]); if (OW_PC_HEAL >= GEN_8) HealPlayerBoxes(); + + // Recharge Tera Orb, if possible. + if (B_FLAG_TERA_ORB_CHARGED != 0 && CheckBagHasItem(ITEM_TERA_ORB, 1)) + FlagSet(B_FLAG_TERA_ORB_CHARGED); } static void HealPlayerBoxes(void) diff --git a/test/dynamax.c b/test/battle/gimmick/dynamax.c similarity index 100% rename from test/dynamax.c rename to test/battle/gimmick/dynamax.c diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c new file mode 100644 index 0000000000..3166e2d67c --- /dev/null +++ b/test/battle/gimmick/terastal.c @@ -0,0 +1,802 @@ +#include "global.h" +#include "test/battle.h" + +// Base Power and STAB Checks + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type preserves other STAB boosts", s16 damage1, s16 damage2) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_BULBASAUR) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_VINE_WHIP, tera: tera); } + TURN { MOVE(player, MOVE_SLUDGE_BOMB); } + } SCENE { + MESSAGE("Bulbasaur used Vine Whip!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_VINE_WHIP, player); + HP_BAR(opponent, captureDamage: &results[i].damage1); + MESSAGE("Bulbasaur used Sludge Bomb!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLUDGE_BOMB, player); + HP_BAR(opponent, captureDamage: &results[i].damage2); + } FINALLY { + EXPECT_EQ(results[0].damage1, results[1].damage1); + EXPECT_EQ(results[0].damage2, results[1].damage2); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing does not affect the power of non-STAB moves", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT, tera: tera); } + } SCENE { + MESSAGE("Wobbuffet used Headbutt!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type gives that type 1.5x STAB", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT, tera: tera); } + } SCENE { + MESSAGE("Wobbuffet used Headbutt!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from no STAB to 1.5x STAB is a 1.5x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type gives that type 2x STAB", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC, tera: tera); } + } SCENE { + MESSAGE("Wobbuffet used Psychic!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from 1.5x STAB to 2.0x STAB is a 1.33x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.33), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into a different type with Adaptability gives 2.0x STAB", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(ABILITY_ADAPTABILITY); TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_HEADBUTT, tera: tera); } + } SCENE { + MESSAGE("Crawdaunt used Headbutt!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEADBUTT, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from no STAB to 2.0x STAB is a 2.0x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into the same type with Adaptability gives 2.25x STAB", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_CRAWDAUNT) { Ability(ABILITY_ADAPTABILITY); TeraType(TYPE_WATER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WATER_PULSE, tera: tera); } + } SCENE { + MESSAGE("Crawdaunt used Water Pulse!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PULSE, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from 2x STAB to 2.25x STAB is a 1.125x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.125), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing boosts moves of the same type to 60 BP", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_ABSORB].power == 20); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GRASS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ABSORB, tera: tera); } + } SCENE { + MESSAGE("Wobbuffet used Absorb!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from 20 BP to 90 BP (60 * 1.5x) is a 4.5x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(4.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technician", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_MEGA_DRAIN].power == 40); + PLAYER(SPECIES_MR_MIME) { Ability(ABILITY_TECHNICIAN); TeraType(TYPE_GRASS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MEGA_DRAIN, tera: tera); } + } SCENE { + MESSAGE("Mr. Mime used Mega Drain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEGA_DRAIN, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // This should be the same as a normal Tera boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor occurs after Technician", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_MR_MIME) { Ability(ABILITY_TECHNICIAN); TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STORED_POWER, tera: tera); } + } SCENE { + MESSAGE("Mr. Mime used Stored Power!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STORED_POWER, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // The jump from 45 BP (20 * 1.5x * 1.5x) to 120 BP (60 * 2.0x) is a 2.667x boost. + EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.667), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor does not apply to multi-hit moves", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FURY_SWIPES, tera: tera); } + } SCENE { + MESSAGE("Wobbuffet used Fury Swipes!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FURY_SWIPES, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallization's 60 BP floor does not apply to priority moves", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_QUICK_ATTACK, tera: tera); } + } SCENE { + MESSAGE("Wobbuffet used Quick Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + +// Defensive Type Checks + +SINGLE_BATTLE_TEST("(TERA) Terastallization changes type effectiveness", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GRASS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: tera); MOVE(opponent, MOVE_WATER_GUN); } + } SCENE { + MESSAGE("Foe Wobbuffet used Water Gun!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(0.5), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallization changes type effectiveness") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_FLYING); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + MESSAGE("Foe Wobbuffet used Earthquake!"); + MESSAGE("It doesn't affect Wobbuffet…"); + NOT { HP_BAR(player); } + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallization persists across switches") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_FLYING); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, MOVE_EARTHQUAKE); } + TURN { SWITCH(player, 1); } + TURN { SWITCH(player, 0); } + TURN { MOVE(opponent, MOVE_EARTHQUAKE); } + } SCENE { + // turn 1 + MESSAGE("Foe Wobbuffet used Earthquake!"); + MESSAGE("It doesn't affect Wobbuffet…"); + NOT { HP_BAR(player); } + // turn 4 + MESSAGE("Foe Wobbuffet used Earthquake!"); + MESSAGE("It doesn't affect Wobbuffet…"); + NOT { HP_BAR(player); } + } +} + +// Other Type Checks + +SINGLE_BATTLE_TEST("(TERA) Terastallization changes the effect of Curse") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CURSE, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Curse!"); + HP_BAR(player); + MESSAGE("Wobbuffet cut its own HP and laid a CURSE on Foe Wobbuffet!"); + NOT { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); } + } +} + +SINGLE_BATTLE_TEST("(TERA) Roost does not remove the user's Flying type while Terastallized") +{ + GIVEN { + PLAYER(SPECIES_ZAPDOS) { HP(1); TeraType(TYPE_FLYING); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST, tera: TRUE); MOVE(opponent, MOVE_ICE_BEAM); } + } SCENE { + MESSAGE("Zapdos used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Foe Wobbuffet used Ice Beam!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_BEAM, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Type-changing moves fail against a Terastallized Pokemon") +{ + u16 move; + PARAMETRIZE { move = MOVE_SOAK; } + PARAMETRIZE { move = MOVE_FORESTS_CURSE; } + PARAMETRIZE { move = MOVE_TRICK_OR_TREAT; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, move); } + } SCENE { + if (move != MOVE_SOAK) + NOT { ANIMATION(ANIM_TYPE_MOVE, move, opponent); } + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Reflect Type fails if used by a Terastallized Pokemon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REFLECT_TYPE, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Reflect Type!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Conversion fails if used by a Terastallized Pokemon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CONVERSION, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Conversion!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Conversion2 fails if used by a Terastallized Pokemon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_PSYCHIC); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); } + TURN { MOVE(player, MOVE_CONVERSION_2, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Conversion 2!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Reflect Type copies a Terastallized Pokemon's Tera Type") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(opponent, MOVE_REFLECT_TYPE); } + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + // turn 2 + MESSAGE("Foe Wobbuffet used Reflect Type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, opponent); + // turn 3 + MESSAGE("Wobbuffet used Tackle!"); + MESSAGE("It doesn't affect Foe Wobbuffet…"); + NOT { HP_BAR(opponent); } + } +} + +SINGLE_BATTLE_TEST("(TERA) Synchronoise uses a Terastallized Pokemon's Tera Type") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } + OPPONENT(SPECIES_WOBBUFFET) { TeraType(TYPE_GHOST); } + } WHEN { + TURN { MOVE(opponent, MOVE_SYNCHRONOISE); MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(opponent, MOVE_SYNCHRONOISE, tera: TRUE); } + } SCENE { + // turn 1 + MESSAGE("Foe Wobbuffet used Synchronoise!"); + MESSAGE("It had no effect on Wobbuffet!"); + // turn 2 + MESSAGE("Foe Wobbuffet used Synchronoise!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SYNCHRONOISE, opponent); + } +} + +SINGLE_BATTLE_TEST("(TERA) Revelation Dance uses a Terastallized Pokemon's Tera Type") +{ + GIVEN { + ASSUME(P_GEN_7_POKEMON); + PLAYER(SPECIES_ORICORIO) { TeraType(TYPE_NORMAL); } + OPPONENT(SPECIES_GENGAR); + } WHEN { + TURN { MOVE(player, MOVE_REVELATION_DANCE, tera: TRUE); } + } SCENE { + #if B_EXPANDED_MOVE_NAMES == TRUE + MESSAGE("Oricorio used Revelation Dance!"); + #else + MESSAGE("Oricorio used RvlationDnce!"); + #endif + MESSAGE("It doesn't affect Foe Gengar…"); + NOT { HP_BAR(opponent); } + } +} + +// This tests that Tera STAB modifiers depend on the user's original types, too. +SINGLE_BATTLE_TEST("(TERA) Double Shock does not remove the user's Electric type while Terastallized, and changes STAB modifier depending on when it is used") +{ + s16 damage[4]; + GIVEN { + ASSUME(gMovesInfo[MOVE_DOUBLE_SHOCK].effect == EFFECT_FAIL_IF_NOT_ARG_TYPE); + PLAYER(SPECIES_PICHU) { TeraType(TYPE_ELECTRIC); } + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_DOUBLE_SHOCK); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_DOUBLE_SHOCK, tera: TRUE); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_DOUBLE_SHOCK); MOVE(opponent, MOVE_RECOVER); } + TURN { SWITCH(player, 1); MOVE(opponent, MOVE_RECOVER); } + TURN { SWITCH(player, 0); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_DOUBLE_SHOCK); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_DOUBLE_SHOCK); } + } SCENE { + // turn 1 - regular STAB + MESSAGE("Pichu used Double Shock!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SHOCK, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 2 - lost Electric type, gained back from Tera + MESSAGE("Pichu used Double Shock!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SHOCK, player); + HP_BAR(opponent, captureDamage: &damage[1]); + // turn 3 - retained Electric type + MESSAGE("Pichu used Double Shock!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SHOCK, player); + // turn 6 - original type reset, regular STAB + Tera boost + MESSAGE("Pichu used Double Shock!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SHOCK, player); + HP_BAR(opponent, captureDamage: &damage[2]); + // turn 7 - regular STAB + Tera boost stays + MESSAGE("Pichu used Double Shock!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SHOCK, player); + HP_BAR(opponent, captureDamage: &damage[3]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + EXPECT_MUL_EQ(damage[0], Q_4_12(1.333), damage[2]); + EXPECT_EQ(damage[2], damage[3]); + } +} + +SINGLE_BATTLE_TEST("(TERA) Transform does not copy the target's Tera Type, and if the user is Terastallized it keeps its own Tera Type") +{ + KNOWN_FAILING; // Transform seems to be bugged in tests. + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE, MOVE_TACKLE, MOVE_EARTHQUAKE); TeraType(TYPE_GHOST); } + OPPONENT(SPECIES_DITTO) { TeraType(TYPE_FLYING); } + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); MOVE(opponent, MOVE_TRANSFORM); } + TURN { MOVE(player, MOVE_EARTHQUAKE); } + // TURN { MOVE(player, MOVE_TACKLE); MOVE(opponent, MOVE_TACKLE, target: player, tera: TRUE); } + } SCENE { + // turn 2 + MESSAGE("Wobbuffet used Earthquake!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + HP_BAR(opponent); + // turn 3 + MESSAGE("Wobbuffet used Tackle!"); + MESSAGE("It doesn't affect Ditto…"); + NOT { HP_BAR(opponent); } + } +} + +// Stellar Type checks +SINGLE_BATTLE_TEST("(TERA) Stellar type does not change the user's defensive profile", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: tera); MOVE(opponent, MOVE_PSYCHIC); } + } SCENE { + MESSAGE("Foe Wobbuffet used Psychic!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, opponent); + HP_BAR(player, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_EQ(results[0].damage, results[1].damage); + } +} + +SINGLE_BATTLE_TEST("(TERA) Reflect Type copies a Stellar-type Pokemon's base type") +{ + GIVEN { + PLAYER(SPECIES_BANETTE) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + TURN { MOVE(opponent, MOVE_REFLECT_TYPE); } + TURN { MOVE(player, MOVE_TACKLE); } + } SCENE { + // turn 2 + MESSAGE("Foe Wobbuffet used Reflect Type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, opponent); + // turn 3 + MESSAGE("Banette used Tackle!"); + MESSAGE("It doesn't affect Foe Wobbuffet…"); + NOT { HP_BAR(opponent); } + } +} + +SINGLE_BATTLE_TEST("(TERA) Revelation Dance uses a Stellar-type Pokemon's base type") +{ + GIVEN { + ASSUME(P_GEN_7_POKEMON); + PLAYER(SPECIES_ORICORIO_SENSU) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_GUMSHOOS); + } WHEN { + TURN { MOVE(player, MOVE_REVELATION_DANCE, tera: TRUE); } + } SCENE { + #if B_EXPANDED_MOVE_NAMES == TRUE + MESSAGE("Oricorio used Revelation Dance!"); + #else + MESSAGE("Oricorio used RvlationDnce!"); + #endif + MESSAGE("It doesn't affect Foe Gumshoos…"); + NOT { HP_BAR(opponent); } + } +} + +SINGLE_BATTLE_TEST("(TERA) Conversion2 fails if last hit by a Stellar-type move") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + TURN { MOVE(opponent, MOVE_CONVERSION_2); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + // turn 2 + MESSAGE("Foe Wobbuffet used Conversion 2!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Roost does not remove Flying-type ground immunity when Terastallized into the Stellar type") +{ + GIVEN { + PLAYER(SPECIES_ZAPDOS) { HP(1); TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ROOST, tera: TRUE); MOVE(opponent, MOVE_ICE_BEAM); } + } SCENE { + MESSAGE("Zapdos used Roost!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROOST, player); + MESSAGE("Foe Wobbuffet used Ice Beam!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_BEAM, opponent); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar-type provides a one-time 2.0x boost to STAB moves") +{ + s16 damage[3]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_EXTRASENSORY); } + TURN { MOVE(player, MOVE_EXTRASENSORY, tera: TRUE); } + TURN { MOVE(player, MOVE_EXTRASENSORY); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Extrasensory!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTRASENSORY, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 2 + MESSAGE("Wobbuffet used Extrasensory!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTRASENSORY, player); + HP_BAR(opponent, captureDamage: &damage[1]); + // turn 3 + MESSAGE("Wobbuffet used Extrasensory!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTRASENSORY, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + // Extrasensory goes from a 50% boost to a 100% boost for a 1.33x total multiplier + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33), damage[1]); + EXPECT_EQ(damage[0], damage[2]); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar-type provides a one-time 1.2x boost to non-STAB moves") +{ + s16 damage[3]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TAKE_DOWN); } + TURN { MOVE(player, MOVE_TAKE_DOWN, tera: TRUE); } + TURN { MOVE(player, MOVE_TAKE_DOWN); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 2 + MESSAGE("Wobbuffet used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[1]); + // turn 3 + MESSAGE("Wobbuffet used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.2), damage[1]); + EXPECT_EQ(damage[0], damage[2]); + } +} + +SINGLE_BATTLE_TEST("(TERA) Terastallizing into the Stellar type boosts all moves up to 60 BP once per type") +{ + s16 damage[4]; + GIVEN { + ASSUME(gMovesInfo[MOVE_MEGA_DRAIN].power == 40); + ASSUME(gMovesInfo[MOVE_BUBBLE].power == 40); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_MEGA_DRAIN); } + TURN { MOVE(player, MOVE_MEGA_DRAIN, tera: TRUE); } + TURN { MOVE(player, MOVE_MEGA_DRAIN); } + TURN { MOVE(player, MOVE_BUBBLE); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Mega Drain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEGA_DRAIN, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 2 + MESSAGE("Wobbuffet used Mega Drain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEGA_DRAIN, player); + HP_BAR(opponent, captureDamage: &damage[1]); + // turn 3 + MESSAGE("Wobbuffet used Mega Drain!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_MEGA_DRAIN, player); + HP_BAR(opponent, captureDamage: &damage[2]); + // turn 4 + MESSAGE("Wobbuffet used Bubble!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BUBBLE, player); + HP_BAR(opponent, captureDamage: &damage[3]); + } THEN { + // The jump from 40 BP to 72 BP (60 * 1.2x) is a 1.8x boost. + EXPECT_MUL_EQ(damage[0], Q_4_12(1.8), damage[1]); + EXPECT_EQ(damage[0], damage[2]); + EXPECT_EQ(damage[1], damage[3]); + } +} + +SINGLE_BATTLE_TEST("(TERA) Protean cannot change the type of a Terastallized Pokemon") +{ + GIVEN { + PLAYER(SPECIES_GRENINJA) { Ability(ABILITY_PROTEAN); TeraType(TYPE_GRASS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_BUBBLE, tera: TRUE); + MOVE(opponent, MOVE_EMBER); } + } SCENE { + MESSAGE("Greninja used Bubble!"); + MESSAGE("Foe Wobbuffet used Ember!"); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("(TERA) Status moves don't expend Stellar's one-time type boost") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GROWL, tera: TRUE); } + TURN { MOVE(player, MOVE_TAKE_DOWN); } + TURN { MOVE(player, MOVE_TAKE_DOWN); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Growl!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, player); + // turn 2 + MESSAGE("Wobbuffet used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 3 + MESSAGE("Wobbuffet used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], UQ_4_12(1.20), damage[0]); + } +} + +SINGLE_BATTLE_TEST("(TERA) Stellar type's one-time boost factors in dynamically-typed moves") +{ + s16 damage[4]; + GIVEN { + ASSUME(gMovesInfo[MOVE_WEATHER_BALL].type == TYPE_NORMAL); + PLAYER(SPECIES_PELIPPER) { Ability(ABILITY_DRIZZLE); TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_WEATHER_BALL, tera: TRUE); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_TAKE_DOWN); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_TAKE_DOWN); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_WATER_PULSE); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_WATER_PULSE); MOVE(opponent, MOVE_RECOVER); } + } SCENE { + MESSAGE("Pelipper used Weather Ball!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WEATHER_BALL, player); + // turn 2 + MESSAGE("Pelipper used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 3 + MESSAGE("Pelipper used Take Down!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, player); + HP_BAR(opponent, captureDamage: &damage[1]); + // turn 4 + MESSAGE("Pelipper used Water Pulse!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PULSE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + // turn 5 + MESSAGE("Pelipper used Water Pulse!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_PULSE, player); + HP_BAR(opponent, captureDamage: &damage[3]); + } THEN { + // Take Down should have a Normal type boost applied + EXPECT_MUL_EQ(damage[1], UQ_4_12(1.20), damage[0]); + // Water Pulse should not have a Water type boost applied + EXPECT_EQ(damage[3], damage[2]); + } +} + +SINGLE_BATTLE_TEST("(TERA) All type indicators function correctly") +{ + u32 type; + PARAMETRIZE { type = TYPE_NORMAL; } + PARAMETRIZE { type = TYPE_FIGHTING; } + PARAMETRIZE { type = TYPE_FLYING; } + PARAMETRIZE { type = TYPE_POISON; } + PARAMETRIZE { type = TYPE_GROUND; } + PARAMETRIZE { type = TYPE_ROCK; } + PARAMETRIZE { type = TYPE_BUG; } + PARAMETRIZE { type = TYPE_GHOST; } + PARAMETRIZE { type = TYPE_STEEL; } + PARAMETRIZE { type = TYPE_MYSTERY; } + PARAMETRIZE { type = TYPE_FIRE; } + PARAMETRIZE { type = TYPE_WATER; } + PARAMETRIZE { type = TYPE_GRASS; } + PARAMETRIZE { type = TYPE_ELECTRIC; } + PARAMETRIZE { type = TYPE_PSYCHIC; } + PARAMETRIZE { type = TYPE_ICE; } + PARAMETRIZE { type = TYPE_DRAGON; } + PARAMETRIZE { type = TYPE_DARK; } + PARAMETRIZE { type = TYPE_FAIRY; } + PARAMETRIZE { type = TYPE_STELLAR; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(type); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + } +} diff --git a/test/battle/move_effect/tera_blast.c b/test/battle/move_effect/tera_blast.c new file mode 100644 index 0000000000..4592cf32a5 --- /dev/null +++ b/test/battle/move_effect/tera_blast.c @@ -0,0 +1,137 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_TERA_BLAST].effect == EFFECT_TERA_BLAST); +} + +SINGLE_BATTLE_TEST("Tera Blast changes from Normal-type to the user's Tera Type") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TERA_BLAST].type == TYPE_NORMAL); + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_DARK); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Tera Blast becomes a physical move if the user is Terastallized and has a higher Attack stat", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_NORMAL); Attack(100); SpAttack(50); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(200); SpDefense(200); } + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, tera: tera); } + } SCENE { + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + // Since Wobbuffett has equal defenses, Tera Blast should do 1.5x more damage + // from gaining STAB and an additional 2.0x damage from using its highest + // attacking stat. + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(3.0), results[1].damage); + } +} + +SINGLE_BATTLE_TEST("Stellar-type Tera Blast lowers both offensive stats") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + MESSAGE("Wobbuffet's Attack fell!"); + MESSAGE("Wobbuffet's Sp. Atk fell!"); + } +} + + +SINGLE_BATTLE_TEST("Stellar-type Tera Blast has 100 BP and a one-time 1.2x boost") +{ + s16 damage[3]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST); MOVE(opponent, MOVE_RECOVER); } + TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); } + TURN { MOVE(player, MOVE_WORK_UP); } + TURN { MOVE(player, MOVE_TERA_BLAST); } + } SCENE { + // turn 1 + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + HP_BAR(opponent, captureDamage: &damage[0]); + // turn 2 + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + HP_BAR(opponent, captureDamage: &damage[1]); + // turn 4 + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + // 80 BP to 120 BP (100 * 1.2) boost upon Terastallizing + EXPECT_MUL_EQ(damage[0], UQ_4_12(1.50), damage[1]); + // 120 BP to 100 BP after Stellar boost expended + EXPECT_MUL_EQ(damage[2], UQ_4_12(1.20), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Stellar-type Tera Blast is super-effective on Stellar-type Pokemon") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); MOVE(opponent, MOVE_CELEBRATE, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + } +} + +SINGLE_BATTLE_TEST("Stellar-type Tera Blast activates a Stellar-type Pokemon's Weakness Policy") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { TeraType(TYPE_STELLAR); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_WEAKNESS_POLICY); TeraType(TYPE_NORMAL); } + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); MOVE(opponent, MOVE_CELEBRATE, tera: TRUE); } + } SCENE { + MESSAGE("Wobbuffet used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + MESSAGE("It's super effective!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} + +SINGLE_BATTLE_TEST("Flying-type Tera Blast does not have its priority boosted by Gale Wings") +{ + GIVEN { + PLAYER(SPECIES_TALONFLAME) { Ability(ABILITY_GALE_WINGS); TeraType(TYPE_FLYING); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_TERA_BLAST, tera: TRUE); MOVE(opponent, MOVE_QUICK_ATTACK); } + } SCENE { + MESSAGE("Foe Wobbuffet used Quick Attack!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponent); + MESSAGE("Talonflame used Tera Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_BLAST, player); + } +} diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index b800f9099f..aaeba97479 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -2075,6 +2075,9 @@ void MoveGetIdAndSlot(s32 battlerId, struct MoveContext *ctx, u32 *moveId, u32 * if (ctx->explicitDynamax && ctx->dynamax) *moveSlot |= RET_DYNAMAX; + + if (ctx->explicitTera && ctx->tera) + *moveSlot |= RET_TERASTAL; } void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)