From 2881d6b10e8ee460c7b1354b56fa0416ae5d9133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 21:54:23 +0000 Subject: [PATCH 01/30] Correctly implement Dancer --- data/abilities.ts | 13 +++++++++++++ sim/battle-actions.ts | 27 ++++++++++++++++----------- sim/battle-queue.ts | 2 ++ sim/battle.ts | 2 +- test/sim/abilities/dancer.js | 15 +++++++++++++++ 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 21a8a105d9..49b962a3e5 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -828,6 +828,19 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { flags: {}, name: "Dancer", // implemented in runMove in scripts.js + condition: { + noCopy: true, // doesn't get copied by Baton Pass + onBeforeMovePriority: 200, + onBeforeMove(source) { + this.add('-activate', source, 'ability: Dancer'); + }, + onTryAddVolatile(status, target) { + // if (status.id === 'lockedmove' && target.abilityState.noLock) return null; + }, + onAfterMove(source) { + source.removeVolatile('dancer'); + }, + }, rating: 1.5, num: 216, }, diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index 3385af9b8a..672910b95b 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -323,23 +323,28 @@ export class BattleActions { dancers.push(currentPoke); } } - // Dancer activates in order of lowest speed stat to highest - // Note that the speed stat used is after any volatile replacements like Speed Swap, - // but before any multipliers like Agility or Choice Scarf - // Ties go to whichever Pokemon has had the ability for the least amount of time - dancers.sort( - (a, b) => -(b.storedStats['spe'] - a.storedStats['spe']) || b.abilityState.effectOrder - a.abilityState.effectOrder - ); const targetOf1stDance = this.battle.activeTarget!; for (const dancer of dancers) { if (this.battle.faintMessages()) break; if (dancer.fainted) continue; - this.battle.add('-activate', dancer, 'ability: Dancer'); + dancer.addVolatile('dancer'); const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? - targetOf1stDance : - pokemon; + targetOf1stDance : pokemon; const dancersTargetLoc = dancer.getLocOf(dancersTarget); - this.runMove(move.id, dancer, dancersTargetLoc, { sourceEffect: this.dex.abilities.get('dancer'), externalMove: true }); + // Dancer activates in order of lowest speed stat to highest + // Note that the speed stat used is after any volatile replacements like Speed Swap, + // but before any multipliers like Agility or Choice Scarf + // Ties go to whichever Pokemon has had the ability for the least amount of time + this.battle.queue.insertChoice({ + choice: 'move', + order: 198 + dancer.storedStats['spe'] / 100000, + effectOrder: dancer.abilityState.effectOrder, + pokemon: dancer, + moveid: move.id, + targetLoc: dancersTargetLoc, + sourceEffect: this.dex.abilities.get('dancer'), + externalMove: true, + }); } } if (noLock && pokemon.volatiles['lockedmove']) delete pokemon.volatiles['lockedmove']; diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 30f1d710c4..95f40993d6 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -44,6 +44,8 @@ export interface MoveAction { maxMove?: string; /** effect that called the move (eg Instruct) if any */ sourceEffect?: Effect | null; + /** TODO */ + externalMove?: boolean; } /** A switch action */ diff --git a/sim/battle.ts b/sim/battle.ts index 54e6fc21ce..20f8e3a438 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2682,7 +2682,7 @@ export class Battle { if (!action.pokemon.isActive) return false; if (action.pokemon.fainted) return false; this.actions.runMove(action.move, action.pokemon, action.targetLoc, { - sourceEffect: action.sourceEffect, zMove: action.zmove, + sourceEffect: action.sourceEffect, zMove: action.zmove, externalMove: action.externalMove, maxMove: action.maxMove, originalTarget: action.originalTarget, }); break; diff --git a/test/sim/abilities/dancer.js b/test/sim/abilities/dancer.js index 591da14aa4..4e17aaea44 100644 --- a/test/sim/abilities/dancer.js +++ b/test/sim/abilities/dancer.js @@ -183,4 +183,19 @@ describe('Dancer', () => { assert.equal(fletchinder.boosts.atk, -2); assert.equal(squawkabilly.boosts.atk, -4); }); + + it('should activate after Eject Button', () => { + battle = common.createBattle({ gameType: 'doubles' }, [[ + { species: 'oricoriopau', ability: 'dancer', moves: ['sleeptalk'] }, + { species: 'volcarona', moves: ['fierydance'] }, + ], [ + { species: 'fletchinder', item: 'ejectbutton', moves: ['sleeptalk'] }, + { species: 'squawkabilly', moves: ['sleeptalk'] }, + { species: 'suicune', moves: ['sleeptalk'] }, + ]]); + const suicune = battle.p2.pokemon[2]; + battle.makeChoices('move sleeptalk, move fierydance 1', 'move sleeptalk, move sleeptalk'); + battle.makeChoices(); + assert.notEqual(suicune.fullHP, suicune.hp); + }); }); From ee490ad216880a78daed3dfd5284205d8fe6dfcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 22:24:00 +0000 Subject: [PATCH 02/30] Implement in onAnyAfterMove --- data/abilities.ts | 36 +++++++++++++++++++++++++++++++----- sim/battle-actions.ts | 39 --------------------------------------- sim/dex-conditions.ts | 1 + 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 49b962a3e5..bdf9d50557 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -827,18 +827,44 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { dancer: { flags: {}, name: "Dancer", - // implemented in runMove in scripts.js + onAnyAfterMovePriority: -200, + onAnyAfterMove(source, target, move) { + const dancer = this.effectState.target as Pokemon; + if (dancer === source || dancer.isSemiInvulnerable() || !move.flags['dance'] || + !this.lastSuccessfulMoveThisTurn || move.isExternal) return; + const targetOf1stDance = this.activeTarget!; + dancer.addVolatile('dancer'); + const dancersTarget = !targetOf1stDance.isAlly(dancer) && source.isAlly(dancer) ? + targetOf1stDance : source; + const dancersTargetLoc = dancer.getLocOf(dancersTarget); + // Dancer activates in order of lowest speed stat to highest + // Note that the speed stat used is after any volatile replacements like Speed Swap, + // but before any multipliers like Agility or Choice Scarf + // Ties go to whichever Pokemon has had the ability for the least amount of time + this.queue.insertChoice({ + choice: 'move', + order: 198 + dancer.storedStats['spe'] / 100000, // HACK + // speed: -source.storedStats['spe'], // speed gets reset + effectOrder: dancer.abilityState.effectOrder, + pokemon: dancer, + moveid: move.id, + targetLoc: dancersTargetLoc, + sourceEffect: this.dex.abilities.get('dancer'), + externalMove: true, + }); + }, condition: { noCopy: true, // doesn't get copied by Baton Pass onBeforeMovePriority: 200, - onBeforeMove(source) { + onBeforeMove(source, target, move) { this.add('-activate', source, 'ability: Dancer'); + this.effectState.noLock = move.isExternal && !source.volatiles['lockedmove']; }, - onTryAddVolatile(status, target) { - // if (status.id === 'lockedmove' && target.abilityState.noLock) return null; + onTryAddVolatile(status) { + if (status.id === 'lockedmove' && this.effectState.noLock) return null; }, onAfterMove(source) { - source.removeVolatile('dancer'); + delete source.volatiles['dancer']; }, }, rating: 1.5, diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index 672910b95b..955d948fc6 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -290,10 +290,6 @@ export class BattleActions { pokemon.moveUsed(move, targetLoc); } - // Dancer Petal Dance hack - // TODO: implement properly - const noLock = externalMove && !pokemon.volatiles['lockedmove']; - if (zMove) { if (pokemon.illusion) { this.battle.singleEvent('End', this.dex.abilities.get('Illusion'), pokemon.abilityState, pokemon); @@ -313,41 +309,6 @@ export class BattleActions { this.battle.add('-hint', `Some effects can force a Pokemon to use ${move.name} again in a row.`); } - // TODO: Refactor to use BattleQueue#prioritizeAction in onAnyAfterMove handlers - // Dancer's activation order is completely different from any other event, so it's handled separately - if (move.flags['dance'] && moveDidSomething && !move.isExternal) { - const dancers = []; - for (const currentPoke of this.battle.getAllActive()) { - if (pokemon === currentPoke) continue; - if (currentPoke.hasAbility('dancer') && !currentPoke.isSemiInvulnerable()) { - dancers.push(currentPoke); - } - } - const targetOf1stDance = this.battle.activeTarget!; - for (const dancer of dancers) { - if (this.battle.faintMessages()) break; - if (dancer.fainted) continue; - dancer.addVolatile('dancer'); - const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? - targetOf1stDance : pokemon; - const dancersTargetLoc = dancer.getLocOf(dancersTarget); - // Dancer activates in order of lowest speed stat to highest - // Note that the speed stat used is after any volatile replacements like Speed Swap, - // but before any multipliers like Agility or Choice Scarf - // Ties go to whichever Pokemon has had the ability for the least amount of time - this.battle.queue.insertChoice({ - choice: 'move', - order: 198 + dancer.storedStats['spe'] / 100000, - effectOrder: dancer.abilityState.effectOrder, - pokemon: dancer, - moveid: move.id, - targetLoc: dancersTargetLoc, - sourceEffect: this.dex.abilities.get('dancer'), - externalMove: true, - }); - } - } - if (noLock && pokemon.volatiles['lockedmove']) delete pokemon.volatiles['lockedmove']; this.battle.faintMessages(); this.battle.checkWin(); diff --git a/sim/dex-conditions.ts b/sim/dex-conditions.ts index 78279cd900..0a38371964 100644 --- a/sim/dex-conditions.ts +++ b/sim/dex-conditions.ts @@ -427,6 +427,7 @@ export interface EventMethods { onAfterMoveSecondarySelfPriority?: number; onAfterMoveSelfPriority?: number; onAfterSetStatusPriority?: number; + onAnyAfterMovePriority?: number; onAnyBasePowerPriority?: number; onAnyInvulnerabilityPriority?: number; onAnyModifyAccuracyPriority?: number; From 8850c59b08c3573f8da31de659dbabfc5933132c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 22:31:29 +0000 Subject: [PATCH 03/30] Final touches --- data/abilities.ts | 4 ++-- sim/battle-queue.ts | 2 +- test/sim/abilities/dancer.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index bdf9d50557..33107da8f7 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -843,8 +843,8 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { // Ties go to whichever Pokemon has had the ability for the least amount of time this.queue.insertChoice({ choice: 'move', - order: 198 + dancer.storedStats['spe'] / 100000, // HACK - // speed: -source.storedStats['spe'], // speed gets reset + order: 198 + dancer.storedStats['spe'] / 100000, // FIXME HACK + speed: -source.storedStats['spe'], // speed gets reset effectOrder: dancer.abilityState.effectOrder, pokemon: dancer, moveid: move.id, diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 95f40993d6..cefa58eca2 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -44,7 +44,7 @@ export interface MoveAction { maxMove?: string; /** effect that called the move (eg Instruct) if any */ sourceEffect?: Effect | null; - /** TODO */ + /** if external, skips LockMove and PP deduction, mostly for use by Dancer */ externalMove?: boolean; } diff --git a/test/sim/abilities/dancer.js b/test/sim/abilities/dancer.js index 4e17aaea44..b62e121a66 100644 --- a/test/sim/abilities/dancer.js +++ b/test/sim/abilities/dancer.js @@ -196,6 +196,6 @@ describe('Dancer', () => { const suicune = battle.p2.pokemon[2]; battle.makeChoices('move sleeptalk, move fierydance 1', 'move sleeptalk, move sleeptalk'); battle.makeChoices(); - assert.notEqual(suicune.fullHP, suicune.hp); + assert.notEqual(suicune.hp, suicune.fullHP); }); }); From f1cae516a8e45a37b545a6dc2d6f18ca8269a883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 22:41:57 +0000 Subject: [PATCH 04/30] Update MoveAction orders --- sim/battle-queue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index cefa58eca2..b9dd7158c0 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -19,7 +19,7 @@ import type { Battle } from './battle'; export interface MoveAction { /** action type */ choice: 'move' | 'beforeTurnMove' | 'priorityChargeMove'; - order: 3 | 5 | 200 | 201 | 199 | 106; + order: 3 | 5 | 107 | 198 | 199 | 200 | 201; /** priority of the action (lower first) */ priority: number; /** fractional priority of the action (lower first) */ From acb00656f5557622c4083945b7180af155188b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 23:14:51 +0000 Subject: [PATCH 05/30] Fix hack --- data/abilities.ts | 4 ++-- sim/battle.ts | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 33107da8f7..96d86c0dfb 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -843,8 +843,8 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { // Ties go to whichever Pokemon has had the ability for the least amount of time this.queue.insertChoice({ choice: 'move', - order: 198 + dancer.storedStats['spe'] / 100000, // FIXME HACK - speed: -source.storedStats['spe'], // speed gets reset + order: 198, + speed: -source.storedStats['spe'], effectOrder: dancer.abilityState.effectOrder, pokemon: dancer, moveid: move.id, diff --git a/sim/battle.ts b/sim/battle.ts index 20f8e3a438..1a81f22db3 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2597,10 +2597,12 @@ export class Battle { if (this.gen > 5) action.move.priority = priority; } - if (!action.pokemon) { - action.speed = 1; - } else { - action.speed = action.pokemon.getActionSpeed(); + if (!action.speed) { + if (!action.pokemon) { + action.speed = 1; + } else { + action.speed = action.pokemon.getActionSpeed(); + } } } From 2b2cbde2bfd2014f29171bdd5f51f11fac6164f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 23:21:48 +0000 Subject: [PATCH 06/30] Fix weird interaction with Magic Coat --- data/abilities.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 96d86c0dfb..62ae2e8014 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -857,14 +857,18 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { noCopy: true, // doesn't get copied by Baton Pass onBeforeMovePriority: 200, onBeforeMove(source, target, move) { - this.add('-activate', source, 'ability: Dancer'); - this.effectState.noLock = move.isExternal && !source.volatiles['lockedmove']; + if (move.isExternal) { + this.add('-activate', source, 'ability: Dancer'); + this.effectState.noLock = move.isExternal && !source.volatiles['lockedmove']; + } }, onTryAddVolatile(status) { if (status.id === 'lockedmove' && this.effectState.noLock) return null; }, - onAfterMove(source) { - delete source.volatiles['dancer']; + onAfterMove(source, target, move) { + if (move.isExternal) { + delete source.volatiles['dancer']; + } }, }, rating: 1.5, From 7a3f4eb016743dad1548b76abb003da79a026df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 23:32:38 +0000 Subject: [PATCH 07/30] Fix hack (correct) --- data/abilities.ts | 6 +++--- sim/battle-queue.ts | 8 ++++---- sim/battle.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 62ae2e8014..faa9ebf1d2 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -843,15 +843,15 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { // Ties go to whichever Pokemon has had the ability for the least amount of time this.queue.insertChoice({ choice: 'move', - order: 198, - speed: -source.storedStats['spe'], + order: 198 + dancer.storedStats['spe'] / 100000, // FIXME HACK + speed: -source.storedStats['spe'], // speed gets reset effectOrder: dancer.abilityState.effectOrder, pokemon: dancer, moveid: move.id, targetLoc: dancersTargetLoc, sourceEffect: this.dex.abilities.get('dancer'), externalMove: true, - }); + }, false, false); }, condition: { noCopy: true, // doesn't get copied by Baton Pass diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index b9dd7158c0..096012dc8c 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -159,7 +159,7 @@ export class BattleQueue { * Returns an array of Actions because some ActionChoices (like mega moves) * resolve to two Actions (mega evolution + use move) */ - resolveAction(action: ActionChoice, midTurn = false): Action[] { + resolveAction(action: ActionChoice, midTurn = false, updateSpeed = true): Action[] { if (!action) throw new Error(`Action not passed to resolveAction`); if (action.choice === 'pass') return []; const actions = [action]; @@ -263,7 +263,7 @@ export class BattleQueue { } action.originalTarget = action.pokemon.getAtLoc(action.targetLoc); } - if (!deferPriority) this.battle.getActionSpeed(action); + if (!deferPriority) this.battle.getActionSpeed(action, updateSpeed); return actions as any; } @@ -362,7 +362,7 @@ export class BattleQueue { * would have happened (sorting by priority/speed), without * re-sorting the existing actions. */ - insertChoice(choices: ActionChoice | ActionChoice[], midTurn = false) { + insertChoice(choices: ActionChoice | ActionChoice[], midTurn = false, updateSpeed = true) { if (Array.isArray(choices)) { for (const choice of choices) { this.insertChoice(choice); @@ -374,7 +374,7 @@ export class BattleQueue { if (choice.pokemon) { choice.pokemon.updateSpeed(); } - const actions = this.resolveAction(choice, midTurn); + const actions = this.resolveAction(choice, midTurn, updateSpeed); let firstIndex = null; let lastIndex = null; diff --git a/sim/battle.ts b/sim/battle.ts index 1a81f22db3..02f25b9341 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2565,7 +2565,7 @@ export class Battle { } } - getActionSpeed(action: AnyObject) { + getActionSpeed(action: AnyObject, updateSpeed = true) { if (action.choice === 'move') { let move = action.move; if (action.zmove) { @@ -2597,7 +2597,7 @@ export class Battle { if (this.gen > 5) action.move.priority = priority; } - if (!action.speed) { + if (updateSpeed) { if (!action.pokemon) { action.speed = 1; } else { From 45f82e9a7c5612d69e72881ece25fa86360fce0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 16 Mar 2025 23:54:59 +0000 Subject: [PATCH 08/30] Smaller hack --- data/abilities.ts | 6 +++--- sim/battle-queue.ts | 8 ++++---- sim/battle.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index faa9ebf1d2..62ae2e8014 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -843,15 +843,15 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { // Ties go to whichever Pokemon has had the ability for the least amount of time this.queue.insertChoice({ choice: 'move', - order: 198 + dancer.storedStats['spe'] / 100000, // FIXME HACK - speed: -source.storedStats['spe'], // speed gets reset + order: 198, + speed: -source.storedStats['spe'], effectOrder: dancer.abilityState.effectOrder, pokemon: dancer, moveid: move.id, targetLoc: dancersTargetLoc, sourceEffect: this.dex.abilities.get('dancer'), externalMove: true, - }, false, false); + }); }, condition: { noCopy: true, // doesn't get copied by Baton Pass diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 096012dc8c..b9dd7158c0 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -159,7 +159,7 @@ export class BattleQueue { * Returns an array of Actions because some ActionChoices (like mega moves) * resolve to two Actions (mega evolution + use move) */ - resolveAction(action: ActionChoice, midTurn = false, updateSpeed = true): Action[] { + resolveAction(action: ActionChoice, midTurn = false): Action[] { if (!action) throw new Error(`Action not passed to resolveAction`); if (action.choice === 'pass') return []; const actions = [action]; @@ -263,7 +263,7 @@ export class BattleQueue { } action.originalTarget = action.pokemon.getAtLoc(action.targetLoc); } - if (!deferPriority) this.battle.getActionSpeed(action, updateSpeed); + if (!deferPriority) this.battle.getActionSpeed(action); return actions as any; } @@ -362,7 +362,7 @@ export class BattleQueue { * would have happened (sorting by priority/speed), without * re-sorting the existing actions. */ - insertChoice(choices: ActionChoice | ActionChoice[], midTurn = false, updateSpeed = true) { + insertChoice(choices: ActionChoice | ActionChoice[], midTurn = false) { if (Array.isArray(choices)) { for (const choice of choices) { this.insertChoice(choice); @@ -374,7 +374,7 @@ export class BattleQueue { if (choice.pokemon) { choice.pokemon.updateSpeed(); } - const actions = this.resolveAction(choice, midTurn, updateSpeed); + const actions = this.resolveAction(choice, midTurn); let firstIndex = null; let lastIndex = null; diff --git a/sim/battle.ts b/sim/battle.ts index 02f25b9341..58ba4e4dfa 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2565,7 +2565,7 @@ export class Battle { } } - getActionSpeed(action: AnyObject, updateSpeed = true) { + getActionSpeed(action: AnyObject) { if (action.choice === 'move') { let move = action.move; if (action.zmove) { @@ -2597,7 +2597,7 @@ export class Battle { if (this.gen > 5) action.move.priority = priority; } - if (updateSpeed) { + if (!action.externalMove) { if (!action.pokemon) { action.speed = 1; } else { From 8c9717a7f0d13b0f951a959189d016d405c0fa01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Mon, 17 Mar 2025 00:00:46 +0000 Subject: [PATCH 09/30] Simplify condition --- sim/battle.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sim/battle.ts b/sim/battle.ts index 58ba4e4dfa..83d77c0e1f 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2597,12 +2597,10 @@ export class Battle { if (this.gen > 5) action.move.priority = priority; } - if (!action.externalMove) { - if (!action.pokemon) { - action.speed = 1; - } else { - action.speed = action.pokemon.getActionSpeed(); - } + if (!action.pokemon) { + action.speed = 1; + } else if (!action.externalMove) { // FIXME HACK + action.speed = action.pokemon.getActionSpeed(); } } From 81a975944a70585b6f73ae7e9b23d2a7476aa978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Tue, 18 Mar 2025 22:26:17 +0000 Subject: [PATCH 10/30] Temporary fix --- data/abilities.ts | 1 - sim/battle.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 62ae2e8014..97c53131ae 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -844,7 +844,6 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { this.queue.insertChoice({ choice: 'move', order: 198, - speed: -source.storedStats['spe'], effectOrder: dancer.abilityState.effectOrder, pokemon: dancer, moveid: move.id, diff --git a/sim/battle.ts b/sim/battle.ts index 83d77c0e1f..0ad903a2c5 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2599,7 +2599,9 @@ export class Battle { if (!action.pokemon) { action.speed = 1; - } else if (!action.externalMove) { // FIXME HACK + } else if (action.sourceEffect?.id === 'dancer') { + action.speed = -action.pokemon.getStat('spe', true, true); + } else { action.speed = action.pokemon.getActionSpeed(); } } From 6d623128a4f47ed3da9568634a6d05d51a59b66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:11:57 +0000 Subject: [PATCH 11/30] Implement correctly for all gens --- data/abilities.ts | 14 ++++---- sim/battle-queue.ts | 8 +++++ sim/battle.ts | 2 -- test/sim/abilities/dancer.js | 62 ++++++++++++++++++++++++------------ 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 97c53131ae..7586003fa4 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -837,11 +837,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { const dancersTarget = !targetOf1stDance.isAlly(dancer) && source.isAlly(dancer) ? targetOf1stDance : source; const dancersTargetLoc = dancer.getLocOf(dancersTarget); - // Dancer activates in order of lowest speed stat to highest - // Note that the speed stat used is after any volatile replacements like Speed Swap, - // but before any multipliers like Agility or Choice Scarf - // Ties go to whichever Pokemon has had the ability for the least amount of time - this.queue.insertChoice({ + const action = this.queue.resolveAction({ choice: 'move', order: 198, effectOrder: dancer.abilityState.effectOrder, @@ -850,7 +846,13 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { targetLoc: dancersTargetLoc, sourceEffect: this.dex.abilities.get('dancer'), externalMove: true, - }); + })[0]; + // Gen 7: Dancer activates in order of lowest speed stat to highest + // Note that the speed stat used is after any volatile replacements like Speed Swap, + // but before any multipliers like Agility or Choice Scarf + // Ties go to whichever Pokemon has had the ability for the least amount of time + action.speed = -dancer.getStat('spe', true, true); + this.queue.insertAction(action); }, condition: { noCopy: true, // doesn't get copied by Baton Pass diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index b9dd7158c0..84db4260ca 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -376,6 +376,14 @@ export class BattleQueue { } const actions = this.resolveAction(choice, midTurn); + this.insertAction(actions); + } + + insertAction(actions: Action | Action[]) { + if (!Array.isArray(actions)) { + actions = [actions]; + } + let firstIndex = null; let lastIndex = null; for (const [i, curAction] of this.list.entries()) { diff --git a/sim/battle.ts b/sim/battle.ts index 0ad903a2c5..20f8e3a438 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2599,8 +2599,6 @@ export class Battle { if (!action.pokemon) { action.speed = 1; - } else if (action.sourceEffect?.id === 'dancer') { - action.speed = -action.pokemon.getStat('spe', true, true); } else { action.speed = action.pokemon.getActionSpeed(); } diff --git a/test/sim/abilities/dancer.js b/test/sim/abilities/dancer.js index b62e121a66..34e82d980e 100644 --- a/test/sim/abilities/dancer.js +++ b/test/sim/abilities/dancer.js @@ -21,13 +21,13 @@ describe('Dancer', () => { assert.statStage(battle.p2.active[0], 'atk', 3); }); - it('should activate in order of lowest to highest raw speed', () => { + it('should activate in order of fastest to slowest', () => { battle = common.createBattle({ gameType: 'doubles' }, [[ - { species: 'Shedinja', level: 98, ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, + { species: 'Shedinja', ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, { species: 'Shedinja', level: 99, ability: 'dancer', moves: ['sleeptalk'] }, ], [ { species: 'Shedinja', ability: 'wonderguard', moves: ['fierydance'] }, - { species: 'Shedinja', ability: 'dancer', moves: ['sleeptalk'] }, + { species: 'Shedinja', level: 98, ability: 'dancer', moves: ['sleeptalk'] }, ]]); const [, fastDancer] = battle.p1.active; const [wwDanceSource, foeDancer] = battle.p2.active; @@ -37,23 +37,6 @@ describe('Dancer', () => { assert.fainted(foeDancer); }); - it('should activate in order of lowest to highest raw speed inside Trick Room', () => { - battle = common.createBattle({ gameType: 'doubles' }, [[ - { species: 'Shedinja', level: 98, ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, - { species: 'Shedinja', level: 99, ability: 'dancer', moves: ['sleeptalk'] }, - ], [ - { species: 'Shedinja', ability: 'wonderguard', moves: ['fierydance', 'trickroom'] }, - { species: 'Shedinja', ability: 'dancer', moves: ['sleeptalk'] }, - ]]); - const [, fastDancer] = battle.p1.active; - const [wwDanceSource, foeDancer] = battle.p2.active; - fastDancer.boostBy({ spe: 6 }); - battle.makeChoices('move sleeptalk, move sleeptalk', 'move trickroom, move sleeptalk'); - battle.makeChoices('move sleeptalk, move sleeptalk', 'move fierydance 1, move sleeptalk'); - assert.fainted(wwDanceSource); - assert.fainted(foeDancer); - }); - it(`should not copy a move that was blocked by Protect`, () => { battle = common.createBattle([[ { species: 'Oricorio', ability: 'dancer', moves: ['protect'] }, @@ -199,3 +182,42 @@ describe('Dancer', () => { assert.notEqual(suicune.hp, suicune.fullHP); }); }); + +describe('[Gen 7] Dancer', () => { + afterEach(() => { + battle.destroy(); + }); + + it('should activate in order of lowest to highest raw speed', () => { + battle = common.gen(7).createBattle({ gameType: 'doubles' }, [[ + { species: 'Shedinja', level: 98, ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, + { species: 'Shedinja', level: 99, ability: 'dancer', moves: ['sleeptalk'] }, + ], [ + { species: 'Shedinja', ability: 'wonderguard', moves: ['fierydance'] }, + { species: 'Shedinja', ability: 'dancer', moves: ['sleeptalk'] }, + ]]); + const [, fastDancer] = battle.p1.active; + const [wwDanceSource, foeDancer] = battle.p2.active; + fastDancer.boostBy({ spe: 6 }); + battle.makeChoices('move sleeptalk, move sleeptalk', 'move fierydance 1, move sleeptalk'); + assert.fainted(wwDanceSource); + assert.fainted(foeDancer); + }); + + it('should activate in order of lowest to highest raw speed inside Trick Room', () => { + battle = common.gen(7).createBattle({ gameType: 'doubles' }, [[ + { species: 'Shedinja', level: 98, ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, + { species: 'Shedinja', level: 99, ability: 'dancer', moves: ['sleeptalk'] }, + ], [ + { species: 'Shedinja', ability: 'wonderguard', moves: ['fierydance', 'trickroom'] }, + { species: 'Shedinja', ability: 'dancer', moves: ['sleeptalk'] }, + ]]); + const [, fastDancer] = battle.p1.active; + const [wwDanceSource, foeDancer] = battle.p2.active; + fastDancer.boostBy({ spe: 6 }); + battle.makeChoices('move sleeptalk, move sleeptalk', 'move trickroom, move sleeptalk'); + battle.makeChoices('move sleeptalk, move sleeptalk', 'move fierydance 1, move sleeptalk'); + assert.fainted(wwDanceSource); + assert.fainted(foeDancer); + }); +}); From 0f27d64141498f02b855fe9e16e07fd0db8582a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Wed, 19 Mar 2025 00:19:08 +0000 Subject: [PATCH 12/30] Simplify abilties.ts --- data/abilities.ts | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 7586003fa4..35109d184b 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -833,7 +833,6 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { if (dancer === source || dancer.isSemiInvulnerable() || !move.flags['dance'] || !this.lastSuccessfulMoveThisTurn || move.isExternal) return; const targetOf1stDance = this.activeTarget!; - dancer.addVolatile('dancer'); const dancersTarget = !targetOf1stDance.isAlly(dancer) && source.isAlly(dancer) ? targetOf1stDance : source; const dancersTargetLoc = dancer.getLocOf(dancersTarget); @@ -854,23 +853,16 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { action.speed = -dancer.getStat('spe', true, true); this.queue.insertAction(action); }, - condition: { - noCopy: true, // doesn't get copied by Baton Pass - onBeforeMovePriority: 200, - onBeforeMove(source, target, move) { - if (move.isExternal) { - this.add('-activate', source, 'ability: Dancer'); - this.effectState.noLock = move.isExternal && !source.volatiles['lockedmove']; - } - }, - onTryAddVolatile(status) { - if (status.id === 'lockedmove' && this.effectState.noLock) return null; - }, - onAfterMove(source, target, move) { - if (move.isExternal) { - delete source.volatiles['dancer']; - } - }, + onBeforeMovePriority: 200, + onBeforeMove(source, target, move) { + if (move.isExternal) { + this.add('-activate', source, 'ability: Dancer'); + } + }, + onTryAddVolatile(status, target, source, sourceEffect) { + if (status.id === 'lockedmove' && (sourceEffect as ActiveMove)?.isExternal) { + return null; + } }, rating: 1.5, num: 216, From fdb9644747ded0cc247554fd843deb42f63fc149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 17:41:50 +0000 Subject: [PATCH 13/30] Fix effectOrder priority --- data/abilities.ts | 27 +-------------------------- sim/battle-actions.ts | 37 +++++++++++++++++++++++++++++++++++++ sim/battle-queue.ts | 8 -------- sim/dex-conditions.ts | 1 - 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 35109d184b..3ffff28597 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -827,32 +827,7 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { dancer: { flags: {}, name: "Dancer", - onAnyAfterMovePriority: -200, - onAnyAfterMove(source, target, move) { - const dancer = this.effectState.target as Pokemon; - if (dancer === source || dancer.isSemiInvulnerable() || !move.flags['dance'] || - !this.lastSuccessfulMoveThisTurn || move.isExternal) return; - const targetOf1stDance = this.activeTarget!; - const dancersTarget = !targetOf1stDance.isAlly(dancer) && source.isAlly(dancer) ? - targetOf1stDance : source; - const dancersTargetLoc = dancer.getLocOf(dancersTarget); - const action = this.queue.resolveAction({ - choice: 'move', - order: 198, - effectOrder: dancer.abilityState.effectOrder, - pokemon: dancer, - moveid: move.id, - targetLoc: dancersTargetLoc, - sourceEffect: this.dex.abilities.get('dancer'), - externalMove: true, - })[0]; - // Gen 7: Dancer activates in order of lowest speed stat to highest - // Note that the speed stat used is after any volatile replacements like Speed Swap, - // but before any multipliers like Agility or Choice Scarf - // Ties go to whichever Pokemon has had the ability for the least amount of time - action.speed = -dancer.getStat('spe', true, true); - this.queue.insertAction(action); - }, + // implemented in runMove in scripts.js onBeforeMovePriority: 200, onBeforeMove(source, target, move) { if (move.isExternal) { diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index 955d948fc6..c6ac5b8135 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -309,6 +309,43 @@ export class BattleActions { this.battle.add('-hint', `Some effects can force a Pokemon to use ${move.name} again in a row.`); } + if (move.flags['dance'] && moveDidSomething && !move.isExternal) { + const dancers = []; + for (const currentPoke of this.battle.getAllActive()) { + if (pokemon === currentPoke) continue; + if (currentPoke.hasAbility('dancer') && !currentPoke.isSemiInvulnerable()) { + dancers.push(currentPoke); + } + } + // Gen 7: Dancer activates in order of lowest speed stat to highest + // Note that the speed stat used is after any volatile replacements like Speed Swap, + // but before any multipliers like Agility or Choice Scarf + // Ties go to whichever Pokemon has had the ability for the least amount of time + this.battle.speedSort(dancers, + (a, b) => (b.storedStats['spe'] - a.storedStats['spe']) || -(b.abilityState.effectOrder - a.abilityState.effectOrder) + ); + const targetOf1stDance = this.battle.activeTarget!; + for (const dancer of dancers) { + if (this.battle.faintMessages()) break; + if (dancer.fainted) continue; + this.battle.add('-activate', dancer, 'ability: Dancer'); + const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? + targetOf1stDance : + pokemon; + const dancersTargetLoc = dancer.getLocOf(dancersTarget); + this.battle.queue.unshift(this.battle.queue.resolveAction({ + choice: 'move', + order: 198, + effectOrder: dancer.abilityState.effectOrder, + pokemon: dancer, + moveid: move.id, + targetLoc: dancersTargetLoc, + sourceEffect: this.dex.abilities.get('dancer'), + externalMove: true, + })[0]); + } + } + this.battle.faintMessages(); this.battle.checkWin(); diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 84db4260ca..b9dd7158c0 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -376,14 +376,6 @@ export class BattleQueue { } const actions = this.resolveAction(choice, midTurn); - this.insertAction(actions); - } - - insertAction(actions: Action | Action[]) { - if (!Array.isArray(actions)) { - actions = [actions]; - } - let firstIndex = null; let lastIndex = null; for (const [i, curAction] of this.list.entries()) { diff --git a/sim/dex-conditions.ts b/sim/dex-conditions.ts index 0a38371964..78279cd900 100644 --- a/sim/dex-conditions.ts +++ b/sim/dex-conditions.ts @@ -427,7 +427,6 @@ export interface EventMethods { onAfterMoveSecondarySelfPriority?: number; onAfterMoveSelfPriority?: number; onAfterSetStatusPriority?: number; - onAnyAfterMovePriority?: number; onAnyBasePowerPriority?: number; onAnyInvulnerabilityPriority?: number; onAnyModifyAccuracyPriority?: number; From 7d44ff87f2bfd61e778e0a70afe46d8060c4e88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:03:59 +0000 Subject: [PATCH 14/30] Normalization --- sim/battle-actions.ts | 30 +++++++++--------------------- sim/battle-queue.ts | 2 ++ sim/battle.ts | 2 ++ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index c6ac5b8135..03885697b0 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -310,32 +310,18 @@ export class BattleActions { } if (move.flags['dance'] && moveDidSomething && !move.isExternal) { - const dancers = []; - for (const currentPoke of this.battle.getAllActive()) { - if (pokemon === currentPoke) continue; - if (currentPoke.hasAbility('dancer') && !currentPoke.isSemiInvulnerable()) { - dancers.push(currentPoke); - } - } - // Gen 7: Dancer activates in order of lowest speed stat to highest - // Note that the speed stat used is after any volatile replacements like Speed Swap, - // but before any multipliers like Agility or Choice Scarf - // Ties go to whichever Pokemon has had the ability for the least amount of time - this.battle.speedSort(dancers, - (a, b) => (b.storedStats['spe'] - a.storedStats['spe']) || -(b.abilityState.effectOrder - a.abilityState.effectOrder) - ); const targetOf1stDance = this.battle.activeTarget!; - for (const dancer of dancers) { - if (this.battle.faintMessages()) break; - if (dancer.fainted) continue; - this.battle.add('-activate', dancer, 'ability: Dancer'); + const actions = []; + for (const dancer of this.battle.getAllActive()) { + if (pokemon === dancer || !dancer.hasAbility('dancer') || dancer.isSemiInvulnerable() || + dancer.fainted) continue; const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? - targetOf1stDance : - pokemon; + targetOf1stDance : pokemon; const dancersTargetLoc = dancer.getLocOf(dancersTarget); - this.battle.queue.unshift(this.battle.queue.resolveAction({ + actions.push(this.battle.queue.resolveAction({ choice: 'move', order: 198, + rawSpeed: true, effectOrder: dancer.abilityState.effectOrder, pokemon: dancer, moveid: move.id, @@ -344,6 +330,8 @@ export class BattleActions { externalMove: true, })[0]); } + this.battle.speedSort(actions); + actions.forEach(action => this.battle.queue.unshift(action)); } this.battle.faintMessages(); diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index b9dd7158c0..270e81f1ef 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -26,6 +26,8 @@ export interface MoveAction { fractionalPriority: number; /** speed of pokemon using move (higher first if priority tie) */ speed: number; + /** if rawSpeed, should sort using Pokemon's raw speed */ + rawSpeed: boolean; /** the pokemon doing the move */ pokemon: Pokemon; /** location of the target, relative to pokemon's side */ diff --git a/sim/battle.ts b/sim/battle.ts index 20f8e3a438..ec5c23e387 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2599,6 +2599,8 @@ export class Battle { if (!action.pokemon) { action.speed = 1; + } else if (action.rawSpeed) { + action.speed = action.pokemon.getStat('spe', true, true) } else { action.speed = action.pokemon.getActionSpeed(); } From 34a8b7b0d9f6c482cff3d813f8157beb12c6edb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:06:23 +0000 Subject: [PATCH 15/30] Lint --- sim/battle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim/battle.ts b/sim/battle.ts index ec5c23e387..94241712ba 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2600,7 +2600,7 @@ export class Battle { if (!action.pokemon) { action.speed = 1; } else if (action.rawSpeed) { - action.speed = action.pokemon.getStat('spe', true, true) + action.speed = action.pokemon.getStat('spe', true, true); } else { action.speed = action.pokemon.getActionSpeed(); } From e00438b1c53cdad4701f70fd1d904277294237f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:13:28 +0000 Subject: [PATCH 16/30] Fix gen8linked --- data/mods/gen8linked/scripts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/mods/gen8linked/scripts.ts b/data/mods/gen8linked/scripts.ts index b952d274ee..8bc0c4f68a 100644 --- a/data/mods/gen8linked/scripts.ts +++ b/data/mods/gen8linked/scripts.ts @@ -107,7 +107,7 @@ export const Scripts: ModdedBattleScriptsData = { const validTarget = this.validTargetLoc(action.targetLoc, action.pokemon, linkedMoves[i].target); const targetLoc = validTarget ? action.targetLoc : 0; const pseudoAction: Action = { - choice: 'move', priority: action.priority, speed: action.speed, pokemon: action.pokemon, + choice: 'move', priority: action.priority, speed: action.speed, rawSpeed: false, pokemon: action.pokemon, targetLoc, moveid: linkedMoves[i].id, move: linkedMoves[i], mega: action.mega, order: action.order, fractionalPriority: action.fractionalPriority, originalTarget: action.originalTarget, }; From c348f0626635d41ac2678414ee89c9bf493709aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:15:36 +0000 Subject: [PATCH 17/30] Refactor --- data/mods/gen8linked/scripts.ts | 2 +- sim/battle-queue.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/mods/gen8linked/scripts.ts b/data/mods/gen8linked/scripts.ts index 8bc0c4f68a..b952d274ee 100644 --- a/data/mods/gen8linked/scripts.ts +++ b/data/mods/gen8linked/scripts.ts @@ -107,7 +107,7 @@ export const Scripts: ModdedBattleScriptsData = { const validTarget = this.validTargetLoc(action.targetLoc, action.pokemon, linkedMoves[i].target); const targetLoc = validTarget ? action.targetLoc : 0; const pseudoAction: Action = { - choice: 'move', priority: action.priority, speed: action.speed, rawSpeed: false, pokemon: action.pokemon, + choice: 'move', priority: action.priority, speed: action.speed, pokemon: action.pokemon, targetLoc, moveid: linkedMoves[i].id, move: linkedMoves[i], mega: action.mega, order: action.order, fractionalPriority: action.fractionalPriority, originalTarget: action.originalTarget, }; diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index 270e81f1ef..f3daf600ba 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -27,7 +27,7 @@ export interface MoveAction { /** speed of pokemon using move (higher first if priority tie) */ speed: number; /** if rawSpeed, should sort using Pokemon's raw speed */ - rawSpeed: boolean; + rawSpeed?: boolean; /** the pokemon doing the move */ pokemon: Pokemon; /** location of the target, relative to pokemon's side */ From f56f141edc481e04448eed649b84e82c7f4bf246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:18:59 +0000 Subject: [PATCH 18/30] Refactor: push all actions --- sim/battle-actions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index 03885697b0..d059c2f901 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -318,7 +318,7 @@ export class BattleActions { const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? targetOf1stDance : pokemon; const dancersTargetLoc = dancer.getLocOf(dancersTarget); - actions.push(this.battle.queue.resolveAction({ + actions.push(...this.battle.queue.resolveAction({ choice: 'move', order: 198, rawSpeed: true, @@ -328,7 +328,7 @@ export class BattleActions { targetLoc: dancersTargetLoc, sourceEffect: this.dex.abilities.get('dancer'), externalMove: true, - })[0]); + })); } this.battle.speedSort(actions); actions.forEach(action => this.battle.queue.unshift(action)); From adff7661c73ed38eb65f0ae845f802f2f7bca4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 23:07:54 +0000 Subject: [PATCH 19/30] Reimplement using AnyAfterMove event --- data/abilities.ts | 25 ++++++++++++++++++++++++- sim/battle-actions.ts | 25 ------------------------- sim/battle.ts | 4 ++++ sim/dex-conditions.ts | 1 + test/sim/abilities/dancer.js | 20 ++++++++++++++++++-- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 3ffff28597..d52e4f5cd2 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -827,7 +827,30 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { dancer: { flags: {}, name: "Dancer", - // implemented in runMove in scripts.js + onAnyAfterMovePriority: -200, + onAnyAfterMove(source, target, move) { + const dancer = this.effectState.target as Pokemon; + if (dancer === source || dancer.isSemiInvulnerable() || !move.flags['dance'] || + !this.lastSuccessfulMoveThisTurn || move.isExternal) return; + const targetOf1stDance = this.activeTarget!; + const dancersTarget = !targetOf1stDance.isAlly(dancer) && source.isAlly(dancer) ? + targetOf1stDance : source; + const dancersTargetLoc = dancer.getLocOf(dancersTarget); + // Gen 7: Dancer activates in order of lowest speed stat to highest + // Note that the speed stat used is after any volatile replacements like Speed Swap, + // but before any multipliers like Agility or Choice Scarf + // Ties go to whichever Pokemon has had the ability for the least amount of time + this.queue.unshift(this.queue.resolveAction({ + choice: 'move', + order: 198, + effectOrder: dancer.abilityState.effectOrder, + pokemon: dancer, + moveid: move.id, + targetLoc: dancersTargetLoc, + sourceEffect: this.dex.abilities.get('dancer'), + externalMove: true, + })[0]); + }, onBeforeMovePriority: 200, onBeforeMove(source, target, move) { if (move.isExternal) { diff --git a/sim/battle-actions.ts b/sim/battle-actions.ts index d059c2f901..955d948fc6 100644 --- a/sim/battle-actions.ts +++ b/sim/battle-actions.ts @@ -309,31 +309,6 @@ export class BattleActions { this.battle.add('-hint', `Some effects can force a Pokemon to use ${move.name} again in a row.`); } - if (move.flags['dance'] && moveDidSomething && !move.isExternal) { - const targetOf1stDance = this.battle.activeTarget!; - const actions = []; - for (const dancer of this.battle.getAllActive()) { - if (pokemon === dancer || !dancer.hasAbility('dancer') || dancer.isSemiInvulnerable() || - dancer.fainted) continue; - const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? - targetOf1stDance : pokemon; - const dancersTargetLoc = dancer.getLocOf(dancersTarget); - actions.push(...this.battle.queue.resolveAction({ - choice: 'move', - order: 198, - rawSpeed: true, - effectOrder: dancer.abilityState.effectOrder, - pokemon: dancer, - moveid: move.id, - targetLoc: dancersTargetLoc, - sourceEffect: this.dex.abilities.get('dancer'), - externalMove: true, - })); - } - this.battle.speedSort(actions); - actions.forEach(action => this.battle.queue.unshift(action)); - } - this.battle.faintMessages(); this.battle.checkWin(); diff --git a/sim/battle.ts b/sim/battle.ts index 94241712ba..c28c9eb134 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -995,6 +995,10 @@ export class Battle { if (handler.effectHolder && (handler.effectHolder as Pokemon).getStat) { const pokemon = handler.effectHolder as Pokemon; handler.speed = pokemon.speed; + if (handler.effect.effectType === 'Ability' && handler.effect.name === 'Dancer' && + callbackName === 'onAnyAfterMove') { + handler.speed = pokemon.getStat('spe', true, true); + } if (callbackName.endsWith('SwitchIn')) { // Pokemon speeds including ties are resolved before all onSwitchIn handlers and aren't re-sorted in-between // so we subtract a fractional speed from each Pokemon's respective event handlers by using the index of their diff --git a/sim/dex-conditions.ts b/sim/dex-conditions.ts index 78279cd900..0a38371964 100644 --- a/sim/dex-conditions.ts +++ b/sim/dex-conditions.ts @@ -427,6 +427,7 @@ export interface EventMethods { onAfterMoveSecondarySelfPriority?: number; onAfterMoveSelfPriority?: number; onAfterSetStatusPriority?: number; + onAnyAfterMovePriority?: number; onAnyBasePowerPriority?: number; onAnyInvulnerabilityPriority?: number; onAnyModifyAccuracyPriority?: number; diff --git a/test/sim/abilities/dancer.js b/test/sim/abilities/dancer.js index 34e82d980e..0c84a6f032 100644 --- a/test/sim/abilities/dancer.js +++ b/test/sim/abilities/dancer.js @@ -24,10 +24,10 @@ describe('Dancer', () => { it('should activate in order of fastest to slowest', () => { battle = common.createBattle({ gameType: 'doubles' }, [[ { species: 'Shedinja', ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, - { species: 'Shedinja', level: 99, ability: 'dancer', moves: ['sleeptalk'] }, + { species: 'Shedinja', level: 98, ability: 'dancer', moves: ['sleeptalk'] }, ], [ { species: 'Shedinja', ability: 'wonderguard', moves: ['fierydance'] }, - { species: 'Shedinja', level: 98, ability: 'dancer', moves: ['sleeptalk'] }, + { species: 'Shedinja', level: 99, ability: 'dancer', moves: ['sleeptalk'] }, ]]); const [, fastDancer] = battle.p1.active; const [wwDanceSource, foeDancer] = battle.p2.active; @@ -37,6 +37,22 @@ describe('Dancer', () => { assert.fainted(foeDancer); }); + it('should activate in order of slowest to faster inside Trick Room', () => { + battle = common.gen(7).createBattle({ gameType: 'doubles' }, [[ + { species: 'Shedinja', ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, + { species: 'Shedinja', level: 98, ability: 'dancer', moves: ['sleeptalk'] }, + ], [ + { species: 'Shedinja', ability: 'wonderguard', moves: ['fierydance', 'trickroom'] }, + { species: 'Shedinja', level: 99, ability: 'dancer', moves: ['sleeptalk'] }, + ]]); + const [wwDanceSource, foeDancer] = battle.p2.active; + foeDancer.boostBy({ spe: 6 }); + battle.makeChoices('move sleeptalk, move sleeptalk', 'move trickroom, move sleeptalk'); + battle.makeChoices('move sleeptalk, move sleeptalk', 'move fierydance 1, move sleeptalk'); + assert.fainted(wwDanceSource); + assert.fainted(foeDancer); + }); + it(`should not copy a move that was blocked by Protect`, () => { battle = common.createBattle([[ { species: 'Oricorio', ability: 'dancer', moves: ['protect'] }, From 565e9209bd469f655a8fa42ff638fb5cc2819ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 23:09:41 +0000 Subject: [PATCH 20/30] Remove outdated code --- sim/battle-queue.ts | 2 -- sim/battle.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index f3daf600ba..b9dd7158c0 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -26,8 +26,6 @@ export interface MoveAction { fractionalPriority: number; /** speed of pokemon using move (higher first if priority tie) */ speed: number; - /** if rawSpeed, should sort using Pokemon's raw speed */ - rawSpeed?: boolean; /** the pokemon doing the move */ pokemon: Pokemon; /** location of the target, relative to pokemon's side */ diff --git a/sim/battle.ts b/sim/battle.ts index c28c9eb134..2c1c51cc9f 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2603,8 +2603,6 @@ export class Battle { if (!action.pokemon) { action.speed = 1; - } else if (action.rawSpeed) { - action.speed = action.pokemon.getStat('spe', true, true); } else { action.speed = action.pokemon.getActionSpeed(); } From 72ed7f1dce1c31198073dff3863a7f1e2e61d15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sun, 23 Mar 2025 23:15:55 +0000 Subject: [PATCH 21/30] Fix test --- test/sim/abilities/dancer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sim/abilities/dancer.js b/test/sim/abilities/dancer.js index 0c84a6f032..831bbbc84b 100644 --- a/test/sim/abilities/dancer.js +++ b/test/sim/abilities/dancer.js @@ -38,7 +38,7 @@ describe('Dancer', () => { }); it('should activate in order of slowest to faster inside Trick Room', () => { - battle = common.gen(7).createBattle({ gameType: 'doubles' }, [[ + battle = common.createBattle({ gameType: 'doubles' }, [[ { species: 'Shedinja', ability: 'dancer', item: 'focussash', moves: ['sleeptalk'] }, { species: 'Shedinja', level: 98, ability: 'dancer', moves: ['sleeptalk'] }, ], [ From d5433825179f9372a314bb7ac027afd36ca19dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Mon, 19 May 2025 10:08:08 +0000 Subject: [PATCH 22/30] Add Dancer test --- test/sim/moves/instruct.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/sim/moves/instruct.js b/test/sim/moves/instruct.js index fca4d72252..170ff9b6f7 100644 --- a/test/sim/moves/instruct.js +++ b/test/sim/moves/instruct.js @@ -17,6 +17,19 @@ describe(`Instruct`, () => { assert.equal(battle.p1.active[0].boosts.def, 2); }); + it(`should ignore moves called by Dancer`, () => { + battle = common.createBattle([ + [{ species: "Murkrow", ability: "prankster", moves: ['aquastep', 'instruct'] }], + [{ species: "Oricorio", ability: "dancer", moves: ['stockpile'] }], + ]); + battle.makeChoices(); + battle.makeChoices('move instruct', 'auto'); + const oricorio = battle.p2.active[0]; + assert.equal(oricorio.boosts.def, 3); + assert.equal(oricorio.boosts.spd, 3); + assert.equal(oricorio.boosts.spe, 1); + }); + it(`should not trigger AfterMove effects of the instructed move for the Instruct user`, () => { battle = common.createBattle([ [{ species: "Swalot", moves: ['stockpile', 'spitup'] }], From a28cd82901feb7062a9c0c4faee8ecc0658531ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Fri, 30 May 2025 00:53:21 +0100 Subject: [PATCH 23/30] Fix orders --- sim/battle-queue.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index b9dd7158c0..dca20d4cf4 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -19,7 +19,7 @@ import type { Battle } from './battle'; export interface MoveAction { /** action type */ choice: 'move' | 'beforeTurnMove' | 'priorityChargeMove'; - order: 3 | 5 | 107 | 198 | 199 | 200 | 201; + order: 3 | 5 | 105 | 198 | 199 | 200 | 201; /** priority of the action (lower first) */ priority: number; /** fractional priority of the action (lower first) */ @@ -52,7 +52,7 @@ export interface MoveAction { export interface SwitchAction { /** action type */ choice: 'switch' | 'instaswitch' | 'revivalblessing'; - order: 3 | 6 | 103; + order: 3 | 6 | 101; /** priority of the action (lower first) */ priority: number; /** speed of pokemon switching (higher first if priority tie) */ @@ -175,14 +175,12 @@ export class BattleQueue { beforeTurnMove: 5, revivalblessing: 6, - runSwitch: 101, - switch: 103, - megaEvo: 104, - megaEvoX: 104, - megaEvoY: 104, - runDynamax: 105, - terastallize: 106, - priorityChargeMove: 107, + runSwitch: 100, + switch: 101, + megaEvo: 102, megaEvoX: 102, megaEvoY: 102, + runDynamax: 103, + terastallize: 104, + priorityChargeMove: 105, shift: 200, // default is 200 (for moves) From ecc8ee357f0f41f5d6acc5c4d07f61a2a2b27dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= Date: Fri, 30 May 2025 01:31:51 +0100 Subject: [PATCH 24/30] Refactor --- data/abilities.ts | 7 +++---- sim/battle-queue.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data/abilities.ts b/data/abilities.ts index 8dc4bee969..6c7992ba80 100644 --- a/data/abilities.ts +++ b/data/abilities.ts @@ -846,16 +846,15 @@ export const Abilities: import('../sim/dex-abilities').AbilityDataTable = { // Note that the speed stat used is after any volatile replacements like Speed Swap, // but before any multipliers like Agility or Choice Scarf // Ties go to whichever Pokemon has had the ability for the least amount of time - this.queue.unshift(this.queue.resolveAction({ + this.queue.prioritizeAction(this.queue.resolveAction({ choice: 'move', - order: 198, effectOrder: dancer.abilityState.effectOrder, pokemon: dancer, moveid: move.id, targetLoc: dancersTargetLoc, - sourceEffect: this.dex.abilities.get('dancer'), + sourceEffect: this.effect, externalMove: true, - })[0]); + })[0] as MoveAction, this.effect); }, onBeforeMovePriority: 200, onBeforeMove(source, target, move) { diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index dca20d4cf4..bf15962e26 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -19,7 +19,7 @@ import type { Battle } from './battle'; export interface MoveAction { /** action type */ choice: 'move' | 'beforeTurnMove' | 'priorityChargeMove'; - order: 3 | 5 | 105 | 198 | 199 | 200 | 201; + order: 3 | 5 | 105 | 199 | 200 | 201; /** priority of the action (lower first) */ priority: number; /** fractional priority of the action (lower first) */ From e7f47175a0e1093451a003cffa3d7c3bc73eb5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= Date: Sun, 1 Jun 2025 09:13:07 +0100 Subject: [PATCH 25/30] Fix lint --- sim/battle.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sim/battle.ts b/sim/battle.ts index d08fab496c..b77a5c72ca 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -1002,9 +1002,9 @@ export class Battle { const pokemon = handler.effectHolder as Pokemon; handler.speed = pokemon.speed; if (handler.effect.effectType === 'Ability' && ( - (handler.effect.name === 'Dancer' && callbackName === 'onAnyAfterMove') || - (handler.effect.name === 'Magic Bounce' && callbackName === 'onAllyTryHitSide') - )) { + (handler.effect.name === 'Dancer' && callbackName === 'onAnyAfterMove') || + (handler.effect.name === 'Magic Bounce' && callbackName === 'onAllyTryHitSide') + )) { handler.speed = pokemon.getStat('spe', true, true); } if (callbackName.endsWith('SwitchIn')) { From 310e6fd79120e26d2185ee3c6a792ff2a834a31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Sat, 7 Jun 2025 13:34:30 +0100 Subject: [PATCH 26/30] Fix order --- sim/battle-queue.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sim/battle-queue.ts b/sim/battle-queue.ts index bf15962e26..4e8e1ff843 100644 --- a/sim/battle-queue.ts +++ b/sim/battle-queue.ts @@ -19,7 +19,7 @@ import type { Battle } from './battle'; export interface MoveAction { /** action type */ choice: 'move' | 'beforeTurnMove' | 'priorityChargeMove'; - order: 3 | 5 | 105 | 199 | 200 | 201; + order: 5 | 105 | 199 | 200 | 201; /** priority of the action (lower first) */ priority: number; /** fractional priority of the action (lower first) */ @@ -276,7 +276,7 @@ export class BattleQueue { } } action.sourceEffect = sourceEffect; - action.order = 3; + action.order = action.choice === 'move' ? 199 : 3; this.list.unshift(action); } From 6d14a1177cd2285a31cd3cbba375807555657fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:03:39 +0100 Subject: [PATCH 27/30] Add comment --- sim/battle.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/sim/battle.ts b/sim/battle.ts index b77a5c72ca..0b52bf37d8 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -1001,6 +1001,7 @@ export class Battle { if (handler.effectHolder && (handler.effectHolder as Pokemon).getStat) { const pokemon = handler.effectHolder as Pokemon; handler.speed = pokemon.speed; + // TODO: Check which other events are sorted based on the unmodified speed if (handler.effect.effectType === 'Ability' && ( (handler.effect.name === 'Dancer' && callbackName === 'onAnyAfterMove') || (handler.effect.name === 'Magic Bounce' && callbackName === 'onAllyTryHitSide') From 673a1bf36a92473bb8a39481b06e5c9dd8f0a8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:05:06 +0100 Subject: [PATCH 28/30] Move comment --- sim/battle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sim/battle.ts b/sim/battle.ts index 0b52bf37d8..583707f7ec 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -1001,11 +1001,11 @@ export class Battle { if (handler.effectHolder && (handler.effectHolder as Pokemon).getStat) { const pokemon = handler.effectHolder as Pokemon; handler.speed = pokemon.speed; - // TODO: Check which other events are sorted based on the unmodified speed if (handler.effect.effectType === 'Ability' && ( (handler.effect.name === 'Dancer' && callbackName === 'onAnyAfterMove') || (handler.effect.name === 'Magic Bounce' && callbackName === 'onAllyTryHitSide') )) { + // TODO: Check which other events are sorted based on the unmodified speed handler.speed = pokemon.getStat('spe', true, true); } if (callbackName.endsWith('SwitchIn')) { From 5e9c6a39749cc38d8e3d46a4511324d709f85f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= Date: Tue, 24 Jun 2025 13:23:21 +0100 Subject: [PATCH 29/30] Add Instruct test --- test/sim/abilities/dancer.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/sim/abilities/dancer.js b/test/sim/abilities/dancer.js index 831bbbc84b..abed9bf253 100644 --- a/test/sim/abilities/dancer.js +++ b/test/sim/abilities/dancer.js @@ -53,6 +53,20 @@ describe('Dancer', () => { assert.fainted(foeDancer); }); + it(`should copy a move that was called by Instruct`, () => { + battle = common.createBattle({ gameType: 'doubles' }, [[ + { species: 'oricoriopau', ability: 'dancer', moves: ['instruct'] }, + { species: 'volcarona', moves: ['fierydance'] }, + ], [ + { species: 'shedinja', item: 'focussash', ability: 'wonderguard', moves: ['sleeptalk'] }, + { species: 'shedinja', item: 'focussash', ability: 'wonderguard', moves: ['sleeptalk'] }, + ]]); + + battle.makeChoices('move instruct -2, move fierydance 1', 'auto'); + assert.fainted(battle.p2.active[0]); + assert.fainted(battle.p2.active[1]); + }); + it(`should not copy a move that was blocked by Protect`, () => { battle = common.createBattle([[ { species: 'Oricorio', ability: 'dancer', moves: ['protect'] }, From 78430fcfcb01ca96685104d79f91a02b573097d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= Date: Wed, 6 Aug 2025 22:55:42 +0100 Subject: [PATCH 30/30] Remove logic from mods --- data/mods/gen8linked/scripts.ts | 31 ----------------------------- data/mods/gen9ssb/scripts.ts | 35 --------------------------------- 2 files changed, 66 deletions(-) diff --git a/data/mods/gen8linked/scripts.ts b/data/mods/gen8linked/scripts.ts index 1c4d7811cf..5c8d75ed82 100644 --- a/data/mods/gen8linked/scripts.ts +++ b/data/mods/gen8linked/scripts.ts @@ -361,8 +361,6 @@ export const Scripts: ModdedBattleScriptsData = { move = this.getActiveMaxMove(baseMove, pokemon); } - move.isExternal = externalMove; - this.battle.setActiveMove(move, pokemon, target); /* if (pokemon.moveThisTurn) { @@ -415,10 +413,6 @@ export const Scripts: ModdedBattleScriptsData = { pokemon.moveUsed(move, targetLoc); } - // Dancer Petal Dance hack - // TODO: implement properly - const noLock = externalMove && !pokemon.volatiles.lockedmove; - if (zMove) { if (pokemon.illusion) { this.battle.singleEvent('End', this.dex.abilities.get('Illusion'), pokemon.abilityState, pokemon); @@ -432,31 +426,6 @@ export const Scripts: ModdedBattleScriptsData = { this.battle.singleEvent('AfterMove', move, null, pokemon, target, move); this.battle.runEvent('AfterMove', pokemon, target, move); - // Dancer's activation order is completely different from any other event, so it's handled separately - if (move.flags['dance'] && moveDidSomething && !move.isExternal) { - const dancers = []; - for (const currentPoke of this.battle.getAllActive()) { - if (pokemon === currentPoke) continue; - if (currentPoke.hasAbility('dancer') && !currentPoke.isSemiInvulnerable()) { - dancers.push(currentPoke); - } - } - // Dancer activates in order of lowest speed stat to highest - // Note that the speed stat used is after any volatile replacements like Speed Swap, - // but before any multipliers like Agility or Choice Scarf - // Ties go to whichever Pokemon has had the ability for the least amount of time - dancers.sort((a, b) => -(b.storedStats['spe'] - a.storedStats['spe']) || - b.abilityState.effectOrder - a.abilityState.effectOrder); - for (const dancer of dancers) { - if (this.battle.faintMessages()) break; - if (dancer.fainted) continue; - this.battle.add('-activate', dancer, 'ability: Dancer'); - const dancersTarget = !target!.isAlly(dancer) && pokemon.isAlly(dancer) ? target! : pokemon; - this.runMove(move.id, dancer, dancer.getLocOf(dancersTarget), - { sourceEffect: this.dex.abilities.get('dancer'), externalMove: true }); - } - } - if (noLock && pokemon.volatiles['lockedmove']) delete pokemon.volatiles['lockedmove']; this.battle.faintMessages(); this.battle.checkWin(); }, diff --git a/data/mods/gen9ssb/scripts.ts b/data/mods/gen9ssb/scripts.ts index 7011b826d6..6bfe36d06a 100644 --- a/data/mods/gen9ssb/scripts.ts +++ b/data/mods/gen9ssb/scripts.ts @@ -1131,8 +1131,6 @@ export const Scripts: ModdedBattleScriptsData = { move = this.getActiveMaxMove(baseMove, pokemon); } - move.isExternal = externalMove; - this.battle.setActiveMove(move, pokemon, target); /* if (pokemon.moveThisTurn) { @@ -1178,10 +1176,6 @@ export const Scripts: ModdedBattleScriptsData = { pokemon.moveUsed(move, targetLoc); } - // Dancer Petal Dance hack - // TODO: implement properly - const noLock = externalMove && !pokemon.volatiles['lockedmove']; - if (zMove) { if (pokemon.illusion) { this.battle.singleEvent('End', this.dex.abilities.get('Illusion'), pokemon.abilityState, pokemon); @@ -1199,35 +1193,6 @@ export const Scripts: ModdedBattleScriptsData = { this.battle.singleEvent('AfterMove', move, null, pokemon, target, move); this.battle.runEvent('AfterMove', pokemon, target, move); - // Dancer's activation order is completely different from any other event, so it's handled separately - if (move.flags['dance'] && moveDidSomething && !move.isExternal) { - const dancers = []; - for (const currentPoke of this.battle.getAllActive()) { - if (pokemon === currentPoke) continue; - if (currentPoke.hasAbility(['dancer', 'virtualidol']) && !currentPoke.isSemiInvulnerable()) { - dancers.push(currentPoke); - } - } - // Dancer activates in order of lowest speed stat to highest - // Note that the speed stat used is after any volatile replacements like Speed Swap, - // but before any multipliers like Agility or Choice Scarf - // Ties go to whichever Pokemon has had the ability for the least amount of time - dancers.sort( - (a, b) => -(b.storedStats['spe'] - a.storedStats['spe']) || b.abilityState.effectOrder - a.abilityState.effectOrder - ); - const targetOf1stDance = this.battle.activeTarget!; - for (const dancer of dancers) { - if (this.battle.faintMessages()) break; - if (dancer.fainted) continue; - this.battle.add('-activate', dancer, 'ability: ' + dancer.getAbility().name); - const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? - targetOf1stDance : - pokemon; - const dancersTargetLoc = dancer.getLocOf(dancersTarget); - this.runMove(move.id, dancer, dancersTargetLoc, { sourceEffect: dancer.getAbility(), externalMove: true }); - } - } - if (noLock && pokemon.volatiles['lockedmove']) delete pokemon.volatiles['lockedmove']; this.battle.faintMessages(); this.battle.checkWin();