From 16ca832d909cee5d22a6e34dcc8f05bce35ee4f0 Mon Sep 17 00:00:00 2001 From: Prof-Holon Date: Sun, 12 Apr 2026 13:41:42 +0100 Subject: [PATCH] final mechanical changes --- data/battle_scripts_1.s | 16 ++++- include/battle.h | 1 + include/constants/battle_string_ids.h | 12 +++- src/battle_main.c | 31 ++++++++-- src/battle_message.c | 27 ++++++--- src/battle_script_commands.c | 85 ++++++++++++++++++++++++--- src/battle_util.c | 62 ++++++++++++------- src/data/pokemon/species_info.h | 6 +- src/pokemon.c | 76 ++++++++++++++++++++++-- 9 files changed, 258 insertions(+), 58 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 858a810e5..cb1d1d7b1 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -452,6 +452,7 @@ BattleScript_DreamEaterWorked: waitmessage B_WAIT_TIME_LONG resultmessage waitmessage B_WAIT_TIME_LONG + jumpifability BS_TARGET, ABILITY_LIQUID_OOZE, BattleScript_AbsorbLiquidOoze negativedamage orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE healthbarupdate BS_ATTACKER @@ -3992,6 +3993,8 @@ BattleScript_IntimidateActivationAnimLoop:: jumpifability BS_TARGET, ABILITY_CLEAR_BODY, BattleScript_IntimidateAbilityFail jumpifability BS_TARGET, ABILITY_HYPER_CUTTER, BattleScript_IntimidateAbilityFail jumpifability BS_TARGET, ABILITY_WHITE_SMOKE, BattleScript_IntimidateAbilityFail + jumpifability BS_TARGET, ABILITY_OBLIVIOUS, BattleScript_IntimidateAbilityFail + jumpifability BS_TARGET, ABILITY_INNER_FOCUS, BattleScript_IntimidateAbilityFail statbuffchange STAT_CHANGE_ALLOW_PTR | STAT_CHANGE_NOT_PROTECT_AFFECTED, BattleScript_IntimidateFail jumpifbyte CMP_GREATER_THAN, cMULTISTRING_CHOOSER, 1, BattleScript_IntimidateFail setgraphicalstatchangevalues @@ -4037,9 +4040,9 @@ BattleScript_TookAttack:: BattleScript_SturdyPreventsOHKO:: pause B_WAIT_TIME_SHORT - printstring STRINGID_PKMNPROTECTEDBY - pause B_WAIT_TIME_LONG - goto BattleScript_MoveEnd + printstring STRINGID_STURDY + pause B_WAIT_TIME_SHORT + return BattleScript_DampStopsExplosion:: pause B_WAIT_TIME_SHORT @@ -4402,3 +4405,10 @@ BattleScript_DampCuresStatus:: updatestatusicon BS_ATTACKER end3 +BattleScript_TruantHeal:: + printstring STRINGID_TRUANTRESTOREDHP + waitmessage B_WAIT_TIME_LONG + orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE + healthbarupdate BS_ATTACKER + datahpupdate BS_ATTACKER + end3 \ No newline at end of file diff --git a/include/battle.h b/include/battle.h index 3771ef33a..c8c3aea4a 100644 --- a/include/battle.h +++ b/include/battle.h @@ -450,6 +450,7 @@ struct BattleStruct u8 wishPerishSongState; u8 wishPerishSongBattlerId; u8 lastAttackerToFaintOpponent; + u8 pickupUsed[MAX_BATTLERS_COUNT]; // align 4 union { struct LinkBattlerHeader linkBattlerHeader; diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index c756f9e3c..9bdb8cc07 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -392,12 +392,20 @@ //add boosting abilities tooltip #define STRINGID_OVERGROWBOOSTED 389 -#define STRINGID_BLAZINGBOOSTED 390 +#define STRINGID_BLAZEBOOSTED 390 #define STRINGID_TORRENTBOOSTED 391 #define STRINGID_SWARMBOOSTED 392 #define STRINGID_EARLYBIRDBOOSTED 393 +#define STRINGID_ILLUMINATEBOOSTED 394 -#define BATTLESTRINGS_COUNT 394 + +//add sturdy +#define STRINGID_STURDY 395 + +//add truant +#define STRINGID_TRUANTRESTOREDHP 396 + +#define BATTLESTRINGS_COUNT 397 // 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 478fd428a..270ead110 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3197,16 +3197,18 @@ static void HandleTurnActionSelectionState(void) break; case B_ACTION_SWITCH: *(gBattleStruct->battlerPartyIndexes + gActiveBattler) = gBattlerPartyIndexes[gActiveBattler]; - if (gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION) || gStatuses3[gActiveBattler] & STATUS3_ROOTED) + if (gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION) || gStatuses3[gActiveBattler] & STATUS3_ROOTED + && gBattleMons[gActiveBattler].ability != ABILITY_RUN_AWAY)) //added { BtlController_EmitChoosePokemon(BUFFER_A, PARTY_ACTION_CANT_SWITCH, 6, ABILITY_NONE, gBattleStruct->battlerPartyOrders[gActiveBattler]); } - else if ((i = ABILITY_ON_OPPOSING_FIELD(gActiveBattler, ABILITY_SHADOW_TAG)) - || ((i = ABILITY_ON_OPPOSING_FIELD(gActiveBattler, ABILITY_ARENA_TRAP)) + else if (gBattleMons[gActiveBattler].ability != ABILITY_RUN_AWAY // ADD + && ((i = ABILITY_ON_OPPOSING_FIELD(gActiveBattler, ABILITY_SHADOW_TAG)) + || ((i = ABILITY_ON_OPPOSING_FIELD(gActiveBattler, ABILITY_ARENA_TRAP)) && !IS_BATTLER_OF_TYPE(gActiveBattler, TYPE_FLYING) && gBattleMons[gActiveBattler].ability != ABILITY_LEVITATE) - || ((i = AbilityBattleEffects(ABILITYEFFECT_CHECK_FIELD_EXCEPT_BATTLER, gActiveBattler, ABILITY_MAGNET_PULL, 0, 0)) - && IS_BATTLER_OF_TYPE(gActiveBattler, TYPE_STEEL))) + || ((i = AbilityBattleEffects(ABILITYEFFECT_CHECK_FIELD_EXCEPT_BATTLER, gActiveBattler, ABILITY_MAGNET_PULL, 0, 0)) + && IS_BATTLER_OF_TYPE(gActiveBattler, TYPE_ELECTRIC || TYPE_ROCK))) { BtlController_EmitChoosePokemon(BUFFER_A, ((i - 1) << 4) | PARTY_ACTION_ABILITY_PREVENTS, 6, gLastUsedAbility, gBattleStruct->battlerPartyOrders[gActiveBattler]); } @@ -3427,6 +3429,22 @@ u8 GetWhoStrikesFirst(u8 battler1, u8 battler2, bool8 ignoreChosenMoves) speedMultiplierBattler1 = (gBattleMons[battler1].ability == ABILITY_AIR_LOCK) ? 2 : 1; speedMultiplierBattler2 = (gBattleMons[battler2].ability == ABILITY_AIR_LOCK) ? 2 : 1; } + // add vital spirit speed boost for both battlers + if ((gBattleMons[battler1].ability == ABILITY_VITAL_SPIRIT && battler1->status1) + speedMultiplierBattler1 = 2; + if ((gBattleMons[battler2].ability == ABILITY_VITAL_SPIRIT && battler2->status1) + speedMultiplierBattler2 = 2; + + // add speed doubling if Minus/Plus ally + if ((gBattleMons[battler1].ability == ABILITY_PLUS && ABILITY_ON_FIELD2(ABILITY_MINUS)) + speedMultiplierBattler1 = 2; + if ((gBattleMons[battler2].ability == ABILITY_PLUS && ABILITY_ON_FIELD2(ABILITY_MINUS)) + speedMultiplierBattler2 = 2; + if ((gBattleMons[battler1].ability == ABILITY_MINUS && ABILITY_ON_FIELD2(ABILITY_PLUS)) + speedMultiplierBattler1 = 2; + if ((gBattleMons[battler2].ability == ABILITY_MINUS && ABILITY_ON_FIELD2(ABILITY_PLUS)) + speedMultiplierBattler2 = 2; + else { speedMultiplierBattler1 = 1; @@ -4331,7 +4349,8 @@ static void HandleAction_Run(void) } else { - if (gBattleMons[gBattlerAttacker].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION)) + if (gBattleMons[gBattlerAttacker].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION) + && gBattleMons[gBattlerAttacker].ability != ABILITY_RUN_AWAY)) //added { gBattleCommunication[MULTISTRING_CHOOSER] = 4; gBattlescriptCurrInstr = BattleScript_PrintFailedToRunString; diff --git a/src/battle_message.c b/src/battle_message.c index 1e46fa457..e8e7fc04f 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -428,20 +428,25 @@ 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!"); +static const u8 sText_OvergrowBoosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Overgrow\nboosted its Grass moves!"); +static const u8 sText_BlazeBoosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Blaze\nboosted its Fire moves!"); +static const u8 sText_TorrentBoosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Torrent\nboosted its Water moves!"); +static const u8 sText_SwarmBoosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Swarm\nboosted its Bug moves!"); +static const u8 sText_EarlyBirdBoosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Early Bird\nboosted its Flying moves!"); +static const u8 sText_IlluminateBoosted[] = _("{B_ATK_NAME_WITH_PREFIX}'s Illuminate\nboosted its Electric moves!"); const u16 gLowHpAbilityBoostStringIds[] = { STRINGID_OVERGROWBOOSTED, // 0 STRINGID_BLAZEBOOSTED, // 1 STRINGID_TORRENTBOOSTED, // 2 STRINGID_SWARMBOOSTED, // 3 - STRINGID_EARLYBIRDBOOSTED, // 4 + STRINGID_EARLYBIRDBOOSTED, // 4 + STRINGID_ILLUMINATEBOOSTED, // 5 }; +static const u8 sText_Sturdy[] = _("{B_ATK_NAME_WITH_PREFIX}'s Sturdy\nallowed it to endure the hit!"); +static const u8 sText_Truant[] = _("{B_ATK_NAME_WITH_PREFIX} is loafing around and recovered HP!"); + static const u8 sText_HP2[] = _("HP"); static const u8 sText_Attack2[] = _("ATTACK"); static const u8 sText_Defense2[] = _("DEFENSE"); @@ -906,7 +911,15 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT - BATTLESTRINGS_TABLE_ST [STRINGID_TRAINER1MON1COMEBACK - BATTLESTRINGS_TABLE_START] = sText_Trainer1RecallPkmn1, [STRINGID_TRAINER1WINTEXT - BATTLESTRINGS_TABLE_START] = sText_Trainer1WinText, [STRINGID_TRAINER1MON2COMEBACK - BATTLESTRINGS_TABLE_START] = sText_Trainer1RecallPkmn2, - [STRINGID_TRAINER1MON1AND2COMEBACK - BATTLESTRINGS_TABLE_START] = sText_Trainer1RecallBoth + [STRINGID_TRAINER1MON1AND2COMEBACK - BATTLESTRINGS_TABLE_START] = sText_Trainer1RecallBoth, + [STRINGID_STURDY - BATTLESTRINGS_TABLE_START] = sText_Sturdy, + [STRINGID_TRUANTRESTOREDHP - BATTLESTRINGS_TABLE_START] = sText_Truant, + [STRINGID_OVERGROWBOOSTED - BATTLESTRINGS_TABLE_START] = sText_OvergrowBoosted, + [STRINGID_BLAZEBOOSTED - BATTLESTRINGS_TABLE_START] = sText_BlazeBoosted, + [STRINGID_TORRENTBOOSTED- BATTLESTRINGS_TABLE_START] = sText_TorrentBoosted, + [STRINGID_SWARMBOOSTED - BATTLESTRINGS_TABLE_START] = sText_SwarmBoosted,, + [STRINGID_EARLYBIRDBOOSTED - BATTLESTRINGS_TABLE_START] = sText_EarlyBirdBoosted, + [STRINGID_ILLUMINATEBOOSTED - BATTLESTRINGS_TABLE_START] = sText_IlluminateBoosted }; const u16 gMissStringIds[] = diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index ca21903cc..637e6fef4 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1064,7 +1064,17 @@ static void Cmd_accuracycheck(void) else { u8 acc = gBattleMons[gBattlerAttacker].statStages[STAT_ACC]; - buff = acc + DEFAULT_STAT_STAGE - gBattleMons[gBattlerTarget].statStages[STAT_EVASION]; + if (gBattleMons[gBattlerAttacker].ability == ABILITY_KEEN_EYE) + { + u8 targetEvasion = gBattleMons[gBattlerTarget].statStages[STAT_EVASION]; + if (targetEvasion > DEFAULT_STAT_STAGE) + targetEvasion = DEFAULT_STAT_STAGE; // cap at neutral, drops still apply + buff = acc + DEFAULT_STAT_STAGE - targetEvasion; + } + else + { + buff = acc + DEFAULT_STAT_STAGE - gBattleMons[gBattlerTarget].statStages[STAT_EVASION]; + } } if (buff < MIN_STAT_STAGE) @@ -1102,7 +1112,7 @@ static void Cmd_accuracycheck(void) gPotentialItemEffectBattler = gBattlerTarget; - if (holdEffect == HOLD_EFFECT_EVASION_UP) + if (holdEffect == HOLD_EFFECT_EVASION_UP && gBattleMons[gBattlerAttacker].ability != ABILITY_KEEN_EYE) calc = (calc * (100 - param)) / 100; // final calculation @@ -1212,6 +1222,15 @@ static void Cmd_critcalc(void) + 2 * (holdEffect == HOLD_EFFECT_LUCKY_PUNCH && gBattleMons[gBattlerAttacker].species == SPECIES_CHANSEY) + 2 * (holdEffect == HOLD_EFFECT_STICK && gBattleMons[gBattlerAttacker].species == SPECIES_FARFETCHD); + // ADD: high crit moves guarantee at least stage 4 + if ((gBattleMoves[gCurrentMove].effect == EFFECT_HIGH_CRITICAL + || gBattleMoves[gCurrentMove].effect == EFFECT_SKY_ATTACK + || gBattleMoves[gCurrentMove].effect == EFFECT_BLAZE_KICK + || gBattleMoves[gCurrentMove].effect == EFFECT_POISON_TAIL) + && critChance < ARRAY_COUNT(sCriticalHitChance) - 1) + critChance = ARRAY_COUNT(sCriticalHitChance) - 1; + // END ADD + if (critChance >= ARRAY_COUNT(sCriticalHitChance)) critChance = ARRAY_COUNT(sCriticalHitChance) - 1; @@ -1222,9 +1241,9 @@ static void Cmd_critcalc(void) gen1Denominator = gen3Denominator; else { - gen1Denominator = (68 * sCriticalHitChance[critChance]) / baseSpeed; // allows it to also scale based on crit stage, anything higher than 80 is better - if (gen1Denominator < 2) - gen1Denominator = 2; + gen1Denominator = (sCriticalHitChance[critChance] * 680) / (13 * baseSpeed); // (1/stage) * (1.3 * speed / 68) inverted + if (gen1Denominator < 1) // ADD: floor at 1 — effectively guaranteed crit + gen1Denominator = 1; } effectiveDenominator = (gen1Denominator < gen3Denominator) ? gen1Denominator : gen3Denominator; // END ADD @@ -1232,7 +1251,7 @@ static void Cmd_critcalc(void) if ((gBattleMons[gBattlerTarget].ability != ABILITY_BATTLE_ARMOR && gBattleMons[gBattlerTarget].ability != ABILITY_SHELL_ARMOR) && !(gStatuses3[gBattlerAttacker] & STATUS3_CANT_SCORE_A_CRIT) && !(gBattleTypeFlags & BATTLE_TYPE_OLD_MAN_TUTORIAL) - && !(Random() % effectiveDenominator) + && ((Random() & 0xFF) < (256 / effectiveDenominator)) && (!(gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE) || BtlCtrl_OakOldMan_TestState2Flag(1)) && !(gBattleTypeFlags & BATTLE_TYPE_POKEDUDE)) gCritMultiplier = 2; @@ -1336,6 +1355,24 @@ static void Cmd_typecalc(void) gBattleCommunication[MISS_TYPE] = B_MSG_GROUND_MISS; RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); } + else if (gBattleMons[gBattlerTarget].ability == ABILITY_IMMUNITY && moveType == TYPE_POISON) + { + gLastUsedAbility = gBattleMons[gBattlerTarget].ability; + gMoveResultFlags |= (MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE); + gLastLandedMoves[gBattlerTarget] = 0; + gLastHitByType[gBattlerTarget] = 0; + gBattleCommunication[MISS_TYPE] = B_MSG_GROUND_MISS; + RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); + } + else if (gBattleMons[gBattlerTarget].ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) + { + gLastUsedAbility = gBattleMons[gBattlerTarget].ability; + gMoveResultFlags |= (MOVE_RESULT_MISSED | MOVE_RESULT_DOESNT_AFFECT_FOE); + gLastLandedMoves[gBattlerTarget] = 0; + gLastHitByType[gBattlerTarget] = 0; + gBattleCommunication[MISS_TYPE] = B_MSG_GROUND_MISS; + RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); + } else { while (TYPE_EFFECT_ATK_TYPE(i) != TYPE_ENDTABLE) @@ -2525,7 +2562,10 @@ void SetMoveEffect(bool8 primary, u8 certain) } else { - gBattleMons[gEffectBattler].status2 |= STATUS2_WRAPPED_TURN((Random() & 2) + 3); // 2-5 turns + if (gBattleMons[gEffectBattler].ability == ABILITY_SUCTION_CUPS) + gBattleMons[gEffectBattler].status2 |= STATUS2_WRAPPED_TURN((Random() & 1) + 4); // 4-5 turns + else + gBattleMons[gEffectBattler].status2 |= STATUS2_WRAPPED_TURN((Random() & 3) + 2); // 2-5 turns *(gBattleStruct->wrappedMove + gEffectBattler * 2 + 0) = gCurrentMove; *(gBattleStruct->wrappedMove + gEffectBattler * 2 + 1) = gCurrentMove >> 8; @@ -4313,6 +4353,21 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_UPDATE_LAST_MOVES: + // ADD: cancel wrap and attacker lock-in if trapping move missed + if (gMoveResultFlags & MOVE_RESULT_MISSED) + { + for (i = 0; i < gBattlersCount; i++) + { + if ((gBattleMons[i].status2 & STATUS2_WRAPPED) + && *(gBattleStruct->wrappedBy + i) == gBattlerAttacker) + { + gBattleMons[i].status2 &= ~STATUS2_WRAPPED; + CancelMultiTurnMoves(gBattlerAttacker); + break; + } + } + } + // END ADD if (gHitMarker & HITMARKER_SWAP_ATTACKER_TARGET) { gActiveBattler = gBattlerAttacker; @@ -5729,6 +5784,15 @@ static void Cmd_removeitem(void) *usedHeldItem = gBattleMons[gActiveBattler].item; gBattleMons[gActiveBattler].item = ITEM_NONE; + // Pickup effect: restore consumed item once per battle + if (gBattleMons[gActiveBattler].ability == ABILITY_PICKUP + && !gBattleStruct->pickupUsed[gActiveBattler] + && *usedHeldItem != ITEM_NONE) + { + gBattleMons[gActiveBattler].item = *usedHeldItem; + gBattleStruct->pickupUsed[gActiveBattler] = TRUE; + } + BtlController_EmitSetMonData(BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gActiveBattler].item), &gBattleMons[gActiveBattler].item); MarkBattlerForControllerExec(gActiveBattler); @@ -7203,9 +7267,12 @@ static void Cmd_tryKO(void) gSpecialStatuses[gBattlerTarget].focusBanded = 1; } - if (gBattleMons[gBattlerTarget].ability == ABILITY_STURDY) + if (gBattleMons[gBattlerTarget].ability == ABILITY_STURDY + && gBattleMons[gBattlerTarget].hp == gBattleMons[gBattlerTarget].maxHP + && gBattleMoveDamage >= gBattleMons[gBattlerTarget].hp + && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) { - gMoveResultFlags |= MOVE_RESULT_MISSED; + gBattleMoveDamage = gBattleMons[gBattlerTarget].hp - 1; gLastUsedAbility = ABILITY_STURDY; gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO; RecordAbilityBattle(gBattlerTarget, ABILITY_STURDY); diff --git a/src/battle_util.c b/src/battle_util.c index aea738760..7364100c2 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -817,7 +817,7 @@ u8 DoBattlerEndTurnEffects(void) } else { - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 8; + gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 16; } if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; @@ -1309,7 +1309,7 @@ u8 AtkCanceller_UnableToUseMove(void) else { u8 toSub; - if (gBattleMons[gBattlerAttacker].ability == ABILITY_EARLY_BIRD) + if (gBattleMons[gBattlerAttacker].ability == ABILITY_LIMBER) // changed old Early Bird effect to Limber toSub = 2; else toSub = 1; @@ -1373,6 +1373,16 @@ u8 AtkCanceller_UnableToUseMove(void) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_LOAFING; gBattlescriptCurrInstr = BattleScript_MoveUsedLoafingAround; gMoveResultFlags |= MOVE_RESULT_MISSED; + // ADD: heal 1/16 max HP on loafing turns + if (gBattleMons[gBattlerAttacker].maxHP > gBattleMons[gBattlerAttacker].hp) + { + gBattleMoveDamage = gBattleMons[gBattlerAttacker].maxHP / 16; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; + gBattleMoveDamage *= -1; + gBattleScripting.battler = gBattlerAttacker; + BattleScriptPushCursorAndCallback(BattleScript_TruantHeal); + } effect = 1; } gBattleStruct->atkCancellerTracker++; @@ -1902,6 +1912,19 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA effect++; } break; + case ABILITY_TRUANT: + if ((gBattleMons[battler].status1 & STATUS1_SLEEP) + && gBattleMons[battler].maxHP > gBattleMons[battler].hp) + { + gBattleMoveDamage = gBattleMons[battler].maxHP / 16; + if (gBattleMoveDamage == 0) + gBattleMoveDamage = 1; + gBattleMoveDamage *= -1; + gBattleScripting.battler = gActiveBattler = battler; + BattleScriptPushCursorAndCallback(BattleScript_TruantHeal); + effect++; + } + break; case ABILITY_SHED_SKIN: if ((gBattleMons[battler].status1 & STATUS1_ANY) && (Random() % 3) == 0) { @@ -1982,24 +2005,8 @@ 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) + // ADD: Oblivious and Inner Focus blocks taunt/redirect moves + else if (gLastUsedAbility == ABILITY_OBLIVIOUS || gLastUsedAbility == ABILITY_INNER_FOCUS) { for (i = 0; sTauntMoves[i] != TAUNT_MOVES_END; i++) { @@ -2068,7 +2075,19 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA } } break; - + case ABILITY_LIGHTNING_ROD: + if (moveType == TYPE_ELECTRIC) + { + gLastUsedAbility = ABILITY_LIGHTNING_ROD; + RecordAbilityBattle(battler, ABILITY_LIGHTNING_ROD); + + // Apply permanent Charge boost until switch + gStatuses3[battler] |= STATUS3_CHARGED_UP; + + gBattlescriptCurrInstr = BattleScript_MoveHPDrain; // reuse generic ability popup + effect = 2; // cancels the hit (immunity) + } + break; } if (effect == 1) { @@ -2239,7 +2258,6 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u8 ability, u8 special, u16 moveA } break; case ABILITY_INSOMNIA: - case ABILITY_VITAL_SPIRIT: if (gBattleMons[battler].status1 & STATUS1_SLEEP) { gBattleMons[battler].status2 &= ~STATUS2_NIGHTMARE; diff --git a/src/data/pokemon/species_info.h b/src/data/pokemon/species_info.h index 86fd412f1..092d3c497 100644 --- a/src/data/pokemon/species_info.h +++ b/src/data/pokemon/species_info.h @@ -3365,7 +3365,7 @@ const struct SpeciesInfo gSpeciesInfo[] = .friendship = 70, .growthRate = GROWTH_MEDIUM_FAST, .eggGroups = {EGG_GROUP_MONSTER, EGG_GROUP_MONSTER}, - .abilities = {ABILITY_INSOMNIA, ABILITY_NONE}, + .abilities = {ABILITY_INSOMNIA, ABILITY_PICKUP}, .safariZoneFleeRate = 125, .bodyColor = BODY_COLOR_BROWN, .noFlip = FALSE, @@ -3713,7 +3713,7 @@ const struct SpeciesInfo gSpeciesInfo[] = .friendship = 70, .growthRate = GROWTH_SLOW, .eggGroups = {EGG_GROUP_BUG, EGG_GROUP_BUG}, - .abilities = {ABILITY_HYPER_CUTTER, ABILITY_NONE}, + .abilities = {ABILITY_HYPER_CUTTER, ABILITY_GUTS}, .safariZoneFleeRate = 125, .bodyColor = BODY_COLOR_BROWN, .noFlip = FALSE, @@ -5308,7 +5308,7 @@ const struct SpeciesInfo gSpeciesInfo[] = .friendship = 70, .growthRate = GROWTH_MEDIUM_SLOW, .eggGroups = {EGG_GROUP_GRASS, EGG_GROUP_GRASS}, - .abilities = {ABILITY_CHLOROPHYLL, ABILITY_NONE}, + .abilities = {ABILITY_CHLOROPHYLL, ABILITY_FLASH_FIRE}, .safariZoneFleeRate = 0, .bodyColor = BODY_COLOR_GREEN, .noFlip = FALSE, diff --git a/src/pokemon.c b/src/pokemon.c index 1699e537b..48dc48b3a 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -2412,6 +2412,36 @@ static bool8 IsMoveInList(u16 move, const u16 *list) return FALSE; } +bool32 HasOtherElectricType(u8 battler) +{ + u8 i; + + for (i = 0; i < gBattlersCount; i++) + { + if (i != battler + && gBattleMons[i].hp != 0 + && (gBattleMons[i].type1 == TYPE_ELECTRIC || gBattleMons[i].type2 == TYPE_ELECTRIC)) + return TRUE; + } + + return FALSE; +} + +bool32 HasOtherRockType(u8 battler) +{ + u8 i; + + for (i = 0; i < gBattlersCount; i++) + { + if (i != battler + && gBattleMons[i].hp != 0 + && (gBattleMons[i].type1 == TYPE_ROCK || gBattleMons[i].type2 == TYPE_ROCK)) + return TRUE; + } + + return FALSE; +} + s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *defender, u32 move, u16 sideStatus, u16 powerOverride, u8 typeOverride, u8 battlerIdAtk, u8 battlerIdDef) { u32 i; @@ -2509,14 +2539,21 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de defense *= 2; if (attackerHoldEffect == HOLD_EFFECT_THICK_CLUB && (attacker->species == SPECIES_CUBONE || attacker->species == SPECIES_MAROWAK)) attack *= 2; + // Apply effects of abilities if (defender->ability == ABILITY_THICK_FAT && (type == TYPE_FIRE || type == TYPE_ICE)) spAttack /= 2; + if (defender->ability == ABILITY_MAGMA_ARMOR && (type == TYPE_FIRE || type == TYPE_GROUND)) + gBattleMovePower = (50 * gBattleMovePower) / 100; if (attacker->ability == ABILITY_HUSTLE) && (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)) + if (attacker->ability == ABILITY_PLUS && HasOtherRockType(gBattlerAttacker)) spAttack = (150 * spAttack) / 100; - if (attacker->ability == ABILITY_MINUS && ABILITY_ON_FIELD2(ABILITY_PLUS)) + if (attacker->ability == ABILITY_MINUS && HasOtherElectricType(gBattlerAttacker)) + spAttack = (150 * spAttack) / 100; + if (defender->ability == ABILITY_PLUS && HasOtherRockType(gBattlerAttacker)) + spAttack = (150 * spAttack) / 100; + if (defender->ability == ABILITY_MINUS && HasOtherElectricType(gBattlerAttacker)) spAttack = (150 * spAttack) / 100; if (attacker->ability == ABILITY_GUTS && attacker->status1) attack = (150 * attack) / 100; @@ -2562,7 +2599,14 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de damage = attack; } else + { + // ADD: Pressure on defender ignores attacker's Attack boosts + if (defender->ability == ABILITY_PRESSURE + && attacker->statStages[STAT_ATK] > DEFAULT_STAT_STAGE) + damage = attack; + else APPLY_STAT_MOD(damage, attacker, attack, STAT_ATK) + } damage = damage * gBattleMovePower; damage *= (2 * attacker->level / 5 + 2); @@ -2576,7 +2620,14 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de damageHelper = defense; } else - APPLY_STAT_MOD(damageHelper, defender, defense, STAT_DEF) + { + // ADD: Pressure on attacker ignores foe Defense boosts + if (attacker->ability == ABILITY_PRESSURE + && defender->statStages[STAT_DEF] > DEFAULT_STAT_STAGE) + damageHelper = defense; + else + APPLY_STAT_MOD(damageHelper, defender, defense, STAT_DEF) + } damage = damage / damageHelper; damage /= 50; @@ -2617,8 +2668,14 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de damage = spAttack; } else - APPLY_STAT_MOD(damage, attacker, spAttack, STAT_SPATK) - + { + // ADD: Pressure on defender ignores attacker's SpAtk boosts + if (defender->ability == ABILITY_PRESSURE + && attacker->statStages[STAT_SPATK] > DEFAULT_STAT_STAGE) + damage = spAttack; + else + APPLY_STAT_MOD(damage, attacker, spAttack, STAT_SPATK) + } damage = damage * gBattleMovePower; damage *= (2 * attacker->level / 5 + 2); @@ -2631,7 +2688,14 @@ s32 CalculateBaseDamage(struct BattlePokemon *attacker, struct BattlePokemon *de damageHelper = spDefense; } else - APPLY_STAT_MOD(damageHelper, defender, spDefense, STAT_SPDEF) + { + // ADD: Pressure ignores SpDef boosts + if (attacker->ability == ABILITY_PRESSURE + && defender->statStages[STAT_SPATK] > DEFAULT_STAT_STAGE) + damageHelper = spDefense; + else + APPLY_STAT_MOD(damageHelper, defender, spDefense, STAT_SPDEF) + } damage = (damage / damageHelper); damage /= 50;