This commit is contained in:
André Bastos Dias 2026-06-02 14:06:01 +01:00 committed by GitHub
commit b868f65e71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 196 additions and 125 deletions

View File

@ -297,13 +297,6 @@ export const Conditions: import('../../../sim/dex-conditions').ModdedConditionDa
onLockMove() {
return this.effectState.move;
},
onBeforeTurn(pokemon) {
const move = this.dex.moves.get(this.effectState.move);
if (move.id) {
this.debug('Forcing into ' + move.id);
this.queue.changeAction(pokemon, { choice: 'move', moveid: move.id });
}
},
onAfterMove(pokemon) {
if (pokemon.volatiles['lockedmove'].time <= 0) {
pokemon.removeVolatile('lockedmove');

View File

@ -193,13 +193,6 @@ export const Conditions: import('../../../sim/dex-conditions').ModdedConditionDa
onMoveAborted(pokemon) {
delete pokemon.volatiles['lockedmove'];
},
onBeforeTurn(pokemon) {
const move = this.dex.moves.get(this.effectState.move);
if (move.id) {
this.debug('Forcing into ' + move.id);
this.queue.changeAction(pokemon, { choice: 'move', moveid: move.id });
}
},
},
futuremove: {
inherit: true,

View File

@ -142,6 +142,7 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
}
this.effectState.move = lockedMove;
this.add('-start', target, 'Encore');
if (this.effectState.move === 'pursuit') target.addVolatile('pursuit', target);
},
onResidualOrder: 13,
onResidualSubOrder: undefined, // no inherit
@ -459,44 +460,32 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
},
pursuit: {
inherit: true,
beforeTurnCallback(pokemon, target) {
if (pokemon.isAlly(target)) return;
target.addVolatile('pursuit');
const data = target.volatiles['pursuit'];
if (!data.sources) {
data.sources = [];
}
data.sources.push(pokemon);
},
onModifyMove: undefined, // no inherit
condition: {
inherit: true,
onBeforeSwitchOut(pokemon) {
onFoeBeforeSwitchOut(pokemon) {
const source: Pokemon = this.effectState.source;
this.debug('Pursuit start');
let alreadyAdded = false;
for (const source of this.effectState.sources) {
if (source.speed < pokemon.speed || (source.speed === pokemon.speed && this.randomChance(1, 2))) {
// Destiny Bond ends if the switch action "outspeeds" the attacker, regardless of host
pokemon.removeVolatile('destinybond');
}
if (!this.queue.cancelMove(source) || !source.hp) continue;
if (!alreadyAdded) {
this.add('-activate', pokemon, 'move: Pursuit');
alreadyAdded = true;
}
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source && action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
this.queue.list.splice(actionIndex, 1);
break;
}
if (this.effectState.targetLoc !== source.getLocOf(pokemon) ||
!source.isAdjacent(pokemon) || !source.hp ||
(source.volatiles['encore'] && source.volatiles['encore'].move !== 'pursuit') ||
!this.queue.cancelMove(source)) return;
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source && action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
this.queue.list.splice(actionIndex, 1);
break;
}
}
this.actions.runMove('pursuit', source, source.getLocOf(pokemon));
}
if (source.speed < pokemon.speed || (source.speed === pokemon.speed && this.randomChance(1, 2))) {
// Destiny Bond ends if the switch action "outspeeds" the attacker, regardless of host
pokemon.removeVolatile('destinybond');
}
this.actions.runMove('pursuit', source, source.getLocOf(pokemon));
},
},
},

View File

@ -199,6 +199,12 @@ export const Abilities: import('../../../sim/dex-abilities').ModdedAbilityDataTa
onStart: undefined, // no inherit
onSwitchIn(pokemon) {
pokemon.truantTurn = this.turn !== 0;
// it is unnecessary to keep a volatile, but it helps with cross-gen implementation
if (pokemon.truantTurn) {
pokemon.addVolatile('truant');
} else {
pokemon.removeVolatile('truant');
}
},
onBeforeMove(pokemon) {
if (pokemon.truantTurn) {
@ -209,6 +215,11 @@ export const Abilities: import('../../../sim/dex-abilities').ModdedAbilityDataTa
onResidualOrder: 27,
onResidual(pokemon) {
pokemon.truantTurn = !pokemon.truantTurn;
if (pokemon.truantTurn) {
pokemon.addVolatile('truant');
} else {
pokemon.removeVolatile('truant');
}
},
},
voltabsorb: {

View File

@ -478,15 +478,37 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
pursuit: {
inherit: true,
beforeTurnCallback(pokemon, target) {
if (['frz', 'slp'].includes(pokemon.status) ||
(pokemon.hasAbility('truant') && pokemon.truantTurn)) return;
if (pokemon.isAlly(target)) return;
target.addVolatile('pursuit');
const data = target.volatiles['pursuit'];
if (!data.sources) {
data.sources = [];
}
data.sources.push(pokemon);
pokemon.addVolatile('pursuit');
pokemon.volatiles['pursuit'].targetLoc = pokemon.getLocOf(target);
},
condition: {
inherit: true,
onFoeBeforeSwitchOut(pokemon) {
const source: Pokemon = this.effectState.source;
this.debug('Pursuit start');
if (this.effectState.targetLoc !== source.getLocOf(pokemon) ||
['frz', 'slp'].includes(source.status) || (source.hasAbility('truant') && source.volatiles['truant']) ||
!source.isAdjacent(pokemon) || !source.hp ||
(source.volatiles['encore'] && source.volatiles['encore'].move !== 'pursuit') ||
!this.queue.cancelMove(source)) return;
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source && action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
this.queue.list.splice(actionIndex, 1);
break;
}
}
}
const move = this.dex.getActiveMove('pursuit');
source.deductPP(move.id);
source.moveUsed(move, pokemon.position);
if (this.actions.useMove(move, source, { target: pokemon }) && source.getItem().isChoice) {
source.addVolatile('choicelock');
}
},
},
},
recover: {

View File

@ -402,6 +402,16 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
durationCallback() {
return this.random(4, 9);
},
onStart(target, source) {
const moveSlot = target.lastMove ? target.getMoveData(target.lastMove.id) : null;
if (!target.lastMove || target.lastMove.flags['failencore'] || !moveSlot || moveSlot.pp <= 0) {
// it failed
return false;
}
this.effectState.move = target.lastMove.id;
this.add('-start', target, 'Encore');
if (this.effectState.move === 'pursuit') target.addVolatile('pursuit', target);
},
onResidualOrder: 10,
onResidualSubOrder: 14,
},
@ -1036,46 +1046,31 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
},
pursuit: {
inherit: true,
beforeTurnCallback(pokemon) {
if (['frz', 'slp'].includes(pokemon.status) ||
(pokemon.hasAbility('truant') && pokemon.volatiles['truant'])) return;
for (const target of pokemon.foes()) {
target.addVolatile('pursuit');
const data = target.volatiles['pursuit'];
if (!data.sources) {
data.sources = [];
}
data.sources.push(pokemon);
}
},
condition: {
inherit: true,
onBeforeSwitchOut(pokemon) {
onFoeBeforeSwitchOut(pokemon) {
const source: Pokemon = this.effectState.source;
this.debug('Pursuit start');
let alreadyAdded = false;
for (const source of this.effectState.sources) {
if (!this.queue.cancelMove(source) || !source.hp) continue;
if (!alreadyAdded) {
this.add('-activate', pokemon, 'move: Pursuit');
alreadyAdded = true;
}
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source && action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
this.queue.list.splice(actionIndex, 1);
break;
}
if (['frz', 'slp'].includes(source.status) || (source.hasAbility('truant') && source.volatiles['truant']) ||
!source.isAdjacent(pokemon) || !source.hp ||
(source.volatiles['encore'] && source.volatiles['encore'].move !== 'pursuit') ||
!this.queue.cancelMove(source)) return;
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source && action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
this.queue.list.splice(actionIndex, 1);
break;
}
}
const move = this.dex.getActiveMove('pursuit');
source.deductPP(move.id);
source.moveUsed(move, pokemon.position);
if (this.actions.useMove(move, source, { target: pokemon }) && source.getItem().isChoice) {
source.addVolatile('choicelock');
}
}
const move = this.dex.getActiveMove('pursuit');
source.deductPP(move.id);
source.moveUsed(move, pokemon.position);
if (this.actions.useMove(move, source, { target: pokemon }) && source.getItem().isChoice) {
source.addVolatile('choicelock');
}
},
},

View File

@ -14390,51 +14390,43 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
pp: 20,
priority: 0,
flags: { contact: 1, protect: 1, mirror: 1, metronome: 1 },
beforeTurnCallback(pokemon) {
for (const target of pokemon.foes()) {
target.addVolatile('pursuit');
const data = target.volatiles['pursuit'];
if (!data.sources) {
data.sources = [];
}
data.sources.push(pokemon);
}
beforeTurnCallback(pokemon, target) {
pokemon.addVolatile('pursuit');
},
onModifyMove(move, source, target) {
if (target?.beingCalledBack || target?.switchFlag) move.accuracy = true;
if (target?.beingCalledBack || target?.switchFlag) {
move.accuracy = true;
move.tracksTarget = true;
}
},
condition: {
duration: 1,
onBeforeSwitchOut(pokemon) {
onFoeBeforeSwitchOut(pokemon) {
const source: Pokemon = this.effectState.source;
this.debug('Pursuit start');
let alreadyAdded = false;
pokemon.removeVolatile('destinybond');
for (const source of this.effectState.sources) {
if (!source.isAdjacent(pokemon) || !this.queue.cancelMove(source) || !source.hp) continue;
if (!alreadyAdded) {
this.add('-activate', pokemon, 'move: Pursuit');
alreadyAdded = true;
}
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst || source.canTerastallize) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source) {
if (action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
} else if (action.choice === 'terastallize') {
// Also a "forme" change that happens before moves, though only possible in NatDex
this.actions.terastallize(source);
} else {
continue;
}
this.queue.list.splice(actionIndex, 1);
break;
if (!source.isAdjacent(pokemon) || !source.hp ||
(source.volatiles['encore'] && source.volatiles['encore'].move !== 'pursuit') ||
!this.queue.cancelMove(source)) return;
// Run through each action in queue to check if the Pursuit user is supposed to Mega Evolve this turn.
// If it is, then Mega Evolve before moving.
if (source.canMegaEvo || source.canUltraBurst || source.canTerastallize) {
for (const [actionIndex, action] of this.queue.entries()) {
if (action.pokemon === source) {
if (action.choice === 'megaEvo') {
this.actions.runMegaEvo(source);
} else if (action.choice === 'terastallize') {
// Also a "forme" change that happens before moves, though only possible in NatDex
this.actions.terastallize(source);
} else {
continue;
}
this.queue.list.splice(actionIndex, 1);
break;
}
}
this.actions.runMove('pursuit', source, source.getLocOf(pokemon));
}
pokemon.removeVolatile('destinybond');
this.actions.runMove('pursuit', source, source.getLocOf(pokemon), { sourceEffect: this.effect });
},
},
target: "normal",

View File

@ -832,7 +832,7 @@ export class Pokemon {
!(move.id.startsWith('solarb') && ['sunnyday', 'desolateland'].includes(this.effectiveWeather(move))) &&
!(move.id === 'electroshot' && ['raindance', 'primordialsea'].includes(this.effectiveWeather(move))) &&
!(this.hasItem('powerherb') && move.id !== 'skydrop');
if (!isCharging && !(move.id === 'pursuit' && (target.beingCalledBack || target.switchFlag))) {
if (!isCharging) {
target = this.battle.priorityEvent('RedirectTarget', this, this, move, target);
}
}

View File

@ -190,6 +190,44 @@ describe(`Pursuit`, () => {
assert.fullHP(jolteon);
});
it(`should not activate if Encored into Pursuit`, () => {
battle = common.createBattle({ gameType: 'doubles' }, [[
{ species: "Empoleon", moves: ['tackle', 'pursuit'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
], [
{ species: "Deoxys", moves: ['sleeptalk', 'encore'] },
{ species: "Infernape", moves: ['sleeptalk', 'uturn'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
]]);
const [deoxys, infernape] = battle.p2.active;
battle.makeChoices('move pursuit 1, move sleeptalk', 'move sleeptalk, move sleeptalk');
deoxys.hp = deoxys.maxhp;
battle.makeChoices('move tackle 1, move sleeptalk', 'move encore 1, move uturn 2');
assert.fullHP(deoxys);
assert.fullHP(infernape);
battle.makeChoices('', 'switch 3');
assert.false.fullHP(deoxys);
});
it(`should not activate other move if Encored out of Pursuit`, () => {
battle = common.createBattle({ gameType: 'doubles' }, [[
{ species: "Empoleon", moves: ['tackle', 'pursuit'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
], [
{ species: "Deoxys", moves: ['sleeptalk', 'encore'] },
{ species: "Infernape", moves: ['sleeptalk', 'uturn'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
]]);
const [deoxys, infernape] = battle.p2.active;
battle.makeChoices('move tackle 1, move sleeptalk', 'move sleeptalk, move sleeptalk');
deoxys.hp = deoxys.maxhp;
battle.makeChoices('move pursuit 1, move sleeptalk', 'move encore 1, move uturn 2');
assert.fullHP(deoxys);
assert.fullHP(infernape);
battle.makeChoices('', 'switch 3');
assert.false.fullHP(deoxys);
});
describe(`[Gen 4]`, () => {
it(`should continue the switch`, () => {
battle = common.gen(4).createBattle([[
@ -234,6 +272,44 @@ describe(`Pursuit`, () => {
battle.makeChoices('move pursuit', 'switch 2');
assert.false.fullHP(jolteon);
});
it(`should activate if Encored into Pursuit`, () => {
battle = common.gen(4).createBattle({ gameType: 'doubles' }, [[
{ species: "Empoleon", moves: ['tackle', 'pursuit'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
], [
{ species: "Deoxys", moves: ['sleeptalk', 'encore'] },
{ species: "Infernape", moves: ['sleeptalk', 'uturn'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
]]);
const [deoxys, infernape] = battle.p2.active;
battle.makeChoices('move pursuit 1, move sleeptalk', 'move sleeptalk, move sleeptalk');
deoxys.hp = deoxys.maxhp;
battle.makeChoices('move tackle 1, move sleeptalk', 'move encore 1, move uturn 2');
assert.fullHP(deoxys);
assert.false.fullHP(infernape);
battle.makeChoices('', 'switch 3');
assert.fullHP(deoxys);
});
it(`should not activate other move if Encored out of Pursuit`, () => {
battle = common.gen(4).createBattle({ gameType: 'doubles' }, [[
{ species: "Empoleon", moves: ['tackle', 'pursuit'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
], [
{ species: "Deoxys", moves: ['sleeptalk', 'encore'] },
{ species: "Infernape", moves: ['sleeptalk', 'uturn'] },
{ species: "Carnivine", moves: ['sleeptalk'] },
]]);
const [deoxys, infernape] = battle.p2.active;
battle.makeChoices('move tackle 1, move sleeptalk', 'move sleeptalk, move sleeptalk');
deoxys.hp = deoxys.maxhp;
battle.makeChoices('move pursuit 1, move sleeptalk', 'move encore 1, move uturn 2');
assert.fullHP(deoxys);
assert.fullHP(infernape);
battle.makeChoices('', 'switch 3');
assert.false.fullHP(deoxys);
});
});
describe(`[Gen 3]`, () => {