import { AbilityName , Condition , Data , Dex , EggGroup , EvoType , FormeName , GenderName , GenerationNum , ID , ItemName , Move , MoveCategory , MoveName , MoveSource , Nature , Nonstandard , Species as DexSpecies , SpeciesAbility , SpeciesName , SpeciesTag , StatID , StatsTable , Tier , Type as DexType , TypeName , } from '@pkmn/dex-types' ; const DEFAULT_EXISTS = ( d ) => { if ( ! d . exists ) return false ; if ( 'isNonstandard' in d && d . isNonstandard ) return false ; if ( d . kind === 'Ability' && d . id === 'noability' ) return false ; return ! ( 'tier' in d && [ 'Illegal' , 'Unreleased' ] . includes ( d . tier ) ) ; } ; const tr = ( num , bits = 0 ) => bits ? ( num >>> 0 ) % ( 2 ** bits ) : num >>> 0 ; export type ExistsFn = ( d : Data , g : GenerationNum ) => boolean ; type BoundExistsFn = ( d : Data ) => boolean ; function assignWithout ( a , b , exclude ) { for ( const key in b ) { if ( Object . prototype . hasOwnProperty . call ( b , key ) && ! exclude . has ( key ) ) { a [ key ] = b [ key ] ; } } return a ; } export function toID ( text ) { if ( text ?. id ) text = text . id ; if ( typeof text !== 'string' && typeof text !== 'number' ) return '' ; return ( '' + text ) . toLowerCase ( ) . replace ( /[^a-z0-9]+/g , '' ) as ID ; } export class Generations { private readonly cache = Object . create ( null ) as { [ num ] : Generation ; } ; private readonly dex : Dex ; private readonly exists : ExistsFn ; static DEFAULT_EXISTS = DEFAULT_EXISTS ; constructor ( dex , exists = Generations . DEFAULT_EXISTS ) { this . dex = dex ; this . exists = exists ; } get ( gen ) { if ( this . cache [ gen ] ) return this . cache [ gen ] ; return ( this . cache [ gen ] = new Generation ( this . dex . forGen ( gen ) , d => this . exists ( d , gen ) ) ) ; } * [ Symbol . iterator ] ( ) { for ( let gen = 1 ; gen <= 8 ; gen ++ ) { yield this . get ( gen as GenerationNum ) ; } } } export class Generation { readonly abilities : Abilities ; readonly items : Items ; readonly moves : Moves ; readonly species : Species ; readonly types : Types ; readonly natures : Natures ; readonly learnsets : Learnsets ; readonly conditions : Conditions ; readonly stats : Stats ; readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; this . abilities = new Abilities ( this . dex , this . exists ) ; this . items = new Items ( this . dex , this . exists ) ; this . moves = new Moves ( this . dex , this . exists ) ; this . species = new Species ( this . dex , this . exists ) ; this . natures = new Natures ( this . dex , this . exists ) ; this . types = new Types ( this . dex , this . exists ) ; this . learnsets = new Learnsets ( this , this . dex , this . exists ) ; this . conditions = new Conditions ( this . dex , this . exists ) ; this . stats = new Stats ( this . dex ) ; } get num ( ) { return this . dex . gen ; } toString ( ) { return `[Generation:${ this . num }]` ; } toJSON ( ) { return this . toString ( ) ; } } export class Abilities { private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; } get ( name ) { const ability = this . dex . abilities . get ( name ) ; return this . exists ( ability ) ? ability : undefined ; } * [ Symbol . iterator ] ( ) { for ( const ability in this . dex . data . Abilities ) { const a = this . get ( ability ) ; if ( a ) yield a ; } } } export class Items { private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; } get ( name ) { const item = this . dex . items . get ( name ) ; return this . exists ( item ) ? item : undefined ; } * [ Symbol . iterator ] ( ) { for ( const item in this . dex . data . Items ) { const i = this . get ( item ) ; if ( i ) yield i ; } } } export class Moves { private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; } get ( name ) { const move = this . dex . moves . get ( name ) ; return this . exists ( move ) ? move : undefined ; } * [ Symbol . iterator ] ( ) { for ( const move in this . dex . data . Moves ) { const m = this . get ( move ) ; if ( m ) yield m ; } } } export class Species { private readonly cache = Object . create ( null ) as { [ id ] : Specie ; } ; private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; } get ( name ) { const species = this . dex . species . get ( name ) ; if ( ! this . exists ( species ) ) return undefined ; const id = ( species as any ) . speciesid || species . id ; const cached = this . cache [ id ] ; if ( cached ) return cached ; return ( this . cache [ id ] = new Specie ( this . dex , this . exists , species ) ) ; } * [ Symbol . iterator ] ( ) { for ( const species in this . dex . data . Species ) { const s = this . get ( species ) ; if ( s ) yield s ; } } } export class Specie implements DexSpecies { readonly id ! : ID ; readonly name ! : SpeciesName ; readonly fullname ! : string ; readonly exists ! : boolean ; readonly num ! : number ; readonly gen ! : GenerationNum ; readonly shortDesc ! : string ; readonly desc ! : string ; readonly isNonstandard ! : Nonstandard | null ; readonly duration ? : number ; readonly effectType ! : 'Pokemon' ; readonly kind ! : 'Species' ; readonly baseStats ! : StatsTable ; readonly baseSpecies ! : SpeciesName ; readonly baseForme ! : FormeName | '' ; readonly forme ! : FormeName | '' ; readonly abilities ! : SpeciesAbility < AbilityName | '' > ; readonly types ! : [ TypeName ] | [ TypeName , TypeName ] ; readonly prevo ? : SpeciesName | '' ; readonly evos ? : SpeciesName [ ] ; readonly nfe : boolean ; readonly eggGroups ! : EggGroup [ ] ; readonly weightkg ! : number ; readonly weighthg ! : number ; readonly tags ! : SpeciesTag [ ] ; readonly unreleasedHidden ! : boolean | 'Past' ; readonly maleOnlyHidden ! : boolean ; readonly inheritsFrom ! : ID ; readonly tier ! : Tier . Singles | Tier . Other ; readonly doublesTier ! : Tier . Doubles ; readonly evoMove ? : MoveName ; readonly changesFrom ? : SpeciesName ; readonly cosmeticFormes ? : SpeciesName [ ] ; readonly otherFormes ? : SpeciesName [ ] ; readonly formeOrder ? : SpeciesName [ ] ; readonly formes ? : SpeciesName [ ] ; readonly genderRatio : { M : number ; F : number ; } ; readonly isMega ? : boolean ; readonly isPrimal ? : boolean ; readonly battleOnly ? : SpeciesName | SpeciesName [ ] ; readonly isGigantamax ? : MoveName ; readonly requiredAbility ? : AbilityName ; readonly requiredItem ? : ItemName ; readonly requiredItems ? : ItemName [ ] ; readonly requiredMove ? : MoveName ; readonly gender ? : GenderName ; readonly maxHP ? : number ; readonly evoLevel ? : number ; readonly evoCondition ? : string ; readonly evoItem ? : string ; readonly evoType ? : EvoType ; readonly condition ? : Partial < Condition > ; readonly canHatch ! : boolean ; private readonly dex : Dex ; private static readonly EXCLUDE = new Set ( [ 'abilities' , 'cosmeticFormes' , 'evos' , 'gender' , 'genderRatio' , 'nfe' , 'otherFormes' , 'prevo' , ] ) ; constructor ( dex , exists , species ) { assignWithout ( this , species , Specie . EXCLUDE ) ; this . dex = dex ; if ( this . dex . gen >= 2 ) { this . gender = species . gender ; this . genderRatio = species . genderRatio ; } else { this . genderRatio = { M : 0 , F : 0 } ; } if ( this . dex . gen >= 3 ) { this . abilities = { 0 : species . abilities [ 0 ] } ; if ( species . abilities [ 1 ] && this . dex . abilities . get ( species . abilities [ 1 ] ) . gen <= this . dex . gen ) { this . abilities [ 1 ] = species . abilities [ 1 ] ; } if ( this . dex . gen >= 5 && species . abilities . H ) this . abilities . H = species . abilities . H ; if ( this . dex . gen >= 7 && species . abilities . S ) this . abilities . S = species . abilities . S ; } else { this . abilities = { 0 : '' } ; } this . evos = species . evos ?. filter ( s => exists ( this . dex . species . get ( s ) ) ) ; this . nfe = ! ! this . evos ?. length ; if ( ! this . nfe ) this . evos = undefined ; this . cosmeticFormes = species . cosmeticFormes ?. filter ( s => exists ( this . dex . species . get ( s ) ) ) ; if ( ! this . cosmeticFormes ?. length ) this . cosmeticFormes = undefined ; this . otherFormes = species . otherFormes ?. filter ( s => exists ( this . dex . species . get ( s ) ) ) ; if ( ! this . otherFormes ?. length ) this . otherFormes = undefined ; this . formeOrder = species . formeOrder ?. filter ( s => exists ( this . dex . species . get ( s ) ) ) ; if ( ! this . formeOrder ?. length ) this . formeOrder = undefined ; this . formes = this . formeOrder ?. filter ( s => ! this . dex . species . get ( s ) . isGigantamax ) ; this . prevo = species . prevo && exists ( this . dex . species . get ( species . prevo ) ) ? species . prevo : undefined ; } get formeNum ( ) { return ( this . baseSpecies === this . name ? this . formeOrder ? this . formeOrder . findIndex ( name => name === this . name ) : 0 : this . dex . species . get ( this . baseSpecies ) . formeOrder ! . findIndex ( name => name === ( this . isGigantamax ? this . baseSpecies : this . name ) ) ) ; } toString ( ) { return this . name ; } toJSON ( ) { return assignWithout ( { } , this , new Set ( [ 'dex' ] ) ) ; } } export class Conditions { private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; } get ( name ) { const condition = this . dex . conditions . get ( name ) ; return this . exists ( condition ) ? condition : undefined ; } } export class Natures { private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; } get ( name ) { if ( this . dex . gen < 3 ) return undefined ; const nature = this . dex . natures . get ( name ) ; return this . exists ( nature ) ? nature : undefined ; } * [ Symbol . iterator ] ( ) { for ( const nature in this . dex . data . Natures ) { const n = this . get ( nature ) ; if ( n ) yield n ; } } } const EFFECTIVENESS = { '-2' : 0.25 , '-1' : 0.5 , '0' : 1 , '1' : 2 , '2' : 4 , } ; type TypeTarget = { getTypes : ( ) => TypeName [ ] ; } | { types : TypeName [ ] ; } | TypeName [ ] | TypeName ; export class Types { private readonly cache = Object . create ( null ) as { [ id ] : Type ; } ; private readonly unknown : Type ; private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( dex , exists ) { this . dex = dex ; this . exists = exists ; this . unknown = new Type ( { effectType : 'Type' , kind : 'Type' , id : '' , name : '???' , exists : dex . gen <= 4 , gen : 1 , damageTaken : { } as { [ t in Exclude < TypeName , '???' > ] : number ; } , HPivs : { } , HPdvs : { } , } , dex , this ) ; } get ( name ) { if ( name === '???' ) return this . unknown ; const type = this . dex . types . get ( name ) ; if ( ! this . exists ( type ) ) return undefined ; const cached = this . cache [ type . id ] ; if ( cached ) return cached ; return ( this . cache [ type . id ] = new Type ( type , this . dex , this ) ) ; } * [ Symbol . iterator ] ( ) { for ( const type in this . dex . data . Types ) { const t = this . get ( type ) ; if ( t ) yield t ; } if ( this . dex . gen >= 2 && this . dex . gen <= 4 ) { yield this . unknown ; } } getHiddenPower ( ivs ) { return this . dex . getHiddenPower ( ivs ) ; } canDamage ( source , target ) { return this . dex . getImmunity ( source , target ) ; } totalEffectiveness ( source , target ) { if ( ! this . canDamage ( source , target ) ) return 0 ; const e = `${ this . dex . getEffectiveness ( source , target ) }` ; return EFFECTIVENESS [ e as keyof typeof EFFECTIVENESS ] ; } } export type TypeEffectiveness = 0 | 0.5 | 1 | 2 ; const DAMAGE_TAKEN = [ 1 , 2 , 0.5 , 0 ] as TypeEffectiveness [ ] ; const SPECIAL = [ 'Fire' , 'Water' , 'Grass' , 'Electric' , 'Ice' , 'Psychic' , 'Dark' , 'Dragon' ] ; export class Type { readonly id ! : ID ; readonly name ! : TypeName ; readonly effectType ! : 'Type' ; readonly kind ! : 'Type' ; readonly exists ! : boolean ; readonly gen ! : GenerationNum ; readonly effectiveness : { [ t in TypeName ] : TypeEffectiveness ; } ; readonly HPivs ! : Partial < StatsTable > ; readonly HPdvs ! : Partial < StatsTable > ; readonly category ? : Exclude < MoveCategory , 'Status' > ; private readonly types : Types ; constructor ( type , dex , types ) { Object . assign ( this , type ) ; this . types = types ; this . category = this . name === 'Fairy' ? undefined : SPECIAL . includes ( this . name ) ? 'Special' : 'Physical' ; this . effectiveness = { '???' : 1 } as { [ t in TypeName ] : TypeEffectiveness ; } ; for ( const k in dex . data . Types ) { const t = k . charAt ( 0 ) . toUpperCase ( ) + k . slice ( 1 ) as Exclude < TypeName , '???' > ; this . effectiveness [ t ] = DAMAGE_TAKEN [ dex . data . Types [ k ] . damageTaken [ this . name ] || 0 ] ; } } canDamage ( target ) { return this . types . canDamage ( this . name , target ) ; } totalEffectiveness ( target ) { return this . types . totalEffectiveness ( this . name , target ) ; } toString ( ) { return this . name ; } toJSON ( ) { return assignWithout ( { } , this , new Set ( [ 'types' ] ) ) ; } } const GEN3_HMS = new Set ( [ 'cut' , 'fly' , 'surf' , 'strength' , 'flash' , 'rocksmash' , 'waterfall' , 'dive' ] as ID [ ] ) ; const GEN4_HMS = new Set ( [ 'cut' , 'fly' , 'surf' , 'strength' , 'rocksmash' , 'waterfall' , 'rockclimb' ] as ID [ ] ) ; type Restriction = 'Pentagon' | 'Plus' | 'Galar' ; export class Learnsets { private readonly cache = Object . create ( null ) as { [ speciesid ] : { [ moveid ] : MoveSource [ ] ; } ; } ; private readonly gen : Generation ; private readonly dex : Dex ; private readonly exists : BoundExistsFn ; constructor ( gen , dex , exists ) { this . gen = gen ; this . dex = dex ; this . exists = exists ; } async get ( name ) { const learnset = await this . dex . learnsets . get ( toID ( name ) ) ; return this . exists ( learnset ) ? learnset : undefined ; } async * [ Symbol . iterator ] ( ) { if ( ! this . dex . data . Learnsets ) await this . dex . learnsets . get ( 'LOAD' as ID ) ; for ( const id in this . dex . data . Learnsets ) { const l = await this . get ( id ) ; if ( l ) yield l ; } } async * all ( species ) { let id = species . id ; let learnset = await this . get ( id ) ; if ( ! learnset ) { id = typeof species . battleOnly === 'string' && species . battleOnly !== species . baseSpecies ? toID ( species . battleOnly ) : toID ( species . baseSpecies ) ; learnset = await this . get ( id ) ; } while ( learnset ) { yield learnset ; if ( id === 'lycanrocdusk' || ( species . id === 'rockruff' && id === 'rockruff' ) ) { id = 'rockruffdusk' as ID ; } else if ( species . id === 'gastrodoneast' ) { id = 'gastrodon' as ID ; } else if ( species . id === 'pumpkaboosuper' ) { id = 'pumpkaboo' as ID ; } else { id = toID ( species . battleOnly || species . changesFrom || species . prevo ) ; } if ( ! id ) break ; const s = this . gen . species . get ( id ) ; if ( ! s ) break ; species = s ; learnset = await this . get ( id ) ; } } async learnable ( name , restriction ? ) { const species = this . gen . species . get ( name ) ; if ( ! species ) return undefined ; if ( ! restriction ) { const cached = this . cache [ species . id ] ; if ( cached ) return cached ; } const moves = { } ; for await ( const learnset of this . all ( species ) ) { if ( learnset . learnset ) { for ( const moveid in learnset . learnset ) { const move = this . gen . moves . get ( moveid ) ; if ( move ) { const sources = learnset . learnset [ moveid ] ; if ( this . isLegal ( move , sources , restriction || this . gen ) ) { moves [ move . id ] = sources . filter ( s => + s . charAt ( 0 ) <= this . gen . num ) ; } } } } } if ( ! restriction ) this . cache [ species . id ] = moves ; return moves ; } async canLearn ( name , move , restriction ? ) { const species = this . gen . species . get ( name ) ; if ( ! species ) return false ; move = typeof move === 'string' && this . gen . moves . get ( move ) || move ; if ( typeof move === 'string' ) return false ; for await ( const learnset of this . all ( species ) ) { if ( this . isLegal ( move , learnset . learnset ?. [ move . id ] , restriction || this . gen ) ) { return true ; } } return false ; } isLegal ( move , sources , gen ) { if ( ! sources ) return undefined ; const gens = sources . map ( x => Number ( x [ 0 ] ) ) ; const minGen = Math . min ( ... gens ) ; const vcOnly = ( minGen === 7 && sources . every ( x => x [ 0 ] !== '7' || x === '7V' ) || minGen === 8 && sources . every ( x => x [ 0 ] !== '8' || x === '8V' ) ) ; if ( gen === 'Pentagon' ) return gens . includes ( 6 ) ; if ( gen === 'Plus' ) return gens . includes ( 7 ) && ! vcOnly ; if ( gen === 'Galar' ) return gens . includes ( 8 ) && ! vcOnly ; if ( this . gen . num >= 3 && minGen <= 4 && ( GEN3_HMS . has ( move . id ) || GEN4_HMS . has ( move . id ) ) ) { let legalGens = '' ; let available = false ; if ( minGen === 3 ) { legalGens += '3' ; available = true ; } if ( available ) available = ! GEN3_HMS . has ( move . id ) ; if ( available || gens . includes ( 4 ) ) { legalGens += '4' ; available = true ; } if ( available ) available = ! GEN4_HMS . has ( move . id ) ; const minUpperGen = available ? 5 : Math . min ( ... gens . filter ( g => g > 4 ) ) ; legalGens += '012345678' . slice ( minUpperGen ) ; return legalGens . includes ( `${ gen . num }` ) ; } else { return '012345678' . slice ( minGen ) . includes ( `${ gen . num }` ) ; } } } const STATS = [ 'hp' , 'atk' , 'def' , 'spe' , 'spa' , 'spd' ] as const ; const NAMES = { HP : 'hp' , hp : 'hp' , Attack : 'atk' , Atk : 'atk' , atk : 'atk' , Defense : 'def' , Def : 'def' , def : 'def' , 'Special Attack' : 'spa' , SpA : 'spa' , SAtk : 'spa' , SpAtk : 'spa' , spa : 'spa' , Special : 'spa' , spc : 'spa' , Spc : 'spa' , 'Special Defense' : 'spd' , SpD : 'spd' , SDef : 'spd' , SpDef : 'spd' , spd : 'spd' , Speed : 'spe' , Spe : 'spe' , Spd : 'spe' , spe : 'spe' , } ; const DISPLAY = { hp : [ 'HP' , 'HP' ] , atk : [ 'Atk' , 'Attack' ] , def : [ 'Def' , 'Defense' ] , spa : [ 'SpA' , 'Special Attack' ] , spd : [ 'SpD' , 'Special Defense' ] , spe : [ 'Spe' , 'Speed' ] , spc : [ 'Spc' , 'Special' ] , } ; export class Stats { private readonly dex : Dex ; constructor ( dex ) { this . dex = dex ; } calc ( stat , base , iv = 31 , ev ? , level = 100 , nature ? ) { if ( ev === undefined ) ev = this . dex . gen < 3 ? 252 : 0 ; if ( this . dex . gen < 3 ) { iv = this . toDV ( iv ) * 2 ; nature = undefined ; } if ( stat === 'hp' ) { return base === 1 ? base : tr ( tr ( 2 * base + iv + tr ( ev / 4 ) + 100 ) * level / 100 + 10 ) ; } else { const val = tr ( tr ( 2 * base + iv + tr ( ev / 4 ) ) * level / 100 + 5 ) ; if ( nature !== undefined ) { if ( nature . plus === stat ) return tr ( tr ( val * 110 , 16 ) / 100 ) ; if ( nature . minus === stat ) return tr ( tr ( val * 90 , 16 ) / 100 ) ; } return val ; } } get ( s ) { return NAMES [ s ] ; } display ( str , full = false ) { let s = NAMES [ str ] ; if ( s === undefined ) return str ; if ( this . dex . gen === 1 && s === 'spa' ) s = 'spc' ; return DISPLAY [ s ] [ + full ] ; } fill < T > ( stats , val ) { for ( const stat of STATS ) { if ( ! ( stat in stats ) ) stats [ stat ] = val ; } return stats as StatsTable < T > ; } getHPDV ( ivs ) { return ( ( this . toDV ( ivs . atk === undefined ? 31 : ivs . atk ) % 2 ) * 8 + ( this . toDV ( ivs . def === undefined ? 31 : ivs . def ) % 2 ) * 4 + ( this . toDV ( ivs . spe === undefined ? 31 : ivs . spe ) % 2 ) * 2 + ( this . toDV ( ivs . spa === undefined ? 31 : ivs . spa ) % 2 ) ) ; } * [ Symbol . iterator ] ( ) { for ( const s of STATS ) { yield s ; } } toDV ( iv ) { return Math . floor ( iv / 2 ) ; } toIV ( dv ) { return dv * 2 + 1 ; } } export { ID , As , Weather , FieldCondition , SideCondition , GenerationNum , GenderName , StatID , StatsTable , BoostID , BoostsTable , MoveCategory , MoveTarget , Nonstandard , EvoType , EggGroup , SideID , Player , GameType , HPColor , StatusName , NatureName , TypeName , HPTypeName , Tier , PokemonSet , AbilityName , ItemName , MoveName , SpeciesName , FormeName , EffectType , Effect , DataKind , Data , EffectData , HitEffect , SecondaryEffect , ConditionData , AbilityData , ItemData , MoveData , SpeciesData , MoveSource , EventInfoData , LearnsetData , TypeData , NatureData , BasicEffect , Condition , Ability , Item , Move , EventInfo , Learnset , Nature , GenID , Dex , } from '@pkmn/dex-types' ;