Skip to content

Commit

Permalink
Expanded Pokémon Follower transformation functionality (#5048)
Browse files Browse the repository at this point in the history
  • Loading branch information
AsparagusEduardo authored Jan 18, 2025
1 parent 243900c commit cd1482b
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 173 deletions.
6 changes: 4 additions & 2 deletions include/config/overworld.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@
#define OW_MON_WANDER_WALK TRUE // If true, OW pokemon with MOVEMENT_TYPE_WANDER will walk-in-place in between steps.
// Follower Pokémon
#define OW_FOLLOWERS_ENABLED FALSE // Enables follower Pokémon, HGSS style. Requires OW_POKEMON_OBJECT_EVENTS. Note that additional scripting may be required for them to be fully supported!
#define OW_FOLLOWERS_BOBBING TRUE // If true, follower pokemon will bob up and down during their idle & walking animations
#define OW_FOLLOWERS_POKEBALLS TRUE // Followers will emerge from the pokeball they are stored in, instead of a normal pokeball
#define OW_FOLLOWERS_BOBBING TRUE // If TRUE, follower Pokémon will bob up and down during their idle & walking animations
#define OW_FOLLOWERS_POKEBALLS TRUE // If TRUE, follower Pokémon will emerge from the Poké Ball they are stored in, instead of a normal Poké Ball
#define OW_FOLLOWERS_WEATHER_FORMS FALSE // If TRUE, Castform and Cherrim gain FORM_CHANGE_OVERWORLD_WEATHER, which will make them transform in the overworld based on the weather.
#define OW_FOLLOWERS_COPY_WILD_PKMN FALSE // If TRUE, follower Pokémon that know Transform or have Illusion/Imposter will copy wild Pokémon at random.

// Out-of-battle Ability effects
#define OW_SYNCHRONIZE_NATURE GEN_LATEST // In Gen8+, if a Pokémon with Synchronize leads the party, wild Pokémon will always have their same Nature as opposed to the 50% chance in previous games. Gift Pokémon excluded.
Expand Down
7 changes: 7 additions & 0 deletions include/constants/form_change_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,11 @@
// param2: ability to check, optional
#define FORM_CHANGE_BATTLE_BEFORE_MOVE_CATEGORY 25

// Form change that activates when overworld weather changes.
// param1: weather to check.
#define FORM_CHANGE_OVERWORLD_WEATHER 26

// Form change that activates when the Pokémon is deposited into the PC or Daycare.
#define FORM_CHANGE_DEPOSIT 27

#endif // GUARD_CONSTANTS_FORM_CHANGE_TYPES_H
1 change: 1 addition & 0 deletions include/event_object_movement.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ void ClearObjectEventMovement(struct ObjectEvent *objectEvent, struct Sprite *sp
void ObjectEventClearHeldMovement(struct ObjectEvent *);
void ObjectEventClearHeldMovementIfActive(struct ObjectEvent *);
struct Pokemon *GetFirstLiveMon(void);
u16 GetOverworldWeatherSpecies(u16 species);
void UpdateFollowingPokemon(void);
void RemoveFollowingPokemon(void);
struct ObjectEvent *GetFollowerObject(void);
Expand Down
198 changes: 95 additions & 103 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -10925,21 +10925,18 @@ bool32 DoesSpeciesUseHoldItemToChangeForm(u16 species, u16 heldItemId)
u32 i;
const struct FormChange *formChanges = GetSpeciesFormChanges(species);

if (formChanges != NULL)
for (i = 0; formChanges != NULL && formChanges[i].method != FORM_CHANGE_TERMINATOR; i++)
{
for (i = 0; formChanges[i].method != FORM_CHANGE_TERMINATOR; i++)
switch (formChanges[i].method)
{
switch (formChanges[i].method)
{
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
case FORM_CHANGE_BATTLE_ULTRA_BURST:
case FORM_CHANGE_ITEM_HOLD:
case FORM_CHANGE_BEGIN_BATTLE:
if (formChanges[i].param1 == heldItemId)
return TRUE;
break;
}
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
case FORM_CHANGE_BATTLE_ULTRA_BURST:
case FORM_CHANGE_ITEM_HOLD:
case FORM_CHANGE_BEGIN_BATTLE:
if (formChanges[i].param1 == heldItemId)
return TRUE;
break;
}
}
return FALSE;
Expand Down Expand Up @@ -11070,103 +11067,98 @@ u16 GetBattleFormChangeTargetSpecies(u32 battler, u16 method)
u32 targetSpecies = species;
const struct FormChange *formChanges = GetSpeciesFormChanges(species);
struct Pokemon *mon = &GetBattlerParty(battler)[gBattlerPartyIndexes[battler]];
u16 heldItem;
u16 heldItem = gBattleMons[battler].item;

if (formChanges != NULL)
for (i = 0; formChanges != NULL && formChanges[i].method != FORM_CHANGE_TERMINATOR; i++)
{
heldItem = gBattleMons[battler].item;

for (i = 0; formChanges[i].method != FORM_CHANGE_TERMINATOR; i++)
if (method == formChanges[i].method && species != formChanges[i].targetSpecies)
{
if (method == formChanges[i].method && species != formChanges[i].targetSpecies)
switch (method)
{
switch (method)
{
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
case FORM_CHANGE_BATTLE_ULTRA_BURST:
if (heldItem == formChanges[i].param1)
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE:
if (gBattleMons[battler].moves[0] == formChanges[i].param1
|| gBattleMons[battler].moves[1] == formChanges[i].param1
|| gBattleMons[battler].moves[2] == formChanges[i].param1
|| gBattleMons[battler].moves[3] == formChanges[i].param1)
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_SWITCH:
if (formChanges[i].param1 == GetBattlerAbility(battler) || formChanges[i].param1 == ABILITY_NONE)
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_HP_PERCENT:
if (formChanges[i].param1 == GetBattlerAbility(battler))
{
// We multiply by 100 to make sure that integer division doesn't mess with the health check.
u32 hpCheck = gBattleMons[battler].hp * 100 * 100 / gBattleMons[battler].maxHP;
switch(formChanges[i].param2)
{
case HP_HIGHER_THAN:
if (hpCheck > formChanges[i].param3 * 100)
targetSpecies = formChanges[i].targetSpecies;
break;
case HP_LOWER_EQ_THAN:
if (hpCheck <= formChanges[i].param3 * 100)
targetSpecies = formChanges[i].targetSpecies;
break;
}
}
break;
case FORM_CHANGE_BATTLE_GIGANTAMAX:
if (GetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR))
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_WEATHER:
// Check if there is a required ability and if the battler's ability does not match it
// or is suppressed. If so, revert to the no weather form.
if (formChanges[i].param2
&& GetBattlerAbility(battler) != formChanges[i].param2
&& formChanges[i].param1 == B_WEATHER_NONE)
{
targetSpecies = formChanges[i].targetSpecies;
}
// We need to revert the weather form if the field is under Air Lock, too.
else if (!WEATHER_HAS_EFFECT && formChanges[i].param1 == B_WEATHER_NONE)
{
targetSpecies = formChanges[i].targetSpecies;
}
// Otherwise, just check for a match between the weather and the form change table.
// Added a check for whether the weather is in effect to prevent end-of-turn soft locks with Cloud Nine / Air Lock
else if (((gBattleWeather & formChanges[i].param1) && WEATHER_HAS_EFFECT)
|| (gBattleWeather == B_WEATHER_NONE && formChanges[i].param1 == B_WEATHER_NONE))
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM:
case FORM_CHANGE_BATTLE_PRIMAL_REVERSION:
case FORM_CHANGE_BATTLE_ULTRA_BURST:
if (heldItem == formChanges[i].param1)
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_MEGA_EVOLUTION_MOVE:
if (gBattleMons[battler].moves[0] == formChanges[i].param1
|| gBattleMons[battler].moves[1] == formChanges[i].param1
|| gBattleMons[battler].moves[2] == formChanges[i].param1
|| gBattleMons[battler].moves[3] == formChanges[i].param1)
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_SWITCH:
if (formChanges[i].param1 == GetBattlerAbility(battler) || formChanges[i].param1 == ABILITY_NONE)
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_HP_PERCENT:
if (formChanges[i].param1 == GetBattlerAbility(battler))
{
// We multiply by 100 to make sure that integer division doesn't mess with the health check.
u32 hpCheck = gBattleMons[battler].hp * 100 * 100 / gBattleMons[battler].maxHP;
switch(formChanges[i].param2)
{
targetSpecies = formChanges[i].targetSpecies;
case HP_HIGHER_THAN:
if (hpCheck > formChanges[i].param3 * 100)
targetSpecies = formChanges[i].targetSpecies;
break;
case HP_LOWER_EQ_THAN:
if (hpCheck <= formChanges[i].param3 * 100)
targetSpecies = formChanges[i].targetSpecies;
break;
}
break;
case FORM_CHANGE_BATTLE_TURN_END:
case FORM_CHANGE_HIT_BY_MOVE:
if (formChanges[i].param1 == GetBattlerAbility(battler))
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_STATUS:
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;
case FORM_CHANGE_BATTLE_BEFORE_MOVE:
if (formChanges[i].param1 == gCurrentMove
&& (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler)))
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_BEFORE_MOVE_CATEGORY:
if (formChanges[i].param1 == GetBattleMoveCategory(gCurrentMove)
&& (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler)))
targetSpecies = formChanges[i].targetSpecies;
break;
}
break;
case FORM_CHANGE_BATTLE_GIGANTAMAX:
if (GetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR))
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_WEATHER:
// Check if there is a required ability and if the battler's ability does not match it
// or is suppressed. If so, revert to the no weather form.
if (formChanges[i].param2
&& GetBattlerAbility(battler) != formChanges[i].param2
&& formChanges[i].param1 == B_WEATHER_NONE)
{
targetSpecies = formChanges[i].targetSpecies;
}
// We need to revert the weather form if the field is under Air Lock, too.
else if (!WEATHER_HAS_EFFECT && formChanges[i].param1 == B_WEATHER_NONE)
{
targetSpecies = formChanges[i].targetSpecies;
}
// Otherwise, just check for a match between the weather and the form change table.
// Added a check for whether the weather is in effect to prevent end-of-turn soft locks with Cloud Nine / Air Lock
else if (((gBattleWeather & formChanges[i].param1) && WEATHER_HAS_EFFECT)
|| (gBattleWeather == B_WEATHER_NONE && formChanges[i].param1 == B_WEATHER_NONE))
{
targetSpecies = formChanges[i].targetSpecies;
}
break;
case FORM_CHANGE_BATTLE_TURN_END:
case FORM_CHANGE_HIT_BY_MOVE:
if (formChanges[i].param1 == GetBattlerAbility(battler))
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_STATUS:
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;
case FORM_CHANGE_BATTLE_BEFORE_MOVE:
if (formChanges[i].param1 == gCurrentMove
&& (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler)))
targetSpecies = formChanges[i].targetSpecies;
break;
case FORM_CHANGE_BATTLE_BEFORE_MOVE_CATEGORY:
if (formChanges[i].param1 == GetBattleMoveCategory(gCurrentMove)
&& (formChanges[i].param2 == ABILITY_NONE || formChanges[i].param2 == GetBattlerAbility(battler)))
targetSpecies = formChanges[i].targetSpecies;
break;
}
}
}
Expand Down
Loading

0 comments on commit cd1482b

Please sign in to comment.