Fixes Lock On (#9496)

This commit is contained in:
Alex 2026-03-12 11:47:36 +01:00 committed by GitHub
parent d2cb9eb14b
commit 726588b8b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 146 additions and 33 deletions

View File

@ -211,7 +211,7 @@ enum VolatileFlags
F(VOLATILE_STICKY_SYRUPED_BY, stickySyrupedBy, (enum BattlerId, MAX_BITS(MAX_BATTLERS_COUNT))) \
F(VOLATILE_GLAIVE_RUSH, glaiveRush, (u32, 1)) \
F(VOLATILE_LEECH_SEED, leechSeed, (enum BattlerId, MAX_BITS(MAX_BATTLERS_COUNT)), V_BATON_PASSABLE) \
F(VOLATILE_LOCK_ON, lockOn, (u32, 2), V_BATON_PASSABLE) \
F(VOLATILE_LOCK_ON, lockOn, (u32, 2)) \
F(VOLATILE_PERISH_SONG, perishSong, (u32, 1), V_BATON_PASSABLE) \
F(VOLATILE_MINIMIZE, minimize, (u32, 1)) \
F(VOLATILE_CHARGE_TIMER, chargeTimer, (u32, 3)) \
@ -254,7 +254,7 @@ enum VolatileFlags
F(VOLATILE_FURY_CUTTER_COUNTER, furyCutterCounter, (u32, UINT8_MAX)) \
F(VOLATILE_METRONOME_ITEM_COUNTER, metronomeItemCounter, (u32, UINT8_MAX)) \
F(VOLATILE_BATTLER_PREVENTING_ESCAPE, battlerPreventingEscape, (enum BattlerId, MAX_BITS(MAX_BATTLERS_COUNT))) \
F(VOLATILE_BATTLER_WITH_SURE_HIT, battlerWithSureHit, (enum BattlerId, MAX_BITS(MAX_BATTLERS_COUNT))) \
F(VOLATILE_BATTLER_WITH_SURE_HIT, battlerWithSureHit, (enum BattlerId, MAX_BATTLERS_COUNT)) \
F(VOLATILE_MIMICKED_MOVES, mimickedMoves, (u32, MAX_BITS(MAX_MON_MOVES))) \
F(VOLATILE_RECHARGE_TIMER, rechargeTimer, (u32, 2)) \
F(VOLATILE_AUTOTOMIZE_COUNT, autotomizeCount, (u32, UINT8_MAX)) \

View File

@ -2321,7 +2321,7 @@ static s32 AI_CheckBadMove(enum BattlerId battlerAtk, enum BattlerId battlerDef,
//TODO
break;
case EFFECT_LOCK_ON:
if (gBattleMons[battlerDef].volatiles.lockOn
if (gBattleMons[battlerAtk].volatiles.battlerWithSureHit == battlerDef + 1
|| aiData->abilities[battlerAtk] == ABILITY_NO_GUARD
|| aiData->abilities[battlerDef] == ABILITY_NO_GUARD
|| DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
@ -4696,7 +4696,7 @@ static s32 AI_CalcMoveEffectScore(enum BattlerId battlerAtk, enum BattlerId batt
case EFFECT_OHKO:
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
break;
else if (gBattleMons[battlerAtk].volatiles.lockOn)
else if (gBattleMons[battlerAtk].volatiles.battlerWithSureHit == battlerDef + 1)
ADJUST_SCORE(BEST_EFFECT);
break;
case EFFECT_MEAN_LOOK:

View File

@ -1356,7 +1356,7 @@ enum MoveComparisonResult CompareMoveEffects(enum Move move1, enum Move move2, e
return MOVE_WON_COMPARISON;
if (effect1minus && !effect2minus)
return MOVE_LOST_COMPARISON;
if (effect2plus && !effect1plus)
return MOVE_LOST_COMPARISON;
if (effect1plus && !effect2plus)
@ -2177,10 +2177,8 @@ bool32 ShouldTryOHKO(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum
if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, atkAbility, move) && defAbility == ABILITY_STURDY)
return FALSE;
if (((gBattleMons[battlerDef].volatiles.lockOn
&& gBattleMons[battlerDef].volatiles.battlerWithSureHit == battlerAtk)
|| atkAbility == ABILITY_NO_GUARD || defAbility == ABILITY_NO_GUARD)
&& gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level)
bool32 sureHit = (gBattleMons[battlerAtk].volatiles.battlerWithSureHit == battlerDef + 1) || atkAbility == ABILITY_NO_GUARD || defAbility == ABILITY_NO_GUARD;
if (sureHit && gBattleMons[battlerAtk].level >= gBattleMons[battlerDef].level)
{
return TRUE;
}

View File

@ -60,8 +60,8 @@ static bool32 HandleEndTurnVarious(enum BattlerId battler)
if (gBattleMons[i].volatiles.throatChopTimer > 0)
gBattleMons[i].volatiles.throatChopTimer--;
if (gBattleMons[i].volatiles.lockOn > 0)
gBattleMons[i].volatiles.lockOn--;
if (gBattleMons[i].volatiles.lockOn > 0 && --gBattleMons[i].volatiles.lockOn == 0)
gBattleMons[i].volatiles.battlerWithSureHit = 0;
if (B_CHARGE < GEN_9 && gBattleMons[i].volatiles.chargeTimer > 0)
gBattleMons[i].volatiles.chargeTimer--;

View File

@ -3185,11 +3185,9 @@ void SwitchInClearSetData(enum BattlerId battler, struct Volatiles *volatilesCop
{
if (gBattleMons[i].volatiles.escapePrevention && gBattleMons[i].volatiles.battlerPreventingEscape == battler)
gBattleMons[i].volatiles.escapePrevention = FALSE;
if (gBattleMons[i].volatiles.lockOn && gBattleMons[i].volatiles.battlerWithSureHit == battler)
{
gBattleMons[i].volatiles.lockOn = 0;
if (gBattleMons[i].volatiles.battlerWithSureHit == battler + 1)
gBattleMons[i].volatiles.battlerWithSureHit = 0;
}
}
}
if (effect != EFFECT_BATON_PASS || GetConfig(B_BATON_PASS_TRAPPING) >= GEN_5)
@ -3215,15 +3213,6 @@ void SwitchInClearSetData(enum BattlerId battler, struct Volatiles *volatilesCop
*/
enum BattlerId i;
for (i = 0; i < gBattlersCount; i++)
{
if (!IsBattlerAlly(battler, i)
&& gBattleMons[i].volatiles.lockOn != 0
&& (gBattleMons[i].volatiles.battlerWithSureHit == battler))
{
gBattleMons[i].volatiles.lockOn = 0;
}
}
if (gBattleMons[battler].volatiles.powerTrick)
SWAP(gBattleMons[battler].attack, gBattleMons[battler].defense, i);
}
@ -3249,7 +3238,6 @@ void SwitchInClearSetData(enum BattlerId battler, struct Volatiles *volatilesCop
if (effect == EFFECT_BATON_PASS)
{
gBattleMons[battler].volatiles.substituteHP = volatilesCopy->substituteHP;
gBattleMons[battler].volatiles.battlerWithSureHit = volatilesCopy->battlerWithSureHit;
gBattleMons[battler].volatiles.perishSongTimer = volatilesCopy->perishSongTimer;
gBattleMons[battler].volatiles.battlerPreventingEscape = volatilesCopy->battlerPreventingEscape;
gBattleMons[battler].volatiles.embargoTimer = volatilesCopy->embargoTimer;
@ -3343,6 +3331,8 @@ const u8* FaintClearSetData(enum BattlerId battler)
for (enum BattlerId i = 0; i < gBattlersCount; i++)
{
if (gBattleMons[i].volatiles.battlerWithSureHit == battler + 1)
gBattleMons[i].volatiles.battlerWithSureHit = 0;
if (gBattleMons[i].volatiles.escapePrevention && gBattleMons[i].volatiles.battlerPreventingEscape == battler)
gBattleMons[i].volatiles.escapePrevention = FALSE;
if (gBattleMons[i].volatiles.infatuation == INFATUATED_WITH(battler))

View File

@ -8897,9 +8897,17 @@ static void Cmd_settypetorandomresistance(void)
static void Cmd_setalwayshitflag(void)
{
CMD_ARGS();
gBattleMons[gBattlerTarget].volatiles.lockOn = 2;
gBattleMons[gBattlerTarget].volatiles.battlerWithSureHit = gBattlerAttacker;
gBattlescriptCurrInstr = cmd->nextInstr;
if (gBattleMons[gBattlerAttacker].volatiles.battlerWithSureHit == 0)
{
gBattleMons[gBattlerAttacker].volatiles.lockOn = 2;
gBattleMons[gBattlerAttacker].volatiles.battlerWithSureHit = gBattlerTarget + 1;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = BattleScript_ButItFailed;
}
}
// Sketch

View File

@ -10309,7 +10309,7 @@ bool32 CanMoveSkipAccuracyCalc(enum BattlerId battlerAtk, enum BattlerId battler
enum Ability ability = ABILITY_NONE;
enum BattleMoveEffects moveEffect = GetMoveEffect(move);
if ((gBattleMons[battlerDef].volatiles.lockOn && gBattleMons[battlerDef].volatiles.battlerWithSureHit == battlerAtk)
if (gBattleMons[battlerAtk].volatiles.battlerWithSureHit == battlerDef + 1
|| CanMoveSkipAccuracyCheck(battlerAtk, move)
|| gBattleMons[battlerDef].volatiles.glaiveRush)
{
@ -10535,7 +10535,7 @@ bool32 DoesOHKOMoveMissTarget(struct BattleCalcValues *cv)
{
lands = NO_HIT;
}
else if ((gBattleMons[cv->battlerDef].volatiles.lockOn && gBattleMons[cv->battlerDef].volatiles.battlerWithSureHit == cv->battlerAtk)
else if (gBattleMons[cv->battlerAtk].volatiles.battlerWithSureHit == cv->battlerDef + 1
|| IsAbilityAndRecord(cv->battlerAtk, cv->abilities[cv->battlerAtk], ABILITY_NO_GUARD)
|| IsAbilityAndRecord(cv->battlerDef, cv->abilities[cv->battlerDef], ABILITY_NO_GUARD))
{
@ -10604,7 +10604,7 @@ bool32 BreaksThroughSemiInvulnerablity(enum BattlerId battlerAtk, enum BattlerId
return TRUE;
if (abilityAtk == ABILITY_NO_GUARD || abilityDef == ABILITY_NO_GUARD)
return TRUE;
if (gBattleMons[battlerDef].volatiles.lockOn && gBattleMons[battlerDef].volatiles.battlerWithSureHit == battlerAtk)
if (gBattleMons[battlerAtk].volatiles.battlerWithSureHit == battlerDef + 1)
return TRUE;
}

View File

@ -1,7 +1,10 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Lock-On/Mind Reader (Move Effect) test titles")
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_LOCK_ON) == EFFECT_LOCK_ON);
}
SINGLE_BATTLE_TEST("Lock-On volatile allows to hit through semi-invulnerability")
{
@ -15,6 +18,120 @@ SINGLE_BATTLE_TEST("Lock-On volatile allows to hit through semi-invulnerability"
ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLY, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player);
HP_BAR(opponent); // Pound hit
HP_BAR(opponent); // Pound hit
}
}
SINGLE_BATTLE_TEST("Lock-On skips the accuracy check for 2 turns (Player uses Lock-On)")
{
PASSES_RANDOMLY(10, 10, RNG_ACCURACY);
GIVEN {
ASSUME(GetMoveAccuracy(MOVE_SKY_UPPERCUT) == 90);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_LOCK_ON); }
TURN { MOVE(player, MOVE_SKY_UPPERCUT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_UPPERCUT, player);
} THEN {
u32 lockOn = gBattleMons[B_BATTLER_0].volatiles.lockOn;
u32 battlerWithSureHit = gBattleMons[B_BATTLER_0].volatiles.battlerWithSureHit;
EXPECT_EQ(lockOn, 0);
EXPECT_EQ(battlerWithSureHit, 0);
}
}
SINGLE_BATTLE_TEST("Lock-On skips the accuracy check for 2 turns (Opponent uses Lock-On)")
{
PASSES_RANDOMLY(10, 10, RNG_ACCURACY);
GIVEN {
ASSUME(GetMoveAccuracy(MOVE_SKY_UPPERCUT) == 90);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_LOCK_ON); }
TURN { MOVE(opponent, MOVE_SKY_UPPERCUT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_UPPERCUT, opponent);
} THEN {
u32 lockOn = gBattleMons[B_BATTLER_1].volatiles.lockOn;
u32 battlerWithSureHit = gBattleMons[B_BATTLER_1].volatiles.battlerWithSureHit;
EXPECT_EQ(lockOn, 0);
EXPECT_EQ(battlerWithSureHit, 0);
}
}
SINGLE_BATTLE_TEST("Lock-On: Baton Pass does not transfer Lock-On volatile")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_LOCK_ON); MOVE(opponent, MOVE_BATON_PASS); SEND_OUT(opponent, 1); }
TURN { MOVE(player, MOVE_SKY_UPPERCUT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_BATON_PASS, opponent);
} THEN {
u32 battlerWithSureHit = gBattleMons[B_BATTLER_0].volatiles.battlerWithSureHit;
EXPECT_EQ(battlerWithSureHit, 0);
}
}
SINGLE_BATTLE_TEST("Lock-On: When locked on attacker faints, the volatile will be removed")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_LOCK_ON); MOVE(opponent, MOVE_POUND); SEND_OUT(player, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponent);
} THEN {
u32 battlerWithSureHit = gBattleMons[B_BATTLER_0].volatiles.battlerWithSureHit;
EXPECT_EQ(battlerWithSureHit, 0);
}
}
SINGLE_BATTLE_TEST("Lock-On: When locked on target faints, the volatile will be removed")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_LOCK_ON); }
TURN { MOVE(player, MOVE_POUND); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player);
} THEN {
u32 battlerWithSureHit = gBattleMons[B_BATTLER_0].volatiles.battlerWithSureHit;
EXPECT_EQ(battlerWithSureHit, 0);
}
}
DOUBLE_BATTLE_TEST("Lock-On fails if a target has been locked on")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_LOCK_ON, target: opponentLeft); }
TURN { MOVE(playerLeft, MOVE_LOCK_ON, target: opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_LOCK_ON, playerLeft);
}
}
TO_DO_BATTLE_TEST("TODO: Write Lock-On/Mind Reader (Move Effect) test titles")