From 601247df5f8983f6086320a2f90a6dad2d842e9d Mon Sep 17 00:00:00 2001 From: Prof-Holon Date: Wed, 8 Apr 2026 11:55:32 +0100 Subject: [PATCH] ability updates --- data/battle_scripts_1.s | 13 ++++ include/battle_message.h | 1 + include/constants/battle.h | 1 + include/constants/battle_string_ids.h | 10 ++- src/battle_main.c | 6 ++ src/battle_message.c | 14 ++++ src/battle_script_commands.c | 8 +- src/battle_util.c | 103 ++++++++++++++++++++++++++ src/pokemon.c | 49 +++++++++++- 9 files changed, 200 insertions(+), 5 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index d0249ebdf..858a810e5 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -4389,3 +4389,16 @@ BattleScript_ActionSelectionItemsCantBeUsed:: BattleScript_FlushMessageBox:: printstring STRINGID_EMPTYSTRING3 return + +BattleScript_LowHpAbilityBoost:: + pause B_WAIT_TIME_SHORT + printfromtable gLowHpAbilityBoostStringIds + waitmessage B_WAIT_TIME_LONG + return + +BattleScript_DampCuresStatus:: + printstring STRINGID_PKMNSXCUREDYPROBLEM + waitmessage B_WAIT_TIME_LONG + updatestatusicon BS_ATTACKER + end3 + diff --git a/include/battle_message.h b/include/battle_message.h index ef5cbcd88..602cb2def 100644 --- a/include/battle_message.h +++ b/include/battle_message.h @@ -234,6 +234,7 @@ extern const u8 *const gStatNamesTable2[]; extern const u16 gMissStringIds[]; extern const u16 gTrappingMoves[]; +extern const u16 gLowHpAbilityBoostStringIds[]; extern const u8 gText_Sleep[]; extern const u8 gText_Poison[]; diff --git a/include/constants/battle.h b/include/constants/battle.h index c227dd7a4..69d093537 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -152,6 +152,7 @@ #define STATUS3_INTIMIDATE_POKES (1 << 19) #define STATUS3_TRACE (1 << 20) #define STATUS3_SEMI_INVULNERABLE (STATUS3_UNDERGROUND | STATUS3_ON_AIR | STATUS3_UNDERWATER) +#define STATUS3_LOW_HP_ABILITY_MSG (1 << 21) // added for blaze/torrent/overgrow message trigger // Not really sure what a "hitmarker" is. #define HITMARKER_WAKE_UP_CLEAR (1 << 4) // Cleared when waking up. Never set or checked. diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index c086632df..c756f9e3c 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -389,7 +389,15 @@ #define STRINGID_PKMNCOPIEDSECONDARYTYPE 387 //add to redefine conversion2 #define STRINGID_PKMNSXSUMMONEDHAIL 388 //ADD snow warning -#define BATTLESTRINGS_COUNT 389 +//add boosting abilities tooltip + +#define STRINGID_OVERGROWBOOSTED 389 +#define STRINGID_BLAZINGBOOSTED 390 +#define STRINGID_TORRENTBOOSTED 391 +#define STRINGID_SWARMBOOSTED 392 +#define STRINGID_EARLYBIRDBOOSTED 393 + +#define BATTLESTRINGS_COUNT 394 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/src/battle_main.c b/src/battle_main.c index 7238f4859..478fd428a 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3421,6 +3421,12 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves) else speedMultiplierBattler2 = 1; } + else if (gBattleWeather != B_WEATHER_NONE) // weather exists but is being suppressed + { + // ADD: Air Lock doubles speed when actively negating weather + speedMultiplierBattler1 = (gBattleMons[battler1].ability == ABILITY_AIR_LOCK) ? 2 : 1; + speedMultiplierBattler2 = (gBattleMons[battler2].ability == ABILITY_AIR_LOCK) ? 2 : 1; + } else { speedMultiplierBattler1 = 1; diff --git a/src/battle_message.c b/src/battle_message.c index a272b2a41..1e46fa457 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -428,6 +428,20 @@ static const u8 sText_ExclamationMark3[] = _("!"); static const u8 sText_ExclamationMark4[] = _("!"); static const u8 sText_ExclamationMark5[] = _("!"); +static const u8 sText_Overgrow_Boosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Overgrow\nboosted its Grass moves!"); +static const u8 sText_Blaze_Boosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Blaze\nboosted its Fire moves!"); +static const u8 sText_Torrent_Boosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Torrent\nboosted its Water moves!"); +static const u8 sText_Swarm_Boosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Swarm\nboosted its Bug moves!"); +static const u8 sText_Early_Bird_Boosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Early Bird\nboosted its Flying moves!"); + +const u16 gLowHpAbilityBoostStringIds[] = { + STRINGID_OVERGROWBOOSTED, // 0 + STRINGID_BLAZEBOOSTED, // 1 + STRINGID_TORRENTBOOSTED, // 2 + STRINGID_SWARMBOOSTED, // 3 + STRINGID_EARLYBIRDBOOSTED, // 4 +}; + static const u8 sText_HP2[] = _("HP"); static const u8 sText_Attack2[] = _("ATTACK"); static const u8 sText_Defense2[] = _("DEFENSE"); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e45e126f9..ca21903cc 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1084,8 +1084,10 @@ static void Cmd_accuracycheck(void) calc = (calc * 130) / 100; // 1.3 compound eyes boost if (WEATHER_HAS_EFFECT && gBattleMons[gBattlerTarget].ability == ABILITY_SAND_VEIL && gBattleWeather & B_WEATHER_SANDSTORM) calc = (calc * 80) / 100; // 1.2 sand veil loss - if (gBattleMons[gBattlerAttacker].ability == ABILITY_HUSTLE && IS_TYPE_PHYSICAL(type)) - calc = (calc * 80) / 100; // 1.2 hustle loss + if (WEATHER_HAS_EFFECT && gBattleMons[gBattlerTarget].ability == ABILITY_WATER_VEIL && gBattleWeather & B_WEATHER_RAIN) + calc = (calc * 80) / 100; // 1.2 water veil loss + if (gBattleMons[gBattlerAttacker].ability == ABILITY_HUSTLE && (gBattleMoves[gCurrentMove].flags & FLAG_MAKES_CONTACT)) + calc = (calc * 80) / 100; // 1.2 hustle loss for contact moves if (gBattleMons[gBattlerTarget].item == ITEM_ENIGMA_BERRY) { @@ -1770,6 +1772,8 @@ static void Cmd_healthbarupdate(void) if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER && gBattleMoveDamage > 0) gBattleResults.playerMonWasDamaged = TRUE; } + if (gBattleMons[gActiveBattler].hp < oldHp && gBattleMons[gActiveBattler].hp != 0) // trigger ability if HP dropped + AbilityBattleEffects(ABILITYEFFECT_HP_CHANGE, gActiveBattler, 0, 0, 0); } gBattlescriptCurrInstr += 2; diff --git a/src/battle_util.c b/src/battle_util.c index c908f054d..aea738760 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -34,6 +34,21 @@ static const u16 sSoundMovesTable[] = MOVE_UPROAR, MOVE_METAL_SOUND, MOVE_GRASS_WHISTLE, MOVE_HYPER_VOICE, SOUND_MOVES_END }; +#define PRIO_MOVES_END 0xFFFF + +static const u16 sPrioMoves[] = +{ + MOVE_QUICK_ATTACK, MOVE_EXTREMESPEED, MOVE_FAKE_OUT, MOVE_FEINT, MOVE_MACH_PUNCH, PRIO_MOVES_END +}; + +#define TAUNT_MOVES_END 0xFFFF + +static const u16 sTauntMoves[] = +{ + MOVE_TAUNT, MOVE_FOLLOW_ME, TAUNT_MOVES_END +}; + + u8 GetBattlerForBattleScript(u8 caseId) { u8 ret = 0; @@ -1836,6 +1851,36 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA } } break; + case ABILITYEFFECT_HP_CHANGE: // <-- ADD YOUR CODE HERE + { + u8 ability = gBattleMons[battler].ability; + + if ((ability == ABILITY_BLAZE + || ability == ABILITY_TORRENT + || ability == ABILITY_OVERGROW + || ability == ABILITY_SWARM) + || ability == ABILITY_EARLY_BIRD + && gBattleMons[battler].hp <= gBattleMons[battler].maxHP / 3 + && !(gStatuses3[battler] & STATUS3_LOW_HP_ABILITY_MSG)) + { + gStatuses3[battler] |= STATUS3_LOW_HP_ABILITY_MSG; + + switch (ability) + { + case ABILITY_OVERGROW: gBattleCommunication[MULTISTRING_CHOOSER] = 0; break; + case ABILITY_BLAZE: gBattleCommunication[MULTISTRING_CHOOSER] = 1; break; + case ABILITY_TORRENT: gBattleCommunication[MULTISTRING_CHOOSER] = 2; break; + case ABILITY_SWARM: gBattleCommunication[MULTISTRING_CHOOSER] = 3; break; + case ABILITY_EARLY_BIRD: gBattleCommunication[MULTISTRING_CHOOSER] = 4; break; + } + + gBattlerAbility = battler; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_LowHpAbilityBoost; + effect++; + } + } + break; } break; case ABILITYEFFECT_ENDTURN: // 1 @@ -1879,6 +1924,31 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA effect++; } break; + case ABILITY_DAMP: + if (WEATHER_HAS_EFFECT + && (gBattleWeather & B_WEATHER_RAIN) + && (gBattleMons[battler].status1 & STATUS1_ANY)) + { + // Copy the same status string pattern from Shed Skin + if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON)) + StringCopy(gBattleTextBuff1, gStatusConditionString_PoisonJpn); + if (gBattleMons[battler].status1 & STATUS1_SLEEP) + StringCopy(gBattleTextBuff1, gStatusConditionString_SleepJpn); + if (gBattleMons[battler].status1 & STATUS1_PARALYSIS) + StringCopy(gBattleTextBuff1, gStatusConditionString_ParalysisJpn); + if (gBattleMons[battler].status1 & STATUS1_BURN) + StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn); + if (gBattleMons[battler].status1 & STATUS1_FREEZE) + StringCopy(gBattleTextBuff1, gStatusConditionString_IceJpn); + gBattleMons[battler].status1 = 0; + gBattleMons[battler].status2 &= ~STATUS2_NIGHTMARE; + gBattleScripting.battler = gActiveBattler = battler; + BattleScriptPushCursorAndCallback(BattleScript_DampCuresStatus); + BtlController_EmitSetMonData(BUFFER_A, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1); + MarkBattlerForControllerExec(gActiveBattler); + effect++; + } + break; case ABILITY_SPEED_BOOST: if (gBattleMons[battler].statStages[STAT_SPEED] < MAX_STAT_STAGE && gDisableStructs[battler].isFirstTurn != 2) { @@ -1912,6 +1982,38 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA effect = 1; } } + // ADD: Inner Focus blocks priority moves + else if (gLastUsedAbility == ABILITY_INNER_FOCUS) + { + for (i = 0; sPrioMoves[i] != PRIO_MOVES_END; i++) + { + if (sPrioMoves[i] == move) + break; + } + if (sPrioMoves[i] != PRIO_MOVES_END) + { + if (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS) + gHitMarker |= HITMARKER_NO_PPDEDUCT; + gBattlescriptCurrInstr = BattleScript_SoundproofProtected; + effect = 1; + } + } + // ADD: Oblivious blocks taunt/redirect moves + else if (gLastUsedAbility == ABILITY_OBLIVIOUS) + { + for (i = 0; sTauntMoves[i] != TAUNT_MOVES_END; i++) + { + if (sTauntMoves[i] == move) + break; + } + if (sTauntMoves[i] != TAUNT_MOVES_END) + { + if (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS) + gHitMarker |= HITMARKER_NO_PPDEDUCT; + gBattlescriptCurrInstr = BattleScript_SoundproofProtected; + effect = 1; + } + } break; case ABILITYEFFECT_ABSORBING: // 3 if (move) @@ -1966,6 +2068,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA } } break; + } if (effect == 1) { diff --git a/src/pokemon.c b/src/pokemon.c index 66a5c3d23..1699e537b 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -1637,6 +1637,26 @@ static const u16 sHMMoves[] = MOVE_ROCK_SMASH, MOVE_WATERFALL, MOVE_DIVE, HM_MOVES_END }; +#define ABILITY_MOVE_LIST_END 0xFFFF + +static const u16 sSlicingMoves[] = +{ + MOVE_CUT, MOVE_RAZOR_LEAF, MOVE_SLASH, MOVE_FURY_CUTTER, + MOVE_AIR_CUTTER, MOVE_AERIAL_ACE, MOVE_LEAF_BLADE, ABILITY_MOVE_LIST_END +}; + +static const u16 sClawMoves[] = +{ + MOVE_VISE_GRIP, MOVE_CLAMP, MOVE_CRABHAMMER, MOVE_METAL_CLAW, + MOVE_CRUSH_CLAW, MOVE_DRAGON_CLAW, MOVE_GUILLOTINE, ABILITY_MOVE_LIST_END +}; + +static const u16 sHeadMoves[] = +{ + MOVE_HEADBUTT, MOVE_MEGAHORN, MOVE_HORN_ATTACK, + MOVE_SKULL_BASH, MOVE_HORN_DRILL, ABILITY_MOVE_LIST_END +}; + #if defined(FIRERED) // Attack forme static const u16 sDeoxysBaseStats[] = @@ -2381,6 +2401,16 @@ static void DeleteFirstMoveAndGiveMoveToBoxMon(struct BoxPokemon *boxMon, u16 mo #define ShouldGetStatBadgeBoost(flag, battler)\ (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_EREADER_TRAINER)) && FlagGet(flag) && GetBattlerSide(battler) == B_SIDE_PLAYER) +static bool8 IsMoveInList(u16 move, const u16 *list) +{ + u32 i; + for (i = 0; list[i] != ABILITY_MOVE_LIST_END; i++) + { + if (list[i] == move) + return TRUE; + } + return FALSE; +} s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *defender, u32 move, u16 sideStatus, u16 powerOverride, u8 typeOverride, u8 battlerIdAtk, u8 battlerIdDef) { @@ -2434,8 +2464,10 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de defenderHoldEffectParam = ItemId_GetHoldEffectParam(defender->item); } - if (attacker->ability == ABILITY_HUGE_POWER || attacker->ability == ABILITY_PURE_POWER) + if (attacker->ability == ABILITY_HUGE_POWER) attack *= 2; + if (attacker->ability == attacker->ability == ABILITY_PURE_POWER) + spAttack *= 2; if (ShouldGetStatBadgeBoost(FLAG_BADGE01_GET, battlerIdAtk)) attack = (110 * attack) / 100; @@ -2480,7 +2512,8 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de if (defender->ability == ABILITY_THICK_FAT && (type == TYPE_FIRE || type == TYPE_ICE)) spAttack /= 2; if (attacker->ability == ABILITY_HUSTLE) - attack = (150 * attack) / 100; + && (gBattleMoves[move].flags & FLAG_MAKES_CONTACT)) // boost only for contact moves + gBattleMovePower = (150 * gBattleMovePower) / 100; // alternatively boost both attack and SpAttack but this is neater if (attacker->ability == ABILITY_PLUS && ABILITY_ON_FIELD2(ABILITY_MINUS)) spAttack = (150 * spAttack) / 100; if (attacker->ability == ABILITY_MINUS && ABILITY_ON_FIELD2(ABILITY_PLUS)) @@ -2501,6 +2534,18 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de gBattleMovePower = (150 * gBattleMovePower) / 100; if (type == TYPE_BUG && attacker->ability == ABILITY_SWARM && attacker->hp <= (attacker->maxHP / 3)) gBattleMovePower = (150 * gBattleMovePower) / 100; + if (type == TYPE_FLYING && attacker->ability == ABILITY_EARLY_BIRD && attacker->hp <= (attacker->maxHP / 3)) + gBattleMovePower = (150 * gBattleMovePower) / 100; + if (type == TYPE_ELECTRIC && attacker->ability == ABILITY_ILLUMINATE && attacker->hp <= (attacker->maxHP / 3)) // ADD illuminate electric boost + gBattleMovePower = (150 * gBattleMovePower) / 100; + // Hyper Cutter: 1.5x boost for slicing or claw moves + if (attacker->ability == ABILITY_HYPER_CUTTER + && (IsMoveInList(move, sSlicingMoves) || IsMoveInList(move, sClawMoves))) + gBattleMovePower = (150 * gBattleMovePower) / 100; + // Rock Head: 1.5x boost for head moves + if (attacker->ability == ABILITY_ROCK_HEAD + && IsMoveInList(move, sHeadMoves)) + gBattleMovePower = (150 * gBattleMovePower) / 100; // Self-destruct / Explosion cut defense in half if (gBattleMoves[gCurrentMove].effect == EFFECT_EXPLOSION)