2024-08-06 07:06:25 -07:00
import { Phase } from "#app/phase" ;
2024-09-02 22:12:34 -04:00
import ErrorInterceptor from "#app/test/utils/errorInterceptor" ;
2024-09-05 15:29:39 +10:00
import { AttemptRunPhase } from "#app/phases/attempt-run-phase" ;
2024-08-22 06:49:33 -07:00
import { BattleEndPhase } from "#app/phases/battle-end-phase" ;
import { BerryPhase } from "#app/phases/berry-phase" ;
import { CheckSwitchPhase } from "#app/phases/check-switch-phase" ;
import { CommandPhase } from "#app/phases/command-phase" ;
import { DamagePhase } from "#app/phases/damage-phase" ;
import { EggLapsePhase } from "#app/phases/egg-lapse-phase" ;
import { EncounterPhase } from "#app/phases/encounter-phase" ;
2024-08-30 20:21:56 -07:00
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase" ;
2024-08-22 06:49:33 -07:00
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase" ;
2024-08-30 20:21:56 -07:00
import { EvolutionPhase } from "#app/phases/evolution-phase" ;
2024-08-22 06:49:33 -07:00
import { FaintPhase } from "#app/phases/faint-phase" ;
2024-09-09 09:11:46 -07:00
import { LearnMovePhase } from "#app/phases/learn-move-phase" ;
2024-09-03 09:14:45 -04:00
import { LevelCapPhase } from "#app/phases/level-cap-phase" ;
2024-08-22 06:49:33 -07:00
import { LoginPhase } from "#app/phases/login-phase" ;
import { MessagePhase } from "#app/phases/message-phase" ;
import { MoveEffectPhase } from "#app/phases/move-effect-phase" ;
import { MoveEndPhase } from "#app/phases/move-end-phase" ;
import { MovePhase } from "#app/phases/move-phase" ;
import { NewBattlePhase } from "#app/phases/new-battle-phase" ;
2024-09-03 09:14:45 -04:00
import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase" ;
2024-08-22 06:49:33 -07:00
import { NextEncounterPhase } from "#app/phases/next-encounter-phase" ;
import { PostSummonPhase } from "#app/phases/post-summon-phase" ;
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase" ;
import { SelectGenderPhase } from "#app/phases/select-gender-phase" ;
import { SelectModifierPhase } from "#app/phases/select-modifier-phase" ;
import { SelectStarterPhase } from "#app/phases/select-starter-phase" ;
import { SelectTargetPhase } from "#app/phases/select-target-phase" ;
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase" ;
import { ShowAbilityPhase } from "#app/phases/show-ability-phase" ;
2024-09-02 22:12:34 -04:00
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase" ;
2024-08-22 06:49:33 -07:00
import { SummonPhase } from "#app/phases/summon-phase" ;
import { SwitchPhase } from "#app/phases/switch-phase" ;
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase" ;
import { TitlePhase } from "#app/phases/title-phase" ;
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase" ;
import { TurnEndPhase } from "#app/phases/turn-end-phase" ;
import { TurnInitPhase } from "#app/phases/turn-init-phase" ;
import { TurnStartPhase } from "#app/phases/turn-start-phase" ;
import { UnavailablePhase } from "#app/phases/unavailable-phase" ;
import { VictoryPhase } from "#app/phases/victory-phase" ;
2024-09-02 22:12:34 -04:00
import { PartyHealPhase } from "#app/phases/party-heal-phase" ;
2024-08-22 06:49:33 -07:00
import UI , { Mode } from "#app/ui/ui" ;
2024-09-26 04:39:59 -04:00
import { SelectBiomePhase } from "#app/phases/select-biome-phase" ;
2024-09-13 22:05:58 -04:00
import {
MysteryEncounterBattlePhase ,
MysteryEncounterOptionSelectedPhase ,
MysteryEncounterPhase ,
MysteryEncounterRewardsPhase ,
PostMysteryEncounterPhase
} from "#app/phases/mystery-encounter-phases" ;
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase" ;
import { PartyExpPhase } from "#app/phases/party-exp-phase" ;
export interface PromptHandler {
phaseTarget? : string ;
mode? : Mode ;
callback ? : ( ) = > void ;
expireFn ? : ( ) = > void ;
awaitingActionInput? : boolean ;
}
2024-09-14 09:35:46 -07:00
import { ExpPhase } from "#app/phases/exp-phase" ;
2024-06-08 00:33:45 +02:00
export default class PhaseInterceptor {
public scene ;
public phases = { } ;
2024-08-06 23:29:51 -07:00
public log : string [ ] ;
2024-06-08 00:33:45 +02:00
private onHold ;
private interval ;
private promptInterval ;
private intervalRun ;
2024-09-13 22:05:58 -04:00
private prompts : PromptHandler [ ] ;
2024-06-08 00:33:45 +02:00
private phaseFrom ;
2024-06-08 21:54:20 +02:00
private inProgress ;
private originalSetMode ;
private originalSuperEnd ;
2024-06-08 00:33:45 +02:00
/ * *
* List of phases with their corresponding start methods .
* /
private PHASES = [
[ LoginPhase , this . startPhase ] ,
[ TitlePhase , this . startPhase ] ,
[ SelectGenderPhase , this . startPhase ] ,
[ EncounterPhase , this . startPhase ] ,
2024-09-03 09:14:45 -04:00
[ NewBiomeEncounterPhase , this . startPhase ] ,
2024-06-08 00:33:45 +02:00
[ SelectStarterPhase , this . startPhase ] ,
[ PostSummonPhase , this . startPhase ] ,
[ SummonPhase , this . startPhase ] ,
[ ToggleDoublePositionPhase , this . startPhase ] ,
[ CheckSwitchPhase , this . startPhase ] ,
[ ShowAbilityPhase , this . startPhase ] ,
[ MessagePhase , this . startPhase ] ,
[ TurnInitPhase , this . startPhase ] ,
[ CommandPhase , this . startPhase ] ,
[ EnemyCommandPhase , this . startPhase ] ,
[ TurnStartPhase , this . startPhase ] ,
[ MovePhase , this . startPhase ] ,
[ MoveEffectPhase , this . startPhase ] ,
[ DamagePhase , this . startPhase ] ,
[ FaintPhase , this . startPhase ] ,
[ BerryPhase , this . startPhase ] ,
[ TurnEndPhase , this . startPhase ] ,
[ BattleEndPhase , this . startPhase ] ,
[ EggLapsePhase , this . startPhase ] ,
[ SelectModifierPhase , this . startPhase ] ,
[ NextEncounterPhase , this . startPhase ] ,
[ NewBattlePhase , this . startPhase ] ,
[ VictoryPhase , this . startPhase ] ,
2024-09-09 09:11:46 -07:00
[ LearnMovePhase , this . startPhase ] ,
2024-06-08 00:33:45 +02:00
[ MoveEndPhase , this . startPhase ] ,
2024-09-02 22:12:34 -04:00
[ StatStageChangePhase , this . startPhase ] ,
2024-06-08 00:33:45 +02:00
[ ShinySparklePhase , this . startPhase ] ,
2024-06-08 21:54:20 +02:00
[ SelectTargetPhase , this . startPhase ] ,
[ UnavailablePhase , this . startPhase ] ,
2024-06-10 16:10:23 +02:00
[ QuietFormChangePhase , this . startPhase ] ,
[ SwitchPhase , this . startPhase ] ,
[ SwitchSummonPhase , this . startPhase ] ,
2024-08-17 21:05:04 -07:00
[ PartyHealPhase , this . startPhase ] ,
2024-08-30 20:21:56 -07:00
[ EvolutionPhase , this . startPhase ] ,
[ EndEvolutionPhase , this . startPhase ] ,
2024-09-03 09:14:45 -04:00
[ LevelCapPhase , this . startPhase ] ,
2024-09-05 15:29:39 +10:00
[ AttemptRunPhase , this . startPhase ] ,
2024-09-26 04:39:59 -04:00
[ SelectBiomePhase , this . startPhase ] ,
2024-09-13 22:05:58 -04:00
[ MysteryEncounterPhase , this . startPhase ] ,
[ MysteryEncounterOptionSelectedPhase , this . startPhase ] ,
[ MysteryEncounterBattlePhase , this . startPhase ] ,
[ MysteryEncounterRewardsPhase , this . startPhase ] ,
[ PostMysteryEncounterPhase , this . startPhase ] ,
[ ModifierRewardPhase , this . startPhase ] ,
2024-09-14 09:35:46 -07:00
[ PartyExpPhase , this . startPhase ] ,
[ ExpPhase , this . startPhase ] ,
2024-06-08 21:54:20 +02:00
] ;
private endBySetMode = [
2024-09-13 22:05:58 -04:00
TitlePhase , SelectGenderPhase , CommandPhase , SelectModifierPhase , MysteryEncounterPhase , PostMysteryEncounterPhase
2024-06-08 00:33:45 +02:00
] ;
/ * *
* Constructor to initialize the scene and properties , and to start the phase handling .
* @param scene - The scene to be managed .
* /
constructor ( scene ) {
this . scene = scene ;
this . onHold = [ ] ;
this . prompts = [ ] ;
2024-08-06 23:29:51 -07:00
this . clearLogs ( ) ;
2024-06-10 16:10:23 +02:00
this . startPromptHandler ( ) ;
2024-06-08 00:33:45 +02:00
this . initPhases ( ) ;
}
2024-08-06 23:29:51 -07:00
/ * *
* Clears phase logs
* /
clearLogs() {
this . log = [ ] ;
}
2024-06-08 21:54:20 +02:00
rejectAll ( error ) {
if ( this . inProgress ) {
clearInterval ( this . promptInterval ) ;
clearInterval ( this . interval ) ;
clearInterval ( this . intervalRun ) ;
this . inProgress . onError ( error ) ;
}
}
2024-06-08 00:33:45 +02:00
/ * *
* Method to set the starting phase .
* @param phaseFrom - The phase to start from .
* @returns The instance of the PhaseInterceptor .
* /
runFrom ( phaseFrom ) {
this . phaseFrom = phaseFrom ;
return this ;
}
/ * *
* Method to transition to a target phase .
* @param phaseTo - The phase to transition to .
* @returns A promise that resolves when the transition is complete .
* /
2024-06-08 21:54:20 +02:00
async to ( phaseTo , runTarget : boolean = true ) : Promise < void > {
return new Promise ( async ( resolve , reject ) = > {
ErrorInterceptor . getInstance ( ) . add ( this ) ;
2024-06-10 16:10:23 +02:00
if ( this . phaseFrom ) {
await this . run ( this . phaseFrom ) . catch ( ( e ) = > reject ( e ) ) ;
this . phaseFrom = null ;
}
2024-06-08 00:33:45 +02:00
const targetName = typeof phaseTo === "string" ? phaseTo : phaseTo.name ;
2024-06-08 21:54:20 +02:00
this . intervalRun = setInterval ( async ( ) = > {
2024-06-08 00:33:45 +02:00
const currentPhase = this . onHold ? . length && this . onHold [ 0 ] ;
2024-06-08 21:54:20 +02:00
if ( currentPhase && currentPhase . name === targetName ) {
2024-06-08 00:33:45 +02:00
clearInterval ( this . intervalRun ) ;
2024-06-08 21:54:20 +02:00
if ( ! runTarget ) {
return resolve ( ) ;
}
await this . run ( currentPhase ) . catch ( ( e ) = > {
clearInterval ( this . intervalRun ) ;
return reject ( e ) ;
} ) ;
2024-06-08 00:33:45 +02:00
return resolve ( ) ;
}
2024-06-08 21:54:20 +02:00
if ( currentPhase && currentPhase . name !== targetName ) {
await this . run ( currentPhase ) . catch ( ( e ) = > {
clearInterval ( this . intervalRun ) ;
return reject ( e ) ;
} ) ;
}
2024-06-08 00:33:45 +02:00
} ) ;
} ) ;
}
/ * *
* Method to run a phase with an optional skip function .
* @param phaseTarget - The phase to run .
* @param skipFn - Optional skip function .
* @returns A promise that resolves when the phase is run .
* /
run ( phaseTarget , skipFn ? ) : Promise < void > {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name ;
this . scene . moveAnimations = null ; // Mandatory to avoid crash
return new Promise ( async ( resolve , reject ) = > {
2024-06-08 21:54:20 +02:00
ErrorInterceptor . getInstance ( ) . add ( this ) ;
2024-06-08 00:33:45 +02:00
const interval = setInterval ( async ( ) = > {
2024-06-08 21:54:20 +02:00
const currentPhase = this . onHold . shift ( ) ;
if ( currentPhase ) {
if ( currentPhase . name !== targetName ) {
clearInterval ( interval ) ;
const skip = skipFn && skipFn ( currentPhase . name ) ;
if ( skip ) {
this . onHold . unshift ( currentPhase ) ;
ErrorInterceptor . getInstance ( ) . remove ( this ) ;
return resolve ( ) ;
}
clearInterval ( interval ) ;
return reject ( ` Wrong phase: this is ${ currentPhase . name } and not ${ targetName } ` ) ;
}
2024-06-08 00:33:45 +02:00
clearInterval ( interval ) ;
2024-06-08 21:54:20 +02:00
this . inProgress = {
name : currentPhase.name ,
callback : ( ) = > {
ErrorInterceptor . getInstance ( ) . remove ( this ) ;
resolve ( ) ;
} ,
onError : ( error ) = > reject ( error ) ,
} ;
currentPhase . call ( ) ;
2024-06-08 00:33:45 +02:00
}
} ) ;
} ) ;
}
whenAboutToRun ( phaseTarget , skipFn ? ) : Promise < void > {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name ;
2024-06-08 21:54:20 +02:00
this . scene . moveAnimations = null ; // Mandatory to avoid crash
return new Promise ( async ( resolve , reject ) = > {
ErrorInterceptor . getInstance ( ) . add ( this ) ;
const interval = setInterval ( async ( ) = > {
2024-06-13 14:42:25 +02:00
const currentPhase = this . onHold [ 0 ] ;
if ( currentPhase ? . name === targetName ) {
clearInterval ( interval ) ;
resolve ( ) ;
2024-06-08 00:33:45 +02:00
}
} ) ;
} ) ;
2024-06-13 14:42:25 +02:00
}
pop() {
this . onHold . pop ( ) ;
this . scene . shiftPhase ( ) ;
2024-06-08 00:33:45 +02:00
}
2024-09-03 09:14:45 -04:00
/ * *
* Remove the current phase from the phase interceptor .
*
* Do not call this unless absolutely necessary . This function is intended
* for cleaning up the phase interceptor when , for whatever reason , a phase
* is manually ended without using the phase interceptor .
*
* @param shouldRun Whether or not the current scene should also be run .
* /
shift ( shouldRun : boolean = false ) : void {
this . onHold . shift ( ) ;
if ( shouldRun ) {
this . scene . shiftPhase ( ) ;
}
}
2024-06-08 00:33:45 +02:00
/ * *
* Method to initialize phases and their corresponding methods .
* /
initPhases() {
2024-06-08 21:54:20 +02:00
this . originalSetMode = UI . prototype . setMode ;
this . originalSuperEnd = Phase . prototype . end ;
UI . prototype . setMode = ( mode , . . . args ) = > this . setMode . call ( this , mode , . . . args ) ;
Phase . prototype . end = ( ) = > this . superEndPhase . call ( this ) ;
for ( const [ phase , methodStart ] of this . PHASES ) {
2024-06-08 00:33:45 +02:00
const originalStart = phase . prototype . start ;
2024-06-08 21:54:20 +02:00
this . phases [ phase . name ] = {
start : originalStart ,
endBySetMode : this.endBySetMode.some ( ( elm ) = > elm . name === phase . name ) ,
} ;
phase . prototype . start = ( ) = > methodStart . call ( this , phase ) ;
2024-06-08 00:33:45 +02:00
}
}
/ * *
* Method to start a phase and log it .
* @param phase - The phase to start .
* /
startPhase ( phase ) {
this . log . push ( phase . name ) ;
const instance = this . scene . getCurrentPhase ( ) ;
this . onHold . push ( {
name : phase.name ,
call : ( ) = > {
2024-06-08 21:54:20 +02:00
this . phases [ phase . name ] . start . apply ( instance ) ;
2024-06-08 00:33:45 +02:00
}
} ) ;
}
2024-06-08 21:54:20 +02:00
unlock() {
this . inProgress ? . callback ( ) ;
this . inProgress = undefined ;
}
/ * *
* Method to end a phase and log it .
* @param phase - The phase to start .
* /
superEndPhase() {
const instance = this . scene . getCurrentPhase ( ) ;
this . originalSuperEnd . apply ( instance ) ;
this . inProgress ? . callback ( ) ;
this . inProgress = undefined ;
}
/ * *
* m2m to set mode .
* @param phase - The phase to start .
* /
setMode ( mode : Mode , . . . args : any [ ] ) : Promise < void > {
const currentPhase = this . scene . getCurrentPhase ( ) ;
const instance = this . scene . ui ;
2024-08-07 09:23:12 -07:00
console . log ( "setMode" , ` ${ Mode [ mode ] } (= ${ mode } ) ` , args ) ;
2024-06-08 21:54:20 +02:00
const ret = this . originalSetMode . apply ( instance , [ mode , . . . args ] ) ;
2024-06-10 16:10:23 +02:00
if ( ! this . phases [ currentPhase . constructor . name ] ) {
2024-09-26 04:39:59 -04:00
throw new Error ( ` missing ${ currentPhase . constructor . name } in phaseInterceptor PHASES list --- Add it to PHASES inside of /test/utils/phaseInterceptor.ts ` ) ;
2024-06-10 16:10:23 +02:00
}
2024-06-08 21:54:20 +02:00
if ( this . phases [ currentPhase . constructor . name ] . endBySetMode ) {
this . inProgress ? . callback ( ) ;
this . inProgress = undefined ;
}
return ret ;
}
2024-06-08 00:33:45 +02:00
/ * *
* Method to start the prompt handler .
* /
2024-06-10 16:10:23 +02:00
startPromptHandler() {
2024-06-08 00:33:45 +02:00
this . promptInterval = setInterval ( ( ) = > {
if ( this . prompts . length ) {
const actionForNextPrompt = this . prompts [ 0 ] ;
const expireFn = actionForNextPrompt . expireFn && actionForNextPrompt . expireFn ( ) ;
const currentMode = this . scene . ui . getMode ( ) ;
2024-09-13 22:05:58 -04:00
const currentPhase = this . scene . getCurrentPhase ( ) ? . constructor . name ;
2024-06-10 16:10:23 +02:00
const currentHandler = this . scene . ui . getHandler ( ) ;
2024-06-08 00:33:45 +02:00
if ( expireFn ) {
this . prompts . shift ( ) ;
2024-06-10 16:10:23 +02:00
} else if ( currentMode === actionForNextPrompt . mode && currentPhase === actionForNextPrompt . phaseTarget && currentHandler . active && ( ! actionForNextPrompt . awaitingActionInput || ( actionForNextPrompt . awaitingActionInput && currentHandler . awaitingActionInput ) ) ) {
2024-09-13 22:05:58 -04:00
const prompt = this . prompts . shift ( ) ;
if ( prompt ? . callback ) {
prompt . callback ( ) ;
}
2024-06-08 00:33:45 +02:00
}
}
} ) ;
}
/ * *
* Method to add an action to the next prompt .
* @param phaseTarget - The target phase for the prompt .
* @param mode - The mode of the UI .
* @param callback - The callback function to execute .
* @param expireFn - The function to determine if the prompt has expired .
2024-09-13 22:05:58 -04:00
* @param awaitingActionInput
2024-06-08 00:33:45 +02:00
* /
2024-08-07 09:23:12 -07:00
addToNextPrompt ( phaseTarget : string , mode : Mode , callback : ( ) = > void , expireFn ? : ( ) = > void , awaitingActionInput : boolean = false ) {
2024-06-08 00:33:45 +02:00
this . prompts . push ( {
phaseTarget ,
mode ,
callback ,
2024-06-10 16:10:23 +02:00
expireFn ,
awaitingActionInput
2024-06-08 00:33:45 +02:00
} ) ;
}
/ * *
* Restores the original state of phases and clears intervals .
*
* This function iterates through all phases and resets their ` start ` method to the original
* function stored in ` this.phases ` . Additionally , it clears the ` promptInterval ` and ` interval ` .
* /
restoreOg() {
for ( const [ phase ] of this . PHASES ) {
2024-06-08 21:54:20 +02:00
phase . prototype . start = this . phases [ phase . name ] . start ;
2024-06-08 00:33:45 +02:00
}
2024-06-08 21:54:20 +02:00
UI . prototype . setMode = this . originalSetMode ;
Phase . prototype . end = this . originalSuperEnd ;
2024-06-08 00:33:45 +02:00
clearInterval ( this . promptInterval ) ;
clearInterval ( this . interval ) ;
2024-06-08 21:54:20 +02:00
clearInterval ( this . intervalRun ) ;
2024-06-08 00:33:45 +02:00
}
}