pokeemmo/src/battle_dynamax.c

500 lines
17 KiB
C

#include "global.h"
#include "battle.h"
#include "battle_anim.h"
#include "battle_controllers.h"
#include "battle_interface.h"
#include "battle_scripts.h"
#include "battle_script_commands.h"
#include "battle_gimmick.h"
#include "data.h"
#include "event_data.h"
#include "graphics.h"
#include "item.h"
#include "pokemon.h"
#include "random.h"
#include "sprite.h"
#include "string_util.h"
#include "util.h"
#include "constants/abilities.h"
#include "constants/battle_move_effects.h"
#include "constants/battle_string_ids.h"
#include "constants/flags.h"
#include "constants/items.h"
#include "constants/moves.h"
static u32 GetMaxPowerTier(u32 move);
struct GMaxMove
{
u16 species;
enum Type moveType;
u16 gmaxMove;
};
static const struct GMaxMove sGMaxMoveTable[] =
{
{SPECIES_VENUSAUR_GMAX, TYPE_GRASS, MOVE_G_MAX_VINE_LASH},
{SPECIES_BLASTOISE_GMAX, TYPE_WATER, MOVE_G_MAX_CANNONADE},
{SPECIES_CHARIZARD_GMAX, TYPE_FIRE, MOVE_G_MAX_WILDFIRE},
{SPECIES_BUTTERFREE_GMAX, TYPE_BUG, MOVE_G_MAX_BEFUDDLE},
{SPECIES_PIKACHU_GMAX, TYPE_ELECTRIC, MOVE_G_MAX_VOLT_CRASH},
{SPECIES_MEOWTH_GMAX, TYPE_NORMAL, MOVE_G_MAX_GOLD_RUSH},
{SPECIES_MACHAMP_GMAX, TYPE_FIGHTING, MOVE_G_MAX_CHI_STRIKE},
{SPECIES_GENGAR_GMAX, TYPE_GHOST, MOVE_G_MAX_TERROR},
{SPECIES_KINGLER_GMAX, TYPE_WATER, MOVE_G_MAX_FOAM_BURST},
{SPECIES_LAPRAS_GMAX, TYPE_ICE, MOVE_G_MAX_RESONANCE},
{SPECIES_EEVEE_GMAX, TYPE_NORMAL, MOVE_G_MAX_CUDDLE},
{SPECIES_SNORLAX_GMAX, TYPE_NORMAL, MOVE_G_MAX_REPLENISH},
{SPECIES_GARBODOR_GMAX, TYPE_POISON, MOVE_G_MAX_MALODOR},
{SPECIES_MELMETAL_GMAX, TYPE_STEEL, MOVE_G_MAX_MELTDOWN},
{SPECIES_RILLABOOM_GMAX, TYPE_GRASS, MOVE_G_MAX_DRUM_SOLO},
{SPECIES_CINDERACE_GMAX, TYPE_FIRE, MOVE_G_MAX_FIREBALL},
{SPECIES_INTELEON_GMAX, TYPE_WATER, MOVE_G_MAX_HYDROSNIPE},
{SPECIES_CORVIKNIGHT_GMAX, TYPE_FLYING, MOVE_G_MAX_WIND_RAGE},
{SPECIES_ORBEETLE_GMAX, TYPE_PSYCHIC, MOVE_G_MAX_GRAVITAS},
{SPECIES_DREDNAW_GMAX, TYPE_WATER, MOVE_G_MAX_STONESURGE},
{SPECIES_COALOSSAL_GMAX, TYPE_ROCK, MOVE_G_MAX_VOLCALITH},
{SPECIES_FLAPPLE_GMAX, TYPE_GRASS, MOVE_G_MAX_TARTNESS},
{SPECIES_APPLETUN_GMAX, TYPE_GRASS, MOVE_G_MAX_SWEETNESS},
{SPECIES_SANDACONDA_GMAX, TYPE_GROUND, MOVE_G_MAX_SANDBLAST},
{SPECIES_TOXTRICITY_AMPED_GMAX, TYPE_ELECTRIC, MOVE_G_MAX_STUN_SHOCK},
{SPECIES_TOXTRICITY_LOW_KEY_GMAX, TYPE_ELECTRIC, MOVE_G_MAX_STUN_SHOCK},
{SPECIES_CENTISKORCH_GMAX, TYPE_FIRE, MOVE_G_MAX_CENTIFERNO},
{SPECIES_HATTERENE_GMAX, TYPE_FAIRY, MOVE_G_MAX_SMITE},
{SPECIES_GRIMMSNARL_GMAX, TYPE_DARK, MOVE_G_MAX_SNOOZE},
{SPECIES_ALCREMIE_GMAX, TYPE_FAIRY, MOVE_G_MAX_FINALE},
{SPECIES_COPPERAJAH_GMAX, TYPE_STEEL, MOVE_G_MAX_STEELSURGE},
{SPECIES_DURALUDON_GMAX, TYPE_DRAGON, MOVE_G_MAX_DEPLETION},
{SPECIES_URSHIFU_SINGLE_STRIKE_GMAX, TYPE_DARK, MOVE_G_MAX_ONE_BLOW},
{SPECIES_URSHIFU_RAPID_STRIKE_GMAX, TYPE_WATER, MOVE_G_MAX_RAPID_FLOW},
};
// Returns whether a battler can Dynamax.
bool32 CanDynamax(u32 battler)
{
u16 species = GetBattlerVisualSpecies(battler);
enum HoldEffect holdEffect = GetBattlerHoldEffectIgnoreNegation(battler);
// Prevents Zigzagoon from dynamaxing in vanilla.
if (gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE && !IsOnPlayerSide(battler))
return FALSE;
// Check if Player has a Dynamax Band.
if (!TESTING && (GetBattlerPosition(battler) == B_POSITION_PLAYER_LEFT
|| (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battler) == B_POSITION_PLAYER_RIGHT)))
{
if (!CheckBagHasItem(ITEM_DYNAMAX_BAND, 1))
return FALSE;
if (B_FLAG_DYNAMAX_BATTLE == 0 || (B_FLAG_DYNAMAX_BATTLE != 0 && !FlagGet(B_FLAG_DYNAMAX_BATTLE)))
return FALSE;
}
// Check if species isn't allowed to Dynamax.
if (GET_BASE_SPECIES_ID(species) == SPECIES_ZACIAN
|| GET_BASE_SPECIES_ID(species) == SPECIES_ZAMAZENTA
|| GET_BASE_SPECIES_ID(species) == SPECIES_ETERNATUS)
return FALSE;
// Check if Trainer has already Dynamaxed.
if (HasTrainerUsedGimmick(battler, GIMMICK_DYNAMAX))
return FALSE;
// Check if AI battler is intended to Dynamaxed.
if (!ShouldTrainerBattlerUseGimmick(battler, GIMMICK_DYNAMAX))
return FALSE;
// Check if battler has another gimmick active.
if (GetActiveGimmick(battler) != GIMMICK_NONE)
return FALSE;
// Check if battler is holding a Z-Crystal or Mega Stone.
if (!TESTING && (holdEffect == HOLD_EFFECT_Z_CRYSTAL || holdEffect == HOLD_EFFECT_MEGA_STONE)) // tests make this check already
return FALSE;
// TODO: Cannot Dynamax in a Max Raid if you don't have Dynamax Energy.
// if (gBattleTypeFlags & BATTLE_TYPE_RAID && gBattleStruct->raid.dynamaxEnergy != battler)
// return FALSE;
// No checks failed, all set!
return TRUE;
}
// Returns whether a battler is transformed into a Gigantamax form.
bool32 IsGigantamaxed(u32 battler)
{
struct Pokemon *mon = GetBattlerMon(battler);
if ((gSpeciesInfo[gBattleMons[battler].species].isGigantamax) && GetMonData(mon, MON_DATA_GIGANTAMAX_FACTOR))
return TRUE;
return FALSE;
}
// Applies the HP Multiplier for Dynamaxed Pokemon and Raid Bosses.
void ApplyDynamaxHPMultiplier(struct Pokemon* mon)
{
if (GetMonData(mon, MON_DATA_SPECIES) == SPECIES_SHEDINJA)
return;
else
{
uq4_12_t multiplier = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), FALSE);
u32 hp = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_HP) * multiplier) + UQ_4_12_ROUND);
u32 maxHP = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_MAX_HP) * multiplier) + UQ_4_12_ROUND);
SetMonData(mon, MON_DATA_HP, &hp);
SetMonData(mon, MON_DATA_MAX_HP, &maxHP);
}
}
// Returns the non-Dynamax HP of a Pokemon.
u16 GetNonDynamaxHP(u32 battler)
{
if (GetActiveGimmick(battler) != GIMMICK_DYNAMAX || gBattleMons[battler].species == SPECIES_SHEDINJA)
return gBattleMons[battler].hp;
else
{
struct Pokemon *mon = GetBattlerMon(battler);
uq4_12_t mult = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), TRUE);
u32 hp = UQ_4_12_TO_INT((gBattleMons[battler].hp * mult) + UQ_4_12_ROUND);
return hp;
}
}
// Returns the non-Dynamax Max HP of a Pokemon.
u16 GetNonDynamaxMaxHP(u32 battler)
{
if (GetActiveGimmick(battler) != GIMMICK_DYNAMAX || gBattleMons[battler].species == SPECIES_SHEDINJA)
return gBattleMons[battler].maxHP;
else
{
struct Pokemon *mon = GetBattlerMon(battler);
uq4_12_t mult = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), TRUE);
u32 maxHP = UQ_4_12_TO_INT((gBattleMons[battler].maxHP * mult) + UQ_4_12_ROUND);
return maxHP;
}
}
// Sets flags used for Dynamaxing and checks Gigantamax forms.
void ActivateDynamax(u32 battler)
{
// Set appropriate use flags.
SetActiveGimmick(battler, GIMMICK_DYNAMAX);
SetGimmickAsActivated(battler, GIMMICK_DYNAMAX);
gBattleStruct->dynamax.dynamaxTurns[battler] = DYNAMAX_TURNS_COUNT;
// Substitute is removed upon Dynamaxing.
gBattleMons[battler].volatiles.substitute = FALSE;
ClearBehindSubstituteBit(battler);
// Choiced Moves are reset upon Dynamaxing.
gBattleStruct->choicedMove[battler] = MOVE_NONE;
// Try Gigantamax form change.
if (!gBattleMons[battler].volatiles.transformed) // Ditto cannot Gigantamax.
TryBattleFormChange(battler, FORM_CHANGE_BATTLE_GIGANTAMAX);
BattleScriptExecute(BattleScript_DynamaxBegins);
}
// Unsets the flags used for Dynamaxing and reverts max HP if needed.
void UndoDynamax(u32 battler)
{
// Revert HP if battler is still Dynamaxed.
if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX)
{
struct Pokemon *mon = GetBattlerMon(battler);
uq4_12_t mult = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), TRUE);
gBattleMons[battler].hp = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_HP) * mult + 1) + UQ_4_12_ROUND); // round up
SetMonData(mon, MON_DATA_HP, &gBattleMons[battler].hp);
CalculateMonStats(mon);
}
// Makes sure there are no Dynamax flags set, including on switch / faint.
SetActiveGimmick(battler, GIMMICK_NONE);
// Undo form change if needed.
if (IsGigantamaxed(battler))
TryBattleFormChange(battler, FORM_CHANGE_END_BATTLE);
}
// Certain moves are blocked by Max Guard that normally ignore protection.
bool32 IsMoveBlockedByMaxGuard(u32 move)
{
switch (move)
{
case MOVE_BLOCK:
case MOVE_FLOWER_SHIELD:
case MOVE_GEAR_UP:
case MOVE_MAGNETIC_FLUX:
case MOVE_PHANTOM_FORCE:
case MOVE_PSYCH_UP:
case MOVE_SHADOW_FORCE:
case MOVE_TEATIME:
case MOVE_TRANSFORM:
return TRUE;
}
return FALSE;
}
static u16 GetTypeBasedMaxMove(u32 battler, enum Type type)
{
// Gigantamax check
u32 i;
u32 species = gBattleMons[battler].species;
u32 targetSpecies = species;
if (!gSpeciesInfo[species].isGigantamax)
targetSpecies = GetBattleFormChangeTargetSpecies(battler, FORM_CHANGE_BATTLE_GIGANTAMAX);
if (targetSpecies != species)
species = targetSpecies;
if (gSpeciesInfo[species].isGigantamax)
{
for (i = 0; i < ARRAY_COUNT(sGMaxMoveTable); i++)
{
if (sGMaxMoveTable[i].species == species && sGMaxMoveTable[i].moveType == type)
return sGMaxMoveTable[i].gmaxMove;
}
}
// Regular Max Move
if (gTypesInfo[type].maxMove == MOVE_NONE) // failsafe
return gTypesInfo[0].maxMove;
return gTypesInfo[type].maxMove;
}
// Returns the appropriate Max Move or G-Max Move for a battler to use.
u16 GetMaxMove(u32 battler, u32 baseMove)
{
enum Type moveType;
SetTypeBeforeUsingMove(baseMove, battler);
moveType = GetBattleMoveType(baseMove);
if (baseMove == MOVE_NONE) // for move display
{
return MOVE_NONE;
}
else if (baseMove == MOVE_STRUGGLE)
{
return MOVE_STRUGGLE;
}
else if (GetMoveCategory(baseMove) == DAMAGE_CATEGORY_STATUS)
{
return MOVE_MAX_GUARD;
}
else
{
return GetTypeBasedMaxMove(battler, moveType);
}
}
// First value is for Fighting, Poison and Multi-Attack. The second is for everything else.
enum
{
MAX_POWER_TIER_1, // 70 or 90 damage
MAX_POWER_TIER_2, // 75 or 100 damage
MAX_POWER_TIER_3, // 80 or 110 damage
MAX_POWER_TIER_4, // 85 or 120 damage
MAX_POWER_TIER_5, // 90 or 130 damage
MAX_POWER_TIER_6, // 95 or 140 damage
MAX_POWER_TIER_7, // 100 or 130 damage
MAX_POWER_TIER_8, // 100 or 150 damage
};
// Gets the base power of a Max Move.
u32 GetMaxMovePower(u32 move)
{
u32 tier;
// G-Max Drum Solo, G-Max Hydrosnipe, and G-Max Fireball always have 160 base power.
if (MoveHasAdditionalEffect(move, MOVE_EFFECT_FIXED_POWER))
return 160;
// Exceptions to all other rules below:
switch (move)
{
case MOVE_TRIPLE_KICK: return 80;
case MOVE_GEAR_GRIND: return 100;
case MOVE_DUAL_WINGBEAT: return 100;
case MOVE_TRIPLE_AXEL: return 140;
}
tier = GetMaxPowerTier(move);
enum Type moveType = GetMoveType(move);
if (moveType == TYPE_FIGHTING
|| moveType == TYPE_POISON
|| move == MOVE_MULTI_ATTACK)
{
switch (tier)
{
default:
case MAX_POWER_TIER_1: return 70;
case MAX_POWER_TIER_2: return 75;
case MAX_POWER_TIER_3: return 80;
case MAX_POWER_TIER_4: return 85;
case MAX_POWER_TIER_5: return 90;
case MAX_POWER_TIER_6: return 95;
case MAX_POWER_TIER_7: return 100;
case MAX_POWER_TIER_8: return 100;
}
}
else
{
switch (tier)
{
default:
case MAX_POWER_TIER_1: return 90;
case MAX_POWER_TIER_2: return 100;
case MAX_POWER_TIER_3: return 110;
case MAX_POWER_TIER_4: return 120;
case MAX_POWER_TIER_5: return 130;
case MAX_POWER_TIER_6: return 140;
case MAX_POWER_TIER_7: return 130;
case MAX_POWER_TIER_8: return 150;
}
}
}
static u32 GetMaxPowerTier(u32 move)
{
u32 strikeCount = GetMoveStrikeCount(move);
if (strikeCount >= 2 && strikeCount <= 5)
{
switch(GetMovePower(move))
{
case 0 ... 25: return MAX_POWER_TIER_2;
case 26 ... 30: return MAX_POWER_TIER_3;
case 31 ... 35: return MAX_POWER_TIER_4;
case 36 ... 50: return MAX_POWER_TIER_5;
default:
case 51 ... 60: return MAX_POWER_TIER_6;
}
}
switch (GetMoveEffect(move))
{
case EFFECT_BIDE:
case EFFECT_FIXED_PERCENT_DAMAGE:
case EFFECT_LEVEL_DAMAGE:
case EFFECT_PSYWAVE:
case EFFECT_COUNTER:
case EFFECT_PRESENT:
case EFFECT_BEAT_UP:
case EFFECT_WEATHER_BALL:
case EFFECT_FLING:
case EFFECT_ELECTRO_BALL:
case EFFECT_METAL_BURST:
case EFFECT_TERRAIN_PULSE:
case EFFECT_PUNISHMENT:
case EFFECT_TRUMP_CARD:
case EFFECT_FIXED_HP_DAMAGE:
case EFFECT_SPIT_UP:
case EFFECT_NATURAL_GIFT:
case EFFECT_MIRROR_COAT:
case EFFECT_FINAL_GAMBIT:
return MAX_POWER_TIER_2;
case EFFECT_OHKO:
case EFFECT_SHEER_COLD:
case EFFECT_RETURN:
case EFFECT_FRUSTRATION:
case EFFECT_HEAT_CRASH:
case EFFECT_STORED_POWER:
case EFFECT_GYRO_BALL:
return MAX_POWER_TIER_5;
case EFFECT_MAGNITUDE:
case EFFECT_POWER_BASED_ON_TARGET_HP:
return MAX_POWER_TIER_6;
case EFFECT_FLAIL:
case EFFECT_LOW_KICK:
return MAX_POWER_TIER_7;
case EFFECT_MULTI_HIT:
switch(GetMovePower(move))
{
case 0 ... 15: return MAX_POWER_TIER_1;
case 16 ... 18: return MAX_POWER_TIER_2;
case 19 ... 20: return MAX_POWER_TIER_4;
default:
case 21 ... 25: return MAX_POWER_TIER_5;
}
default:
break;
}
switch (GetMovePower(move))
{
case 0 ... 40: return MAX_POWER_TIER_1;
case 45 ... 50: return MAX_POWER_TIER_2;
case 55 ... 60: return MAX_POWER_TIER_3;
case 65 ... 70: return MAX_POWER_TIER_4;
case 75 ... 100: return MAX_POWER_TIER_5;
case 110 ... 140: return MAX_POWER_TIER_6;
default:
case 150 ... 250: return MAX_POWER_TIER_8;
}
}
// Returns whether a move is a Max Move or not.
bool32 IsMaxMove(u32 move)
{
return move >= FIRST_MAX_MOVE && move <= LAST_MAX_MOVE;
}
// Assigns the multistring to use for the "Damage Non- Types" G-Max effect.
void ChooseDamageNonTypesString(enum Type type)
{
switch (type)
{
case TYPE_GRASS:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TRAPPED_WITH_VINES;
break;
case TYPE_WATER:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_CAUGHT_IN_VORTEX;
break;
case TYPE_FIRE:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SURROUNDED_BY_FIRE;
break;
case TYPE_ROCK:
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SURROUNDED_BY_ROCKS;
break;
default:
break;
}
}
// Updates Dynamax HP multipliers and healthboxes.
void BS_UpdateDynamax(void)
{
NATIVE_ARGS();
u32 battler = gBattleScripting.battler;
struct Pokemon *mon = GetBattlerMon(battler);
if (!IsGigantamaxed(battler)) // RecalcBattlerStats will get called on form change.
RecalcBattlerStats(battler, mon, GetActiveGimmick(battler) == GIMMICK_DYNAMAX);
UpdateHealthboxAttribute(gHealthboxSpriteIds[battler], mon, HEALTHBOX_ALL);
gBattlescriptCurrInstr = cmd->nextInstr;
}
// Goes to the jump instruction if the target is Dynamaxed.
void BS_JumpIfDynamaxed(void)
{
NATIVE_ARGS(const u8 *jumpInstr);
if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
gBattlescriptCurrInstr = cmd->jumpInstr;
else
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_UndoDynamax(void)
{
NATIVE_ARGS(u8 battler);
u32 battler = GetBattlerForBattleScript(cmd->battler);
if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX)
{
UndoDynamax(battler);
gBattleScripting.battler = battler;
BattleScriptCall(BattleScript_DynamaxEnds_Ret);
return;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}