2025-01-12 15:33:05 -08:00
import { globalScene } from "#app/global-scene" ;
import type { TurnCommand } from "#app/battle" ;
import { BattleType } from "#app/battle" ;
import type { EncoreTag } from "#app/data/battler-tags" ;
import { TrappedTag } from "#app/data/battler-tags" ;
import type { MoveTargetSet } from "#app/data/move" ;
import { getMoveTargets } from "#app/data/move" ;
2024-10-02 09:20:19 -04:00
import { speciesStarterCosts } from "#app/data/balance/starters" ;
2024-09-01 20:39:26 -07:00
import { Abilities } from "#app/enums/abilities" ;
import { BattlerTagType } from "#app/enums/battler-tag-type" ;
import { Biome } from "#app/enums/biome" ;
import { Moves } from "#app/enums/moves" ;
2024-11-08 14:44:34 -08:00
import { PokeballType } from "#enums/pokeball" ;
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
import type { PlayerPokemon , TurnMove } from "#app/field/pokemon" ;
2025-01-12 15:33:05 -08:00
import { FieldPosition } from "#app/field/pokemon" ;
2024-09-01 20:39:26 -07:00
import { getPokemonNameWithAffix } from "#app/messages" ;
import { Command } from "#app/ui/command-ui-handler" ;
import { Mode } from "#app/ui/ui" ;
2024-08-19 03:23:52 +01:00
import i18next from "i18next" ;
import { FieldPhase } from "./field-phase" ;
import { SelectTargetPhase } from "./select-target-phase" ;
2024-09-13 22:05:58 -04:00
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode" ;
2024-09-19 09:44:10 -04:00
import { isNullOrUndefined } from "#app/utils" ;
2024-11-03 20:56:54 -06:00
import { ArenaTagSide } from "#app/data/arena-tag" ;
import { ArenaTagType } from "#app/enums/arena-tag-type" ;
2024-08-19 03:23:52 +01:00
export class CommandPhase extends FieldPhase {
protected fieldIndex : integer ;
2025-01-12 15:33:05 -08:00
constructor ( fieldIndex : integer ) {
super ( ) ;
2024-08-19 03:23:52 +01:00
this . fieldIndex = fieldIndex ;
}
start() {
super . start ( ) ;
2025-01-12 15:33:05 -08:00
globalScene . updateGameInfo ( ) ;
2024-11-06 09:42:39 -07:00
2025-01-12 15:33:05 -08:00
const commandUiHandler = globalScene . ui . handlers [ Mode . COMMAND ] ;
2024-12-30 16:35:45 +01:00
// If one of these conditions is true, we always reset the cursor to Command.FIGHT
2025-01-12 15:33:05 -08:00
const cursorResetEvent = globalScene . currentBattle . battleType === BattleType . MYSTERY_ENCOUNTER ||
globalScene . currentBattle . battleType === BattleType . TRAINER ||
globalScene . arena . biomeType === Biome . END ;
2024-12-30 16:35:45 +01:00
2024-10-22 13:05:37 -07:00
if ( commandUiHandler ) {
2025-01-12 15:33:05 -08:00
if ( ( globalScene . currentBattle . turn === 1 && ( ! globalScene . commandCursorMemory || cursorResetEvent ) ) || commandUiHandler . getCursor ( ) === Command . POKEMON ) {
2024-10-22 13:05:37 -07:00
commandUiHandler . setCursor ( Command . FIGHT ) ;
} else {
commandUiHandler . setCursor ( commandUiHandler . getCursor ( ) ) ;
}
}
2024-08-19 03:23:52 +01:00
if ( this . fieldIndex ) {
// If we somehow are attempting to check the right pokemon but there's only one pokemon out
// Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching
2025-01-12 15:33:05 -08:00
if ( globalScene . getPlayerField ( ) . filter ( p = > p . isActive ( ) ) . length === 1 ) {
2024-08-19 03:23:52 +01:00
this . fieldIndex = FieldPosition . CENTER ;
} else {
2025-01-12 15:33:05 -08:00
const allyCommand = globalScene . currentBattle . turnCommands [ this . fieldIndex - 1 ] ;
2024-08-19 03:23:52 +01:00
if ( allyCommand ? . command === Command . BALL || allyCommand ? . command === Command . RUN ) {
2025-01-12 15:33:05 -08:00
globalScene . currentBattle . turnCommands [ this . fieldIndex ] = { command : allyCommand?.command , skip : true } ;
2024-08-19 03:23:52 +01:00
}
}
}
2024-11-05 09:35:43 -08:00
// If the Pokemon has applied Commander's effects to its ally, skip this command
2025-01-12 15:33:05 -08:00
if ( globalScene . currentBattle ? . double && this . getPokemon ( ) . getAlly ( ) ? . getTag ( BattlerTagType . COMMANDED ) ? . getSourcePokemon ( ) === this . getPokemon ( ) ) {
globalScene . currentBattle . turnCommands [ this . fieldIndex ] = { command : Command.FIGHT , move : { move : Moves.NONE , targets : [ ] } , skip : true } ;
2024-11-05 09:35:43 -08:00
}
2024-11-08 09:35:33 -08:00
// Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP.
const encoreTag = this . getPokemon ( ) . getTag ( BattlerTagType . ENCORE ) as EncoreTag ;
if ( encoreTag ) {
this . getPokemon ( ) . lapseTag ( BattlerTagType . ENCORE ) ;
}
2025-01-12 15:33:05 -08:00
if ( globalScene . currentBattle . turnCommands [ this . fieldIndex ] ? . skip ) {
2024-08-19 03:23:52 +01:00
return this . end ( ) ;
}
2025-01-12 15:33:05 -08:00
const playerPokemon = globalScene . getPlayerField ( ) [ this . fieldIndex ] ;
2024-08-19 03:23:52 +01:00
const moveQueue = playerPokemon . getMoveQueue ( ) ;
while ( moveQueue . length && moveQueue [ 0 ]
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
&& moveQueue [ 0 ] . move && ! moveQueue [ 0 ] . virtual && ( ! playerPokemon . getMoveset ( ) . find ( m = > m ? . moveId === moveQueue [ 0 ] . move )
2024-08-19 03:23:52 +01:00
|| ! playerPokemon . getMoveset ( ) [ playerPokemon . getMoveset ( ) . findIndex ( m = > m ? . moveId === moveQueue [ 0 ] . move ) ] ! . isUsable ( playerPokemon , moveQueue [ 0 ] . ignorePP ) ) ) { // TODO: is the bang correct?
moveQueue . shift ( ) ;
}
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
if ( moveQueue . length > 0 ) {
2024-08-19 03:23:52 +01:00
const queuedMove = moveQueue [ 0 ] ;
if ( ! queuedMove . move ) {
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
this . handleCommand ( Command . FIGHT , - 1 ) ;
2024-08-19 03:23:52 +01:00
} else {
const moveIndex = playerPokemon . getMoveset ( ) . findIndex ( m = > m ? . moveId === queuedMove . move ) ;
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
if ( ( moveIndex > - 1 && playerPokemon . getMoveset ( ) [ moveIndex ] ! . isUsable ( playerPokemon , queuedMove . ignorePP ) ) || queuedMove . virtual ) { // TODO: is the bang correct?
this . handleCommand ( Command . FIGHT , moveIndex , queuedMove . ignorePP , queuedMove ) ;
2024-08-19 03:23:52 +01:00
} else {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-08-19 03:23:52 +01:00
}
}
} else {
2025-01-12 15:33:05 -08:00
if ( globalScene . currentBattle . isBattleMysteryEncounter ( ) && globalScene . currentBattle . mysteryEncounter ? . skipToFightInput ) {
globalScene . ui . clearText ( ) ;
globalScene . ui . setMode ( Mode . FIGHT , this . fieldIndex ) ;
2024-09-13 22:05:58 -04:00
} else {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-09-13 22:05:58 -04:00
}
2024-08-19 03:23:52 +01:00
}
}
handleCommand ( command : Command , cursor : integer , . . . args : any [ ] ) : boolean {
2025-01-12 15:33:05 -08:00
const playerPokemon = globalScene . getPlayerField ( ) [ this . fieldIndex ] ;
2024-11-05 09:35:43 -08:00
let success : boolean = false ;
2024-08-19 03:23:52 +01:00
switch ( command ) {
2024-10-19 18:44:36 -07:00
case Command . FIGHT :
let useStruggle = false ;
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
const turnMove : TurnMove | undefined = ( args . length === 2 ? ( args [ 1 ] as TurnMove ) : undefined ) ;
2024-10-19 18:44:36 -07:00
if ( cursor === - 1 ||
2024-08-19 03:23:52 +01:00
playerPokemon . trySelectMove ( cursor , args [ 0 ] as boolean ) ||
( useStruggle = cursor > - 1 && ! playerPokemon . getMoveset ( ) . filter ( m = > m ? . isUsable ( playerPokemon ) ) . length ) ) {
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
let moveId : Moves ;
if ( useStruggle ) {
moveId = Moves . STRUGGLE ;
} else if ( turnMove !== undefined ) {
moveId = turnMove . move ;
} else if ( cursor > - 1 ) {
moveId = playerPokemon . getMoveset ( ) [ cursor ] ! . moveId ;
} else {
moveId = Moves . NONE ;
}
2024-10-19 18:44:36 -07:00
const turnCommand : TurnCommand = { command : Command.FIGHT , cursor : cursor , move : { move : moveId , targets : [ ] , ignorePP : args [ 0 ] } , args : args } ;
[Bug][Move] Refactor moves that call a random move (#3380)
* Combine moveset from allies and uses it to get a move
* Clearer implementation of combining user and teammates' moves
* Refactor assist and sleep talk to use metronome's attribute for calling a move
* Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk
* Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr
* Correct invalid move lists, adds Max/Z moves to metronome's list
* Remove ignoresVirtual from beta merge
* Remove Max/Z moves per frutescens' comment
* Fix bug with metronome/copycat/assist/sleep talk targeting ally
* Experimental async/await to be tested
* Refactor other attributes to extend CallMoveAttr
* Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome
* Fix Swallow test due to TurnMove refactor
* Further fixes for TurnMove refactor
* Fix metronome two turn moves for enemy pokemon
* Replace nested ternary with if-else block per DayKev's comment
* Minor fixes
* Adjust command phase args handling
* Create metronome test, refactor RandomMoveAttr for easier testing
* Add unit test for recharge moves
* Refactor Copycat and Mirror Move, adjust move targeting
* Add unit test for ally targeting with Aromatic Mist
* Add tests for secondary effects and recharge moves for metronome
* Add test for Roar, remove test for Acupressure
* Create test for Assist
* Add test for assist failing
* Add sleep talk unit test coverage
* Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat
* Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests
* Correct mirror move implementation, rewrite unit test to adjust
* Add docs to attrs, update assist to only grab allies sets
* Update assist unit test to match expected functionality
* Update metronome unit test to use getMoveOverride
* Update copycat unit test to use metronome getMoveOverride mock
* Fix phase interception
* Add docs from missed conversations
* Update assist tests to use manual moveset overrides
Minor fixes to other tests
* Remove `export` from `CallMoveAttr`
* Add missing `.unimplemented()` to some Max- and Z-Moves
---------
Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-01-14 18:26:35 -05:00
const moveTargets : MoveTargetSet = turnMove === undefined ? getMoveTargets ( playerPokemon , moveId ) : { targets : turnMove.targets , multiple : turnMove.targets.length > 1 } ;
2024-10-19 18:44:36 -07:00
if ( ! moveId ) {
turnCommand . targets = [ this . fieldIndex ] ;
}
console . log ( moveTargets , getPokemonNameWithAffix ( playerPokemon ) ) ;
if ( moveTargets . targets . length > 1 && moveTargets . multiple ) {
2025-01-12 15:33:05 -08:00
globalScene . unshiftPhase ( new SelectTargetPhase ( this . fieldIndex ) ) ;
2024-10-19 18:44:36 -07:00
}
if ( moveTargets . targets . length <= 1 || moveTargets . multiple ) {
2024-08-19 03:23:52 +01:00
turnCommand . move ! . targets = moveTargets . targets ; //TODO: is the bang correct here?
2024-10-19 18:44:36 -07:00
} else if ( playerPokemon . getTag ( BattlerTagType . CHARGING ) && playerPokemon . getMoveQueue ( ) . length >= 1 ) {
2024-08-19 03:23:52 +01:00
turnCommand . move ! . targets = playerPokemon . getMoveQueue ( ) [ 0 ] . targets ; //TODO: is the bang correct here?
2024-10-19 18:44:36 -07:00
} else {
2025-01-12 15:33:05 -08:00
globalScene . unshiftPhase ( new SelectTargetPhase ( this . fieldIndex ) ) ;
2024-10-19 18:44:36 -07:00
}
2025-01-12 15:33:05 -08:00
globalScene . currentBattle . turnCommands [ this . fieldIndex ] = turnCommand ;
2024-10-19 18:44:36 -07:00
success = true ;
} else if ( cursor < playerPokemon . getMoveset ( ) . length ) {
const move = playerPokemon . getMoveset ( ) [ cursor ] ! ; //TODO: is this bang correct?
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . MESSAGE ) ;
2024-10-19 18:44:36 -07:00
// Decides between a Disabled, Not Implemented, or No PP translation message
const errorMessage =
2024-10-13 00:30:04 -07:00
playerPokemon . isMoveRestricted ( move . moveId , playerPokemon )
? playerPokemon . getRestrictingTag ( move . moveId , playerPokemon ) ! . selectionDeniedText ( playerPokemon , move . moveId )
2024-09-05 04:44:22 -04:00
: move . getName ( ) . endsWith ( " (N)" ) ? "battle:moveNotImplemented" : "battle:moveNoPP" ;
2024-10-19 18:44:36 -07:00
const moveName = move . getName ( ) . replace ( " (N)" , "" ) ; // Trims off the indicator
2024-08-19 03:23:52 +01:00
2025-01-12 15:33:05 -08:00
globalScene . ui . showText ( i18next . t ( errorMessage , { moveName : moveName } ) , null , ( ) = > {
globalScene . ui . clearText ( ) ;
globalScene . ui . setMode ( Mode . FIGHT , this . fieldIndex ) ;
2024-10-19 18:44:36 -07:00
} , null , true ) ;
}
break ;
case Command . BALL :
2025-01-12 15:33:05 -08:00
const notInDex = ( globalScene . getEnemyField ( ) . filter ( p = > p . isActive ( true ) ) . some ( p = > ! globalScene . gameData . dexData [ p . species . speciesId ] . caughtAttr ) && globalScene . gameData . getStarterCount ( d = > ! ! d . caughtAttr ) < Object . keys ( speciesStarterCosts ) . length - 1 ) ;
if ( globalScene . arena . biomeType === Biome . END && ( ! globalScene . gameMode . isClassic || globalScene . gameMode . isFreshStartChallenge ( ) || notInDex ) ) {
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
globalScene . ui . showText ( i18next . t ( "battle:noPokeballForce" ) , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-10-19 18:44:36 -07:00
} , null , true ) ;
2025-01-12 15:33:05 -08:00
} else if ( globalScene . currentBattle . battleType === BattleType . TRAINER ) {
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
globalScene . ui . showText ( i18next . t ( "battle:noPokeballTrainer" ) , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-10-19 18:44:36 -07:00
} , null , true ) ;
2025-01-12 15:33:05 -08:00
} else if ( globalScene . currentBattle . isBattleMysteryEncounter ( ) && ! globalScene . currentBattle . mysteryEncounter ! . catchAllowed ) {
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
globalScene . ui . showText ( i18next . t ( "battle:noPokeballMysteryEncounter" ) , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-08-19 03:23:52 +01:00
} , null , true ) ;
2024-10-19 18:44:36 -07:00
} else {
2025-01-12 15:33:05 -08:00
const targets = globalScene . getEnemyField ( ) . filter ( p = > p . isActive ( true ) ) . map ( p = > p . getBattlerIndex ( ) ) ;
2024-10-19 18:44:36 -07:00
if ( targets . length > 1 ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
globalScene . ui . showText ( i18next . t ( "battle:noPokeballMulti" ) , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-08-19 03:23:52 +01:00
} , null , true ) ;
2024-10-19 18:44:36 -07:00
} else if ( cursor < 5 ) {
2025-01-12 15:33:05 -08:00
const targetPokemon = globalScene . getEnemyField ( ) . find ( p = > p . isActive ( true ) ) ;
2024-10-19 18:44:36 -07:00
if ( targetPokemon ? . isBoss ( ) && targetPokemon ? . bossSegmentIndex >= 1 && ! targetPokemon ? . hasAbility ( Abilities . WONDER_GUARD , false , true ) && cursor < PokeballType . MASTER_BALL ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
globalScene . ui . showText ( i18next . t ( "battle:noPokeballStrong" ) , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-10-19 18:44:36 -07:00
} , null , true ) ;
} else {
2025-01-12 15:33:05 -08:00
globalScene . currentBattle . turnCommands [ this . fieldIndex ] = { command : Command.BALL , cursor : cursor } ;
globalScene . currentBattle . turnCommands [ this . fieldIndex ] ! . targets = targets ;
2024-08-19 03:23:52 +01:00
if ( this . fieldIndex ) {
2025-01-12 15:33:05 -08:00
globalScene . currentBattle . turnCommands [ this . fieldIndex - 1 ] ! . skip = true ;
2024-08-19 03:23:52 +01:00
}
success = true ;
2024-10-19 18:44:36 -07:00
}
2024-08-19 03:23:52 +01:00
}
}
2024-10-19 18:44:36 -07:00
break ;
case Command . POKEMON :
case Command . RUN :
const isSwitch = command === Command . POKEMON ;
2025-01-12 15:33:05 -08:00
const { currentBattle , arena } = globalScene ;
2024-10-19 18:44:36 -07:00
const mysteryEncounterFleeAllowed = currentBattle . mysteryEncounter ? . fleeAllowed ;
if ( ! isSwitch && ( arena . biomeType === Biome . END || ( ! isNullOrUndefined ( mysteryEncounterFleeAllowed ) && ! mysteryEncounterFleeAllowed ) ) ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
globalScene . ui . showText ( i18next . t ( "battle:noEscapeForce" ) , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-10-19 18:44:36 -07:00
} , null , true ) ;
} else if ( ! isSwitch && ( currentBattle . battleType === BattleType . TRAINER || currentBattle . mysteryEncounter ? . encounterMode === MysteryEncounterMode . TRAINER_BATTLE ) ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
globalScene . ui . showText ( i18next . t ( "battle:noEscapeTrainer" ) , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-09-01 20:39:26 -07:00
} , null , true ) ;
} else {
2024-10-19 18:44:36 -07:00
const batonPass = isSwitch && args [ 0 ] as boolean ;
const trappedAbMessages : string [ ] = [ ] ;
if ( batonPass || ! playerPokemon . isTrapped ( trappedAbMessages ) ) {
2024-09-19 09:44:10 -04:00
currentBattle . turnCommands [ this . fieldIndex ] = isSwitch
2024-08-19 03:23:52 +01:00
? { command : Command.POKEMON , cursor : cursor , args : args }
: { command : Command.RUN } ;
2024-10-19 18:44:36 -07:00
success = true ;
if ( ! isSwitch && this . fieldIndex ) {
currentBattle . turnCommands [ this . fieldIndex - 1 ] ! . skip = true ;
}
} else if ( trappedAbMessages . length > 0 ) {
if ( ! isSwitch ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . MESSAGE ) ;
2024-10-19 18:44:36 -07:00
}
2025-01-12 15:33:05 -08:00
globalScene . ui . showText ( trappedAbMessages [ 0 ] , null , ( ) = > {
globalScene . ui . showText ( "" , 0 ) ;
2024-08-19 03:23:52 +01:00
if ( ! isSwitch ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-08-19 03:23:52 +01:00
}
} , null , true ) ;
2024-10-19 18:44:36 -07:00
} else {
const trapTag = playerPokemon . getTag ( TrappedTag ) ;
2025-01-12 15:33:05 -08:00
const fairyLockTag = globalScene . arena . getTagOnSide ( ArenaTagType . FAIRY_LOCK , ArenaTagSide . PLAYER ) ;
2024-10-19 18:44:36 -07:00
2024-11-03 20:56:54 -06:00
if ( ! trapTag && ! fairyLockTag ) {
2024-11-05 09:35:43 -08:00
i18next . t ( ` battle:noEscape ${ isSwitch ? "Switch" : "Flee" } ` ) ;
2024-10-19 18:44:36 -07:00
break ;
}
if ( ! isSwitch ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
globalScene . ui . setMode ( Mode . MESSAGE ) ;
2024-10-19 18:44:36 -07:00
}
2024-11-03 20:56:54 -06:00
const showNoEscapeText = ( tag : any ) = > {
2025-01-12 15:33:05 -08:00
globalScene . ui . showText (
2024-11-03 20:56:54 -06:00
i18next . t ( "battle:noEscapePokemon" , {
2025-01-12 15:33:05 -08:00
pokemonName : tag.sourceId && globalScene . getPokemonById ( tag . sourceId ) ? getPokemonNameWithAffix ( globalScene . getPokemonById ( tag . sourceId ) ! ) : "" ,
2024-11-03 20:56:54 -06:00
moveName : tag.getMoveName ( ) ,
escapeVerb : isSwitch ? i18next . t ( "battle:escapeVerbSwitch" ) : i18next . t ( "battle:escapeVerbFlee" )
} ) ,
null ,
( ) = > {
2025-01-12 15:33:05 -08:00
globalScene . ui . showText ( "" , 0 ) ;
2024-11-03 20:56:54 -06:00
if ( ! isSwitch ) {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . COMMAND , this . fieldIndex ) ;
2024-11-03 20:56:54 -06:00
}
} ,
null ,
true
) ;
} ;
if ( trapTag ) {
showNoEscapeText ( trapTag ) ;
} else if ( fairyLockTag ) {
showNoEscapeText ( fairyLockTag ) ;
}
2024-10-19 18:44:36 -07:00
}
2024-08-19 03:23:52 +01:00
}
2024-10-19 18:44:36 -07:00
break ;
2024-08-19 03:23:52 +01:00
}
2024-11-05 09:35:43 -08:00
if ( success ) {
2024-08-19 03:23:52 +01:00
this . end ( ) ;
}
2024-11-05 09:35:43 -08:00
return success ;
2024-08-19 03:23:52 +01:00
}
cancel() {
if ( this . fieldIndex ) {
2025-01-12 15:33:05 -08:00
globalScene . unshiftPhase ( new CommandPhase ( 0 ) ) ;
globalScene . unshiftPhase ( new CommandPhase ( 1 ) ) ;
2024-08-19 03:23:52 +01:00
this . end ( ) ;
}
}
getFieldIndex ( ) : integer {
return this . fieldIndex ;
}
getPokemon ( ) : PlayerPokemon {
2025-01-12 15:33:05 -08:00
return globalScene . getPlayerField ( ) [ this . fieldIndex ] ;
2024-08-19 03:23:52 +01:00
}
end() {
2025-01-12 15:33:05 -08:00
globalScene . ui . setMode ( Mode . MESSAGE ) . then ( ( ) = > super . end ( ) ) ;
2024-08-19 03:23:52 +01:00
}
}