Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamax #2417

Merged
merged 104 commits into from
Oct 19, 2023
Merged

Dynamax #2417

merged 104 commits into from
Oct 19, 2023

Conversation

AgustinGDLV
Copy link
Collaborator

@AgustinGDLV AgustinGDLV commented Oct 27, 2022

Description

This PR adds Dynamax to the expansion! The player will be able to Dynamax a Pokémon during Raid Battles (not yet implemented) or any battle while B_FLAG_DYNAMAX_BATTLE is active and the player has a Dynamax Band in their bag. This can be used once per battle, lasts 3 turns, transforms all moves into Max Moves, and increases HP by 1.5x while active. The opponent is able to do so as well, and will always Dynamax its final remaining Pokémon.

What is done?

  • Battle UI
    • There is a toggleable "use" sprite for choosing to Dynamax.
    • When Dynamax is selected, Max Moves are displayed instead of base moves, including G-Max Moves if applicable.
    • There is an indicator sprite in a Dynamaxed Pokémon's healthbox.
  • Dynamax Features
    • A battler has its HP and max HP multiplied by 1.5x when Dynamaxing.
    • Dynamax expires after three turns, after which a battler has its HP and max HP divided by the above multiplier, rounded up.
    • Dynamaxing is only possible in battle when a configurable flag is set; the player requires a Dynamax Band.
    • Pokémon who can Mega Evolve or use a Z-Move cannot Dynamax.
    • Every interaction listed in this research thread and this research video is implemented unless stated otherwise.
    • The majority of the interactions above have tests written.
    • TODO: Shake the Pokémon when it first Dynamaxes for cool effect!
  • Max Moves
    • Every single Max Move and G-Max Move effect is implemented.
    • Every single Max Move and G-Max Move has a test, including for some, but not all, interactions in the above research links.
    • Almost every single Max Move and G-Max Move have a placeholder animation.
    • Max Moves change type as you'd expect (i.e Weather Ball in rain becomes Max Geyser).
    • Max Moves bypass protection, except for Max Guard, except for G-Max One Blow and Rapid Flow.
    • Max Moves cannot be used against allies.
    • Max Guard has a couple weird behaviors, including preventing Block but not Mean Look.
    • -ate abilities should not boost the power of a Max Move.
  • Gigantamax Forms
    • All Gigantamax Forms have proper species data and sprites.
    • All Gigantamax Forms have proper cries.
    • Pokémon turn into their Gigantamax Forms upon Dynamaxing.
    • Pokémon revert to their base form upon switch, faint, battle end, or when Dynamax expires.
    • Gigantamax form changes do not undo the effects of Soak, Magic Powder, Speed Swap, etc., as they are not "proper form changes" apparently.
  • AI
    • The opponent will Dynamax if they are on their last Pokémon and are able to Dynamax.
  • Miscellaneous
    • Leveling up does not undo the Dynamax HP multiplier or display a bugged out level-up box!
    • Current HP increases with max HP when leveling up.
    • Most effects that heal or damage based off a percentage of health do so using non-Dynamax HP.

Give me more!

Not entirely exhaustive list of specific cases:
  • Dynamaxed Pokémon (or Pokémon that have selected to Dynamax) can choose moves otherwise disabled by Disable, Encore, Choice Items, Gorilla Tactics, and Imprison, but not Taunt or Assault Vest.
  • Dynamaxed Pokémon will still use Struggle if they are unable to select any moves.
  • Dynamaxed Pokémon cannot be forced to switch out with phazing moves or Red Card. These moves will still deal damage if applicable and Red Card is still consumed.
  • Dynamaxed Pokémon can be forced to switch out via Eject Button or Emergency Exit.
  • Transformed Pokémon can Dynamax, but do not change into Gigantamax forms.
  • Shedinja has 1 HP even if Dynamaxed.
  • Dynamaxing causes a Pokémon's substitute to fade.
  • Zacian, Zamazenta, and Eterantus cannot Dynamax, and neither can a Pokémon transformed into them.
  • Gorilla Tactics and Choice Items have no effect during Dynamax.
  • Destiny Bond cannot take down a Dynamax Pokémon, but Grudge still eats all of a Max Move's PP if it succeeds.
  • Perish Song can take down a Dynamax Pokémon.
  • Disable works against a Dynamax Pokémon if its last used move was not a Max Move, but fails otherwise.
  • Mentioned previously, but any effect that damages or heals based off a proportion of HP does so based on Dynamax HP, including Pain Split, Endeavor, Leftovers, Life Orb, so on. The only exception is G-Max Finale.
  • Entrainment, Skill Swap, and Wandering Spirit fail against a Dynamaxed Pokémon.
  • Gastro Acid, Simple Beam, and Worry seed work against a Dynamaxed Pokémon.
  • Type-changing moves appear as Max Strike (or whatever they would expect to be) in move selection, but change type on use.
  • Sheer Force does not have any interactions with Max Move effects.
  • Feint can hit through Max Guard, but does not break protection.
  • Encore can be used to encore the base move of the last Max Move after Dynamax expires.
  • Max Moves have an effect even if they hit a Substitute.
  • Copycat copies the last Max Move's base move.
  • Max Guard blocks some moves that bypass protection, including Block, but not Mean Look.
  • G-Max Replenish recycles both or neither ally Pokémon's berries.
  • Serene Grace does not apply to G-Max Move effects.
  • G-Max Chi Strike is stackable.
  • G-Max Centiferno and Sandblast continue trap and damage opponents even if the user switches out.
  • Multi-Attack uses the same power calculation as Fighting and Poison-type moves.
  • When an active Dynamaxed Pokémon levels up, its max and current HP increase! This doesn't happen if a party Pokémon not in play levels up (lots of bugs have been squashed).
  • G-Max Stun Shock and G-Max Befuddle choose their status conditions before considering immunities and can apply different status conditions to each opponent.

What needs to be done?

  • RNG-based tests need to be rewritten!
  • When a player partner Pokemon is fainted by Max Flare, the game may softlock.
  • Not in this PR, but in 2.0, Dynamax Level and Gigantamax factor need to be implemented.
  • Not in this PR, but Gigantamax is not technically a form change, and should be reworked later to use some undecided swanky sprite system that Edu talked about.

Tell me about the code!

DynamaxData struct

In the same vein of Mega Evolution and Z-Moves, a struct is added to gBattleStruct containing battle-related Dynamax data. Its fields and their purposes are listed below (note that numbers are used instead of constants for brevity):

  • bool8 playerSelect - toggled every time the player presses START while Dynamax is available, checked on move selection to mark a battler as ready to Dynamax; still active after Dynamax, but does nothing.
  • u8 triggerSpriteId - stores sprite ID for the Dynamax trigger
  • u8 indicatorSpriteId[4] - stores sprite IDs for each battler's Dynamax indicator, since I wasn't able to find "room" in the healthbox data section
  • bool8 toDynamax[4] - stores whether a battler has chosen to Dynamax
  • bool8 alreadyDynamaxed[2] - stores whether a side has already Dynamaxed
  • bool8 dynamaxed[4] - stores whether a battler is currently Dynamaxed; used for the function IsDynamaxed(battler), which is used by most code for style.
  • u8 dynamaxTurns[4] - stores how many turns of Dynamax a battler has remaining, decremented in DoBattlerEndTurnEffects
  • u8 usingMaxMove[4] - stores whether a battler is using a Max Move, would be marginally redundant if not for the fact that Raid Bosses are not always using a Max Move despite being Dynamaxed; more details in the Dynamaxing section.
  • u8 activeSplit - stores whether the current Max Move is physical or special
  • u8 splits[4] - stores whether the base move a Dynamaxed battler selected is physical or special; both of these are modeled after Z-Moves
  • u16 baseMove[4] - stores the base move used by a Dynamaxed battler; used for effects like Encore and Grudge
  • u16 lastUsedBaseMove - stores the last used base move on the field; used for effects like Copycat (unfortunately just Copycat)
  • u16 levelUpHP - stores the HP a Dynamaxed battler has before level-up to avoid having it reset

Dynamaxing

The activation mechanism is copied straight from Mega Evolution. A bit flag is set when a move is chosen, and this is checked to mark a Pokémon as ready to Dynamax using gBattleStruct->dynamax.toDynamax[battler]. In CheckMegaEvolutionBeforeTurn, the Pokémon will have the appropriate fields filled using PrepareBattlerForDynamax and BattleScript_DynamaxBegins is executed. This script will perform the Dynamax animation (note the tint occurs in battle_gfx_sfx_util, just search // dynamax tint) and call the various command updatedynamax, which is also used when Dynamax ends. This command calls RecalcBattlerStats and updates the battler's healthbox.

The Dynamax HP Multiplier is handled through ApplyDynamaxHPMultiplier, which will make it very easy to handle Dynamax Level in the future. This function is called in RecalcBattlerStats, as well as on level-up to reapply it after HP is recalculated.

The trigger sprite and indicators are also copied directly from Mega Evolution, which is especially nice since the latter was recently completely reworked so there should be no bugs. The Dynamax indicator sprite shares a palette with the Alpha and Omega indicators, since this palette is always loaded and I want to avoid bloat.

Max Move Usage

In HandleTurnActionSelectionState, a check is made using ShouldUseMaxMove to decide whether to mark the usingMaxMove field for a battler. Currently, this function just checks if a battler is Dynamaxed, but it will also check Raid Bosses in the future. Once marked, the selected move will be transformed in HandleAction_UseMove, which will also mark the correct move split to be used. If a status move is selected, its priority will be set to that of Max Guard in GetMovePriority. The field is cleared in HandleAction_ActionFinished.

Max Move effects are handled using a SetMaxMoveEffect script command and function in battle_dynamax.c. This command functions as a custom setmoveeffect, since Sheer Force and Serene Grace don't apply and I wanted to keep code self-contained instead of adding ~30 new move effect constants. Because of this, Max Moves have a custom script that is almost identical to EffectHit, however tryfaintmon is called before setmaxmoveeffect, which may be causing a bug?

Max Move Scripts

Max Move effect scripts all follow a similar loop, storing the original target or attacker, trying to apply their effect, moving to the target's ally, then restoring the original target or attacker once the script ends. This was modeled after Jungle Healing's script, since I wanted to avoid looping through all battlers unnecessarily.

G-Max Steelsurge uses a script similar to Stone Axe (thanks Lunos!), and its hazards (generally just referred to in code as steelsurge) are handled just like Stealth Rocks.

G-Max Wildfire, Cannonade, Vine Lash, and Volcalith share a script and a side status called damageNonTypes. The timer and type unaffected by the damage-over-time effect are stored in gSideTimers.

The status-inducing effects are handled using two scripts (trysetstatus1 and trysetstatus2) which are very generalized and simply check the current move's argument to decide which status to apply. This allows for easy randomization in the case of G-Max Befuddle and Stun Shock, and spares the expansion ~10 additional various commands (especially since Torment, Mean Look, Infatuation, and Confusion are compressed into one).

The various jumpiftargetdynamaxed is used to prevent the phazing effect of phazing moves. There is also a various for tryhealsixthhealth (which uses Dynamaxed HP) and tryrecycleberry, since G-Max Replenish only works for berries.

Gigantamax

Gigantamax Forms are handled as species, with sprites, cries, and species info like everything else. Form-changing is handled using FORM_CHANGE_BATTLE_GIGANTAMAX, added to Edu's Form Change refactor. Everything is pretty straightforward, but it's worth noting that because form changing calls RecalcBattlerStats, the call to this function is avoided when Dynamaxing to avoid calling it twice and thus applying the Dynamax multiplier twice.

Miscellaneous

Weight-based moves are checked and stopped in attackcanceler, modeled after how the effects of Primal Groudon and Kyogre's weather cancelling Water and Fire-type moves, respectively. A check is made using IsMoveBlockedByDynamax so that this might be expanded for Max Raids.

Max Guard's funky protect interactions are handled in IsBattlerProtected using a check to IsMoveBlockedByMaxGuard.

Max Move battlestrings print the full name of a Max Move similar to how Z-Moves are printed using a call to GetMaxMoveName and a table.

Almost every instance where gBattleMoveDamage was assigned a value based off the target's max HP or current HP have been replaced with an assignment using GetNonDynamaxMaxHP or GetNonDynamaxHP.

Tests

Tests have been added for every Max Move effect, as well as many of the specific interactions listed a section or two above. G-Max Chi Strike is marked with KNOWN_FAILING since there seems to be an unrelated issue with moves with +3 crit stages not having 100% chance to be a critical hit. A couple other moves are currently marked with KNOWN_FAILING because they need to have their tests rewritten, but they are perfectly functional.

Discord Contact Info

Agustin#1522

@AsparagusEduardo AsparagusEduardo changed the base branch from master to upcoming October 28, 2022 01:32
@AsparagusEduardo
Copy link
Collaborator

AsparagusEduardo commented Oct 28, 2022

I will try to help as much as I can with this :)
That said, here are some pointers that I've noticed already:

  • You added Alolan Form, Castform and Cherrim image duplicates. Please remove those.
  • Use current folder format. /gmax_alcremie/ -> /alcremie/gmax/
  • Pleas take the chance to combine the Regis into Legendary too.
  • Spaces instead of tabs.
  • I recommend defining Max Moves/G-Max moves only once, and setting the move category based on the base move, like how Z-Moves are handled currently.
  • Max Move power should have their own formula , following this table: https://www.smogon.com/forums/threads/national-dex-mechanics-thread.3656786/post-8518210
  • Please order gFormChangeTablePointers by base form's gen.
  • sMelmetalFormSpeciesIdTable should go in Gen 7, not 8
  • Remember to use changedSpecies to store the original form for species that gigantamax into the same form (Toxtricity and Alcremie). I'll be streamlining how it's handled in Refactoring Battle Form changes into the form change tables #2411 to help out.

@AgustinGDLV
Copy link
Collaborator Author

AgustinGDLV commented Feb 22, 2023

Max Move Showcase

Max Moves are mostly functional. This is what's left:

  • Max Overgrowth doesn't set Grassy Terrain despite my frustration.
  • Gigantamax Moves aren't able to be used or have coded effects.
  • Custom battle strings for Max Moves that include the whole move name.
  • No proper check on when to use Max Moves (currently always returns true since Dynamax doesn't exist).
  • Max Moves bypass Protect entirely, as opposed to dealing 25% damage.
  • Potential jank with how move split is decided.
  • Max Move interactions with -ate abilities, Multi Attack, Weather Ball, Electrify, Terrain Pulse, Nature Power, etc.

@AsparagusEduardo AsparagusEduardo added this to the Generation 8 milestone Jul 16, 2023
AsparagusEduardo and others added 17 commits October 9, 2023 16:51
# Conflicts:
#	asm/macros/battle_script.inc
#	data/battle_anim_scripts.s
#	data/battle_scripts_1.s
#	include/battle.h
#	include/battle_controllers.h
#	include/battle_interface.h
#	include/config/battle.h
#	include/constants/battle.h
#	include/constants/battle_anim.h
#	include/constants/battle_move_effects.h
#	include/constants/battle_string_ids.h
#	include/data.h
#	include/random.h
#	sound/cry_tables.inc
#	src/battle_anim_new.c
#	src/battle_controller_opponent.c
#	src/battle_controller_player.c
#	src/battle_controller_player_partner.c
#	src/battle_interface.c
#	src/battle_main.c
#	src/battle_message.c
#	src/battle_script_commands.c
#	src/battle_util.c
#	src/data/pokemon/form_change_table_pointers.h
#	src/data/pokemon/species_info.h
#	src/data/text/move_names.h
#	test/test_runner_battle.c
# Conflicts:
#	include/battle.h
#	include/constants/battle_move_effects.h
#	include/constants/battle_string_ids.h
#	src/battle_main.c
#	src/battle_message.c
#	src/battle_script_commands.c
#	src/battle_util.c
Updated Dynamax branch to Upcoming + Fixes
@AsparagusEduardo AsparagusEduardo merged commit 14b7a70 into rh-hideout:upcoming Oct 19, 2023
1 check passed
@Bassoonian Bassoonian mentioned this pull request Nov 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants