Terastallization (#4110)

* wrote foundational terastal tests

* implemented baseline test-only Tera functionality; modified GetBattlerType + STAB calculations, misc. changes to some moves

* added tests and func. for Stellar type, more tests for Tera Blast

* more tests for Stellar type, Conversion fixes, Color Change + Conversion2 future proof

* implemented tera blast, expanded stellar type func., fixed tests

* last set of Tera/Tera Blast tests for checklist, protean fix

* implemented in-battle Terastallization, WIP stellar indicator and tera animation

* fixed bad merge

* expanded NUMBER_OF_MON_TYPES, cut down on TYPE_STELLAR hackiness, added Stellar type to summary

* fixed type indicators

* added tera logic to AI

* implemented code review changes, added B_TERA_ORB_NO_COST

* updated AI to calc damage with Tera when applicable; minor rework to AI gimmick handling

* fixed Tera Blast split choice occuring when not Terastallized

* fixed Tera Blast using Last Respects BP calcs

* added tera type to TrainerMon, code review tweaks
This commit is contained in:
AgustinGDLV 2024-04-24 02:17:46 -07:00 committed by GitHub
parent aaaccbfe6c
commit 84a9d4ffcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 2151 additions and 92 deletions

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
graphics/types/stellar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -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 ###

View file

@ -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) \
{ \

View file

@ -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
{

View file

@ -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
{

View file

@ -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

30
include/battle_terastal.h Normal file
View file

@ -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

View file

@ -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);

View file

@ -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.

View file

@ -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

View file

@ -350,6 +350,7 @@ enum {
EFFECT_DRAGON_CHEER,
EFFECT_LAST_RESPECTS,
EFFECT_TIDY_UP,
EFFECT_TERA_BLAST,
NUM_BATTLE_MOVE_EFFECTS,
};

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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));
}

View file

@ -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]);

View file

@ -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())
{

View file

@ -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)
{

View file

@ -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))

View file

@ -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,

View file

@ -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;
}

786
src/battle_terastal.c Normal file
View file

@ -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

View file

@ -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)

View file

@ -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
},
};

View file

@ -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] =

View file

@ -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,

View file

@ -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)

View file

@ -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); }
}
}

View file

@ -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);
}
}

View file

@ -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)