From 7f8f480ecb339f346a2fe73fa6e94aeb490ae3d5 Mon Sep 17 00:00:00 2001 From: kittenchilly Date: Tue, 4 Jun 2024 06:38:49 -0500 Subject: [PATCH] Add FORM_CHANGE_BATTLE_TERASTALLIZATION + allow species to force tera types (#4438) * Add FORM_CHANGE_BATTLE_TERASTALLIZATION and allow species to force tera types * Fix form change not changing tera type * Update form_species_tables.h * Address reviews * Can't change the forced Tera Type anymore * Revert "Can't change the forced Tera Type anymore" This reverts commit 67157250efeaf5ffefc45ac1955b09f27f09788b. * Fix a lot of things * Oops * Update pokemon.h * Update pokemon.h * Address reviews * Update tera_starstorm.c * Update test/battle/gimmick/terastal.c --------- Co-authored-by: Eduardo Quezada --- data/battle_anim_scripts.s | 3 +- data/battle_scripts_1.s | 16 +++++ include/battle_scripts.h | 1 + include/constants/form_change_types.h | 6 +- include/pokemon.h | 4 +- src/battle_main.c | 5 +- src/battle_script_commands.c | 18 +++--- src/battle_terastal.c | 7 +-- src/battle_tower.c | 31 ++++++---- src/battle_util.c | 16 ++++- src/data/pokemon/form_change_tables.h | 18 ++++-- src/data/pokemon/form_species_tables.h | 6 ++ .../pokemon/species_info/gen_9_families.h | 24 +++++--- src/pokemon.c | 8 ++- test/battle/gimmick/terastal.c | 20 +++++- test/battle/move_effect/tera_starstorm.c | 61 +++++++++++++++++++ 16 files changed, 193 insertions(+), 51 deletions(-) create mode 100644 test/battle/move_effect/tera_starstorm.c diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 586698b2f2f4..536c7854cb9e 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -27436,12 +27436,13 @@ General_TeraCharge: delay 20 createvisualtask AnimTask_BlendBattleAnimPalExclude, 5, 5, 2, 0, 16, RGB_WHITEALPHA waitforvisualfinish + createvisualtask AnimTask_TransformMon, 2, 1, 0 call TeraChargeParticles playsewithpan SE_M_BRICK_BREAK, SOUND_PAN_ATTACKER clearmonbg ANIM_ATK_PARTNER blendoff end - + TeraChargeParticles: createsprite gTeraCrystalSpreadSpriteTemplate, ANIM_TARGET, 0, 0, -5, 8 createsprite gTeraCrystalSpreadSpriteTemplate, ANIM_TARGET, 0, 1, 5, 9 diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index b85aac18250e..291344eabc59 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -32,6 +32,22 @@ BattleScript_Terastallization:: waitmessage B_WAIT_TIME_LONG end3 +BattleScript_TeraFormChange:: + @ TODO: no string prints in S/V, but right now this helps with clarity + printstring STRINGID_PKMNSTORINGENERGY + handleformchange BS_ATTACKER, 0 + handleformchange BS_ATTACKER, 1 + playanimation BS_ATTACKER, B_ANIM_TERA_CHARGE + waitanimation + applyterastallization + playanimation BS_ATTACKER, B_ANIM_TERA_ACTIVATE + waitanimation + handleformchange BS_ATTACKER, 2 + printstring STRINGID_PKMNTERASTALLIZEDINTO + waitmessage B_WAIT_TIME_LONG + switchinabilities BS_ATTACKER + 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 diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 93b9567f8551..6982b0c33929 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -510,6 +510,7 @@ extern const u8 BattleScript_LowerAtkSpAtk[]; extern const u8 BattleScript_Terastallization[]; extern const u8 BattleScript_BoosterEnergyEnd2[]; extern const u8 BattleScript_TeraShellDistortingTypeMatchups[]; +extern const u8 BattleScript_TeraFormChange[]; // zmoves extern const u8 BattleScript_ZMoveActivateDamaging[]; diff --git a/include/constants/form_change_types.h b/include/constants/form_change_types.h index d94cc90a37ff..b371c84067f6 100644 --- a/include/constants/form_change_types.h +++ b/include/constants/form_change_types.h @@ -121,6 +121,10 @@ #define FORM_CHANGE_STATUS 20 // Form change that activates after move is used. Currently only used for activating Gulp Missile. -#define FORM_CHANGE_HIT_BY_MOVE 21 +#define FORM_CHANGE_HIT_BY_MOVE 21 + +// Form change that activates when terastallized as as a specific type +// param1: tera type +#define FORM_CHANGE_BATTLE_TERASTALLIZATION 22 #endif // GUARD_CONSTANTS_FORM_CHANGE_TYPES_H diff --git a/include/pokemon.h b/include/pokemon.h index c7a59cb84096..420bf5729536 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -360,7 +360,7 @@ struct SpeciesInfo /*0x8C*/ /* 0x05 */ u8 baseSpDefense; /* 0x06 */ u8 types[2]; /* 0x08 */ u8 catchRate; - /* 0x09 */ u8 padding1; + /* 0x09 */ u8 forceTeraType; /* 0x0A */ u16 expYield; // expYield was changed from u8 to u16 for the new Exp System. /* 0x0C */ u16 evYield_HP:2; u16 evYield_Attack:2; @@ -378,6 +378,7 @@ struct SpeciesInfo /*0x8C*/ /* 0x16 */ u8 eggGroups[2]; /* 0x18 */ u16 abilities[NUM_ABILITY_SLOTS]; // 3 abilities, no longer u8 because we have over 255 abilities now. /* 0x1E */ u8 safariZoneFleeRate; + // Pokédex data /* 0x1F */ u8 categoryName[13]; /* 0x1F */ u8 speciesName[POKEMON_NAME_LENGTH + 1]; @@ -431,6 +432,7 @@ struct SpeciesInfo /*0x8C*/ u32 isPrimalReversion:1; u32 isUltraBurst:1; u32 isGigantamax:1; + u32 isTeraForm:1; u32 isAlolanForm:1; u32 isGalarianForm:1; u32 isHisuianForm:1; diff --git a/src/battle_main.c b/src/battle_main.c index f55f4816599b..8c2d5b7de7f2 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -5086,7 +5086,10 @@ static bool32 TryDoGimmicksBeforeMoves(void) gBattleStruct->tera.toTera &= ~(gBitTable[gBattlerAttacker]); PrepareBattlerForTera(gBattlerAttacker); PREPARE_TYPE_BUFFER(gBattleTextBuff1, GetBattlerTeraType(gBattlerAttacker)); - BattleScriptExecute(BattleScript_Terastallization); + if (TryBattleFormChange(gBattlerAttacker, FORM_CHANGE_BATTLE_TERASTALLIZATION)) + BattleScriptExecute(BattleScript_TeraFormChange); + else + BattleScriptExecute(BattleScript_Terastallization); return TRUE; } // Dynamax Check diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 66f819cf8879..6e55da104d05 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2050,7 +2050,7 @@ static void Cmd_adjustdamage(void) gLastUsedItem = gBattleMons[gBattlerTarget].item; gSpecialStatuses[gBattlerTarget].focusBanded = FALSE; gSpecialStatuses[gBattlerTarget].focusSashed = FALSE; - + } else if (gSpecialStatuses[gBattlerTarget].sturdied) { @@ -3172,8 +3172,8 @@ void SetMoveEffect(bool32 primary, bool32 certain) { gBattleMons[gEffectBattler].status2 |= sStatusFlagsForMoveEffects[gBattleScripting.moveEffect]; gBattlescriptCurrInstr++; - } - else + } + else { gBattlescriptCurrInstr++; } @@ -6243,7 +6243,7 @@ static void Cmd_moveend(void) break; } } - + if (!(gBattleStruct->lastMoveFailed & gBitTable[gBattlerAttacker] || (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove && gBattleStruct->bouncedMoveIsUsed))) @@ -6341,9 +6341,9 @@ static void Cmd_moveend(void) && (gMoveResultFlags & MOVE_RESULT_NO_EFFECT) // And it is unusable && (gBattleMons[gBattlerAttacker].status2 & STATUS2_LOCK_CONFUSE) != STATUS2_LOCK_CONFUSE_TURN(1)) // And won't end this turn CancelMultiTurnMoves(gBattlerAttacker); // Cancel it - - - + + + if (gBattleStruct->savedAttackerCount > 0) { // #if TESTING @@ -16845,8 +16845,8 @@ void BS_AllySwitchFailChance(void) void BS_SetPhotonGeyserCategory(void) { NATIVE_ARGS(); - if (!(gMovesInfo[gCurrentMove].effect == EFFECT_TERA_BLAST && !IsTerastallized(gBattlerAttacker)) - && !(gMovesInfo[gCurrentMove].effect == EFFECT_TERA_STARSTORM && gBattleMons[gBattlerAttacker].species != SPECIES_TERAPAGOS_STELLAR)) + if (!((gMovesInfo[gCurrentMove].effect == EFFECT_TERA_BLAST && !IsTerastallized(gBattlerAttacker)) + || (gMovesInfo[gCurrentMove].effect == EFFECT_TERA_STARSTORM && !(IsTerastallized(gBattlerAttacker) && gBattleMons[gBattlerAttacker].species == SPECIES_TERAPAGOS_STELLAR)))) gBattleStruct->swapDamageCategory = (GetCategoryBasedOnStats(gBattlerAttacker) == DAMAGE_CATEGORY_PHYSICAL); gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_terastal.c b/src/battle_terastal.c index afe7f3a5cefd..62ab3afacbe3 100644 --- a/src/battle_terastal.c +++ b/src/battle_terastal.c @@ -25,7 +25,7 @@ void PrepareBattlerForTera(u32 battler) gBattleStruct->tera.isTerastallized[side] |= gBitTable[index]; gBattleStruct->tera.alreadyTerastallized[battler] = TRUE; - // Remove Tera Orb charge. + // 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 @@ -91,8 +91,7 @@ bool32 CanTerastallize(u32 battler) // Returns a battler's Tera type. u32 GetBattlerTeraType(u32 battler) { - struct Pokemon *mon = &GetBattlerParty(battler)[gBattlerPartyIndexes[battler]]; - return GetMonData(mon, MON_DATA_TERA_TYPE); + return GetMonData(&GetBattlerParty(battler)[gBattlerPartyIndexes[battler]], MON_DATA_TERA_TYPE); } // Returns whether a battler is terastallized. @@ -128,7 +127,7 @@ uq4_12_t GetTeraMultiplier(u32 battler, u32 type) // Safety check. if (!IsTerastallized(battler)) return UQ_4_12(1.0); - + // Stellar-type checks. if (teraType == TYPE_STELLAR) { diff --git a/src/battle_tower.c b/src/battle_tower.c index 6811cc74086e..c5b4fcbef194 100644 --- a/src/battle_tower.c +++ b/src/battle_tower.c @@ -1568,7 +1568,7 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 u8 ball = (fmon->ball == 0xFF) ? Random() % POKEBALL_COUNT : fmon->ball; u16 move; u32 personality, ability, friendship, j; - + if (fmon->gender == TRAINER_MON_MALE) { personality = GeneratePersonalityForGender(MON_MALE, fmon->species); @@ -1577,10 +1577,10 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 { personality = GeneratePersonalityForGender(MON_FEMALE, fmon->species); } - + ModifyPersonalityForNature(&personality, fmon->nature); CreateMon(dst, fmon->species, level, fixedIV, TRUE, personality, otID, OT_ID_PRESET); - + friendship = MAX_FRIENDSHIP; // Give the chosen Pokémon its specified moves. for (j = 0; j < MAX_MON_MOVES; j++) @@ -1588,7 +1588,7 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 move = fmon->moves[j]; if (flags & FLAG_FRONTIER_MON_FACTORY && move == MOVE_RETURN) move = MOVE_FRUSTRATION; - + SetMonMoveSlot(dst, move, j); if (gMovesInfo[move].effect == EFFECT_FRUSTRATION) friendship = 0; // Frustration is more powerful the lower the pokemon's friendship is. @@ -1596,7 +1596,7 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 SetMonData(dst, MON_DATA_FRIENDSHIP, &friendship); SetMonData(dst, MON_DATA_HELD_ITEM, &fmon->heldItem); - + // try to set ability. Otherwise, random of non-hidden as per vanilla if (fmon->ability != ABILITY_NONE) { @@ -1611,7 +1611,7 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 ability = 0; SetMonData(dst, MON_DATA_ABILITY_NUM, &ability); } - + if (fmon->ev != NULL) { SetMonData(dst, MON_DATA_HP_EV, &(fmon->ev[0])); @@ -1621,10 +1621,10 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 SetMonData(dst, MON_DATA_SPDEF_EV, &(fmon->ev[4])); SetMonData(dst, MON_DATA_SPEED_EV, &(fmon->ev[5])); } - + if (fmon->iv) SetMonData(dst, MON_DATA_IVS, &(fmon->iv)); - + if (fmon->isShiny) { u32 data = TRUE; @@ -1640,8 +1640,13 @@ void CreateFacilityMon(const struct TrainerMon *fmon, u16 level, u8 fixedIV, u32 u32 data = fmon->gigantamaxFactor; SetMonData(dst, MON_DATA_GIGANTAMAX_FACTOR, &data); } - - + if (fmon->teraType) + { + u32 data = fmon->teraType; + SetMonData(dst, MON_DATA_TERA_TYPE, &data); + } + + SetMonData(dst, MON_DATA_POKEBALL, &ball); CalculateMonStats(dst); } @@ -1743,7 +1748,7 @@ static void FillTrainerParty(u16 trainerId, u8 firstMonId, u8 monCount) continue; chosenMonIndices[i] = monId; - + // Place the chosen Pokémon into the trainer's party. CreateFacilityMon(&gFacilityTrainerMons[monId], level, fixedIV, otID, 0, &gEnemyParty[i + firstMonId]); @@ -2032,7 +2037,7 @@ void DoSpecialTrainerBattle(void) BattleTransition_StartOnField(GetSpecialBattleTransition(B_TRANSITION_GROUP_SECRET_BASE)); break; case SPECIAL_BATTLE_EREADER: - #if FREE_BATTLE_TOWER_E_READER == FALSE + #if FREE_BATTLE_TOWER_E_READER == FALSE ZeroEnemyPartyMons(); for (i = 0; i < (int)ARRAY_COUNT(gSaveBlock2Ptr->frontier.ereaderTrainer.party); i++) CreateBattleTowerMon(&gEnemyParty[i], &gSaveBlock2Ptr->frontier.ereaderTrainer.party[i]); @@ -3095,7 +3100,7 @@ static void FillPartnerParty(u16 trainerId) for (i = 0; i < FRONTIER_MULTI_PARTY_SIZE; i++) { monId = gSaveBlock2Ptr->frontier.trainerIds[i + 18]; - CreateFacilityMon(&gFacilityTrainerMons[monId], level, ivs, otID, 0, &gPlayerParty[MULTI_PARTY_SIZE + i]); + CreateFacilityMon(&gFacilityTrainerMons[monId], level, ivs, otID, 0, &gPlayerParty[MULTI_PARTY_SIZE + i]); for (j = 0; j < PLAYER_NAME_LENGTH + 1; j++) trainerName[j] = gFacilityTrainers[trainerId].trainerName[j]; SetMonData(&gPlayerParty[MULTI_PARTY_SIZE + i], MON_DATA_OT_NAME, &trainerName); diff --git a/src/battle_util.c b/src/battle_util.c index 2e1cbd2d4296..6b5e489691ab 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10595,6 +10595,14 @@ bool32 IsBattlerUltraBursted(u32 battler) return (gSpeciesInfo[gBattleMons[battler].species].isUltraBurst); } +bool32 IsBattlerInTeraForm(u32 battler) +{ + // While Transform does copy stats and visuals, it shouldn't be counted as a true Tera Form. + if (gBattleMons[battler].status2 & STATUS2_TRANSFORMED) + return FALSE; + return (gSpeciesInfo[gBattleMons[battler].species].isTeraForm); +} + // Returns SPECIES_NONE if no form change is possible u16 GetBattleFormChangeTargetSpecies(u32 battler, u16 method) { @@ -10685,6 +10693,10 @@ u16 GetBattleFormChangeTargetSpecies(u32 battler, u16 method) if (gBattleMons[battler].status1 & formChanges[i].param1) targetSpecies = formChanges[i].targetSpecies; break; + case FORM_CHANGE_BATTLE_TERASTALLIZATION: + if (GetBattlerTeraType(battler) == formChanges[i].param1) + targetSpecies = formChanges[i].targetSpecies; + break; } } } @@ -10700,7 +10712,7 @@ bool32 CanBattlerFormChange(u32 battler, u16 method) && B_TRANSFORM_FORM_CHANGES >= GEN_5) return FALSE; // Mega Evolved and Ultra Bursted Pokémon should always revert to normal upon fainting or ending the battle. - if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE)) + if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE)) return TRUE; else if (IsBattlerPrimalReverted(battler) && (method == FORM_CHANGE_END_BATTLE)) return TRUE; @@ -10740,7 +10752,7 @@ bool32 TryBattleFormChange(u32 battler, u16 method) bool32 restoreSpecies = FALSE; // Mega Evolved and Ultra Bursted Pokémon should always revert to normal upon fainting or ending the battle, so no need to add it to the form change tables. - if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE)) + if ((IsBattlerMegaEvolved(battler) || IsBattlerUltraBursted(battler) || IsBattlerInTeraForm(battler)) && (method == FORM_CHANGE_FAINT || method == FORM_CHANGE_END_BATTLE)) restoreSpecies = TRUE; // Unlike Megas, Primal Reversion isn't canceled on fainting. diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index 0c5d9d420cd4..85ff50940ad5 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -1259,10 +1259,16 @@ static const struct FormChange sPalafinZeroFormChangeTable[] = #if P_FAMILY_OGERPON static const struct FormChange sOgerponFormChangeTable[] = { - {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_TEAL_MASK, ITEM_NONE}, - {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_WELLSPRING_MASK, ITEM_WELLSPRING_MASK}, - {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_HEARTHFLAME_MASK, ITEM_HEARTHFLAME_MASK}, - {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_CORNERSTONE_MASK, ITEM_CORNERSTONE_MASK}, + {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_TEAL_MASK, ITEM_NONE}, + {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_WELLSPRING_MASK, ITEM_WELLSPRING_MASK}, + {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_HEARTHFLAME_MASK, ITEM_HEARTHFLAME_MASK}, + {FORM_CHANGE_ITEM_HOLD, SPECIES_OGERPON_CORNERSTONE_MASK, ITEM_CORNERSTONE_MASK}, +#if P_TERA_FORMS + {FORM_CHANGE_BATTLE_TERASTALLIZATION, SPECIES_OGERPON_TEAL_MASK_TERA, TYPE_GRASS}, + {FORM_CHANGE_BATTLE_TERASTALLIZATION, SPECIES_OGERPON_WELLSPRING_MASK_TERA, TYPE_WATER}, + {FORM_CHANGE_BATTLE_TERASTALLIZATION, SPECIES_OGERPON_HEARTHFLAME_MASK_TERA, TYPE_FIRE}, + {FORM_CHANGE_BATTLE_TERASTALLIZATION, SPECIES_OGERPON_CORNERSTONE_MASK_TERA, TYPE_ROCK}, +#endif {FORM_CHANGE_TERMINATOR}, }; #endif //P_FAMILY_OGERPON @@ -1271,9 +1277,9 @@ static const struct FormChange sOgerponFormChangeTable[] = { static const struct FormChange sTerapagosFormChangeTable[] = { {FORM_CHANGE_BATTLE_SWITCH, SPECIES_TERAPAGOS_TERASTAL, ABILITY_TERA_SHIFT}, #if P_TERA_FORMS - //{FORM_CHANGE_TERASTALLIZATION, SPECIES_TERAPAGOS_STELLAR}, + {FORM_CHANGE_BATTLE_TERASTALLIZATION, SPECIES_TERAPAGOS_STELLAR, TYPE_STELLAR}, #endif - {FORM_CHANGE_END_BATTLE, SPECIES_TERAPAGOS_NORMAL}, + {FORM_CHANGE_END_BATTLE, SPECIES_TERAPAGOS_NORMAL}, {FORM_CHANGE_TERMINATOR}, }; #endif //P_FAMILY_TERAPAGOS diff --git a/src/data/pokemon/form_species_tables.h b/src/data/pokemon/form_species_tables.h index af35503fe336..f099f8665fd0 100644 --- a/src/data/pokemon/form_species_tables.h +++ b/src/data/pokemon/form_species_tables.h @@ -2161,6 +2161,12 @@ static const u16 sOgerponFormSpeciesIdTable[] = { SPECIES_OGERPON_WELLSPRING_MASK, SPECIES_OGERPON_HEARTHFLAME_MASK, SPECIES_OGERPON_CORNERSTONE_MASK, +#if P_TERA_FORMS + SPECIES_OGERPON_TEAL_MASK_TERA, + SPECIES_OGERPON_WELLSPRING_MASK_TERA, + SPECIES_OGERPON_HEARTHFLAME_MASK_TERA, + SPECIES_OGERPON_CORNERSTONE_MASK_TERA, +#endif FORM_SPECIES_END, }; #endif //P_FAMILY_OGERPON diff --git a/src/data/pokemon/species_info/gen_9_families.h b/src/data/pokemon/species_info/gen_9_families.h index 901134e3ad75..971971f8da49 100644 --- a/src/data/pokemon/species_info/gen_9_families.h +++ b/src/data/pokemon/species_info/gen_9_families.h @@ -6301,7 +6301,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = #endif //P_FAMILY_FEZANDIPITI #if P_FAMILY_OGERPON -#define OGERPON_SPECIES_INFO(Form, type, ability, color, iconpalette) \ +#define OGERPON_SPECIES_INFO(Form, type, ability, color, iconpalette, isTeraform) \ { \ .baseHP = 80, \ .baseAttack = 120, \ @@ -6310,6 +6310,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .baseSpAttack = 60, \ .baseSpDefense = 96, \ .types = MON_TYPES(TYPE_GRASS, type), \ + .forceTeraType = type, \ .catchRate = 5, \ .expYield = 275, \ .evYield_Attack = 3, \ @@ -6350,17 +6351,18 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .formSpeciesIdTable = sOgerponFormSpeciesIdTable, \ .formChangeTable = sOgerponFormChangeTable, \ .isLegendary = TRUE, \ + .isTeraForm = isTeraform, \ } - [SPECIES_OGERPON_TEAL_MASK] = OGERPON_SPECIES_INFO(TealMask, TYPE_GRASS, ABILITY_DEFIANT, BODY_COLOR_GREEN, 1), - [SPECIES_OGERPON_WELLSPRING_MASK] = OGERPON_SPECIES_INFO(WellspringMask, TYPE_WATER, ABILITY_WATER_ABSORB, BODY_COLOR_BLUE, 0), - [SPECIES_OGERPON_HEARTHFLAME_MASK] = OGERPON_SPECIES_INFO(HearthflameMask, TYPE_FIRE, ABILITY_MOLD_BREAKER, BODY_COLOR_RED, 0), - [SPECIES_OGERPON_CORNERSTONE_MASK] = OGERPON_SPECIES_INFO(CornerstoneMask, TYPE_ROCK, ABILITY_STURDY, BODY_COLOR_GRAY, 0), + [SPECIES_OGERPON_TEAL_MASK] = OGERPON_SPECIES_INFO(TealMask, TYPE_GRASS, ABILITY_DEFIANT, BODY_COLOR_GREEN, 1, FALSE), + [SPECIES_OGERPON_WELLSPRING_MASK] = OGERPON_SPECIES_INFO(WellspringMask, TYPE_WATER, ABILITY_WATER_ABSORB, BODY_COLOR_BLUE, 0, FALSE), + [SPECIES_OGERPON_HEARTHFLAME_MASK] = OGERPON_SPECIES_INFO(HearthflameMask, TYPE_FIRE, ABILITY_MOLD_BREAKER, BODY_COLOR_RED, 0, FALSE), + [SPECIES_OGERPON_CORNERSTONE_MASK] = OGERPON_SPECIES_INFO(CornerstoneMask, TYPE_ROCK, ABILITY_STURDY, BODY_COLOR_GRAY, 0, FALSE), #if P_TERA_FORMS - [SPECIES_OGERPON_TEAL_MASK_TERA] = OGERPON_SPECIES_INFO(TealMask, TYPE_GRASS, ABILITY_EMBODY_ASPECT_TEAL_MASK, BODY_COLOR_GREEN, 1), - [SPECIES_OGERPON_WELLSPRING_MASK_TERA] = OGERPON_SPECIES_INFO(WellspringMask, TYPE_WATER, ABILITY_EMBODY_ASPECT_WELLSPRING_MASK, BODY_COLOR_BLUE, 0), - [SPECIES_OGERPON_HEARTHFLAME_MASK_TERA] = OGERPON_SPECIES_INFO(HearthflameMask, TYPE_FIRE, ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK, BODY_COLOR_RED, 0), - [SPECIES_OGERPON_CORNERSTONE_MASK_TERA] = OGERPON_SPECIES_INFO(CornerstoneMask, TYPE_ROCK, ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK, BODY_COLOR_GRAY, 0), + [SPECIES_OGERPON_TEAL_MASK_TERA] = OGERPON_SPECIES_INFO(TealMask, TYPE_GRASS, ABILITY_EMBODY_ASPECT_TEAL_MASK, BODY_COLOR_GREEN, 1, TRUE), + [SPECIES_OGERPON_WELLSPRING_MASK_TERA] = OGERPON_SPECIES_INFO(WellspringMask, TYPE_WATER, ABILITY_EMBODY_ASPECT_WELLSPRING_MASK, BODY_COLOR_BLUE, 0, TRUE), + [SPECIES_OGERPON_HEARTHFLAME_MASK_TERA] = OGERPON_SPECIES_INFO(HearthflameMask, TYPE_FIRE, ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK, BODY_COLOR_RED, 0, TRUE), + [SPECIES_OGERPON_CORNERSTONE_MASK_TERA] = OGERPON_SPECIES_INFO(CornerstoneMask, TYPE_ROCK, ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK, BODY_COLOR_GRAY, 0, TRUE), #endif //P_TERA_FORMS #endif //P_FAMILY_OGERPON @@ -6594,6 +6596,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .baseSpAttack = 65, .baseSpDefense = 85, .types = MON_TYPES(TYPE_NORMAL), + .forceTeraType = TYPE_STELLAR, .catchRate = 255, .expYield = 90, .evYield_Defense = 1, @@ -6651,6 +6654,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .baseSpAttack = 105, .baseSpDefense = 110, .types = MON_TYPES(TYPE_NORMAL), + .forceTeraType = TYPE_STELLAR, .catchRate = 255, .expYield = 120, .evYield_Defense = 2, @@ -6709,6 +6713,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .baseSpAttack = 130, .baseSpDefense = 110, .types = MON_TYPES(TYPE_NORMAL), + .forceTeraType = TYPE_STELLAR, .catchRate = 255, .expYield = 140, .evYield_HP = 3, @@ -6749,6 +6754,7 @@ const struct SpeciesInfo gSpeciesInfoGen9[] = .iconPalIndex = 0, FOOTPRINT(TerapagosStellar) .isLegendary = TRUE, + .isTeraForm = TRUE, .isFrontierBanned = TRUE, .levelUpLearnset = sTerapagosLevelUpLearnset, .teachableLearnset = sTerapagosTeachableLearnset, diff --git a/src/pokemon.c b/src/pokemon.c index 6371b38719f3..a706c9578697 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -2773,8 +2773,11 @@ u32 GetBoxMonData3(struct BoxPokemon *boxMon, s32 field, u8 *data) retVal = substruct3->gigantamaxFactor; break; case MON_DATA_TERA_TYPE: - { - if (substruct0->teraType == TYPE_NONE) + if (gSpeciesInfo[substruct0->species].forceTeraType) + { + retVal = gSpeciesInfo[substruct0->species].forceTeraType; + } + else if (substruct0->teraType == TYPE_NONE) // Tera Type hasn't been modified so we can just use the personality { const u8 *types = gSpeciesInfo[substruct0->species].types; retVal = (boxMon->personality & 0x1) == 0 ? types[0] : types[1]; @@ -2784,7 +2787,6 @@ u32 GetBoxMonData3(struct BoxPokemon *boxMon, s32 field, u8 *data) retVal = substruct0->teraType; } break; - } case MON_DATA_EVOLUTION_TRACKER: evoTracker.asField.a = substruct1->evolutionTracker1; evoTracker.asField.b = substruct1->evolutionTracker2; diff --git a/test/battle/gimmick/terastal.c b/test/battle/gimmick/terastal.c index c40b6823e70c..5b53eb637392 100644 --- a/test/battle/gimmick/terastal.c +++ b/test/battle/gimmick/terastal.c @@ -686,7 +686,7 @@ SINGLE_BATTLE_TEST("(TERA) Protean cannot change the type of a Terastallized Pok PLAYER(SPECIES_GRENINJA) { Ability(ABILITY_PROTEAN); TeraType(TYPE_GRASS); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_BUBBLE, tera: TRUE); + TURN { MOVE(player, MOVE_BUBBLE, tera: TRUE); MOVE(opponent, MOVE_EMBER); } } SCENE { MESSAGE("Greninja used Bubble!"); @@ -793,3 +793,21 @@ SINGLE_BATTLE_TEST("(TERA) All type indicators function correctly") TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); } } } + +SINGLE_BATTLE_TEST("(TERA) Pokemon with Tera forms change upon Terastallizing") +{ + u32 species, targetSpecies; + PARAMETRIZE { species = SPECIES_OGERPON_TEAL_MASK; targetSpecies = SPECIES_OGERPON_TEAL_MASK_TERA; } + PARAMETRIZE { species = SPECIES_OGERPON_WELLSPRING_MASK; targetSpecies = SPECIES_OGERPON_WELLSPRING_MASK_TERA; } + PARAMETRIZE { species = SPECIES_OGERPON_HEARTHFLAME_MASK; targetSpecies = SPECIES_OGERPON_HEARTHFLAME_MASK_TERA; } + PARAMETRIZE { species = SPECIES_OGERPON_CORNERSTONE_MASK; targetSpecies = SPECIES_OGERPON_CORNERSTONE_MASK_TERA; } + PARAMETRIZE { species = SPECIES_TERAPAGOS_TERASTAL; targetSpecies = SPECIES_TERAPAGOS_STELLAR; } + GIVEN { + PLAYER(species); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, tera: TRUE); } + } THEN { + EXPECT_EQ(player->species, targetSpecies); + } +} diff --git a/test/battle/move_effect/tera_starstorm.c b/test/battle/move_effect/tera_starstorm.c new file mode 100644 index 000000000000..cad4f9a015d0 --- /dev/null +++ b/test/battle/move_effect/tera_starstorm.c @@ -0,0 +1,61 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_TERA_STARSTORM].effect == EFFECT_TERA_STARSTORM); +} + +SINGLE_BATTLE_TEST("Tera Starstorm changes from Normal-type to Stellar-type if used by Terapagos-Stellar") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TERA_STARSTORM].type == TYPE_NORMAL); + PLAYER(SPECIES_TERAPAGOS_STELLAR); + OPPONENT(SPECIES_MISDREAVUS); + } WHEN { + TURN { MOVE(player, MOVE_TERA_STARSTORM); } + } SCENE { + MESSAGE("Terapagos used Tera Starstorm!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_STARSTORM, player); + HP_BAR(opponent); + NOT { MESSAGE("It doesn't affect Foe Misdreavus…"); } + } +} + +DOUBLE_BATTLE_TEST("Tera Starstorm targets both opponents in a double battle if used by Terapagos-Stellar") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TERA_STARSTORM].target == MOVE_TARGET_SELECTED); + PLAYER(SPECIES_TERAPAGOS_STELLAR); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TERA_STARSTORM, target:opponentLeft); } + } SCENE { + MESSAGE("Terapagos used Tera Starstorm!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_STARSTORM, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + } +} + +SINGLE_BATTLE_TEST("Tera Starstorm becomes a physical move if the user is Terapagos-Stellar, is Terastallized, and has a higher Attack stat", s16 damage) +{ + bool32 tera; + PARAMETRIZE { tera = FALSE; } + PARAMETRIZE { tera = TRUE; } + GIVEN { + ASSUME(gMovesInfo[MOVE_TERA_STARSTORM].category == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_TERAPAGOS_STELLAR) { Attack(100); SpAttack(50); } + OPPONENT(SPECIES_WOBBUFFET) { Defense(200); SpDefense(200); } + } WHEN { + TURN { MOVE(player, MOVE_TERA_STARSTORM, tera: tera); } + } SCENE { + MESSAGE("Terapagos used Tera Starstorm!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TERA_STARSTORM, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(2.5), results[1].damage); + } +}