2024-02-29 20:08:50 -05:00
import BattleScene from "../battle-scene" ;
2024-05-25 14:47:18 +02:00
import { pokemonPrevolutions } from "../data/pokemon-evolutions" ;
import PokemonSpecies , { getPokemonSpecies } from "../data/pokemon-species" ;
import {
TrainerConfig ,
TrainerPartyCompoundTemplate ,
TrainerPartyTemplate ,
TrainerPoolTier ,
TrainerSlot ,
trainerConfigs ,
trainerPartyTemplates ,
signatureSpecies
} from "../data/trainer-config" ;
import { EnemyPokemon } from "./pokemon" ;
2024-02-29 20:08:50 -05:00
import * as Utils from "../utils" ;
2024-05-25 14:47:18 +02:00
import { PersistentModifier } from "../modifier/modifier" ;
import { trainerNamePools } from "../data/trainer-names" ;
import { ArenaTagSide , ArenaTrapTag } from "#app/data/arena-tag" ;
2024-05-16 11:05:25 +02:00
import { getIsInitialized , initI18n } from "#app/plugins/i18n" ;
import i18next from "i18next" ;
2024-06-13 18:44:23 -04:00
import { PartyMemberStrength } from "#enums/party-member-strength" ;
import { Species } from "#enums/species" ;
import { TrainerType } from "#enums/trainer-type" ;
2024-03-21 00:57:28 -04:00
export enum TrainerVariant {
2024-05-25 14:47:18 +02:00
DEFAULT ,
FEMALE ,
DOUBLE
2024-03-21 00:57:28 -04:00
}
2023-10-07 16:08:33 -04:00
export default class Trainer extends Phaser . GameObjects . Container {
public config : TrainerConfig ;
2024-03-21 00:57:28 -04:00
public variant : TrainerVariant ;
2023-10-18 18:01:15 -04:00
public partyTemplateIndex : integer ;
2024-03-21 00:57:28 -04:00
public name : string ;
public partnerName : string ;
2023-10-07 16:08:33 -04:00
2024-03-21 00:57:28 -04:00
constructor ( scene : BattleScene , trainerType : TrainerType , variant : TrainerVariant , partyTemplateIndex? : integer , name? : string , partnerName? : string ) {
2023-10-07 16:08:33 -04:00
super ( scene , - 72 , 80 ) ;
2023-12-14 00:41:35 -05:00
this . config = trainerConfigs . hasOwnProperty ( trainerType )
? trainerConfigs [ trainerType ]
: trainerConfigs [ TrainerType . ACE_TRAINER ] ;
2024-03-21 00:57:28 -04:00
this . variant = variant ;
2024-05-24 01:45:04 +02:00
this . partyTemplateIndex = Math . min ( partyTemplateIndex !== undefined ? partyTemplateIndex : Utils.randSeedWeightedItem ( this . config . partyTemplates . map ( ( _ , i ) = > i ) ) ,
2023-11-10 16:41:02 -05:00
this . config . partyTemplates . length - 1 ) ;
2024-03-21 00:57:28 -04:00
if ( trainerNamePools . hasOwnProperty ( trainerType ) ) {
const namePool = trainerNamePools [ trainerType ] ;
this . name = name || Utils . randSeedItem ( Array . isArray ( namePool [ 0 ] ) ? namePool [ variant === TrainerVariant . FEMALE ? 1 : 0 ] : namePool ) ;
if ( variant === TrainerVariant . DOUBLE ) {
if ( this . config . doubleOnly ) {
2024-05-23 17:03:10 +02:00
if ( partnerName ) {
2024-03-21 00:57:28 -04:00
this . partnerName = partnerName ;
2024-05-23 17:03:10 +02:00
} else {
2024-05-25 14:47:18 +02:00
[ this . name , this . partnerName ] = this . name . split ( " & " ) ;
2024-05-23 17:03:10 +02:00
}
} else {
2024-03-21 00:57:28 -04:00
this . partnerName = partnerName || Utils . randSeedItem ( Array . isArray ( namePool [ 0 ] ) ? namePool [ 1 ] : namePool ) ;
2024-05-23 17:03:10 +02:00
}
2024-03-21 00:57:28 -04:00
}
}
switch ( this . variant ) {
2024-05-23 17:03:10 +02:00
case TrainerVariant . FEMALE :
if ( ! this . config . hasGenders ) {
variant = TrainerVariant . DEFAULT ;
}
break ;
case TrainerVariant . DOUBLE :
if ( ! this . config . hasDouble ) {
variant = TrainerVariant . DEFAULT ;
}
break ;
2024-03-21 00:57:28 -04:00
}
2023-10-18 18:01:15 -04:00
console . log ( Object . keys ( trainerPartyTemplates ) [ Object . values ( trainerPartyTemplates ) . indexOf ( this . getPartyTemplate ( ) ) ] ) ;
2023-10-07 16:08:33 -04:00
2024-03-21 00:57:28 -04:00
const getSprite = ( hasShadow? : boolean , forceFemale? : boolean ) = > {
2024-05-26 16:48:28 +02:00
const ret = this . scene . addFieldSprite ( 0 , 0 , this . config . getSpriteKey ( variant === TrainerVariant . FEMALE || forceFemale , this . isDouble ( ) ) ) ;
2023-10-07 16:08:33 -04:00
ret . setOrigin ( 0.5 , 1 ) ;
2024-05-25 14:47:18 +02:00
ret . setPipeline ( this . scene . spritePipeline , { tone : [ 0.0 , 0.0 , 0.0 , 0.0 ] , hasShadow : ! ! hasShadow } ) ;
2023-10-07 16:08:33 -04:00
return ret ;
} ;
2024-05-24 01:45:04 +02:00
2023-10-07 16:08:33 -04:00
const sprite = getSprite ( true ) ;
const tintSprite = getSprite ( ) ;
tintSprite . setVisible ( false ) ;
this . add ( sprite ) ;
this . add ( tintSprite ) ;
2024-03-21 00:57:28 -04:00
if ( variant === TrainerVariant . DOUBLE && ! this . config . doubleOnly ) {
const partnerSprite = getSprite ( true , true ) ;
const partnerTintSprite = getSprite ( false , true ) ;
partnerTintSprite . setVisible ( false ) ;
2024-03-21 01:29:19 -04:00
sprite . x = - 4 ;
tintSprite . x = - 4 ;
partnerSprite . x = 28 ;
partnerTintSprite . x = 28 ;
2024-03-21 00:57:28 -04:00
this . add ( partnerSprite ) ;
this . add ( partnerTintSprite ) ;
}
2023-10-07 16:08:33 -04:00
}
2024-03-21 00:57:28 -04:00
getKey ( forceFemale? : boolean ) : string {
2024-05-26 16:48:28 +02:00
return this . config . getSpriteKey ( this . variant === TrainerVariant . FEMALE || forceFemale , this . isDouble ( ) ) ;
2023-10-07 16:08:33 -04:00
}
2024-05-19 13:23:24 +02:00
/ * *
* Returns the name of the trainer based on the provided trainer slot and the option to include a title .
* @param { TrainerSlot } trainerSlot - The slot to determine which name to use . Defaults to TrainerSlot . NONE .
* @param { boolean } includeTitle - Whether to include the title in the returned name . Defaults to false .
* @returns { string } - The formatted name of the trainer .
* * /
2024-03-21 00:57:28 -04:00
getName ( trainerSlot : TrainerSlot = TrainerSlot . NONE , includeTitle : boolean = false ) : string {
2024-05-19 13:23:24 +02:00
// Get the base title based on the trainer slot and variant.
2024-03-21 00:57:28 -04:00
let name = this . config . getTitle ( trainerSlot , this . variant ) ;
2024-05-16 11:05:25 +02:00
2024-05-19 13:23:24 +02:00
// Determine the title to include based on the configuration and includeTitle flag.
let title = includeTitle && this . config . title ? this . config.title : null ;
2024-08-07 16:31:02 +02:00
const evilTeamTitles = [ "grunt" ] ;
2024-08-03 16:34:05 -04:00
if ( this . name === "" && evilTeamTitles . some ( t = > name . toLocaleLowerCase ( ) . includes ( t ) ) ) {
2024-06-09 19:02:29 -04:00
// This is a evil team grunt so we localize it by only using the "name" as the title
title = i18next . t ( ` trainerClasses: ${ name . toLowerCase ( ) . replace ( /\s/g , "_" ) } ` ) ;
console . log ( "Localized grunt name: " + title ) ;
// Since grunts are not named we can just return the title
return title ;
}
2024-05-19 13:23:24 +02:00
// If the trainer has a name (not null or undefined).
2024-03-21 00:57:28 -04:00
if ( this . name ) {
2024-05-19 13:23:24 +02:00
// If the title should be included.
if ( includeTitle ) {
// Check if the internationalization (i18n) system is initialized.
2024-05-16 11:05:25 +02:00
if ( ! getIsInitialized ( ) ) {
2024-05-19 13:23:24 +02:00
// Initialize the i18n system if it is not already initialized.
initI18n ( ) ;
2024-05-16 11:05:25 +02:00
}
2024-05-19 13:23:24 +02:00
// Get the localized trainer class name from the i18n file and set it as the title.
// This is used for trainer class names, not titles like "Elite Four, Champion, etc."
2024-05-23 17:03:10 +02:00
title = i18next . t ( ` trainerClasses: ${ name . toLowerCase ( ) . replace ( /\s/g , "_" ) } ` ) ;
2024-05-19 13:23:24 +02:00
}
// If no specific trainer slot is set.
2024-03-21 00:57:28 -04:00
if ( ! trainerSlot ) {
2024-05-19 13:23:24 +02:00
// Use the trainer's name.
2024-03-21 00:57:28 -04:00
name = this . name ;
2024-05-19 13:23:24 +02:00
// If there is a partner name, concatenate it with the trainer's name using "&".
if ( this . partnerName ) {
2024-03-21 00:57:28 -04:00
name = ` ${ name } & ${ this . partnerName } ` ;
2024-05-19 13:23:24 +02:00
}
} else {
// Assign the name based on the trainer slot:
// Use 'this.name' if 'trainerSlot' is TRAINER.
// Otherwise, use 'this.partnerName' if it exists, or 'this.name' if it doesn't.
2024-03-21 00:57:28 -04:00
name = trainerSlot === TrainerSlot . TRAINER ? this . name : this.partnerName || this . name ;
2024-05-19 13:23:24 +02:00
}
2024-03-21 00:57:28 -04:00
}
2024-05-16 11:05:25 +02:00
2024-05-25 14:47:18 +02:00
if ( this . config . titleDouble && this . variant === TrainerVariant . DOUBLE && ! this . config . doubleOnly ) {
title = this . config . titleDouble ;
name = i18next . t ( ` trainerNames: ${ this . config . nameDouble . toLowerCase ( ) . replace ( /\s/g , "_" ) } ` ) ;
}
2024-08-07 16:31:02 +02:00
console . log ( title ? ` ${ title } ${ name } ` : name ) ;
2024-05-19 13:23:24 +02:00
// Return the formatted name, including the title if it is set.
2024-03-21 00:57:28 -04:00
return title ? ` ${ title } ${ name } ` : name ;
}
2024-05-19 13:23:24 +02:00
2024-03-21 00:57:28 -04:00
isDouble ( ) : boolean {
return this . config . doubleOnly || this . variant === TrainerVariant . DOUBLE ;
2023-10-09 20:20:02 -04:00
}
2024-06-08 20:51:37 -05:00
getMixedBattleBgm ( ) : string {
return this . config . mixedBattleBgm ;
}
2023-10-18 18:01:15 -04:00
getBattleBgm ( ) : string {
return this . config . battleBgm ;
}
getEncounterBgm ( ) : string {
2024-03-21 00:57:28 -04:00
return ! this . variant ? this . config . encounterBgm : ( this . variant === TrainerVariant . DOUBLE ? this . config.doubleEncounterBgm : this.config.femaleEncounterBgm ) || this . config . encounterBgm ;
2023-10-18 18:01:15 -04:00
}
2024-02-14 14:41:39 -05:00
getEncounterMessages ( ) : string [ ] {
2024-03-21 00:57:28 -04:00
return ! this . variant ? this . config . encounterMessages : ( this . variant === TrainerVariant . DOUBLE ? this . config.doubleEncounterMessages : this.config.femaleEncounterMessages ) || this . config . encounterMessages ;
2024-02-14 14:41:39 -05:00
}
getVictoryMessages ( ) : string [ ] {
2024-03-21 00:57:28 -04:00
return ! this . variant ? this . config . victoryMessages : ( this . variant === TrainerVariant . DOUBLE ? this . config.doubleVictoryMessages : this.config.femaleVictoryMessages ) || this . config . victoryMessages ;
2024-02-14 14:41:39 -05:00
}
getDefeatMessages ( ) : string [ ] {
2024-03-21 00:57:28 -04:00
return ! this . variant ? this . config . defeatMessages : ( this . variant === TrainerVariant . DOUBLE ? this . config.doubleDefeatMessages : this.config.femaleDefeatMessages ) || this . config . defeatMessages ;
2024-02-14 14:41:39 -05:00
}
2023-10-18 18:01:15 -04:00
getPartyTemplate ( ) : TrainerPartyTemplate {
2024-05-23 17:03:10 +02:00
if ( this . config . partyTemplateFunc ) {
2023-10-20 11:38:41 -04:00
return this . config . partyTemplateFunc ( this . scene ) ;
2024-05-23 17:03:10 +02:00
}
2023-10-18 18:01:15 -04:00
return this . config . partyTemplates [ this . partyTemplateIndex ] ;
}
getPartyLevels ( waveIndex : integer ) : integer [ ] {
const ret = [ ] ;
const partyTemplate = this . getPartyTemplate ( ) ;
2024-05-24 01:45:04 +02:00
2024-03-16 22:06:56 -04:00
const difficultyWaveIndex = this . scene . gameMode . getWaveForDifficulty ( waveIndex ) ;
2024-05-23 17:03:10 +02:00
const baseLevel = 1 + difficultyWaveIndex / 2 + Math . pow ( difficultyWaveIndex / 25 , 2 ) ;
2023-10-18 18:01:15 -04:00
2024-05-23 17:03:10 +02:00
if ( this . isDouble ( ) && partyTemplate . size < 2 ) {
2024-05-09 15:39:20 +10:00
partyTemplate . size = 2 ;
2024-05-23 17:03:10 +02:00
}
2024-05-09 15:39:20 +10:00
2023-10-18 18:01:15 -04:00
for ( let i = 0 ; i < partyTemplate . size ; i ++ ) {
let multiplier = 1 ;
2024-05-24 01:45:04 +02:00
2024-02-25 01:03:06 -05:00
const strength = partyTemplate . getStrength ( i ) ;
2024-05-24 01:45:04 +02:00
2023-10-18 18:01:15 -04:00
switch ( strength ) {
2024-05-23 17:03:10 +02:00
case PartyMemberStrength . WEAKER :
multiplier = 0.95 ;
break ;
case PartyMemberStrength . WEAK :
multiplier = 1.0 ;
break ;
case PartyMemberStrength . AVERAGE :
multiplier = 1.1 ;
break ;
case PartyMemberStrength . STRONG :
multiplier = 1.2 ;
break ;
case PartyMemberStrength . STRONGER :
multiplier = 1.25 ;
break ;
2023-10-18 18:01:15 -04:00
}
2024-02-25 01:03:06 -05:00
let levelOffset = 0 ;
2024-03-28 14:05:15 -04:00
if ( strength < PartyMemberStrength . STRONG ) {
2024-03-16 22:06:56 -04:00
multiplier = Math . min ( multiplier + 0.025 * Math . floor ( difficultyWaveIndex / 25 ) , 1.2 ) ;
2024-03-28 14:05:15 -04:00
levelOffset = - Math . floor ( ( difficultyWaveIndex / 50 ) * ( PartyMemberStrength . STRONG - strength ) ) ;
2024-02-25 01:03:06 -05:00
}
const level = Math . ceil ( baseLevel * multiplier ) + levelOffset ;
2023-10-18 18:01:15 -04:00
ret . push ( level ) ;
}
return ret ;
}
genPartyMember ( index : integer ) : EnemyPokemon {
2023-10-09 20:20:02 -04:00
const battle = this . scene . currentBattle ;
2023-10-18 18:01:15 -04:00
const level = battle . enemyLevels [ index ] ;
2024-05-24 01:45:04 +02:00
2023-10-18 18:01:15 -04:00
let ret : EnemyPokemon ;
this . scene . executeWithSeedOffset ( ( ) = > {
2023-10-20 11:38:41 -04:00
const template = this . getPartyTemplate ( ) ;
2024-03-28 14:05:15 -04:00
const strength : PartyMemberStrength = template . getStrength ( index ) ;
2023-10-20 11:38:41 -04:00
2023-10-18 18:01:15 -04:00
2024-05-25 14:47:18 +02:00
// If the battle is not one of the named trainer doubles
if ( ! ( this . config . trainerTypeDouble && this . isDouble ( ) && ! this . config . doubleOnly ) ) {
if ( this . config . partyMemberFuncs . hasOwnProperty ( index ) ) {
ret = this . config . partyMemberFuncs [ index ] ( this . scene , level , strength ) ;
return ;
}
if ( this . config . partyMemberFuncs . hasOwnProperty ( index - template . size ) ) {
ret = this . config . partyMemberFuncs [ index - template . size ] ( this . scene , level , template . getStrength ( index ) ) ;
return ;
}
}
2023-10-18 18:01:15 -04:00
let offset = 0 ;
if ( template instanceof TrainerPartyCompoundTemplate ) {
2024-05-23 17:03:10 +02:00
for ( const innerTemplate of template . templates ) {
if ( offset + innerTemplate . size > index ) {
2023-10-18 18:01:15 -04:00
break ;
2024-05-23 17:03:10 +02:00
}
2023-10-18 18:01:15 -04:00
offset += innerTemplate . size ;
}
}
2024-05-25 14:47:18 +02:00
// Create an empty species pool (which will be set to one of the species pools based on the index)
let newSpeciesPool = [ ] ;
let useNewSpeciesPool = false ;
// If we are in a double battle of named trainers, we need to use alternate species pools (generate half the party from each trainer)
if ( this . config . trainerTypeDouble && this . isDouble ( ) && ! this . config . doubleOnly ) {
// Use the new species pool for this party generation
useNewSpeciesPool = true ;
// Get the species pool for the partner trainer and the current trainer
const speciesPoolPartner = signatureSpecies [ TrainerType [ this . config . trainerTypeDouble ] ] ;
const speciesPool = signatureSpecies [ TrainerType [ this . config . trainerType ] ] ;
// Get the species that are already in the enemy party so we dont generate the same species twice
const AlreadyUsedSpecies = battle . enemyParty . map ( p = > p . species . speciesId ) ;
// Filter out the species that are already in the enemy party from the main trainer species pool
const speciesPoolFiltered = speciesPool . filter ( species = > {
// Since some species pools have arrays in them (use either of those species), we need to check if one of the species is already in the party and filter the whole array if it is
if ( Array . isArray ( species ) ) {
return ! species . some ( s = > AlreadyUsedSpecies . includes ( s ) ) ;
}
return ! AlreadyUsedSpecies . includes ( species ) ;
} ) ;
// Filter out the species that are already in the enemy party from the partner trainer species pool
const speciesPoolPartnerFiltered = speciesPoolPartner . filter ( species = > {
// Since some species pools have arrays in them (use either of those species), we need to check if one of the species is already in the party and filter the whole array if it is
if ( Array . isArray ( species ) ) {
return ! species . some ( s = > AlreadyUsedSpecies . includes ( s ) ) ;
}
return ! AlreadyUsedSpecies . includes ( species ) ;
} ) ;
// If the index is even, use the species pool for the main trainer (that way he only uses his own pokemon in battle)
if ( ! ( index % 2 ) ) {
2024-06-02 01:15:17 +02:00
// Since the only currently allowed double battle with named trainers is Tate & Liza, we need to make sure that Solrock is the first pokemon in the party for Tate and Lunatone for Liza
if ( index === 0 && ( TrainerType [ this . config . trainerType ] === TrainerType [ TrainerType . TATE ] ) ) {
newSpeciesPool = [ Species . SOLROCK ] ;
} else if ( index === 0 && ( TrainerType [ this . config . trainerType ] === TrainerType [ TrainerType . LIZA ] ) ) {
newSpeciesPool = [ Species . LUNATONE ] ;
} else {
newSpeciesPool = speciesPoolFiltered ;
}
2024-05-25 14:47:18 +02:00
} else {
// If the index is odd, use the species pool for the partner trainer (that way he only uses his own pokemon in battle)
2024-06-02 01:15:17 +02:00
// Since the only currently allowed double battle with named trainers is Tate & Liza, we need to make sure that Solrock is the first pokemon in the party for Tate and Lunatone for Liza
if ( index === 1 && ( TrainerType [ this . config . trainerTypeDouble ] === TrainerType [ TrainerType . TATE ] ) ) {
newSpeciesPool = [ Species . SOLROCK ] ;
} else if ( index === 1 && ( TrainerType [ this . config . trainerTypeDouble ] === TrainerType [ TrainerType . LIZA ] ) ) {
newSpeciesPool = [ Species . LUNATONE ] ;
} else {
newSpeciesPool = speciesPoolPartnerFiltered ;
}
2024-05-25 14:47:18 +02:00
}
// Fallback for when the species pool is empty
if ( newSpeciesPool . length === 0 ) {
// If all pokemon from this pool are already in the party, generate a random species
useNewSpeciesPool = false ;
}
}
// If useNewSpeciesPool is true, we need to generate a new species from the new species pool, otherwise we generate a random species
2024-05-30 00:33:18 +02:00
let species = useNewSpeciesPool
2024-05-25 14:47:18 +02:00
? getPokemonSpecies ( newSpeciesPool [ Math . floor ( Math . random ( ) * newSpeciesPool . length ) ] )
: template . isSameSpecies ( index ) && index > offset
? getPokemonSpecies ( battle . enemyParty [ offset ] . species . getTrainerSpeciesForLevel ( level , false , template . getStrength ( offset ) ) )
: this . genNewPartyMemberSpecies ( level , strength ) ;
2024-05-24 01:45:04 +02:00
2024-05-30 00:33:18 +02:00
// If the species is from newSpeciesPool, we need to adjust it based on the level and strength
if ( newSpeciesPool ) {
species = getPokemonSpecies ( species . getSpeciesForLevel ( level , true , true , strength ) ) ;
}
2024-03-21 00:57:28 -04:00
ret = this . scene . addEnemyPokemon ( species , level , ! this . isDouble ( ) || ! ( index % 2 ) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER ) ;
2023-12-26 12:29:18 -05:00
} , this . config . hasStaticParty ? this . config . getDerivedType ( ) + ( ( index + 1 ) << 8 ) : this . scene . currentBattle . waveIndex + ( this . config . getDerivedType ( ) << 10 ) + ( ( ( ! this . config . useSameSeedForAllMembers ? index : 0 ) + 1 ) << 8 ) ) ;
2023-10-18 18:01:15 -04:00
return ret ;
}
2024-05-25 14:47:18 +02:00
2024-03-28 14:05:15 -04:00
genNewPartyMemberSpecies ( level : integer , strength : PartyMemberStrength , attempt? : integer ) : PokemonSpecies {
2023-10-18 18:01:15 -04:00
const battle = this . scene . currentBattle ;
const template = this . getPartyTemplate ( ) ;
2023-12-07 23:10:45 -05:00
let species : PokemonSpecies ;
2023-10-18 18:01:15 -04:00
if ( this . config . speciesPools ) {
const tierValue = Utils . randSeedInt ( 512 ) ;
2024-05-23 17:03:10 +02:00
let tier = tierValue >= 156 ? TrainerPoolTier.COMMON : tierValue >= 32 ? TrainerPoolTier.UNCOMMON : tierValue >= 6 ? TrainerPoolTier.RARE : tierValue >= 1 ? TrainerPoolTier.SUPER_RARE : TrainerPoolTier.ULTRA_RARE ;
2023-10-18 18:01:15 -04:00
console . log ( TrainerPoolTier [ tier ] ) ;
2023-10-20 19:07:47 -04:00
while ( ! this . config . speciesPools . hasOwnProperty ( tier ) || ! this . config . speciesPools [ tier ] . length ) {
2023-10-18 18:01:15 -04:00
console . log ( ` Downgraded trainer Pokemon rarity tier from ${ TrainerPoolTier [ tier ] } to ${ TrainerPoolTier [ tier - 1 ] } ` ) ;
tier -- ;
}
const tierPool = this . config . speciesPools [ tier ] ;
2024-02-17 00:40:03 -05:00
species = getPokemonSpecies ( Utils . randSeedItem ( tierPool ) ) ;
2024-05-23 17:03:10 +02:00
} else {
2023-12-07 23:10:45 -05:00
species = this . scene . randomSpecies ( battle . waveIndex , level , false , this . config . speciesFilter ) ;
2024-05-23 17:03:10 +02:00
}
2023-10-18 18:01:15 -04:00
2024-03-28 14:05:15 -04:00
let ret = getPokemonSpecies ( species . getTrainerSpeciesForLevel ( level , true , strength ) ) ;
2023-12-07 23:10:45 -05:00
let retry = false ;
console . log ( ret . getName ( ) ) ;
2024-05-23 17:03:10 +02:00
if ( pokemonPrevolutions . hasOwnProperty ( species . speciesId ) && ret . speciesId !== species . speciesId ) {
2023-12-07 23:10:45 -05:00
retry = true ;
2024-05-23 17:03:10 +02:00
} else if ( template . isBalanced ( battle . enemyParty . length ) ) {
2024-02-17 00:40:03 -05:00
const partyMemberTypes = battle . enemyParty . map ( p = > p . getTypes ( true ) ) . flat ( ) ;
2024-05-23 17:03:10 +02:00
if ( partyMemberTypes . indexOf ( ret . type1 ) > - 1 || ( ret . type2 !== null && partyMemberTypes . indexOf ( ret . type2 ) > - 1 ) ) {
2023-12-07 23:10:45 -05:00
retry = true ;
2024-05-23 17:03:10 +02:00
}
2023-12-07 23:10:45 -05:00
}
2023-12-30 23:31:26 -05:00
if ( ! retry && this . config . specialtyTypes . length && ! this . config . specialtyTypes . find ( t = > ret . isOfType ( t ) ) ) {
retry = true ;
2024-05-23 17:03:10 +02:00
console . log ( "Attempting reroll of species evolution to fit specialty type..." ) ;
2023-12-30 23:31:26 -05:00
let evoAttempt = 0 ;
while ( retry && evoAttempt ++ < 10 ) {
2024-03-28 14:05:15 -04:00
ret = getPokemonSpecies ( species . getTrainerSpeciesForLevel ( level , true , strength ) ) ;
2023-12-30 23:31:26 -05:00
console . log ( ret . name ) ;
2024-05-23 17:03:10 +02:00
if ( this . config . specialtyTypes . find ( t = > ret . isOfType ( t ) ) ) {
2023-12-30 23:31:26 -05:00
retry = false ;
2024-05-23 17:03:10 +02:00
}
2023-12-30 23:31:26 -05:00
}
}
2023-12-07 23:10:45 -05:00
if ( retry && ( attempt || 0 ) < 10 ) {
2024-05-23 17:03:10 +02:00
console . log ( "Rerolling party member..." ) ;
2024-03-28 14:05:15 -04:00
ret = this . genNewPartyMemberSpecies ( level , strength , ( attempt || 0 ) + 1 ) ;
2023-10-09 20:20:02 -04:00
}
return ret ;
2023-10-07 16:08:33 -04:00
}
2024-03-31 12:00:54 -04:00
getPartyMemberMatchupScores ( trainerSlot : TrainerSlot = TrainerSlot . NONE , forSwitch : boolean = false ) : [ integer , integer ] [ ] {
2024-05-23 17:03:10 +02:00
if ( trainerSlot && ! this . isDouble ( ) ) {
2024-03-21 12:34:19 -04:00
trainerSlot = TrainerSlot . NONE ;
2024-05-23 17:03:10 +02:00
}
2024-05-24 01:45:04 +02:00
2023-10-07 16:08:33 -04:00
const party = this . scene . getEnemyParty ( ) ;
2024-06-25 05:32:21 +08:00
const nonFaintedLegalPartyMembers = party . slice ( this . scene . currentBattle . getBattlerCount ( ) ) . filter ( p = > p . isAllowedInBattle ( ) ) . filter ( p = > ! trainerSlot || p . trainerSlot === trainerSlot ) ;
const partyMemberScores = nonFaintedLegalPartyMembers . map ( p = > {
const playerField = this . scene . getPlayerField ( ) . filter ( p = > p . isAllowedInBattle ( ) ) ;
2023-10-07 16:08:33 -04:00
let score = 0 ;
2024-06-24 21:15:11 -07:00
if ( playerField . length > 0 ) {
for ( const playerPokemon of playerField ) {
score += p . getMatchupScore ( playerPokemon ) ;
if ( playerPokemon . species . legendary ) {
score /= 2 ;
}
}
score /= playerField . length ;
if ( forSwitch && ! p . isOnField ( ) ) {
this . scene . arena . findTagsOnSide ( t = > t instanceof ArenaTrapTag , ArenaTagSide . ENEMY ) . map ( t = > score *= ( t as ArenaTrapTag ) . getMatchupScoreMultiplier ( p ) ) ;
2024-05-23 17:03:10 +02:00
}
2023-10-18 18:01:15 -04:00
}
2024-06-24 21:15:11 -07:00
2024-05-25 14:47:18 +02:00
return [ party . indexOf ( p ) , score ] ;
2024-05-29 18:14:32 -05:00
} ) as [ integer , integer ] [ ] ;
2023-10-07 16:08:33 -04:00
2024-01-15 00:20:26 -05:00
return partyMemberScores ;
}
getSortedPartyMemberMatchupScores ( partyMemberScores : [ integer , integer ] [ ] = this . getPartyMemberMatchupScores ( ) ) {
2023-10-07 16:08:33 -04:00
const sortedPartyMemberScores = partyMemberScores . slice ( 0 ) ;
sortedPartyMemberScores . sort ( ( a , b ) = > {
const scoreA = a [ 1 ] ;
const scoreB = b [ 1 ] ;
return scoreA < scoreB ? 1 : scoreA > scoreB ? - 1 : 0 ;
} ) ;
2024-01-15 00:20:26 -05:00
return sortedPartyMemberScores ;
}
2024-03-21 00:57:28 -04:00
getNextSummonIndex ( trainerSlot : TrainerSlot = TrainerSlot . NONE , partyMemberScores : [ integer , integer ] [ ] = this . getPartyMemberMatchupScores ( trainerSlot ) ) : integer {
2024-05-23 17:03:10 +02:00
if ( trainerSlot && ! this . isDouble ( ) ) {
2024-03-21 12:18:10 -04:00
trainerSlot = TrainerSlot . NONE ;
2024-05-23 17:03:10 +02:00
}
2024-03-21 12:18:10 -04:00
2024-01-15 00:20:26 -05:00
const sortedPartyMemberScores = this . getSortedPartyMemberMatchupScores ( partyMemberScores ) ;
2023-10-07 16:08:33 -04:00
const maxScorePartyMemberIndexes = partyMemberScores . filter ( pms = > pms [ 1 ] === sortedPartyMemberScores [ 0 ] [ 1 ] ) . map ( pms = > pms [ 0 ] ) ;
2024-01-15 00:20:26 -05:00
if ( maxScorePartyMemberIndexes . length > 1 ) {
let rand : integer ;
this . scene . executeWithSeedOffset ( ( ) = > rand = Utils . randSeedInt ( maxScorePartyMemberIndexes . length ) , this . scene . currentBattle . turn << 2 ) ;
return maxScorePartyMemberIndexes [ rand ] ;
}
return maxScorePartyMemberIndexes [ 0 ] ;
2023-10-07 16:08:33 -04:00
}
2024-05-24 01:45:04 +02:00
2023-10-24 10:05:07 -04:00
getPartyMemberModifierChanceMultiplier ( index : integer ) : number {
switch ( this . getPartyTemplate ( ) . getStrength ( index ) ) {
2024-05-23 17:03:10 +02:00
case PartyMemberStrength . WEAKER :
return 0.75 ;
case PartyMemberStrength . WEAK :
return 0.675 ;
case PartyMemberStrength . AVERAGE :
return 0.5625 ;
case PartyMemberStrength . STRONG :
return 0.45 ;
case PartyMemberStrength . STRONGER :
return 0.375 ;
2023-10-24 10:05:07 -04:00
}
}
2023-10-07 16:08:33 -04:00
2024-02-17 00:40:03 -05:00
genModifiers ( party : EnemyPokemon [ ] ) : PersistentModifier [ ] {
2024-05-23 17:03:10 +02:00
if ( this . config . genModifiersFunc ) {
2024-02-17 00:40:03 -05:00
return this . config . genModifiersFunc ( party ) ;
2024-05-23 17:03:10 +02:00
}
2024-02-17 00:40:03 -05:00
return [ ] ;
}
2023-10-07 16:08:33 -04:00
loadAssets ( ) : Promise < void > {
2024-03-21 00:57:28 -04:00
return this . config . loadAssets ( this . scene , this . variant ) ;
2023-10-07 16:08:33 -04:00
}
2023-10-18 18:01:15 -04:00
initSprite ( ) : void {
2024-03-21 00:57:28 -04:00
this . getSprites ( ) . map ( ( sprite , i ) = > sprite . setTexture ( this . getKey ( ! ! i ) ) . setFrame ( 0 ) ) ;
this . getTintSprites ( ) . map ( ( tintSprite , i ) = > tintSprite . setTexture ( this . getKey ( ! ! i ) ) . setFrame ( 0 ) ) ;
2023-10-18 18:01:15 -04:00
}
2024-05-17 12:37:38 -05:00
/ * *
* Attempts to animate a given set of { @linkcode Phaser . GameObjects . Sprite }
* @see { @linkcode Phaser . GameObjects . Sprite . play }
* @param sprite { @linkcode Phaser . GameObjects . Sprite } to animate
* @param tintSprite { @linkcode Phaser . GameObjects . Sprite } placed on top of the sprite to add a color tint
* @param animConfig { @linkcode Phaser . Types . Animations . PlayAnimationConfig } to pass to { @linkcode Phaser . GameObjects . Sprite . play }
* @returns true if the sprite was able to be animated
* /
tryPlaySprite ( sprite : Phaser.GameObjects.Sprite , tintSprite : Phaser.GameObjects.Sprite , animConfig : Phaser.Types.Animations.PlayAnimationConfig ) : boolean {
// Show an error in the console if there isn't a texture loaded
2024-05-23 17:03:10 +02:00
if ( sprite . texture . key === "__MISSING" ) {
2024-05-17 12:37:38 -05:00
console . error ( ` No texture found for ' ${ animConfig . key } '! ` ) ;
return false ;
}
// Don't try to play an animation when there isn't one
if ( sprite . texture . frameTotal <= 1 ) {
console . warn ( ` No animation found for ' ${ animConfig . key } '. Is this intentional? ` ) ;
return false ;
}
sprite . play ( animConfig ) ;
tintSprite . play ( animConfig ) ;
2024-05-24 01:45:04 +02:00
return true ;
2024-05-17 12:37:38 -05:00
}
2023-10-07 16:08:33 -04:00
playAnim ( ) : void {
const trainerAnimConfig = {
2023-10-18 18:01:15 -04:00
key : this.getKey ( ) ,
2023-10-20 11:38:41 -04:00
repeat : 0 ,
startFrame : 0
2023-10-07 16:08:33 -04:00
} ;
2024-03-21 00:57:28 -04:00
const sprites = this . getSprites ( ) ;
const tintSprites = this . getTintSprites ( ) ;
2024-05-17 00:01:35 -05:00
2024-05-17 12:37:38 -05:00
this . tryPlaySprite ( sprites [ 0 ] , tintSprites [ 0 ] , trainerAnimConfig ) ;
2024-05-17 00:01:35 -05:00
2024-05-17 12:37:38 -05:00
// Queue an animation for the second trainer if this is a double battle against two separate trainers
2024-03-21 00:57:28 -04:00
if ( this . variant === TrainerVariant . DOUBLE && ! this . config . doubleOnly ) {
const partnerTrainerAnimConfig = {
key : this.getKey ( true ) ,
repeat : 0 ,
startFrame : 0
} ;
2024-05-17 00:01:35 -05:00
2024-05-17 12:37:38 -05:00
this . tryPlaySprite ( sprites [ 1 ] , tintSprites [ 1 ] , partnerTrainerAnimConfig ) ;
2024-03-21 00:57:28 -04:00
}
2023-10-07 16:08:33 -04:00
}
2024-03-21 00:57:28 -04:00
getSprites ( ) : Phaser . GameObjects . Sprite [ ] {
const ret : Phaser.GameObjects.Sprite [ ] = [
this . getAt ( 0 )
] ;
2024-05-23 17:03:10 +02:00
if ( this . variant === TrainerVariant . DOUBLE && ! this . config . doubleOnly ) {
2024-03-21 00:57:28 -04:00
ret . push ( this . getAt ( 2 ) ) ;
2024-05-23 17:03:10 +02:00
}
2024-03-21 00:57:28 -04:00
return ret ;
2023-10-07 16:08:33 -04:00
}
2024-03-21 00:57:28 -04:00
getTintSprites ( ) : Phaser . GameObjects . Sprite [ ] {
const ret : Phaser.GameObjects.Sprite [ ] = [
this . getAt ( 1 )
] ;
2024-05-23 17:03:10 +02:00
if ( this . variant === TrainerVariant . DOUBLE && ! this . config . doubleOnly ) {
2024-03-21 00:57:28 -04:00
ret . push ( this . getAt ( 3 ) ) ;
2024-05-23 17:03:10 +02:00
}
2024-03-21 00:57:28 -04:00
return ret ;
2023-10-07 16:08:33 -04:00
}
tint ( color : number , alpha? : number , duration? : integer , ease? : string ) : void {
2024-03-21 00:57:28 -04:00
const tintSprites = this . getTintSprites ( ) ;
tintSprites . map ( tintSprite = > {
tintSprite . setTintFill ( color ) ;
tintSprite . setVisible ( true ) ;
if ( duration ) {
tintSprite . setAlpha ( 0 ) ;
this . scene . tweens . add ( {
targets : tintSprite ,
alpha : alpha || 1 ,
duration : duration ,
2024-05-23 17:03:10 +02:00
ease : ease || "Linear"
2024-03-21 00:57:28 -04:00
} ) ;
2024-05-23 17:03:10 +02:00
} else {
2024-03-21 00:57:28 -04:00
tintSprite . setAlpha ( alpha ) ;
2024-05-23 17:03:10 +02:00
}
2024-03-21 00:57:28 -04:00
} ) ;
2023-10-07 16:08:33 -04:00
}
untint ( duration : integer , ease? : string ) : void {
2024-03-21 00:57:28 -04:00
const tintSprites = this . getTintSprites ( ) ;
tintSprites . map ( tintSprite = > {
if ( duration ) {
this . scene . tweens . add ( {
targets : tintSprite ,
alpha : 0 ,
duration : duration ,
2024-05-23 17:03:10 +02:00
ease : ease || "Linear" ,
2024-03-21 00:57:28 -04:00
onComplete : ( ) = > {
tintSprite . setVisible ( false ) ;
tintSprite . setAlpha ( 1 ) ;
}
} ) ;
} else {
tintSprite . setVisible ( false ) ;
tintSprite . setAlpha ( 1 ) ;
}
} ) ;
2023-10-07 16:08:33 -04:00
}
}
export default interface Trainer {
2024-05-25 14:47:18 +02:00
scene : BattleScene
2024-05-23 17:03:10 +02:00
}