From 788642347b1f4cf7ef5d6d81008c690cc21f2725 Mon Sep 17 00:00:00 2001 From: AgustinGDLV Date: Tue, 21 Mar 2023 11:36:04 -0700 Subject: [PATCH] added HP-based damage/healing interactions with Dynamax Pokemon --- include/battle_dynamax.h | 2 + src/battle_dynamax.c | 26 ++++++++ src/battle_script_commands.c | 81 ++++++++++++------------- src/battle_util.c | 66 ++++++++++---------- test/dynamax.c | 113 +++++++++++++++++++++++++++++++++-- 5 files changed, 211 insertions(+), 77 deletions(-) diff --git a/include/battle_dynamax.h b/include/battle_dynamax.h index 1d6d8cd807..d5cb0c2a5b 100644 --- a/include/battle_dynamax.h +++ b/include/battle_dynamax.h @@ -60,6 +60,8 @@ bool32 IsDynamaxed(u16 battlerId); bool32 CanDynamax(u16 battlerId); void ApplyDynamaxHPMultiplier(u16 battlerId, struct Pokemon* mon); void PrepareBattlerForDynamax(u16 battlerId); +u16 GetNonDynamaxHP(u16 battlerId); +u16 GetNonDynamaxMaxHP(u16 battlerId); void UndoDynamax(u16 battlerId); bool32 IsMoveBlockedByDynamax(u16 move); diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index 87f7c2291f..fa2909b58d 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -132,6 +132,32 @@ void ApplyDynamaxHPMultiplier(u16 battlerId, struct Pokemon* mon) } } +// Returns the non-Dynamax HP of a Pokemon. +u16 GetNonDynamaxHP(u16 battlerId) +{ + if (!IsDynamaxed(battlerId) || gBattleMons[battlerId].species == SPECIES_SHEDINJA) + return gBattleMons[battlerId].hp; + else + { + u16 mult = UQ_4_12(1.0/1.5); // placeholder + u16 hp = UQ_4_12_TO_INT((gBattleMons[battlerId].hp * mult) + UQ_4_12_ROUND); + return hp; + } +} + +// Returns the non-Dynamax Max HP of a Pokemon. +u16 GetNonDynamaxMaxHP(u16 battlerId) +{ + if (!IsDynamaxed(battlerId) || gBattleMons[battlerId].species == SPECIES_SHEDINJA) + return gBattleMons[battlerId].maxHP; + else + { + u16 mult = UQ_4_12(1.0/1.5); // placeholder + u16 maxHP = UQ_4_12_TO_INT((gBattleMons[battlerId].maxHP * mult) + UQ_4_12_ROUND); + return maxHP; + } +} + // Sets flags used for Dynamaxing and checks Gigantamax forms. void PrepareBattlerForDynamax(u16 battlerId) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 327415e072..78bd161f4d 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5400,7 +5400,7 @@ static void Cmd_moveend(void) if (gProtectStructs[gBattlerTarget].spikyShielded && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; PREPARE_MOVE_BUFFER(gBattleTextBuff1, MOVE_SPIKY_SHIELD); @@ -7030,7 +7030,7 @@ static void Cmd_switchineffects(void) && IsBattlerGrounded(gActiveBattler)) { u8 spikesDmg = (5 - gSideTimers[GetBattlerSide(gActiveBattler)].spikesAmount) * 2; - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / (spikesDmg); + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / (spikesDmg); if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -7819,7 +7819,7 @@ static bool32 TryCheekPouch(u32 battlerId, u32 itemId) && gBattleStruct->ateBerry[GetBattlerSide(battlerId)] & gBitTable[gBattlerPartyIndexes[battlerId]] && !BATTLER_MAX_HP(battlerId)) { - gBattleMoveDamage = gBattleMons[battlerId].maxHP / 3; + gBattleMoveDamage = GetNonDynamaxMaxHP(battlerId) / 3; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -9018,7 +9018,7 @@ static void Cmd_various(void) } else { - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -9653,9 +9653,9 @@ static void Cmd_various(void) else { if (GetBattlerAbility(gBattlerAttacker) == ABILITY_MEGA_LAUNCHER && gBattleMoves[gCurrentMove].flags & FLAG_MEGA_LAUNCHER_BOOST) - gBattleMoveDamage = -(gBattleMons[gActiveBattler].maxHP * 75 / 100); + gBattleMoveDamage = -(GetNonDynamaxMaxHP(gActiveBattler) * 75 / 100); else - gBattleMoveDamage = -(gBattleMons[gActiveBattler].maxHP / 2); + gBattleMoveDamage = -(GetNonDynamaxMaxHP(gActiveBattler) / 2); if (gBattleMoveDamage == 0) gBattleMoveDamage = -1; @@ -10408,7 +10408,7 @@ static void Cmd_various(void) case VARIOUS_TRY_HEAL_QUARTER_HP: { VARIOUS_ARGS(const u8 *failInstr); - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -10738,7 +10738,7 @@ static void Cmd_various(void) VARIOUS_ARGS(const u8 *failInstr); bool8 atLeastOneStatBoosted = FALSE; - u16 hpFraction = max(1, gBattleMons[gBattlerAttacker].maxHP / 3); + u16 hpFraction = max(1, GetNonDynamaxMaxHP(gBattlerAttacker) / 3); for (i = 1; i < NUM_STATS; i++) { @@ -11170,7 +11170,7 @@ static void Cmd_various(void) && IsBattlerAlive(gBattlerAttacker) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 6; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 6; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; } @@ -11293,6 +11293,7 @@ static void Cmd_various(void) case VARIOUS_TRY_HEAL_SIXTH_HP: { VARIOUS_ARGS(const u8 *failInstr); + // This heals based on Dynamaxed HP! gBattleMoveDamage = gBattleMons[gBattlerTarget].maxHP / 6; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -11526,7 +11527,7 @@ static void Cmd_tryhealhalfhealth(void) if (cmd->battler == BS_ATTACKER) gBattlerTarget = gBattlerAttacker; - gBattleMoveDamage = gBattleMons[gBattlerTarget].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerTarget) / 2; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -11661,10 +11662,10 @@ static void Cmd_manipulatedamage(void) break; case DMG_RECOIL_FROM_MISS: #if B_RECOIL_IF_MISS_DMG >= GEN_5 - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; #elif B_RECOIL_IF_MISS_DMG == GEN_4 if ((gBattleMons[gBattlerTarget].maxHP / 2) < gBattleMoveDamage) - gBattleMoveDamage = gBattleMons[gBattlerTarget].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerTarget) / 2; #else gBattleMoveDamage /= 2; #endif @@ -11675,24 +11676,24 @@ static void Cmd_manipulatedamage(void) gBattleMoveDamage *= 2; break; case DMG_1_8_TARGET_HP: - gBattleMoveDamage = gBattleMons[gBattlerTarget].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerTarget) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; break; case DMG_FULL_ATTACKER_HP: - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker); break; case DMG_CURR_ATTACKER_HP: - gBattleMoveDamage = gBattleMons[gBattlerAttacker].hp; + gBattleMoveDamage = GetNonDynamaxHP(gBattlerAttacker); break; case DMG_BIG_ROOT: gBattleMoveDamage = GetDrainedBigRootHp(gBattlerAttacker, gBattleMoveDamage); break; case DMG_1_2_ATTACKER_HP: - gBattleMoveDamage = (gBattleMons[gBattlerAttacker].maxHP + 1) / 2; // Half of Max HP Rounded UP + gBattleMoveDamage = (GetNonDynamaxMaxHP(gBattlerAttacker) + 1) / 2; // Half of Max HP Rounded UP break; case DMG_RECOIL_FROM_IMMUNE: - gBattleMoveDamage = gBattleMons[gBattlerTarget].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerTarget) / 2; break; } @@ -11877,7 +11878,7 @@ static void Cmd_stockpiletohpheal(void) } else { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / (1 << (3 - gDisableStructs[gBattlerAttacker].stockpileCounter)); + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / (1 << (3 - gDisableStructs[gBattlerAttacker].stockpileCounter)); if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -12785,7 +12786,7 @@ static void Cmd_damagetohalftargethp(void) { CMD_ARGS(); - gBattleMoveDamage = gBattleMons[gBattlerTarget].hp / 2; + gBattleMoveDamage = GetNonDynamaxHP(gBattlerTarget) / 2; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -12829,7 +12830,7 @@ static void Cmd_weatherdamage(void) && !(gStatuses3[gBattlerAttacker] & (STATUS3_UNDERGROUND | STATUS3_UNDERWATER)) && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_SAFETY_GOGGLES) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; } @@ -12842,7 +12843,7 @@ static void Cmd_weatherdamage(void) && !(gStatuses3[gBattlerAttacker] & STATUS3_HEAL_BLOCK)) { gBattlerAbility = gBattlerAttacker; - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -12854,7 +12855,7 @@ static void Cmd_weatherdamage(void) && !(gStatuses3[gBattlerAttacker] & (STATUS3_UNDERGROUND | STATUS3_UNDERWATER)) && GetBattlerHoldEffect(gBattlerAttacker, TRUE) != HOLD_EFFECT_SAFETY_GOGGLES) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; } @@ -13035,8 +13036,8 @@ static void Cmd_setsubstitute(void) { CMD_ARGS(); - u32 hp = gBattleMons[gBattlerAttacker].maxHP / 4; - if (gBattleMons[gBattlerAttacker].maxHP / 4 == 0) + u32 hp = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; + if (GetNonDynamaxMaxHP(gBattlerAttacker) / 4 == 0) hp = 1; if (gBattleMons[gBattlerAttacker].hp <= hp) @@ -13046,7 +13047,7 @@ static void Cmd_setsubstitute(void) } else { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 4; // one bit value will only work for pokemon which max hp can go to 1020(which is more than possible in games) + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; // one bit value will only work for pokemon which max hp can go to 1020(which is more than possible in games) if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -13300,7 +13301,7 @@ static void Cmd_painsplitdmgcalc(void) if (!(DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove))) { - s32 hpDiff = (gBattleMons[gBattlerAttacker].hp + gBattleMons[gBattlerTarget].hp) / 2; + s32 hpDiff = (gBattleMons[gBattlerAttacker].hp + GetNonDynamaxHP(gBattlerTarget)) / 2; s32 painSplitHp = gBattleMoveDamage = gBattleMons[gBattlerTarget].hp - hpDiff; u8 *storeLoc = (void *)(&gBattleScripting.painSplitHp); @@ -13749,7 +13750,7 @@ static void Cmd_cursetarget(void) else { gBattleMons[gBattlerTarget].status2 |= STATUS2_CURSED; - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -13915,7 +13916,7 @@ static void Cmd_presentdamagecalculation(void) } else { - gBattleMoveDamage = gBattleMons[gBattlerTarget].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerTarget) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -14081,9 +14082,9 @@ static void Cmd_maxattackhalvehp(void) { CMD_ARGS(const u8 *failInstr); - u32 halfHp = gBattleMons[gBattlerAttacker].maxHP / 2; + u32 halfHp = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; - if (!(gBattleMons[gBattlerAttacker].maxHP / 2)) + if (!(GetNonDynamaxMaxHP(gBattlerAttacker) / 2)) halfHp = 1; // Belly Drum fails if the user's current HP is less than half its maximum, or if the user's Attack is already at +6 (even if the user has Contrary). @@ -14091,7 +14092,7 @@ static void Cmd_maxattackhalvehp(void) && gBattleMons[gBattlerAttacker].hp > halfHp) { gBattleMons[gBattlerAttacker].statStages[STAT_ATK] = MAX_STAT_STAGE; - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -14199,18 +14200,18 @@ static void Cmd_recoverbasedonsunlight(void) if (gCurrentMove == MOVE_SHORE_UP) { if (WEATHER_HAS_EFFECT && gBattleWeather & B_WEATHER_SANDSTORM) - gBattleMoveDamage = 20 * gBattleMons[gBattlerAttacker].maxHP / 30; + gBattleMoveDamage = 20 * GetNonDynamaxMaxHP(gBattlerAttacker) / 30; else - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; } else { if (!(gBattleWeather & B_WEATHER_ANY) || !WEATHER_HAS_EFFECT || GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_UTILITY_UMBRELLA) - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 2; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 2; else if (gBattleWeather & B_WEATHER_SUN) - gBattleMoveDamage = 20 * gBattleMons[gBattlerAttacker].maxHP / 30; + gBattleMoveDamage = 20 * GetNonDynamaxMaxHP(gBattlerAttacker) / 30; else // not sunny weather - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; } if (gBattleMoveDamage == 0) @@ -14737,7 +14738,7 @@ static void Cmd_trywish(void) else gBattleMoveDamage = max(1, GetMonData(&gEnemyParty[gWishFutureKnock.wishPartyId[gBattlerTarget]], MON_DATA_MAX_HP) / 2); #else - gBattleMoveDamage = max(1, gBattleMons[gBattlerTarget].maxHP / 2); + gBattleMoveDamage = max(1, GetNonDynamaxMaxHP(gBattlerAttacker) / 2); #endif gBattleMoveDamage *= -1; @@ -14817,13 +14818,13 @@ static void Cmd_setdamagetohealthdifference(void) { CMD_ARGS(const u8 *failInstr); - if (gBattleMons[gBattlerTarget].hp <= gBattleMons[gBattlerAttacker].hp) + if (GetNonDynamaxHP(gBattlerTarget) <= gBattleMons[gBattlerAttacker].hp) { gBattlescriptCurrInstr = cmd->failInstr; } else { - gBattleMoveDamage = gBattleMons[gBattlerTarget].hp - gBattleMons[gBattlerAttacker].hp; + gBattleMoveDamage = GetNonDynamaxHP(gBattlerTarget) - gBattleMons[gBattlerAttacker].hp; gBattlescriptCurrInstr = cmd->nextInstr; } } @@ -15091,7 +15092,7 @@ static void Cmd_switchoutabilities(void) MarkBattlerForControllerExec(gActiveBattler); break; case ABILITY_REGENERATOR: - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 3; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 3; gBattleMoveDamage += gBattleMons[gActiveBattler].hp; if (gBattleMoveDamage > gBattleMons[gActiveBattler].maxHP) gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP; diff --git a/src/battle_util.c b/src/battle_util.c index 76d5820fea..edaebfdb3a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2707,7 +2707,7 @@ u8 DoBattlerEndTurnEffects(void) && !(gStatuses3[gActiveBattler] & STATUS3_HEAL_BLOCK) && gBattleMons[gActiveBattler].hp != 0) { - gBattleMoveDamage = GetDrainedBigRootHp(gActiveBattler, gBattleMons[gActiveBattler].maxHP / 16); + gBattleMoveDamage = GetDrainedBigRootHp(gActiveBattler, GetNonDynamaxMaxHP(gActiveBattler) / 16); BattleScriptExecute(BattleScript_IngrainTurnHeal); effect++; } @@ -2719,7 +2719,7 @@ u8 DoBattlerEndTurnEffects(void) && !(gStatuses3[gActiveBattler] & STATUS3_HEAL_BLOCK) && gBattleMons[gActiveBattler].hp != 0) { - gBattleMoveDamage = GetDrainedBigRootHp(gActiveBattler, gBattleMons[gActiveBattler].maxHP / 16); + gBattleMoveDamage = GetDrainedBigRootHp(gActiveBattler, GetNonDynamaxMaxHP(gActiveBattler) / 16); BattleScriptExecute(BattleScript_AquaRingHeal); effect++; } @@ -2753,7 +2753,7 @@ u8 DoBattlerEndTurnEffects(void) MAGIC_GUARD_CHECK; gBattlerTarget = gStatuses3[gActiveBattler] & STATUS3_LEECHSEED_BATTLER; // Notice gBattlerTarget is actually the HP receiver. - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleScripting.animArg1 = gBattlerTarget; @@ -2773,7 +2773,7 @@ u8 DoBattlerEndTurnEffects(void) { if (!BATTLER_MAX_HP(gActiveBattler) && !(gStatuses3[gActiveBattler] & STATUS3_HEAL_BLOCK)) { - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -2783,7 +2783,7 @@ u8 DoBattlerEndTurnEffects(void) } else { - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_PoisonTurnDmg); @@ -2802,7 +2802,7 @@ u8 DoBattlerEndTurnEffects(void) { if (!BATTLER_MAX_HP(gActiveBattler) && !(gStatuses3[gActiveBattler] & STATUS3_HEAL_BLOCK)) { - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -2812,7 +2812,7 @@ u8 DoBattlerEndTurnEffects(void) } else { - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; if ((gBattleMons[gActiveBattler].status1 & STATUS1_TOXIC_COUNTER) != STATUS1_TOXIC_TURN(15)) // not 16 turns @@ -2830,9 +2830,9 @@ u8 DoBattlerEndTurnEffects(void) { MAGIC_GUARD_CHECK; #if B_BURN_DAMAGE >= GEN_7 - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 16; #else - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 8; #endif if (ability == ABILITY_HEATPROOF) { @@ -2856,7 +2856,7 @@ u8 DoBattlerEndTurnEffects(void) // persist even after the affected Pokemon has been awakened by Shed Skin. if (gBattleMons[gActiveBattler].status1 & STATUS1_SLEEP) { - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_NightmareTurnDmg); @@ -2874,7 +2874,7 @@ u8 DoBattlerEndTurnEffects(void) && gBattleMons[gActiveBattler].hp != 0) { MAGIC_GUARD_CHECK; - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_CurseTurnDmg); @@ -2895,13 +2895,13 @@ u8 DoBattlerEndTurnEffects(void) gBattlescriptCurrInstr = BattleScript_WrapTurnDmg; if (GetBattlerHoldEffect(gBattleStruct->wrappedBy[gActiveBattler], TRUE) == HOLD_EFFECT_BINDING_BAND) #if B_BINDING_DAMAGE >= GEN_6 - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 6; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 6; else - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 8; #else - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 8; else - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gActiveBattler) / 16; #endif if (gBattleMoveDamage == 0) @@ -3795,7 +3795,7 @@ u8 AtkCanceller_UnableToUseMove(void) if (moveType == TYPE_FIRE) { gProtectStructs[gBattlerAttacker].powderSelfDmg = TRUE; - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; gBattlescriptCurrInstr = BattleScript_MoveUsedPowder; effect = 1; } @@ -4961,7 +4961,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && !(gStatuses3[battler] & STATUS3_HEAL_BLOCK)) { BattleScriptPushCursorAndCallback(BattleScript_RainDishActivates); - gBattleMoveDamage = gBattleMons[battler].maxHP / (gLastUsedAbility == ABILITY_RAIN_DISH ? 16 : 8); + gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / (gLastUsedAbility == ABILITY_RAIN_DISH ? 16 : 8); if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -5065,7 +5065,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) { BattleScriptPushCursorAndCallback(BattleScript_SolarPowerActivates); - gBattleMoveDamage = gBattleMons[battler].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; effect++; @@ -5275,7 +5275,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move else gBattlescriptCurrInstr = BattleScript_MoveHPDrain_PPLoss; - gBattleMoveDamage = gBattleMons[battler].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(battler) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -5567,9 +5567,9 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && IsMoveMakingContact(move, gBattlerAttacker)) { #if B_ROUGH_SKIN_DMG >= GEN_4 - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 8; #else - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 16; #endif if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -5594,7 +5594,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move } else { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; BattleScriptPushCursor(); @@ -5800,7 +5800,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move gBattleMons[gBattlerTarget].species = SPECIES_CRAMORANT; if (GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; } @@ -5813,7 +5813,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move gBattleMons[gBattlerTarget].species = SPECIES_CRAMORANT; if (GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 4; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; } @@ -6597,7 +6597,7 @@ static u8 HealConfuseBerry(u32 battlerId, u32 itemId, u8 flavorId, bool32 end2) { PREPARE_FLAVOR_BUFFER(gBattleTextBuff1, flavorId); - gBattleMoveDamage = gBattleMons[battlerId].maxHP / GetBattlerItemHoldEffectParam(battlerId, itemId); + gBattleMoveDamage = GetNonDynamaxMaxHP(battlerId) / GetBattlerItemHoldEffectParam(battlerId, itemId); if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -6788,7 +6788,7 @@ static u8 ItemHealHp(u32 battlerId, u32 itemId, bool32 end2, bool32 percentHeal) && HasEnoughHpToEatBerry(battlerId, 2, itemId)) { if (percentHeal) - gBattleMoveDamage = (gBattleMons[battlerId].maxHP * GetBattlerItemHoldEffectParam(battlerId, itemId) / 100) * -1; + gBattleMoveDamage = (GetNonDynamaxMaxHP(battlerId) * GetBattlerItemHoldEffectParam(battlerId, itemId) / 100) * -1; else gBattleMoveDamage = GetBattlerItemHoldEffectParam(battlerId, itemId) * -1; @@ -7389,7 +7389,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) } else if (GetBattlerAbility(battlerId) != ABILITY_MAGIC_GUARD && !moveTurn) { - gBattleMoveDamage = gBattleMons[battlerId].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(battlerId) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_ItemHurtEnd2); @@ -7406,7 +7406,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) if (gBattleMons[battlerId].hp < gBattleMons[battlerId].maxHP && !moveTurn) #endif { - gBattleMoveDamage = gBattleMons[battlerId].maxHP / 16; + gBattleMoveDamage = GetNonDynamaxMaxHP(battlerId) / 16; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; @@ -7695,7 +7695,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD && gSpecialStatuses[gBattlerAttacker].damagedMons) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 10; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 10; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; effect = ITEM_HP_CHANGE; @@ -7741,7 +7741,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) && IsBattlerAlive(gBattlerAttacker) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 6; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 6; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; effect = ITEM_HP_CHANGE; @@ -7812,7 +7812,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) && IS_MOVE_PHYSICAL(gCurrentMove) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) @@ -7832,7 +7832,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) && IS_MOVE_SPECIAL(gCurrentMove) && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) { - gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; if (GetBattlerAbility(battlerId) == ABILITY_RIPEN) @@ -7897,7 +7897,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) case HOLD_EFFECT_STICKY_BARB: // Not an orb per se, but similar effect, and needs to NOT activate with pickpocket if (battlerAbility != ABILITY_MAGIC_GUARD) { - gBattleMoveDamage = gBattleMons[battlerId].maxHP / 8; + gBattleMoveDamage = GetNonDynamaxMaxHP(battlerId) / 8; if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; BattleScriptExecute(BattleScript_ItemHurtEnd2); diff --git a/test/dynamax.c b/test/dynamax.c index 114b5b51e7..980e65f722 100644 --- a/test/dynamax.c +++ b/test/dynamax.c @@ -2,10 +2,20 @@ #include "test_battle.h" // TODO: -// Max Guard protects against Transform, Block (not Mean Look), Flower Shield, Gear Up, and so on (see Bulba). -// Imprison doesn't stop Max Moves. -// Max Moves change type as you'd expect with Normalize, Weather Ball, etc. -// (Unrelated) Refactor code to remove dynamax.usingMaxMove? +// TEST: Max Guard protects against Transform, Block (not Mean Look), Flower Shield, Gear Up, and so on (see Bulba). +// TEST: Imprison doesn't stop Max Moves. (how?) +// TEST: Max Moves change type as you'd expect with Normalize, Weather Ball, etc. +// TEST: Reverting HP after Dynamax rounds up, so does Endeavor? +// TEST: You use Struggle while Dynamaxed if out of PP. +// Refactor code to remove dynamax.usingMaxMove? Might keep for Raids +// Ditto cannot turn into a Gigantamax form. +// Interactions with a Dynamaxed Pokemon with Magic Bounce. +// Dynamax should not reset Speed Swap, Soak, or anything else from form changing. +// Multi Attack is treated in Max Move power calcs like a Fighting or Poison type move. +// Max Moves cannot be used against allies. +// G-Max Befuddle applies statuses before considering immunities. +// G-Max Replenish guarantees either both allies' berries or neithers. +// G-Max Sandwhatever still keeps Sand Tomb up when Sandaconda switches. // ============= DYNAMAX AND MAX MOVE INTERACTIONS =================== SINGLE_BATTLE_TEST("(DYNAMAX) Dynamax increases HP and max HP by 1.5x") @@ -353,6 +363,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon take double damage from Dynamax PARAMETRIZE { dynamaxed = FALSE; } PARAMETRIZE { dynamaxed = TRUE; } GIVEN { + ASSUME(gBattleMoves[MOVE_DYNAMAX_CANNON].effect == EFFECT_DYNAMAX_DOUBLE_DMG); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -511,6 +522,100 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon cannot use Max Guard while holdi } } +// Almost anything that calculates gBattleMoveDamage based on HP has been changed to non-Dynamax HP. +// This includes Leftovers, Life Orb, Heal Pulse, Rocky Helmet, Sandstorm, etc. etc. +// There are some redundant cases (i.e Substitute) that can never be used by a Dynamaxed pokemon. +// Anything that is conditional based off max HP still uses gBattleMons[battler].maxHP. +// Below are some tests, but very far from all encompassing: + +SINGLE_BATTLE_TEST("(DYNAMAX) Endeavor uses a Pokemon's non-Dynamax HP") +{ + s16 damage; + GIVEN { + ASSUME(gBattleMoves[MOVE_ENDEAVOR].effect == EFFECT_ENDEAVOR); + PLAYER(SPECIES_WOBBUFFET) { MaxHP(100); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(100); Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_ENDEAVOR); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + } SCENE { + MESSAGE("Foe Wobbuffet used Endeavor!"); + HP_BAR(player, captureDamage: &damage); + } FINALLY { + EXPECT_EQ(damage, 99); // difference between foe's HP and player's non-dynamax HP + } +} + +SINGLE_BATTLE_TEST("(DYNAMAX) Super Fang uses a Pokemon's non-Dynamax HP") +{ + s16 damage; + GIVEN { + ASSUME(gBattleMoves[MOVE_SUPER_FANG].effect == EFFECT_SUPER_FANG); + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(100); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUPER_FANG); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + } SCENE { + MESSAGE("Foe Wobbuffet used Super Fang!"); + HP_BAR(player, captureDamage: &damage); + } FINALLY { + EXPECT_EQ(damage, 25); + } +} + +SINGLE_BATTLE_TEST("(DYNAMAX) Pain Split uses a Pokemon's non-Dynamax HP") +{ + s16 damage; + GIVEN { + ASSUME(gBattleMoves[MOVE_PAIN_SPLIT].effect == EFFECT_PAIN_SPLIT); + PLAYER(SPECIES_WOBBUFFET) { HP(60); MaxHP(100); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { HP(40); MaxHP(100); Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_PAIN_SPLIT); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + } SCENE { + MESSAGE("Foe Wobbuffet used Pain Split!"); + HP_BAR(player, captureDamage: &damage); + } FINALLY { + EXPECT_EQ(damage, 10); + } +} + +SINGLE_BATTLE_TEST("(DYNAMAX) Sitrus Berries heal based on a Pokemon's non-Dynamax HP") +{ + s16 damage; + GIVEN { + ASSUME(I_SITRUS_BERRY_HEAL >= GEN_4); + ASSUME(gItems[ITEM_SITRUS_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PCT_HP); + PLAYER(SPECIES_WOBBUFFET) { HP(60); MaxHP(100); Speed(50); Item(ITEM_SITRUS_BERRY); } + OPPONENT(SPECIES_WOBBUFFET) { HP(40); MaxHP(100); Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_PAIN_SPLIT); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + } SCENE { + MESSAGE("Foe Wobbuffet used Pain Split!"); + HP_BAR(player); + MESSAGE("Wobbuffet's Sitrus Berry restored health!"); + HP_BAR(player, captureDamage: &damage); + } FINALLY { + EXPECT_EQ(damage, 25); + } +} + +SINGLE_BATTLE_TEST("(DYNAMAX) Heal Pulse heals based on a Pokemon's non-Dynamax HP") +{ + s16 damage; + GIVEN { + ASSUME(gBattleMoves[MOVE_HEAL_PULSE].effect == EFFECT_HEAL_PULSE); + PLAYER(SPECIES_WOBBUFFET) { HP(50); MaxHP(100); Speed(50); } + OPPONENT(SPECIES_WOBBUFFET) { MaxHP(100); Speed(100); } + } WHEN { + TURN { MOVE(opponent, MOVE_HEAL_PULSE); MOVE(player, MOVE_TACKLE, dynamax: TRUE); } + } SCENE { + MESSAGE("Foe Wobbuffet used Heal Pulse!"); + HP_BAR(player, captureDamage: &damage); + } FINALLY { + EXPECT_EQ(damage, 50); + } +} + // ============= MAX MOVE EFFECTS ========================================== SINGLE_BATTLE_TEST("(DYNAMAX) Max Strike lowers single opponent's speed") {