Refactor OHKO Moves (#8916)

This commit is contained in:
Alex 2026-01-21 11:55:30 +01:00 committed by GitHub
parent 3ceaec2e60
commit 6c05a08750
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 282 additions and 308 deletions

View File

@ -2675,18 +2675,6 @@ BattleScript_AbilityPreventsRest::
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
BattleScript_EffectOHKO::
attackcanceler
typecalc
jumpifmovehadnoeffect BattleScript_HitFromAtkAnimation
tryKO BattleScript_KOFail
goto BattleScript_HitFromAtkAnimation
BattleScript_KOFail::
pause B_WAIT_TIME_LONG
printfromtable gKOFailedStringIds
waitmessage B_WAIT_TIME_LONG
goto BattleScript_MoveEnd
BattleScript_RecoilIfMiss::
printstring STRINGID_PKMNCRASHED
waitmessage B_WAIT_TIME_LONG

View File

@ -587,7 +587,6 @@ extern const u8 BattleScript_EffectLightScreen[];
extern const u8 BattleScript_EffectRest[];
extern const u8 BattleScript_RestIsAlreadyAsleep[];
extern const u8 BattleScript_InsomniaProtects[];
extern const u8 BattleScript_EffectOHKO[];
extern const u8 BattleScript_EffectHealBlock[];
extern const u8 BattleScript_RecoilIfMiss[];
extern const u8 BattleScript_EffectMist[];

View File

@ -463,6 +463,8 @@ bool32 IsHazardOnSideAndClear(enum BattleSide side, enum Hazards hazardType);
void RemoveHazardFromField(enum BattleSide side, enum Hazards hazardType);
bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move, enum ResultOption option);
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, enum Move move, enum Ability atkAbility, enum Ability defAbility, enum HoldEffect atkHoldEffect, enum HoldEffect defHoldEffect);
bool32 DoesOHKOMoveMissTarget(struct BattleCalcValues *cv);
bool32 DoesMoveMissTarget(struct BattleCalcValues *cv);
bool32 IsSemiInvulnerable(u32 battler, enum SemiInvulnerableExclusion excludeCommander);
bool32 BreaksThroughSemiInvulnerablity(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move);
bool32 HasPartnerTrainer(u32 battler);

View File

@ -406,11 +406,13 @@ enum TypeSideHazard
#define MOVE_RESULT_NOT_VERY_EFFECTIVE (1 << 2)
#define MOVE_RESULT_DOESNT_AFFECT_FOE (1 << 3)
#define MOVE_RESULT_ONE_HIT_KO (1 << 4)
#define MOVE_RESULT_FAILED (1 << 5)
#define MOVE_RESULT_FOE_ENDURED (1 << 6)
#define MOVE_RESULT_FOE_HUNG_ON (1 << 7)
#define MOVE_RESULT_STURDIED (1 << 8)
#define MOVE_RESULT_FOE_ENDURED_AFFECTION (1 << 9)
#define MOVE_RESULT_ONE_HIT_KO_NO_AFFECT (1 << 5)
#define MOVE_RESULT_ONE_HIT_KO_STURDY (1 << 6)
#define MOVE_RESULT_FAILED (1 << 7)
#define MOVE_RESULT_FOE_ENDURED (1 << 8)
#define MOVE_RESULT_FOE_HUNG_ON (1 << 9)
#define MOVE_RESULT_STURDIED (1 << 10)
#define MOVE_RESULT_FOE_ENDURED_AFFECTION (1 << 11)
#define MOVE_RESULT_AVOIDED_ATTACK (MOVE_RESULT_MISSED | MOVE_RESULT_FAILED)
#define MOVE_RESULT_NO_EFFECT (MOVE_RESULT_MISSED | MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE)

View File

@ -33,7 +33,6 @@ enum __attribute__((packed)) BattleMoveEffects
EFFECT_LIGHT_SCREEN,
EFFECT_REST,
EFFECT_OHKO,
EFFECT_SHEER_COLD, // Same as EFFECT_OHKO but Ice-types are immune to it and has decreased accuracy for non Ice-type users.
EFFECT_FUSION_COMBO,
EFFECT_FIXED_PERCENT_DAMAGE,
EFFECT_FIXED_HP_DAMAGE,

View File

@ -1,6 +1,7 @@
#ifndef GUARD_MOVES_H
#define GUARD_MOVES_H
#include "generational_changes.h"
#include "contest_effect.h"
#include "constants/battle.h"
#include "constants/battle_factory.h"
@ -94,6 +95,7 @@ struct MoveInfo
u32 criticalHitStage:2;
bool32 alwaysCriticalHit:1;
u32 numAdditionalEffects:3; // limited to 7
// Flags
bool32 makesContact:1;
bool32 ignoresProtect:1;
@ -126,6 +128,12 @@ struct MoveInfo
bool32 alwaysHitsInRain:1;
bool32 accuracy50InSun:1;
bool32 alwaysHitsInHailSnow:1;
bool32 alwaysHitsOnSameType:1; // Always hits if user is of same type as move
bool32 noAffectOnSameTypeTarget:1; // Fails if target is of same type as move
bool32 accIncreaseByTenOnSameType:1; // Accuracy is increased by 10% if user is of same type as move
bool32 padding1:15;
// end of word
// Ban flags
bool32 gravityBanned:1;
bool32 mirrorMoveBanned:1;
@ -143,7 +151,7 @@ struct MoveInfo
bool32 dampBanned:1;
//Other
bool32 validApprenticeMove:1;
u32 padding:3;
u32 padding2:17;
// end of word
union {
@ -165,7 +173,7 @@ struct MoveInfo
} reflectDamage;
struct {
u16 terrain;
u16 percent:14;
u16 percent:13;
enum TerrainGroundCheck groundCheck:2;
u16 hitsBothFoes:1;
} terrainBoost;
@ -458,6 +466,29 @@ static inline bool32 MoveAlwaysHitsInHailSnow(enum Move moveId)
return gMovesInfo[SanitizeMoveId(moveId)].alwaysHitsInHailSnow;
}
static inline bool32 MoveAlwaysHitsOnSameType(enum Move moveId)
{
#if TESTING
if (moveId == MOVE_TOXIC && GetConfig(CONFIG_TOXIC_NEVER_MISS) < GEN_6)
return FALSE;
#endif
return gMovesInfo[SanitizeMoveId(moveId)].alwaysHitsOnSameType;
}
static inline bool32 MoveHasNoEffectOnSameType(enum Move moveId)
{
#if TESTING
if (moveId == MOVE_SHEER_COLD && GetConfig(CONFIG_SHEER_COLD_IMMUNITY) < GEN_7)
return FALSE;
#endif
return gMovesInfo[SanitizeMoveId(moveId)].noAffectOnSameTypeTarget;
}
static inline bool32 MoveHasIncreasedAccByTenOnSameType(enum Move moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].accIncreaseByTenOnSameType;
}
static inline bool32 IsMoveGravityBanned(enum Move moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].gravityBanned;

View File

@ -1098,18 +1098,14 @@ void Environment_(u32 sourceLine, u32 environment);
static inline bool8 IsMultibattleTest(void)
{
if (TESTING)
#if TESTING
{
if (((gBattleTypeFlags & BATTLE_MULTI_TEST) == BATTLE_MULTI_TEST)
|| ((gBattleTypeFlags & BATTLE_TWO_VS_ONE_TEST) == BATTLE_TWO_VS_ONE_TEST))
|| ((gBattleTypeFlags & BATTLE_TWO_VS_ONE_TEST) == BATTLE_TWO_VS_ONE_TEST))
return TRUE;
else
return FALSE;
}
else
{
return FALSE;
}
#endif
return FALSE;
}
// Created for easy use of EXPECT_MOVES, so the user can provide 1, 2, 3 or 4 moves for AI which can pass the test.

View File

@ -1805,10 +1805,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, enum Move move, s32 s
|| !(weather & (B_WEATHER_ICY_ANY)))
ADJUST_SCORE(-10);
break;
case EFFECT_SHEER_COLD:
if (GetConfig(CONFIG_SHEER_COLD_IMMUNITY) >= GEN_7 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE))
RETURN_SCORE_MINUS(20);
// fallthrough
case EFFECT_OHKO:
if (!ShouldTryOHKO(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move))
ADJUST_SCORE(-10);
@ -4638,7 +4634,6 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, enum Move move
}
break;
case EFFECT_OHKO:
case EFFECT_SHEER_COLD:
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
break;
else if (gBattleMons[battlerAtk].volatiles.lockOn)
@ -4758,7 +4753,7 @@ static s32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, enum Move move
ADJUST_SCORE(BEST_EFFECT);
break;
case EFFECT_LOCK_ON:
if (HasMoveWithEffect(battlerAtk, EFFECT_OHKO) || HasMoveWithEffect(battlerAtk, EFFECT_SHEER_COLD))
if (HasMoveWithEffect(battlerAtk, EFFECT_OHKO))
ADJUST_SCORE(GOOD_EFFECT);
else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 85, TRUE))
ADJUST_SCORE(GOOD_EFFECT);
@ -6344,7 +6339,6 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, enum Move move, s32 score)
case EFFECT_FLATTER:
case EFFECT_ATTRACT:
case EFFECT_OHKO:
case EFFECT_SHEER_COLD:
ADJUST_SCORE(AVERAGE_RISKY_EFFECT);
break;
case EFFECT_HIT:

View File

@ -2171,7 +2171,7 @@ bool32 ShouldTryOHKO(u32 battlerAtk, u32 battlerDef, enum Ability atkAbility, en
else // test the odds
{
u32 odds = accuracy + (gBattleMons[battlerAtk].level - gBattleMons[battlerDef].level);
if (B_SHEER_COLD_ACC >= GEN_7 && GetMoveEffect(move) == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE))
if (MoveHasIncreasedAccByTenOnSameType(move) && !IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(move)))
odds -= 10;
if (Random() % 100 + 1 < odds && gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level)
return TRUE;

View File

@ -344,8 +344,9 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler)
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
}
if (TESTING)
TestRunner_Battle_CheckSwitch(battler, chosenMonId);
#if TESTING
TestRunner_Battle_CheckSwitch(battler, chosenMonId);
#endif
BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, chosenMonId, NULL);
BtlController_Complete(battler);
}

View File

@ -3945,7 +3945,6 @@ static bool32 IsDomeLuckyMove(enum Move move)
return FALSE;
// fallthrough
case EFFECT_OHKO:
case EFFECT_SHEER_COLD:
case EFFECT_METRONOME:
case EFFECT_MIRROR_MOVE:
case EFFECT_SKETCH:

View File

@ -402,7 +402,6 @@ static enum MaxPowerTier GetMaxPowerTier(enum Move move)
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:

View File

@ -44,10 +44,11 @@ bool32 CanActivateGimmick(u32 battler, enum Gimmick gimmick)
bool32 IsGimmickSelected(u32 battler, enum Gimmick gimmick)
{
// There's no player select in tests, but some gimmicks need to test choice before they are fully activated.
if (TESTING)
return (gBattleStruct->gimmick.toActivate & (1u << battler)) && gBattleStruct->gimmick.usableGimmick[battler] == gimmick;
else
return gBattleStruct->gimmick.usableGimmick[battler] == gimmick && gBattleStruct->gimmick.playerSelect;
#if TESTING
return (gBattleStruct->gimmick.toActivate & (1u << battler)) && gBattleStruct->gimmick.usableGimmick[battler] == gimmick;
#else
return gBattleStruct->gimmick.usableGimmick[battler] == gimmick && gBattleStruct->gimmick.playerSelect;
#endif
}
// Sets a battler as having a gimmick active using their party index.
@ -66,13 +67,11 @@ enum Gimmick GetActiveGimmick(u32 battler)
bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick)
{
// There are no trainer party settings in battles, but the AI needs to know which gimmick to use.
if (TESTING)
{
return gimmick == TestRunner_Battle_GetChosenGimmick(GetBattlerTrainer(battler), gBattlerPartyIndexes[battler]);
}
#if TESTING
return gimmick == TestRunner_Battle_GetChosenGimmick(GetBattlerTrainer(battler), gBattlerPartyIndexes[battler]);
#else
// The player can bypass these checks because they can choose through the controller.
else if (IsOnPlayerSide(battler)
&& !((gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT))
if (IsOnPlayerSide(battler) && !((gBattleTypeFlags & BATTLE_TYPE_MULTI) && battler == B_POSITION_PLAYER_RIGHT))
{
return TRUE;
}
@ -84,6 +83,7 @@ bool32 ShouldTrainerBattlerUseGimmick(u32 battler, enum Gimmick gimmick)
if (gimmick == GIMMICK_DYNAMAX && gBattleStruct->opponentMonCanDynamax & 1 << gBattlerPartyIndexes[battler])
return TRUE;
}
#endif
return FALSE;
}

View File

@ -567,11 +567,10 @@ static void CB2_InitBattleInternal(void)
TryFormChange(i, B_SIDE_OPPONENT, FORM_CHANGE_BEGIN_BATTLE);
}
if (TESTING)
{
gPlayerPartyCount = CalculatePartyCount(gPlayerParty);
gEnemyPartyCount = CalculatePartyCount(gEnemyParty);
}
#if TESTING
gPlayerPartyCount = CalculatePartyCount(gPlayerParty);
gEnemyPartyCount = CalculatePartyCount(gEnemyParty);
#endif
gBattleCommunication[MULTIUSE_STATE] = 0;
}
@ -2989,10 +2988,10 @@ static void ClearSetBScriptingStruct(void)
memset(&gBattleScripting, 0, sizeof(gBattleScripting));
gBattleScripting.windowsType = temp;
if (TESTING)
gBattleScripting.battleStyle = gSaveBlock2Ptr->optionsBattleStyle;
#if TESTING
gBattleScripting.battleStyle = OPTIONS_BATTLE_STYLE_SET;
else
gBattleScripting.battleStyle = gSaveBlock2Ptr->optionsBattleStyle;
#endif
gBattleScripting.expOnCatch = (GetConfig(CONFIG_EXP_CATCH) >= GEN_6);
gBattleScripting.specialTrainerBattleType = specialBattleType;
}
@ -6030,8 +6029,7 @@ void SetTypeBeforeUsingMove(enum Move move, u32 battler)
if (holdEffect == HOLD_EFFECT_GEMS
&& GetBattleMoveType(move) == GetItemSecondaryId(heldItem)
&& effect != EFFECT_PLEDGE
&& effect != EFFECT_OHKO
&& effect != EFFECT_SHEER_COLD)
&& effect != EFFECT_OHKO)
{
gSpecialStatuses[battler].gemParam = GetBattlerHoldEffectParam(battler);
gSpecialStatuses[battler].gemBoost = TRUE;

View File

@ -1117,9 +1117,8 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u
{
enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker);
enum HoldEffect holdEffectAtk = GetBattlerHoldEffect(gBattlerAttacker);
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
if (ShouldSkipAccuracyCalcPastFirstHit(gBattlerAttacker, abilityAtk, holdEffectAtk, effect)
if (ShouldSkipAccuracyCalcPastFirstHit(gBattlerAttacker, abilityAtk, holdEffectAtk, GetMoveEffect(gCurrentMove))
|| IsMaxMove(gCurrentMove)
|| IsZMove(gCurrentMove))
{
@ -1143,23 +1142,19 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u
continue;
numTargets++;
enum Ability abilityDef = GetBattlerAbility(battlerDef);
if (CanMoveSkipAccuracyCalc(gBattlerAttacker, battlerDef, abilityAtk, abilityDef, gCurrentMove, RUN_SCRIPT))
continue;
struct BattleCalcValues cv = {0};
cv.move = gCurrentMove;
cv.battlerAtk = gBattlerAttacker;
cv.battlerDef = battlerDef;
cv.abilities[gBattlerAttacker] = abilityAtk;
cv.abilities[battlerDef] = GetBattlerAbility(battlerDef);
cv.holdEffects[gBattlerAttacker] = holdEffectAtk;
cv.holdEffects[battlerDef] = GetBattlerHoldEffect(battlerDef);
u32 holdEffectDef = GetBattlerHoldEffect(battlerDef);
u32 accuracy = GetTotalAccuracy(gBattlerAttacker,
battlerDef,
gCurrentMove,
abilityAtk,
abilityDef,
holdEffectAtk,
holdEffectDef);
if (!RandomPercentage(RNG_ACCURACY, accuracy))
if (DoesMoveMissTarget(&cv))
{
gBattleStruct->moveResultFlags[battlerDef] = MOVE_RESULT_MISSED;
gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_MISSED;
gBattleCommunication[MISS_TYPE] = B_MSG_MISSED;
numMisses++;
@ -1192,10 +1187,24 @@ static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_MISSED)
{
gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_MISSED;
gLastLandedMoves[gBattlerTarget] = 0;
gLastHitByType[gBattlerTarget] = 0;
gBattlescriptCurrInstr = failInstr;
if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_ONE_HIT_KO_STURDY)
{
gLastUsedAbility = ABILITY_STURDY;
gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO;
gBattlerAbility = gBattlerTarget;
}
else
{
gBattlescriptCurrInstr = failInstr;
}
if (gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_ONE_HIT_KO_NO_AFFECT | MOVE_RESULT_ONE_HIT_KO_STURDY))
gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_DOESNT_AFFECT_FOE;
else
gBattleStruct->moveResultFlags[gBattlerTarget] = MOVE_RESULT_MISSED;
}
else
{
@ -8338,126 +8347,9 @@ static void Cmd_setlightscreen(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
// for var lands
#define NO_HIT 0
#define CALC_ACC 1
#define SURE_HIT 2
// for var endured
#define NOT_ENDURED 0
#define FOCUS_SASHED 1
#define FOCUS_BANDED 2
#define AFFECTION_ENDURED 3
static void Cmd_tryKO(void)
{
CMD_ARGS(const u8 *failInstr);
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
enum HoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget);
enum Ability targetAbility = GetBattlerAbility(gBattlerTarget);
u32 rand = Random() % 100;
u32 affectionScore = GetBattlerAffectionHearts(gBattlerTarget);
u32 endured = NOT_ENDURED;
// Dynamaxed Pokemon cannot be hit by OHKO moves.
if ((GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX))
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_UNAFFECTED;
gBattlescriptCurrInstr = cmd->failInstr;
return;
}
gPotentialItemEffectBattler = gBattlerTarget;
if (holdEffect == HOLD_EFFECT_FOCUS_BAND
&& (Random() % 100) < GetBattlerHoldEffectParam(gBattlerTarget))
{
endured = FOCUS_BANDED;
RecordItemEffectBattle(gBattlerTarget, holdEffect);
}
else if (holdEffect == HOLD_EFFECT_FOCUS_SASH && IsBattlerAtMaxHp(gBattlerTarget))
{
endured = FOCUS_SASHED;
RecordItemEffectBattle(gBattlerTarget, holdEffect);
}
else if (B_AFFECTION_MECHANICS == TRUE && IsOnPlayerSide(gBattlerTarget) && affectionScore >= AFFECTION_THREE_HEARTS)
{
if ((affectionScore == AFFECTION_FIVE_HEARTS && rand < 20)
|| (affectionScore == AFFECTION_FOUR_HEARTS && rand < 15)
|| (affectionScore == AFFECTION_THREE_HEARTS && rand < 10))
endured = AFFECTION_ENDURED;
}
if (targetAbility == ABILITY_STURDY)
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
gLastUsedAbility = ABILITY_STURDY;
gBattlescriptCurrInstr = BattleScript_SturdyPreventsOHKO;
gBattlerAbility = gBattlerTarget;
}
else
{
u32 lands = NO_HIT;
if (gBattleMons[gBattlerTarget].level > gBattleMons[gBattlerAttacker].level)
lands = NO_HIT;
else if ((gBattleMons[gBattlerTarget].volatiles.lockOn && gBattleMons[gBattlerTarget].volatiles.battlerWithSureHit == gBattlerAttacker)
|| IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_NO_GUARD)
|| IsAbilityAndRecord(gBattlerTarget, targetAbility, ABILITY_NO_GUARD))
lands = SURE_HIT;
else
lands = CALC_ACC;
if (lands == CALC_ACC)
{
u16 odds = GetMoveAccuracy(gCurrentMove) + (gBattleMons[gBattlerAttacker].level - gBattleMons[gBattlerTarget].level);
if (B_SHEER_COLD_ACC >= GEN_7 && effect == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE))
odds -= 10;
if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level)
lands = SURE_HIT;
}
if (lands == SURE_HIT)
{
if (gBattleMons[gBattlerTarget].volatiles.endured)
{
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1;
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_ENDURED;
}
else if (endured == FOCUS_BANDED || endured == FOCUS_SASHED)
{
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1;
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_HUNG_ON;
gLastUsedItem = gBattleMons[gBattlerTarget].item;
}
else if (endured == AFFECTION_ENDURED)
{
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1;
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_ENDURED_AFFECTION;
}
else
{
gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp;
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_ONE_HIT_KO;
}
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED;
if (gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_MISS;
else
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_KO_UNAFFECTED;
gBattlescriptCurrInstr = cmd->failInstr;
}
}
}
#undef NO_HIT
#undef CALC_ACC
#undef SURE_HIT
#undef NOT_ENDURED
#undef FOCUS_SASHED
#undef FOCUS_BANDED
#undef AFFECTION_ENDURED
static void Cmd_checknonvolatiletrigger(void)
{

View File

@ -3853,7 +3853,6 @@ static void ForewarnChooseMove(u32 battler)
switch (GetMoveEffect(data[count].moveId))
{
case EFFECT_OHKO:
case EFFECT_SHEER_COLD:
data[count].power = 150;
break;
case EFFECT_REFLECT_DAMAGE:
@ -4880,9 +4879,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab
ctx.isAnticipation = TRUE;
modifier = CalcTypeEffectivenessMultiplier(&ctx);
if (modifier >= UQ_4_12(2.0)
|| moveEffect == EFFECT_OHKO
|| moveEffect == EFFECT_SHEER_COLD)
if (modifier >= UQ_4_12(2.0) || moveEffect == EFFECT_OHKO)
{
effect++;
break;
@ -9508,6 +9505,9 @@ s32 DoFixedDamageMoveCalc(struct BattleContext *ctx)
dmg = GetNonDynamaxHP(ctx->battlerDef) - gBattleMons[ctx->battlerAtk].hp;
}
break;
case EFFECT_OHKO:
dmg = gBattleMons[ctx->battlerDef].hp;
break;
default:
break;
}
@ -10028,7 +10028,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct BattleCont
ctx->airBalloonBlocked = TRUE;
}
}
else if (GetConfig(CONFIG_SHEER_COLD_IMMUNITY) >= GEN_7 && GetMoveEffect(ctx->move) == EFFECT_SHEER_COLD && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_ICE))
else if (MoveHasNoEffectOnSameType(ctx->move) && IS_BATTLER_OF_TYPE(ctx->battlerDef, GetMoveType(ctx->move)))
{
modifier = UQ_4_12(0.0);
}
@ -12058,11 +12058,9 @@ void RemoveHazardFromField(enum BattleSide side, enum Hazards hazardType)
}
}
static bool32 CanToxicSkipAccuracyCheck(u32 battlerAtk, u32 move)
static bool32 CanMoveSkipAccuracyCheck(u32 battlerAtk, u32 move)
{
if (GetConfig(CONFIG_TOXIC_NEVER_MISS) < GEN_6)
return FALSE;
return move == MOVE_TOXIC && IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON);
return MoveAlwaysHitsOnSameType(move) && IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(move));
}
bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, enum Move move, enum ResultOption option)
@ -12072,7 +12070,7 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abil
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
if ((gBattleMons[battlerDef].volatiles.lockOn && gBattleMons[battlerDef].volatiles.battlerWithSureHit == battlerAtk)
|| CanToxicSkipAccuracyCheck(battlerAtk, move)
|| CanMoveSkipAccuracyCheck(battlerAtk, move)
|| gBattleMons[battlerDef].volatiles.glaiveRush)
{
effect = TRUE;
@ -12095,8 +12093,7 @@ bool32 CanMoveSkipAccuracyCalc(u32 battlerAtk, u32 battlerDef, enum Ability abil
// If the target is under the effects of Telekinesis, and the move isn't a OH-KO move, move hits.
else if (gBattleMons[battlerDef].volatiles.telekinesis
&& !IsSemiInvulnerable(battlerDef, CHECK_ALL)
&& moveEffect != EFFECT_OHKO
&& moveEffect != EFFECT_SHEER_COLD)
&& moveEffect != EFFECT_OHKO)
{
effect = TRUE;
}
@ -12223,6 +12220,9 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, enum Move move, enum Abilit
break;
}
if (MoveHasIncreasedAccByTenOnSameType(move) && !IS_BATTLER_OF_TYPE(battlerAtk, GetMoveType(move)))
calc = (calc * 110) / 100;
// Attacker's hold effect
switch (atkHoldEffect)
{
@ -12268,6 +12268,85 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, enum Move move, enum Abilit
return calc;
}
bool32 DoesOHKOMoveMissTarget(struct BattleCalcValues *cv)
{
enum OHKOResult {
NO_HIT,
CALC_ACC,
SURE_HIT,
};
// Dynamaxed Pokemon cannot be hit by OHKO moves.
if (GetActiveGimmick(cv->battlerDef) == GIMMICK_DYNAMAX)
{
gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_NO_AFFECT;
return TRUE;
}
if (cv->abilities[cv->battlerDef] == ABILITY_STURDY)
{
gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_STURDY;
return TRUE;
}
enum OHKOResult lands = NO_HIT;
if (gBattleMons[cv->battlerDef].level > gBattleMons[cv->battlerAtk].level)
{
lands = NO_HIT;
}
else if ((gBattleMons[cv->battlerDef].volatiles.lockOn && gBattleMons[cv->battlerDef].volatiles.battlerWithSureHit == cv->battlerAtk)
|| IsAbilityAndRecord(cv->battlerAtk, cv->abilities[cv->battlerAtk], ABILITY_NO_GUARD)
|| IsAbilityAndRecord(cv->battlerDef, cv->abilities[cv->battlerDef], ABILITY_NO_GUARD))
{
lands = SURE_HIT;
}
else
{
lands = CALC_ACC;
}
if (lands == CALC_ACC)
{
u32 odds = GetMoveAccuracy(cv->move) + (gBattleMons[cv->battlerAtk].level - gBattleMons[cv->battlerDef].level);
if (MoveHasIncreasedAccByTenOnSameType(cv->move) && !IS_BATTLER_OF_TYPE(cv->battlerAtk, GetMoveType(cv->move)))
odds -= 10;
if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[cv->battlerAtk].level >= gBattleMons[cv->battlerDef].level)
lands = SURE_HIT;
}
if (lands == SURE_HIT)
{
gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_NO_AFFECT;
return FALSE;
}
if (gBattleMons[cv->battlerAtk].level < gBattleMons[cv->battlerDef].level)
gBattleStruct->moveResultFlags[cv->battlerDef] |= MOVE_RESULT_ONE_HIT_KO_NO_AFFECT;
return TRUE;
}
bool32 DoesMoveMissTarget(struct BattleCalcValues *cv)
{
if (GetMoveEffect(cv->move) == EFFECT_OHKO)
return DoesOHKOMoveMissTarget(cv);
if (CanMoveSkipAccuracyCalc(cv->battlerAtk, cv->battlerDef, cv->abilities[cv->battlerAtk], cv->abilities[cv->battlerDef], cv->move, RUN_SCRIPT))
return FALSE;
u32 accuracy = GetTotalAccuracy(
cv->battlerAtk,
cv->battlerDef,
cv->move,
cv->abilities[cv->battlerAtk],
cv->abilities[cv->battlerDef],
cv->holdEffects[cv->battlerAtk],
cv->holdEffects[cv->battlerDef]
);
return !RandomPercentage(RNG_ACCURACY, accuracy);
}
bool32 IsSemiInvulnerable(u32 battler, enum SemiInvulnerableExclusion excludeCommander)
{
if (gBattleMons[battler].volatiles.semiInvulnerable == STATE_COMMANDER)
@ -12281,7 +12360,7 @@ bool32 BreaksThroughSemiInvulnerablity(u32 battlerAtk, u32 battlerDef, enum Abil
if (state != STATE_COMMANDER)
{
if (CanToxicSkipAccuracyCheck(battlerAtk, move))
if (CanMoveSkipAccuracyCheck(battlerAtk, move))
return TRUE;
if (abilityAtk == ABILITY_NO_GUARD || abilityDef == ABILITY_NO_GUARD)
return TRUE;

View File

@ -548,7 +548,7 @@ u32 GetZMovePower(enum Move move)
if (GetMoveCategory(move) == DAMAGE_CATEGORY_STATUS)
return 0;
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
if (moveEffect == EFFECT_OHKO || moveEffect == EFFECT_SHEER_COLD)
if (moveEffect == EFFECT_OHKO)
return 180;
u32 power = GetMoveZPowerOverride(move);

View File

@ -216,17 +216,11 @@ const struct BattleMoveEffect gBattleMoveEffects[NUM_BATTLE_MOVE_EFFECTS] =
[EFFECT_OHKO] =
{
.battleScript = BattleScript_EffectOHKO,
.battleScript = BattleScript_EffectHit,
.battleTvScore = 7,
.battleFactoryStyle = FACTORY_STYLE_HIGH_RISK,
},
[EFFECT_SHEER_COLD] =
{
.battleScript = BattleScript_EffectOHKO,
.battleTvScore = 7,
},
[EFFECT_FUSION_COMBO] =
{
.battleScript = BattleScript_EffectHit,

View File

@ -2544,6 +2544,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.argument = { .nonVolatileStatus = MOVE_EFFECT_TOXIC },
.zMove = { .effect = Z_EFFECT_DEF_UP_1 },
.magicCoatAffected = TRUE,
.alwaysHitsOnSameType = B_TOXIC_NEVER_MISS >= GEN_6,
.contestEffect = CONTEST_EFFECT_WORSEN_CONDITION_OF_PREV_MONS,
.contestCategory = CONTEST_CATEGORY_SMART,
.contestComboStarterId = COMBO_STARTER_TOXIC,
@ -8839,13 +8840,15 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.description = COMPOUND_STRING(
"A chilling attack that\n"
"causes fainting if it hits."),
.effect = EFFECT_SHEER_COLD,
.effect = EFFECT_OHKO,
.power = 1,
.type = TYPE_ICE,
.accuracy = 30,
.pp = 5,
.target = TARGET_SELECTED,
.priority = 0,
.noAffectOnSameTypeTarget = B_SHEER_COLD_IMMUNITY >= GEN_7,
.accIncreaseByTenOnSameType = B_SHEER_COLD_ACC >= GEN_7,
.category = DAMAGE_CATEGORY_SPECIAL,
.contestEffect = CONTEST_EFFECT_BADLY_STARTLE_MONS_WITH_GOOD_APPEALS,
.contestCategory = CONTEST_CATEGORY_BEAUTY,

View File

@ -299,7 +299,7 @@ AI_DOUBLE_BATTLE_TEST("AI can use all moves, 301-400")
switch (effect)
{
//TODO: AI HANDLING
case EFFECT_SHEER_COLD: // Guillotine is crashing the test entirely.
case EFFECT_OHKO: // Guillotine is crashing the test entirely.
case EFFECT_WATER_SPORT:
case EFFECT_LUCKY_CHANT:
case EFFECT_ME_FIRST:

View File

@ -224,7 +224,7 @@ SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon cannot be hit by OHKO moves")
} SCENE {
MESSAGE("Wobbuffet used Max Strike!");
MESSAGE("The opposing Machamp used Fissure!");
MESSAGE("Wobbuffet is unaffected!");
MESSAGE("It doesn't affect Wobbuffet…");
NONE_OF { HP_BAR(player); }
}
}

View File

@ -310,7 +310,7 @@ static void WhenSingles(enum Move move, struct BattlePokemon *attacker, struct B
MOVE(attacker, move);
MOVE(defender, MOVE_SWORDS_DANCE);
}
else if (effect == EFFECT_OHKO || effect == EFFECT_SHEER_COLD)
else if (effect == EFFECT_OHKO)
{ // defender needs to send out a different team member
MOVE(attacker, move);
SEND_OUT(defender, 1);
@ -526,7 +526,7 @@ static void DoublesWhen(enum Move move, struct BattlePokemon *attacker, struct B
MOVE(attacker, move, target: target);
MOVE(target, MOVE_SWORDS_DANCE);
}
else if (effect == EFFECT_OHKO || effect == EFFECT_SHEER_COLD)
else if (effect == EFFECT_OHKO)
{ // Opponent needs to send out a different team member
MOVE(attacker, move, target: target);
SEND_OUT(target, 2);

View File

@ -83,6 +83,51 @@ SINGLE_BATTLE_TEST("OHKO moves fail if target protects")
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player);
}
}
SINGLE_BATTLE_TEST("Sheer Cold can hit semi-invulnerable mons when the user has No-Guard")
{
GIVEN {
ASSUME(GetItemHoldEffect(ITEM_FOCUS_SASH) == HOLD_EFFECT_FOCUS_SASH);
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_NO_GUARD); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_FLY); }
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
HP_BAR(opponent, hp: 0);
}
}
SINGLE_BATTLE_TEST("Sheer Cold can be endured by Focus Sash")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_FOCUS_SASH); }
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
HP_BAR(opponent, hp: 1);
MESSAGE("The opposing Wobbuffet hung on using its Focus Sash!");
}
}
SINGLE_BATTLE_TEST("Sheer Cold can be endured by Sturdy")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); }
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
ABILITY_POPUP(opponent, ABILITY_STURDY);
}
}
TO_DO_BATTLE_TEST("OHKO moves faints the target, skipping regular damage calculations")
TO_DO_BATTLE_TEST("OHKO moves's accuracy increases by 1% for every level the user has over the target")
TO_DO_BATTLE_TEST("OHKO moves's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes
TO_DO_BATTLE_TEST("OHKO moves ignore non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes
TO_DO_BATTLE_TEST("OHKO: Sheer Cold's accuracy decreasaes by 10% if the user is not Ice type")

View File

@ -1,86 +0,0 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_SHEER_COLD) == EFFECT_SHEER_COLD);
}
SINGLE_BATTLE_TEST("Sheer Cold doesn't affect Ice-type Pokémon (Gen3-6)")
{
GIVEN {
WITH_CONFIG(CONFIG_SHEER_COLD_IMMUNITY, GEN_6);
ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_GLALIE);
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
NOT MESSAGE("It doesn't affect the opposing Glalie…");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
HP_BAR(opponent, hp: 0);
}
}
SINGLE_BATTLE_TEST("Sheer Cold doesn't affect Ice-type Pokémon (Gen7+)")
{
GIVEN {
WITH_CONFIG(CONFIG_SHEER_COLD_IMMUNITY, GEN_7);
ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_GLALIE);
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
MESSAGE("It doesn't affect the opposing Glalie…");
}
}
SINGLE_BATTLE_TEST("Sheer Cold can hit semi-invulnerable mons when the user has No-Guard")
{
GIVEN {
ASSUME(GetItemHoldEffect(ITEM_FOCUS_SASH) == HOLD_EFFECT_FOCUS_SASH);
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_NO_GUARD); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_FLY); }
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
HP_BAR(opponent, hp: 0);
}
}
SINGLE_BATTLE_TEST("Sheer Cold can be endured by Focus Sash")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_FOCUS_SASH); }
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
HP_BAR(opponent, hp: 1);
MESSAGE("The opposing Wobbuffet hung on using its Focus Sash!");
}
}
SINGLE_BATTLE_TEST("Sheer Cold can be endured by Sturdy")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GEODUDE) { Ability(ABILITY_STURDY); }
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
ABILITY_POPUP(opponent, ABILITY_STURDY);
}
}
TO_DO_BATTLE_TEST("Sheer Cold faints the target, skipping regular damage calculations")
TO_DO_BATTLE_TEST("Sheer Cold always fails if the target has a higher level than the user")
TO_DO_BATTLE_TEST("Sheer Cold's accuracy increases by 1% for every level the user has over the target")
TO_DO_BATTLE_TEST("Sheer Cold's accuracy decreasaes by 10% if the user is not Ice type")
TO_DO_BATTLE_TEST("Sheer Cold's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes

View File

@ -0,0 +1,39 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(MoveHasNoEffectOnSameType(MOVE_SHEER_COLD));
}
SINGLE_BATTLE_TEST("Sheer Cold does affect Ice-type Pokémon (Gen3-6)")
{
GIVEN {
WITH_CONFIG(CONFIG_SHEER_COLD_IMMUNITY, GEN_6);
ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_GLALIE);
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
NOT MESSAGE("It doesn't affect the opposing Glalie…");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
HP_BAR(opponent, hp: 0);
}
}
SINGLE_BATTLE_TEST("Sheer Cold doesn't affect Ice-type Pokémon (Gen7+)")
{
GIVEN {
WITH_CONFIG(CONFIG_SHEER_COLD_IMMUNITY, GEN_7);
ASSUME(GetSpeciesType(SPECIES_GLALIE, 0) == TYPE_ICE);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_GLALIE);
} WHEN {
TURN { MOVE(player, MOVE_SHEER_COLD); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SHEER_COLD, player);
MESSAGE("It doesn't affect the opposing Glalie…");
}
}