diff --git a/public/images/trainer/hex_maniac.json b/public/images/trainer/hex_maniac.json new file mode 100644 index 000000000..0a0387b49 --- /dev/null +++ b/public/images/trainer/hex_maniac.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "hex_maniac.png", + "format": "RGBA8888", + "size": { + "w": 72, + "h": 72 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 72, + "h": 60 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 72, + "h": 60 + }, + "frame": { + "x": 0, + "y": 0, + "w": 72, + "h": 60 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:f2c166f63ec69a969a603195197ac4c5:3ddb08593bf4265e8a457607f5bc100e:9c98ceb90f2b76c43d8cccba92593697$" + } +} diff --git a/public/images/trainer/hex_maniac.png b/public/images/trainer/hex_maniac.png new file mode 100644 index 000000000..7557dbf77 Binary files /dev/null and b/public/images/trainer/hex_maniac.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5c920f916..d9a8ab312 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -25,9 +25,9 @@ import { GameMode, GameModes, gameModes } from './game-mode'; import FieldSpritePipeline from './pipelines/field-sprite'; import SpritePipeline from './pipelines/sprite'; import PartyExpBar from './ui/party-exp-bar'; -import { trainerConfigs } from './data/trainer-config'; +import { TrainerSlot, trainerConfigs } from './data/trainer-config'; import { TrainerType } from "./data/enums/trainer-type"; -import Trainer from './field/trainer'; +import Trainer, { TrainerVariant } from './field/trainer'; import TrainerData from './system/trainer-data'; import SoundFade from 'phaser3-rex-plugins/plugins/soundfade'; import { pokemonPrevolutions } from './data/pokemon-evolutions'; @@ -351,9 +351,9 @@ export default class BattleScene extends Phaser.Scene { Utils.getEnumValues(TrainerType).map(tt => { const config = trainerConfigs[tt]; - this.loadAtlas(config.getKey(), 'trainer'); - if (config.isDouble) - this.loadAtlas(config.getKey(true), 'trainer'); + this.loadAtlas(config.getSpriteKey(), 'trainer'); + if (config.doubleOnly || config.hasDouble) + this.loadAtlas(config.getSpriteKey(true), 'trainer'); }); // Load character sprites @@ -756,8 +756,8 @@ export default class BattleScene extends Phaser.Scene { return pokemon; } - addEnemyPokemon(species: PokemonSpecies, level: integer, trainer: boolean, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon { - const pokemon = new EnemyPokemon(this, species, level, trainer, boss, dataSource); + addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon { + const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, dataSource); if (postProcess) postProcess(pokemon); pokemon.init(); @@ -839,6 +839,8 @@ export default class BattleScene extends Phaser.Scene { let battleConfig: FixedBattleConfig = null; this.resetSeed(newWaveIndex); + + const playerField = this.getPlayerField(); if (this.gameMode.hasFixedBattles && fixedBattles.hasOwnProperty(newWaveIndex) && trainerData === undefined) { battleConfig = fixedBattles[newWaveIndex]; @@ -856,13 +858,21 @@ export default class BattleScene extends Phaser.Scene { newBattleType = battleType; if (newBattleType === BattleType.TRAINER) { - newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, this.arena.randomTrainerType(newWaveIndex), !!Utils.randSeedInt(2)); + const trainerType = this.arena.randomTrainerType(newWaveIndex); + let doubleTrainer = false; + if (trainerConfigs[trainerType].doubleOnly) + doubleTrainer = true; + else if (trainerConfigs[trainerType].hasDouble) { + const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); + this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); + playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance)); + doubleTrainer = !Utils.randSeedInt(doubleChance.value); + } + newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); this.field.add(newTrainer); } } - const playerField = this.getPlayerField(); - if (double === undefined && newWaveIndex > 1) { if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) { const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); @@ -870,7 +880,7 @@ export default class BattleScene extends Phaser.Scene { playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance)); newDouble = !Utils.randSeedInt(doubleChance.value); } else if (newBattleType === BattleType.TRAINER) - newDouble = newTrainer.config.isDouble; + newDouble = newTrainer.variant === TrainerVariant.DOUBLE; } else if (!battleConfig) newDouble = !!double; diff --git a/src/battle.ts b/src/battle.ts index a049127dd..1f29d7f67 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -2,7 +2,7 @@ import BattleScene from "./battle-scene"; import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./field/pokemon"; import { Command } from "./ui/command-ui-handler"; import * as Utils from "./utils"; -import Trainer from "./field/trainer"; +import Trainer, { TrainerVariant } from "./field/trainer"; import { Species } from "./data/enums/species"; import { Moves } from "./data/enums/moves"; import { TrainerType } from "./data/enums/trainer-type"; @@ -259,7 +259,7 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[]): Get : trainerPoolEntry; trainerTypes.push(trainerType); } - return new Trainer(scene, trainerTypes[rand]); + return new Trainer(scene, trainerTypes[rand], TrainerVariant.DEFAULT); }; } @@ -269,17 +269,17 @@ interface FixedBattleConfigs { export const fixedBattles: FixedBattleConfigs = { [5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.YOUNGSTER, !!Utils.randSeedInt(2))), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL, scene.gameData.gender === PlayerGender.MALE)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [25]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [55]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE)), + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [182]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.RIKA, TrainerType.CRISPIN ])), [184]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) @@ -291,5 +291,5 @@ export const fixedBattles: FixedBattleConfigs = { [190]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, TrainerType.HAU, [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN, TrainerType.LEON ])), [195]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_6, scene.gameData.gender === PlayerGender.MALE)) + .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_6, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) }; \ No newline at end of file diff --git a/src/data/biomes.ts b/src/data/biomes.ts index 8e6da695e..800ce32d3 100644 --- a/src/data/biomes.ts +++ b/src/data/biomes.ts @@ -1828,7 +1828,7 @@ export const biomeTrainerPools: BiomeTrainerPools = { }, [Biome.GRAVEYARD]: { [BiomePoolTier.COMMON]: [ TrainerType.PSYCHIC ], - [BiomePoolTier.UNCOMMON]: [], + [BiomePoolTier.UNCOMMON]: [ TrainerType.HEX_MANIAC ], [BiomePoolTier.RARE]: [], [BiomePoolTier.SUPER_RARE]: [], [BiomePoolTier.ULTRA_RARE]: [], @@ -7210,7 +7210,6 @@ export const biomeTrainerPools: BiomeTrainerPools = { [ TrainerType.LINEBACKER, [] ], [ TrainerType.MAID, [] ], [ TrainerType.MUSICIAN, [] ], - [ TrainerType.NURSE, [] ], [ TrainerType.NURSERY_AIDE, [] ], [ TrainerType.OFFICER, [ [ Biome.METROPOLIS, BiomePoolTier.COMMON ], @@ -7284,6 +7283,10 @@ export const biomeTrainerPools: BiomeTrainerPools = { [ Biome.TOWN, BiomePoolTier.COMMON ] ] ], + [ TrainerType.HEX_MANIAC, [ + [ Biome.GRAVEYARD, BiomePoolTier.UNCOMMON ] + ] + ], [ TrainerType.BROCK, [ [ Biome.CAVE, BiomePoolTier.BOSS ] ] diff --git a/src/data/enums/trainer-type.ts b/src/data/enums/trainer-type.ts index d48267366..c30b09896 100644 --- a/src/data/enums/trainer-type.ts +++ b/src/data/enums/trainer-type.ts @@ -1,6 +1,7 @@ export enum TrainerType { UNKNOWN, + ACE_TRAINER, ARTIST, BACKERS, @@ -26,7 +27,7 @@ export enum TrainerType { LINEBACKER, MAID, MUSICIAN, - NURSE, + HEX_MANIAC, NURSERY_AIDE, OFFICER, PARASOL_LADY, diff --git a/src/data/move.ts b/src/data/move.ts index a342db05f..1118eba4e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -5,7 +5,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat"; import { EncoreTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { getPokemonMessage } from "../messages"; -import Pokemon, { AttackMoveResult, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; +import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; import { StatusEffect, getStatusEffectHealText } from "./status-effect"; import { Type } from "./type"; import * as Utils from "../utils"; @@ -2144,7 +2144,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } const party = player ? user.scene.getParty() : user.scene.getEnemyParty(); - return (!player && !user.scene.currentBattle.battleType) || party.filter(p => !p.isFainted()).length > user.scene.currentBattle.getBattlerCount(); + return (!player && !user.scene.currentBattle.battleType) || party.filter(p => !p.isFainted() && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount(); }; } diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index f9a1a102d..fe4aa8d54 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -12,6 +12,7 @@ import { tmSpecies } from "./tms"; import { Type } from "./type"; import { initTrainerTypeDialogue } from "./dialogue"; import { PersistentModifier } from "../modifier/modifier"; +import { TrainerVariant } from "../field/trainer"; export enum TrainerPoolTier { COMMON, @@ -34,6 +35,12 @@ export enum TrainerPartyMemberStrength { STRONGER } +export enum TrainerSlot { + NONE, + TRAINER, + TRAINER_PARTNER +} + export class TrainerPartyTemplate { public size: integer; public strength: TrainerPartyMemberStrength; @@ -171,10 +178,12 @@ export class TrainerConfig { public trainerType: TrainerType; public name: string; public nameFemale: string; + public nameDouble: string; public title: string; public hasGenders: boolean = false; + public hasDouble: boolean = false; public hasCharSprite: boolean = false; - public isDouble: boolean = false; + public doubleOnly: boolean = false; public moneyMultiplier: number = 1; public isBoss: boolean = false; public hasStaticParty: boolean = false; @@ -182,6 +191,7 @@ export class TrainerConfig { public battleBgm: string; public encounterBgm: string; public femaleEncounterBgm: string; + public doubleEncounterBgm: string; public victoryBgm: string; public genModifiersFunc: GenModifiersFunc; public modifierRewardFuncs: ModifierTypeFunc[] = []; @@ -200,6 +210,10 @@ export class TrainerConfig { public femaleVictoryMessages: string[]; public femaleDefeatMessages: string[]; + public doubleEncounterMessages: string[]; + public doubleVictoryMessages: string[]; + public doubleDefeatMessages: string[]; + constructor(trainerType: TrainerType, allowLegendaries?: boolean) { this.trainerType = trainerType; this.name = Utils.toReadableString(TrainerType[this.getDerivedType()]); @@ -209,8 +223,12 @@ export class TrainerConfig { this.speciesFilter = species => allowLegendaries || (!species.legendary && !species.pseudoLegendary && !species.mythical); } - getKey(female?: boolean): string { - let ret = TrainerType[this.getDerivedType()].toString().toLowerCase(); + getKey(): string { + return TrainerType[this.getDerivedType()].toString().toLowerCase(); + } + + getSpriteKey(female?: boolean): string { + let ret = this.getKey(); if (this.hasGenders) ret += `_${female ? 'f' : 'm'}`; return ret; @@ -255,13 +273,21 @@ export class TrainerConfig { return this; } + setHasDouble(nameDouble: string, doubleEncounterBgm?: TrainerType | string): TrainerConfig { + this.hasDouble = true; + this.nameDouble = nameDouble; + if (doubleEncounterBgm) + this.doubleEncounterBgm = typeof doubleEncounterBgm === 'number' ? TrainerType[doubleEncounterBgm].toString().replace(/\_/g, ' ').toLowerCase() : doubleEncounterBgm; + return this; + } + setHasCharSprite(): TrainerConfig { this.hasCharSprite = true; return this; } - setDouble(): TrainerConfig { - this.isDouble = true; + setDoubleOnly(): TrainerConfig { + this.doubleOnly = true; return this; } @@ -410,29 +436,39 @@ export class TrainerConfig { return this; } - getName(female?: boolean): string { + getTitle(trainerSlot: TrainerSlot = TrainerSlot.NONE, variant: TrainerVariant): string { let ret = this.name; + + if (!trainerSlot && variant === TrainerVariant.DOUBLE && this.nameDouble) + return this.nameDouble; if (this.hasGenders) { if (this.nameFemale) { - if (female) + if (variant === TrainerVariant.FEMALE || (variant === TrainerVariant.DOUBLE && trainerSlot === TrainerSlot.TRAINER_PARTNER)) return this.nameFemale; } else - ret += !female ? '♂' : '♀'; + ret += !variant ? '♂' : '♀'; } return ret; } - loadAssets(scene: BattleScene, female: boolean): Promise { + loadAssets(scene: BattleScene, variant: TrainerVariant): Promise { return new Promise(resolve => { - const trainerKey = this.getKey(female); + const isDouble = variant === TrainerVariant.DOUBLE; + const trainerKey = this.getSpriteKey(variant === TrainerVariant.FEMALE); + const partnerTrainerKey = this.getSpriteKey(true); scene.loadAtlas(trainerKey, 'trainer'); + if (isDouble) + scene.loadAtlas(partnerTrainerKey, 'trainer'); scene.load.once(Phaser.Loader.Events.COMPLETE, () => { const originalWarn = console.warn; // Ignore warnings for missing frames, because there will be a lot console.warn = () => {}; const frameNames = scene.anims.generateFrameNames(trainerKey, { zeroPad: 4, suffix: ".png", start: 1, end: 128 }); + const partnerFrameNames = isDouble + ? scene.anims.generateFrameNames(partnerTrainerKey, { zeroPad: 4, suffix: ".png", start: 1, end: 128 }) + : null; console.warn = originalWarn; scene.anims.create({ key: trainerKey, @@ -440,6 +476,14 @@ export class TrainerConfig { frameRate: 24, repeat: -1 }); + if (isDouble) { + scene.anims.create({ + key: partnerTrainerKey, + frames: partnerFrameNames, + frameRate: 24, + repeat: -1 + }); + } resolve(); }); if (!scene.load.isLoading()) @@ -462,18 +506,18 @@ function getGymLeaderPartyTemplate(scene: BattleScene) { return getWavePartyTemplate(scene, trainerPartyTemplates.GYM_LEADER_1, trainerPartyTemplates.GYM_LEADER_2, trainerPartyTemplates.GYM_LEADER_3, trainerPartyTemplates.GYM_LEADER_4, trainerPartyTemplates.GYM_LEADER_5); } -function getRandomPartyMemberFunc(speciesPool: Species[], postProcess?: (enemyPokemon: EnemyPokemon) => void): PartyMemberFunc { +function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, postProcess?: (enemyPokemon: EnemyPokemon) => void): PartyMemberFunc { return (scene: BattleScene, level: integer) => { const species = getPokemonSpecies(Utils.randSeedItem(speciesPool)).getSpeciesForLevel(level, true, true, scene.currentBattle.trainer.config.isBoss); - return scene.addEnemyPokemon(getPokemonSpecies(species), level, true, undefined, undefined, postProcess); + return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess); }; } -function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilter, allowLegendaries?: boolean, postProcess?: (EnemyPokemon: EnemyPokemon) => void): PartyMemberFunc { +function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilter, trainerSlot: TrainerSlot = TrainerSlot.TRAINER, allowLegendaries?: boolean, postProcess?: (EnemyPokemon: EnemyPokemon) => void): PartyMemberFunc { const originalSpeciesFilter = speciesFilter; speciesFilter = (species: PokemonSpecies) => allowLegendaries || (!species.legendary && !species.pseudoLegendary && !species.mythical) && originalSpeciesFilter(species); return (scene: BattleScene, level: integer) => { - const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getSpeciesForLevel(level, true, true, scene.currentBattle.trainer.config.isBoss)), level, true, undefined, undefined, postProcess); + const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getSpeciesForLevel(level, true, true, scene.currentBattle.trainer.config.isBoss)), level, trainerSlot, undefined, undefined, postProcess); return ret; }; } @@ -491,12 +535,12 @@ function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: T export const trainerConfigs: TrainerConfigs = { [TrainerType.UNKNOWN]: new TrainerConfig(0).setHasGenders(), - [TrainerType.ACE_TRAINER]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER) + [TrainerType.ACE_TRAINER]: new TrainerConfig(++t).setHasGenders().setHasDouble('Ace Trainers').setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER) .setPartyTemplateFunc(scene => getWavePartyTemplate(scene, trainerPartyTemplates.THREE_WEAK_BALANCED, trainerPartyTemplates.FOUR_WEAK_BALANCED, trainerPartyTemplates.FIVE_WEAK_BALANCED, trainerPartyTemplates.SIX_WEAK_BALANCED)), [TrainerType.ARTIST]: new TrainerConfig(++t).setEncounterBgm(TrainerType.RICH).setPartyTemplates(trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.THREE_AVG) .setSpeciesPools([ Species.SMEARGLE ]), - [TrainerType.BACKERS]: new TrainerConfig(++t).setHasGenders().setDouble().setEncounterBgm(TrainerType.CYCLIST), - [TrainerType.BACKPACKER]: new TrainerConfig(++t).setHasGenders().setSpeciesFilter(s => s.isOfType(Type.FLYING) || s.isOfType(Type.ROCK)).setEncounterBgm(TrainerType.BACKPACKER) + [TrainerType.BACKERS]: new TrainerConfig(++t).setHasGenders().setDoubleOnly().setEncounterBgm(TrainerType.CYCLIST), + [TrainerType.BACKPACKER]: new TrainerConfig(++t).setHasGenders().setHasDouble('Backpackers').setSpeciesFilter(s => s.isOfType(Type.FLYING) || s.isOfType(Type.ROCK)).setEncounterBgm(TrainerType.BACKPACKER) .setPartyTemplates(trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.ONE_WEAK_ONE_STRONG, trainerPartyTemplates.ONE_AVG_ONE_STRONG) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.RHYHORN, Species.AIPOM, Species.MAKUHITA, Species.MAWILE, Species.NUMEL, Species.LILLIPUP, Species.SANDILE, Species.WOOLOO ], @@ -507,7 +551,7 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.BAKER]: new TrainerConfig(++t).setEncounterBgm(TrainerType.CLERK).setMoneyMultiplier(1.35).setSpeciesFilter(s => s.isOfType(Type.GRASS) || s.isOfType(Type.FIRE)), [TrainerType.BEAUTY]: new TrainerConfig(++t).setMoneyMultiplier(1.55).setEncounterBgm(TrainerType.PARASOL_LADY), [TrainerType.BIKER]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.ROUGHNECK).setSpeciesFilter(s => s.isOfType(Type.POISON)), - [TrainerType.BLACK_BELT]: new TrainerConfig(++t).setHasGenders('Battle Girl', TrainerType.PSYCHIC).setEncounterBgm(TrainerType.ROUGHNECK).setSpecialtyTypes(Type.FIGHTING) + [TrainerType.BLACK_BELT]: new TrainerConfig(++t).setHasGenders('Battle Girl', TrainerType.PSYCHIC).setHasDouble('Fighters').setEncounterBgm(TrainerType.ROUGHNECK).setSpecialtyTypes(Type.FIGHTING) .setPartyTemplates(trainerPartyTemplates.TWO_WEAK_ONE_AVG, trainerPartyTemplates.TWO_WEAK_ONE_AVG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_WEAK_ONE_STRONG, trainerPartyTemplates.THREE_AVG, trainerPartyTemplates.TWO_AVG_ONE_STRONG) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.NIDORAN_F, Species.NIDORAN_M, Species.MACHOP, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.TIMBURR ], @@ -516,16 +560,16 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [ Species.HITMONTOP, Species.INFERNAPE, Species.GALLADE, Species.HAWLUCHA, Species.HAKAMO_O ], [TrainerPoolTier.ULTRA_RARE]: [ Species.KUBFU ] }), - [TrainerType.BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(1.325).setEncounterBgm(TrainerType.POKEFAN).setHasGenders().setDouble() + [TrainerType.BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(1.325).setEncounterBgm(TrainerType.POKEFAN).setHasGenders().setHasDouble('Breeders') .setPartyTemplateFunc(scene => getWavePartyTemplate(scene, trainerPartyTemplates.FOUR_WEAKER, trainerPartyTemplates.FIVE_WEAKER, trainerPartyTemplates.SIX_WEAKER)), - [TrainerType.CLERK]: new TrainerConfig(++t).setHasGenders().setEncounterBgm(TrainerType.CLERK) + [TrainerType.CLERK]: new TrainerConfig(++t).setHasGenders().setHasDouble('Clerks').setEncounterBgm(TrainerType.CLERK) .setPartyTemplates(trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.THREE_WEAK, trainerPartyTemplates.ONE_AVG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_WEAK_ONE_AVG) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.MEOWTH, Species.PSYDUCK, Species.BUDEW, Species.PIDOVE, Species.CINCCINO, Species.LITLEO ], [TrainerPoolTier.UNCOMMON]: [ Species.JIGGLYPUFF, Species.MAGNEMITE, Species.MARILL, Species.COTTONEE, Species.SKIDDO ], [TrainerPoolTier.RARE]: [ Species.BUIZEL, Species.SNEASEL, Species.KLEFKI, Species.INDEEDEE ] }), - [TrainerType.CYCLIST]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setHasGenders().setEncounterBgm(TrainerType.CYCLIST) + [TrainerType.CYCLIST]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setHasGenders().setHasDouble('Cyclists').setEncounterBgm(TrainerType.CYCLIST) .setPartyTemplates(trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.ONE_AVG) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.PICHU, Species.STARLY, Species.TAILLOW, Species.BOLTUND ], @@ -542,7 +586,8 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [ Species.POPPLIO ] }), [TrainerType.DEPOT_AGENT]: new TrainerConfig(++t).setMoneyMultiplier(1.45).setEncounterBgm(TrainerType.CLERK), - [TrainerType.DOCTOR]: new TrainerConfig(++t).setMoneyMultiplier(3).setEncounterBgm(TrainerType.CLERK), + [TrainerType.DOCTOR]: new TrainerConfig(++t).setHasGenders('Nurse', 'lass').setHasDouble('Hospital Staff').setMoneyMultiplier(3).setEncounterBgm(TrainerType.CLERK) + .setSpeciesFilter(s => !!s.getLevelMoves().find(plm => plm[1] === Moves.HEAL_PULSE)), [TrainerType.FISHERMAN]: new TrainerConfig(++t).setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.BACKPACKER).setSpecialtyTypes(Type.WATER) .setPartyTemplates(trainerPartyTemplates.TWO_WEAK_SAME_ONE_AVG, trainerPartyTemplates.ONE_AVG, trainerPartyTemplates.THREE_WEAK_SAME, trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.SIX_WEAKER) .setSpeciesPools({ @@ -561,14 +606,16 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.RARE]: [ Species.TORKOAL, Species.TRAPINCH, Species.BARBOACH, Species.GOLETT, Species.ALOLA_DIGLETT, Species.ALOLA_GEODUDE, Species.GALAR_STUNFISK, Species.PALDEA_WOOPER ], [TrainerPoolTier.SUPER_RARE]: [ Species.MAGBY, Species.LARVITAR ] }), - [TrainerType.HOOLIGANS]: new TrainerConfig(++t).setDouble().setEncounterBgm(TrainerType.ROUGHNECK).setSpeciesFilter(s => s.isOfType(Type.POISON) || s.isOfType(Type.DARK)), + [TrainerType.HOOLIGANS]: new TrainerConfig(++t).setDoubleOnly().setEncounterBgm(TrainerType.ROUGHNECK).setSpeciesFilter(s => s.isOfType(Type.POISON) || s.isOfType(Type.DARK)), [TrainerType.HOOPSTER]: new TrainerConfig(++t).setMoneyMultiplier(1.2).setEncounterBgm(TrainerType.CYCLIST), [TrainerType.INFIELDER]: new TrainerConfig(++t).setMoneyMultiplier(1.2).setEncounterBgm(TrainerType.CYCLIST), [TrainerType.JANITOR]: new TrainerConfig(++t).setMoneyMultiplier(1.1).setEncounterBgm(TrainerType.CLERK), [TrainerType.LINEBACKER]: new TrainerConfig(++t).setMoneyMultiplier(1.2).setEncounterBgm(TrainerType.CYCLIST), [TrainerType.MAID]: new TrainerConfig(++t).setMoneyMultiplier(1.6).setEncounterBgm(TrainerType.RICH), [TrainerType.MUSICIAN]: new TrainerConfig(++t).setEncounterBgm(TrainerType.ROUGHNECK).setSpeciesFilter(s => !!s.getLevelMoves().find(plm => plm[1] === Moves.SING)), - [TrainerType.NURSE]: new TrainerConfig(++t).setMoneyMultiplier(1.8).setEncounterBgm('lass').setSpeciesFilter(s => !!s.getLevelMoves().find(plm => plm[1] === Moves.CHARM) || !!s.getLevelMoves().find(plm => plm[1] === Moves.HEAL_PULSE)), + [TrainerType.HEX_MANIAC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.PSYCHIC) + .setPartyTemplates(trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.ONE_AVG_ONE_STRONG, trainerPartyTemplates.TWO_AVG_SAME_ONE_AVG, trainerPartyTemplates.THREE_AVG, trainerPartyTemplates.TWO_STRONG) + .setSpeciesFilter(s => s.isOfType(Type.GHOST)), [TrainerType.NURSERY_AIDE]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setEncounterBgm('lass'), [TrainerType.OFFICER]: new TrainerConfig(++t).setMoneyMultiplier(1.55).setEncounterBgm(TrainerType.CLERK) .setPartyTemplates(trainerPartyTemplates.ONE_AVG, trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_WEAK_SAME_ONE_AVG) @@ -581,9 +628,9 @@ export const trainerConfigs: TrainerConfigs = { }), [TrainerType.PARASOL_LADY]: new TrainerConfig(++t).setMoneyMultiplier(1.55).setEncounterBgm(TrainerType.PARASOL_LADY).setSpeciesFilter(s => s.isOfType(Type.WATER)), [TrainerType.PILOT]: new TrainerConfig(++t).setEncounterBgm(TrainerType.CLERK).setSpeciesFilter(s => tmSpecies[Moves.FLY].indexOf(s.speciesId) > -1), - [TrainerType.POKEFAN]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setHasGenders().setEncounterBgm(TrainerType.POKEFAN) + [TrainerType.POKEFAN]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setName('Poké Fan').setHasGenders().setHasDouble('Poké Fan Couple').setEncounterBgm(TrainerType.POKEFAN) .setPartyTemplates(trainerPartyTemplates.SIX_WEAKER, trainerPartyTemplates.FOUR_WEAK, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.FOUR_WEAK_SAME, trainerPartyTemplates.FIVE_WEAK, trainerPartyTemplates.SIX_WEAKER_SAME), - [TrainerType.PRESCHOOLER]: new TrainerConfig(++t).setMoneyMultiplier(0.2).setEncounterBgm(TrainerType.YOUNGSTER).setHasGenders(undefined, 'lass') + [TrainerType.PRESCHOOLER]: new TrainerConfig(++t).setMoneyMultiplier(0.2).setEncounterBgm(TrainerType.YOUNGSTER).setHasGenders(undefined, 'lass').setHasDouble('Preschoolers') .setPartyTemplates(trainerPartyTemplates.THREE_WEAK, trainerPartyTemplates.FOUR_WEAKER, trainerPartyTemplates.TWO_WEAK_SAME_ONE_AVG, trainerPartyTemplates.FIVE_WEAKER) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.CATERPIE, Species.PICHU, Species.SANDSHREW, Species.LEDYBA, Species.BUDEW, Species.BURMY, Species.WOOLOO, Species.PAWMI, Species.SMOLIV ], @@ -591,7 +638,7 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.RARE]: [ Species.RALTS, Species.RIOLU, Species.JOLTIK, Species.TANDEMAUS ], [TrainerPoolTier.SUPER_RARE]: [ Species.DARUMAKA, Species.TINKATINK ], }), - [TrainerType.PSYCHIC]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.PSYCHIC) + [TrainerType.PSYCHIC]: new TrainerConfig(++t).setHasGenders().setHasDouble('Psychics').setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.PSYCHIC) .setPartyTemplates(trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_WEAK_SAME_ONE_AVG, trainerPartyTemplates.TWO_WEAK_SAME_TWO_WEAK_SAME, trainerPartyTemplates.ONE_STRONGER) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.ABRA, Species.DROWZEE, Species.RALTS, Species.SPOINK, Species.GOTHITA, Species.SOLOSIS, Species.BLIPBUG, Species.ESPURR, Species.HATENNA ], @@ -599,17 +646,17 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.RARE]: [ Species.ELGYEM, Species.SIGILYPH, Species.BALTOY, Species.GIRAFARIG, Species.MEOWSTIC ], [TrainerPoolTier.SUPER_RARE]: [ Species.BELDUM, Species.ESPEON, Species.STANTLER ], }), - [TrainerType.RANGER]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.BACKPACKER).setHasGenders() + [TrainerType.RANGER]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.BACKPACKER).setHasGenders().setHasDouble('Rangers') .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.PICHU, Species.GROWLITHE, Species.PONYTA, Species.ZIGZAGOON, Species.SEEDOT, Species.BIDOOF, Species.RIOLU, Species.SEWADDLE, Species.SKIDDO, Species.SALANDIT, Species.YAMPER ], [TrainerPoolTier.UNCOMMON]: [ Species.AZURILL, Species.TAUROS, Species.MAREEP, Species.FARFETCHD, Species.TEDDIURSA, Species.SHROOMISH, Species.ELECTRIKE, Species.BUDEW, Species.BUIZEL, Species.MUDBRAY, Species.STUFFUL ], [TrainerPoolTier.RARE]: [ Species.EEVEE, Species.SCYTHER, Species.KANGASKHAN, Species.RALTS, Species.MUNCHLAX, Species.ZORUA, Species.PALDEA_TAUROS, Species.TINKATINK, Species.CYCLIZAR, Species.FLAMIGO ], [TrainerPoolTier.SUPER_RARE]: [ Species.LARVESTA ], }), - [TrainerType.RICH]: new TrainerConfig(++t).setMoneyMultiplier(5).setName('Gentleman').setHasGenders(), - [TrainerType.RICH_KID]: new TrainerConfig(++t).setMoneyMultiplier(3.75).setName('Rich Boy').setHasGenders('Lady').setEncounterBgm(TrainerType.RICH), + [TrainerType.RICH]: new TrainerConfig(++t).setMoneyMultiplier(5).setName('Gentleman').setHasGenders('Madame').setHasDouble('Rich Couple'), + [TrainerType.RICH_KID]: new TrainerConfig(++t).setMoneyMultiplier(3.75).setName('Rich Boy').setHasGenders('Lady').setHasDouble('Rich Kids').setEncounterBgm(TrainerType.RICH), [TrainerType.ROUGHNECK]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.ROUGHNECK).setSpeciesFilter(s => s.isOfType(Type.DARK)), - [TrainerType.SCIENTIST]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(1.7).setEncounterBgm(TrainerType.SCIENTIST) + [TrainerType.SCIENTIST]: new TrainerConfig(++t).setHasGenders().setHasDouble('Scientists').setMoneyMultiplier(1.7).setEncounterBgm(TrainerType.SCIENTIST) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.MAGNEMITE, Species.GRIMER, Species.DROWZEE, Species.VOLTORB, Species.KOFFING ], [TrainerPoolTier.UNCOMMON]: [ Species.BALTOY, Species.BRONZOR, Species.FERROSEED, Species.KLINK, Species.CHARJABUG, Species.BLIPBUG, Species.HELIOPTILE ], @@ -620,28 +667,28 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.SMASHER]: new TrainerConfig(++t).setMoneyMultiplier(1.2).setEncounterBgm(TrainerType.CYCLIST), [TrainerType.SNOW_WORKER]: new TrainerConfig(++t).setName('Worker').setMoneyMultiplier(1.7).setEncounterBgm(TrainerType.CLERK).setSpeciesFilter(s => s.isOfType(Type.ICE) || s.isOfType(Type.STEEL)), [TrainerType.STRIKER]: new TrainerConfig(++t).setMoneyMultiplier(1.2).setEncounterBgm(TrainerType.CYCLIST), - [TrainerType.STUDENT]: new TrainerConfig(++t).setMoneyMultiplier(0.75).setEncounterBgm(TrainerType.YOUNGSTER).setHasGenders(undefined, 'lass') + [TrainerType.STUDENT]: new TrainerConfig(++t).setMoneyMultiplier(0.75).setEncounterBgm(TrainerType.YOUNGSTER).setHasGenders(undefined, 'lass').setHasDouble('Students') .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.ODDISH, Species.EXEGGCUTE, Species.TEDDIURSA, Species.WURMPLE, Species.RALTS, Species.SHROOMISH, Species.FLETCHLING ], [TrainerPoolTier.UNCOMMON]: [ Species.VOLTORB, Species.WHISMUR, Species.MEDITITE, Species.MIME_JR, Species.NYMBLE ], [TrainerPoolTier.RARE]: [ Species.TANGELA, Species.EEVEE, Species.YANMA ], [TrainerPoolTier.SUPER_RARE]: [ Species.TADBULB ] }), - [TrainerType.SWIMMER]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setEncounterBgm(TrainerType.PARASOL_LADY).setHasGenders().setSpecialtyTypes(Type.WATER).setSpeciesFilter(s => s.isOfType(Type.WATER)), - [TrainerType.TWINS]: new TrainerConfig(++t).setDouble().setMoneyMultiplier(0.65).setUseSameSeedForAllMembers() + [TrainerType.SWIMMER]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setEncounterBgm(TrainerType.PARASOL_LADY).setHasGenders().setHasDouble('Swimmers').setSpecialtyTypes(Type.WATER).setSpeciesFilter(s => s.isOfType(Type.WATER)), + [TrainerType.TWINS]: new TrainerConfig(++t).setDoubleOnly().setMoneyMultiplier(0.65).setUseSameSeedForAllMembers() .setPartyTemplateFunc(scene => getWavePartyTemplate(scene, trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_STRONG)) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PLUSLE, Species.VOLBEAT, Species.PACHIRISU, Species.SILCOON, Species.METAPOD, Species.IGGLYBUFF, Species.PETILIL, Species.EEVEE ])) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MINUN, Species.ILLUMISE, Species.EMOLGA, Species.CASCOON, Species.KAKUNA, Species.CLEFFA, Species.COTTONEE, Species.EEVEE ])) + .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MINUN, Species.ILLUMISE, Species.EMOLGA, Species.CASCOON, Species.KAKUNA, Species.CLEFFA, Species.COTTONEE, Species.EEVEE ], TrainerSlot.TRAINER_PARTNER)) .setEncounterBgm(TrainerType.TWINS), - [TrainerType.VETERAN]: new TrainerConfig(++t).setHasGenders().setMoneyMultiplier(2.5).setEncounterBgm(TrainerType.ACE_TRAINER).setSpeciesFilter(s => s.isOfType(Type.DRAGON)), - [TrainerType.WAITER]: new TrainerConfig(++t).setMoneyMultiplier(1.5).setHasGenders('Waitress').setEncounterBgm(TrainerType.CLERK) + [TrainerType.VETERAN]: new TrainerConfig(++t).setHasGenders().setHasDouble('Veteran Couple').setMoneyMultiplier(2.5).setEncounterBgm(TrainerType.ACE_TRAINER).setSpeciesFilter(s => s.isOfType(Type.DRAGON)), + [TrainerType.WAITER]: new TrainerConfig(++t).setHasGenders('Waitress').setHasDouble('Restaurant Staff').setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.CLERK) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.CHATOT, Species.PANSAGE, Species.PANSEAR, Species.PANPOUR, Species.MINCCINO ], [TrainerPoolTier.UNCOMMON]: [ Species.TROPIUS, Species.PETILIL, Species.BOUNSWEET, Species.INDEEDEE ], [TrainerPoolTier.RARE]: [ Species.APPLIN, Species.SINISTEA, Species.POLTCHAGEIST ] }), [TrainerType.WORKER]: new TrainerConfig(++t).setEncounterBgm(TrainerType.CLERK).setMoneyMultiplier(1.7).setSpeciesFilter(s => s.isOfType(Type.ROCK) || s.isOfType(Type.STEEL)), - [TrainerType.YOUNGSTER]: new TrainerConfig(++t).setMoneyMultiplier(0.5).setEncounterBgm(TrainerType.YOUNGSTER).setHasGenders('Lass', 'lass').setPartyTemplates(trainerPartyTemplates.TWO_WEAKER) + [TrainerType.YOUNGSTER]: new TrainerConfig(++t).setMoneyMultiplier(0.5).setEncounterBgm(TrainerType.YOUNGSTER).setHasGenders('Lass', 'lass').setHasDouble('Beginners').setPartyTemplates(trainerPartyTemplates.TWO_WEAKER) .setSpeciesPools( [ Species.CATERPIE, Species.WEEDLE, Species.RATTATA, Species.SENTRET, Species.POOCHYENA, Species.ZIGZAGOON, Species.WURMPLE, Species.BIDOOF, Species.PATRAT, Species.LILLIPUP ] ), @@ -798,7 +845,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ])) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setSpeciesFilter(species => species.baseTotal >= 540) - .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ], p => { + .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ], TrainerSlot.TRAINER, p => { p.setBoss(); p.pokeball = PokeballType.MASTER_BALL; })) @@ -811,7 +858,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ])) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setSpeciesFilter(species => species.baseTotal >= 540) - .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ], p => { + .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.RAYQUAZA ], TrainerSlot.TRAINER, p => { p.setBoss(); p.pokeball = PokeballType.MASTER_BALL; p.formIndex = 1; diff --git a/src/data/trainer-names.ts b/src/data/trainer-names.ts new file mode 100644 index 000000000..3b6626050 --- /dev/null +++ b/src/data/trainer-names.ts @@ -0,0 +1,193 @@ +import { TrainerType } from "./enums/trainer-type"; +import * as Utils from "../utils"; + +class TrainerNameConfig { + public urls: string[]; + public femaleUrls: string[]; + + constructor(type: TrainerType, ...urls: string[]) { + this.urls = urls.length ? urls : [ Utils.toReadableString(TrainerType[type]).replace(/ /g, '_') ]; + } + + hasGenderVariant(...femaleUrls: string[]): TrainerNameConfig { + this.femaleUrls = femaleUrls.length ? femaleUrls : null; + return this; + } +} + +interface TrainerNameConfigs { + [key: integer]: TrainerNameConfig +} + +const trainerNameConfigs: TrainerNameConfigs = { + [TrainerType.ACE_TRAINER]: new TrainerNameConfig(TrainerType.ACE_TRAINER), + [TrainerType.ARTIST]: new TrainerNameConfig(TrainerType.ARTIST), + [TrainerType.BACKERS]: new TrainerNameConfig(TrainerType.BACKERS), + [TrainerType.BACKPACKER]: new TrainerNameConfig(TrainerType.BACKPACKER), + [TrainerType.BAKER]: new TrainerNameConfig(TrainerType.BAKER), + [TrainerType.BEAUTY]: new TrainerNameConfig(TrainerType.BEAUTY), + [TrainerType.BIKER]: new TrainerNameConfig(TrainerType.BIKER), + [TrainerType.BLACK_BELT]: new TrainerNameConfig(TrainerType.BLACK_BELT).hasGenderVariant('Battle_Girl'), + [TrainerType.BREEDER]: new TrainerNameConfig(TrainerType.BREEDER, 'Pokémon_Breeder'), + [TrainerType.CLERK]: new TrainerNameConfig(TrainerType.CLERK), + [TrainerType.CYCLIST]: new TrainerNameConfig(TrainerType.CYCLIST), + [TrainerType.DANCER]: new TrainerNameConfig(TrainerType.DANCER), + [TrainerType.DEPOT_AGENT]: new TrainerNameConfig(TrainerType.DEPOT_AGENT), + [TrainerType.DOCTOR]: new TrainerNameConfig(TrainerType.DOCTOR).hasGenderVariant('Nurse'), + [TrainerType.FISHERMAN]: new TrainerNameConfig(TrainerType.FISHERMAN), + [TrainerType.GUITARIST]: new TrainerNameConfig(TrainerType.GUITARIST), + [TrainerType.HARLEQUIN]: new TrainerNameConfig(TrainerType.HARLEQUIN), + [TrainerType.HIKER]: new TrainerNameConfig(TrainerType.HIKER), + [TrainerType.HOOLIGANS]: new TrainerNameConfig(TrainerType.HOOLIGANS), + [TrainerType.HOOPSTER]: new TrainerNameConfig(TrainerType.HOOPSTER), + [TrainerType.INFIELDER]: new TrainerNameConfig(TrainerType.INFIELDER), + [TrainerType.JANITOR]: new TrainerNameConfig(TrainerType.JANITOR), + [TrainerType.LINEBACKER]: new TrainerNameConfig(TrainerType.LINEBACKER), + [TrainerType.MAID]: new TrainerNameConfig(TrainerType.MAID), + [TrainerType.MUSICIAN]: new TrainerNameConfig(TrainerType.MUSICIAN), + [TrainerType.HEX_MANIAC]: new TrainerNameConfig(TrainerType.HEX_MANIAC), + [TrainerType.NURSERY_AIDE]: new TrainerNameConfig(TrainerType.NURSERY_AIDE), + [TrainerType.OFFICER]: new TrainerNameConfig(TrainerType.OFFICER), + [TrainerType.PARASOL_LADY]: new TrainerNameConfig(TrainerType.PARASOL_LADY), + [TrainerType.PILOT]: new TrainerNameConfig(TrainerType.PILOT), + [TrainerType.POKEFAN]: new TrainerNameConfig(TrainerType.POKEFAN, 'Poké_Fan'), + [TrainerType.PRESCHOOLER]: new TrainerNameConfig(TrainerType.PRESCHOOLER), + [TrainerType.PSYCHIC]: new TrainerNameConfig(TrainerType.PSYCHIC), + [TrainerType.RANGER]: new TrainerNameConfig(TrainerType.RANGER), + [TrainerType.RICH]: new TrainerNameConfig(TrainerType.RICH, 'Gentleman').hasGenderVariant('Madame'), + [TrainerType.RICH_KID]: new TrainerNameConfig(TrainerType.RICH_KID, 'Rich_Boy').hasGenderVariant('Lady'), + [TrainerType.ROUGHNECK]: new TrainerNameConfig(TrainerType.ROUGHNECK), + [TrainerType.SCIENTIST]: new TrainerNameConfig(TrainerType.SCIENTIST), + [TrainerType.SMASHER]: new TrainerNameConfig(TrainerType.SMASHER), + [TrainerType.SNOW_WORKER]: new TrainerNameConfig(TrainerType.SNOW_WORKER, 'Worker'), + [TrainerType.STRIKER]: new TrainerNameConfig(TrainerType.STRIKER), + [TrainerType.STUDENT]: new TrainerNameConfig(TrainerType.STUDENT, 'School_Kid'), + [TrainerType.SWIMMER]: new TrainerNameConfig(TrainerType.SWIMMER), + [TrainerType.TWINS]: new TrainerNameConfig(TrainerType.TWINS), + [TrainerType.VETERAN]: new TrainerNameConfig(TrainerType.VETERAN), + [TrainerType.WAITER]: new TrainerNameConfig(TrainerType.WAITER).hasGenderVariant('Waitress'), + [TrainerType.WORKER]: new TrainerNameConfig(TrainerType.WORKER), + [TrainerType.YOUNGSTER]: new TrainerNameConfig(TrainerType.YOUNGSTER).hasGenderVariant('Lass') +}; + +export const trainerNamePools = { + [TrainerType.ACE_TRAINER]: [["Aaron","Allen","Blake","Brian","Gaven","Jake","Kevin","Mike","Nick","Paul","Ryan","Sean","Darin","Albert","Berke","Clyde","Edgar","George","Leroy","Owen","Parker","Randall","Ruben","Samuel","Vincent","Warren","Wilton","Zane","Alfred","Braxton","Felix","Gerald","Jonathan","Leonel","Marcel","Mitchell","Quincy","Roderick","Colby","Rolando","Yuji","Abel","Anton","Arthur","Cesar","Dalton","Dennis","Ernest","Garrett","Graham","Henry","Isaiah","Jonah","Jose","Keenan","Micah","Omar","Quinn","Rodolfo","Saul","Sergio","Skylar","Stefan","Zachery","Alton","Arabella","Bonita","Cal","Cody","French","Kobe","Paulo","Shaye","Austin","Beckett","Charlie","Corky","David","Dwayne","Elmer","Jesse","Jared","Johan","Jordan","Kipp","Lou","Terry","Tom","Webster","Billy","Doyle","Enzio","Geoff","Grant","Kelsey","Miguel","Pierce","Ray","Santino","Shel","Adelbert","Bence","Emil","Evan","Mathis","Maxim","Neil","Rico","Robbie","Theo","Viktor","Benedict","Cornelius","Hisato","Leopold","Neville","Vito","Chase","Cole","Hiroshi","Jackson","Jim","Kekoa","Makana","Yuki","Elwood","Seth","Alvin","Arjun","Arnold","Cameron","Carl","Carlton","Christopher","Dave","Dax","Dominic","Edmund","Finn","Fred","Garret","Grayson","Jace","Jaxson","Jay","Jirard","Johnson","Kayden","Kite","Louis","Mac","Marty","Percy","Raymond","Ronnie","Satch","Tim","Zach","Conner","Vince","Bedro","Boda","Botan","Daras","Dury","Herton","Rewn","Stum","Tock","Trilo","Berki","Cruik","Dazon","Desid","Dillot","Farfin","Forgon","Hebel","Morfon","Moril","Shadd","Vanhub","Bardo","Carben","Degin","Gorps","Klept","Lask","Malex","Mopar","Niled","Noxon","Teslor","Tetil"],["Beth","Carol","Cybil","Emma","Fran","Gwen","Irene","Jenn","Joyce","Kate","Kelly","Lois","Lola","Megan","Quinn","Reena","Cara","Alexa","Brooke","Caroline","Elaine","Hope","Jennifer","Jody","Julie","Lori","Mary","Michelle","Shannon","Wendy","Alexia","Alicia","Athena","Carolina","Cristin","Darcy","Dianne","Halle","Jazmyn","Katelynn","Keira","Marley","Allyson","Kathleen","Naomi","Alyssa","Ariana","Brandi","Breanna","Brenda","Brenna","Catherine","Clarice","Dana","Deanna","Destiny","Jamie","Jasmin","Kassandra","Laura","Maria","Mariah","Maya","Meagan","Mikayla","Monique","Natasha","Olivia","Sandra","Savannah","Sydney","Moira","Piper","Salma","Allison","Beverly","Cathy","Cheyenne","Clara","Dara","Eileen","Glinda","Junko","Lena","Lucille","Mariana","Olwen","Shanta","Stella","Angi","Belle","Chandra","Cora","Eve","Jacqueline","Jeanne","Juliet","Kathrine","Layla","Lucca","Melina","Miki","Nina","Sable","Shelly","Summer","Trish","Vicki","Alanza","Cordelia","Hilde","Imelda","Michele","Mireille","Claudia","Constance","Harriet","Honor","Melba","Portia","Alexis","Angela","Karla","Lindsey","Tori","Sheri","Jada","Kailee","Amanda","Annie","Kindra","Kyla","Sofia","Yvette","Becky","Flora","Gloria","Buna","Ferda","Lehan","Liqui","Lomen","Neira","Atilo","Detta","Gilly","Gosney","Levens","Moden","Rask","Rateis","Rosno","Tynan","Veron","Zoel","Cida","Dibsin","Dodin","Ebson","Equin","Flostin","Gabsen","Halsion","Hileon","Quelor","Rapeel","Roze","Tensin"]], + [TrainerType.ARTIST]: [["Ismael","William","Horton","Pierre","Zach","Gough","Salvador","Vincent","Duncan"],["Georgia"]], + [TrainerType.BACKERS]: [["Alf & Fred","Hawk & Dar","Joe & Ross","Les & Web","Masa & Yas","Stu & Art"],["Ai & Ciel","Ami & Eira","Cam & Abby","Fey & Sue","Kat & Phae","Kay & Ali","Ava & Aya","Cleo & Rio","May & Mal"]], + [TrainerType.BACKPACKER]: [["Alexander","Carlos","Herman","Jerome","Keane","Kelsey","Kiyo","Michael","Nate","Peter","Sam","Stephen","Talon","Terrance","Toru","Waylon","Boone","Clifford","Ivan","Kendall","Lowell","Randall","Reece","Roland","Shane","Walt","Farid","Heike","Joren","Lane","Roderick","Darnell","Deon","Emory","Graeme","Grayson","Ashley","Mikiko","Kiana","Perdy","Maria","Yuho","Peren","Barbara","Diane","Ruth","Aitor","Alex","Arturo","Asier","Jaime","Jonathan","Julio","Kevin","Kosuke","Lander","Markel","Mateo","Nil","Pau","Samuel"],["Anna","Corin","Elaine","Emi","Jill","Kumiko","Liz","Lois","Lora","Molly","Patty","Ruth","Vicki","Annie","Blossom","Clara","Eileen","Mae","Myra","Rachel","Tami"]], + [TrainerType.BAKER]: ["Chris","Jenn","Lilly"], + [TrainerType.BEAUTY]: ["Cassie","Julia","Olivia","Samantha","Valerie","Victoria","Bridget","Connie","Jessica","Johanna","Melissa","Sheila","Shirley","Tiffany","Namiko","Thalia","Grace","Lola","Lori","Maura","Tamia","Cyndy","Devon","Gabriella","Harley","Lindsay","Nicola","Callie","Charlotte","Kassandra","December","Fleming","Nikola","Aimee","Anais","Brigitte","Cassandra","Andrea","Brittney","Carolyn","Krystal","Alexis","Alice","Aina","Anya","Arianna","Aubrey","Beverly","Camille","Beauty","Evette","Hansol","Haruka","Jill","Jo","Lana","Lois","Lucy","Mai","Nickie","Nicole","Prita","Rose","Shelly","Suzy","Tessa","Anita","Alissa","Rita","Cudsy","Eloff","Miru","Minot","Nevah","Niven","Ogoin"], + [TrainerType.BIKER]: ["Charles","Dwayne","Glenn","Harris","Joel","Riley","Zeke","Alex","Billy","Ernest","Gerald","Hideo","Isaac","Jared","Jaren","Jaxon","Jordy","Lao","Lukas","Malik","Nikolas","Ricardo","Ruben","Virgil","William","Aiden","Dale","Dan","Jacob","Markey","Reese","Teddy","Theron","Jeremy","Morgann","Phillip","Philip","Stanley","Dillon"], + [TrainerType.BLACK_BELT]: [["Kenji","Lao","Lung","Nob","Wai","Yoshi","Atsushi","Daisuke","Hideki","Hitoshi","Kiyo","Koichi","Koji","Yuji","Cristian","Rhett","Takao","Theodore","Zander","Aaron","Hugh","Mike","Nicolas","Shea","Takashi","Adam","Carl","Colby","Darren","David","Davon","Derek","Eddie","Gregory","Griffin","Jarrett","Jeffery","Kendal","Kyle","Luke","Miles","Nathaniel","Philip","Rafael","Ray","Ricky","Sean","Willie","Ander","Manford","Benjamin","Corey","Edward","Grant","Jay","Kendrew","Kentaro","Ryder","Teppei","Thomas","Tyrone","Andrey","Donny","Drago","Gordon","Grigor","Jeriel","Kenneth","Martell","Mathis","Rich","Rocky","Rodrigo","Wesley","Zachery","Alonzo","Cadoc","Gunnar","Igor","Killian","Markus","Ricardo","Yanis","Banting","Clayton","Duane","Earl","Greg","Roy","Terry","Tracy","Walter","Alvaro","Curtis","Francis","Ross","Brice","Cheng","Dudley","Eric","Kano","Masahiro","Randy","Ryuji","Steve","Tadashi","Wong","Yuen","Brian","Carter","Reece","Nick","Yang"],["Cora","Cyndy","Jill","Laura","Sadie","Tessa","Vivian","Aisha","Callie","Danielle","Helene","Jocelyn","Lilith","Paula","Reyna","Helen","Kelsey","Tyler","Amy","Chandra","Hillary","Janie","Lee","Maggie","Mikiko","Miriam","Sharon","Susie","Xiao","Alize","Azra","Brenda","Chalina","Chan","Glinda","Maki","Tia","Tiffany","Wendy","Andrea","Gabrielle","Gerardine","Hailey","Hedvig","Justine","Kinsey","Sigrid","Veronique","Tess"]], + [TrainerType.BREEDER]: [["Isaac","Myles","Salvadore","Allison","Alize","Bethany","Lily","Albert","Kahlil","Eustace","Galen","Owen","Addison","Marcus","Foster","Cory","Glenn","Jay","Wesley","William","Adrian","Bradley","Jaime"],["Lydia","Gabrielle","Jayden","Pat","Veronica","Amber","Jennifer","Kaylee","Adelaide","Brooke","Ethel","April","Irene","Magnolia","Amala","Mercy","Amanda","Ikue","Savannah","Yuka","Chloe","Debra","Denise","Elena"]], + [TrainerType.CLERK]: [["Chaz","Clemens","Doug","Fredric","Ivan","Isaac","Nelson","Wade","Warren","Augustin","Gilligan","Cody","Jeremy","Shane","Dugal","Royce","Ronald"],["Alberta","Ingrid","Katie","Piper","Trisha","Wren","Britney","Lana","Jessica","Kristen","Michelle","Gabrielle"]], + [TrainerType.CYCLIST]: [["Axel","James","John","Ryan","Hector","Jeremiah"],["Kayla","Megan","Nicole","Rachel","Krissa","Adelaide"]], + [TrainerType.DANCER]: ["Brian","Davey","Dirk","Edmond","Mickey","Raymond","Cara","Julia","Maika","Mireille","Ronda","Zoe"], + [TrainerType.DEPOT_AGENT]: ["Josh","Hank","Vincent"], + [TrainerType.DOCTOR]: [["Hank","Jerry","Jules","Logan","Wayne","Braid","Derek","Heath","Julius","Kit","Graham"],["Kirsten","Sachiko","Shery","Carol","Dixie","Mariah"]], + [TrainerType.FISHERMAN]: ["Andre","Arnold","Barney","Chris","Edgar","Henry","Jonah","Justin","Kyle","Martin","Marvin","Ralph","Raymond","Scott","Stephen","Wilton","Tully","Andrew","Barny","Carter","Claude","Dale","Elliot","Eugene","Ivan","Ned","Nolan","Roger","Ronald","Wade","Wayne","Darian","Kai","Chip","Hank","Kaden","Tommy","Tylor","Alec","Brett","Cameron","Cody","Cole","Cory","Erick","George","Joseph","Juan","Kenneth","Luc","Miguel","Travis","Walter","Zachary","Josh","Gideon","Kyler","Liam","Murphy","Bruce","Damon","Devon","Hubert","Jones","Lydon","Mick","Pete","Sean","Sid","Vince","Bucky","Dean","Eustace","Kenzo","Leroy","Mack","Ryder","Ewan","Finn","Murray","Seward","Shad","Wharton","Finley","Fisher","Fisk","River","Sheaffer","Timin","Carl","Ernest","Hal","Herbert","Hisato","Mike","Vernon","Harriet","Marina","Chase"], + [TrainerType.GUITARIST]: ["Clyde","Vincent","Dalton","Kirk","Shawn","Fabian","Fernando","Joseph","Marcos","Arturo","Jerry","Lonnie","Preston","Tony","Anna","Beverly","January","Tina"], + [TrainerType.HARLEQUIN]: ["Charley","Ian","Jack","Kerry","Louis","Pat","Paul","Rick","Anders","Clarence","Gary"], + [TrainerType.HIKER]: ["Anthony","Bailey","Benjamin","Daniel","Erik","Jim","Kenny","Leonard","Michael","Parry","Phillip","Russell","Sidney","Tim","Timothy","Alan","Brice","Clark","Eric","Lenny","Lucas","Mike","Trent","Devan","Eli","Marc","Sawyer","Allen","Daryl","Dudley","Earl","Franklin","Jeremy","Marcos","Nob","Oliver","Wayne","Alexander","Damon","Jonathan","Justin","Kevin","Lorenzo","Louis","Maurice","Nicholas","Reginald","Robert","Theodore","Bruce","Clarke","Devin","Dwight","Edwin","Eoin","Noland","Russel","Andy","Bret","Darrell","Gene","Hardy","Hugh","Jebediah","Jeremiah","Kit","Neil","Terrell","Don","Doug","Hunter","Jared","Jerome","Keith","Manuel","Markus","Otto","Shelby","Stephen","Teppei","Tobias","Wade","Zaiem","Aaron","Alain","Bergin","Bernard","Brent","Corwin","Craig","Delmon","Dunstan","Orestes","Ross","Davian","Calhoun","David","Gabriel","Ryan","Thomas","Travis","Zachary","Anuhea","Barnaby","Claus","Collin","Colson","Dexter","Dillan","Eugine","Farkas","Hisato","Julius","Kenji","Irwin","Lionel","Paul","Richter","Valentino","Donald","Douglas","Kevyn","Angela","Carla","Celia","Daniela","Estela","Fatima","Helena","Leire","Lucia","Luna","Manuela","Mar","Marina","Miyu","Nancy","Nerea","Paula","Rocio","Yanira","Chester"], + [TrainerType.HOOLIGANS]: ["Jim & Cas","Rob & Sal"], + [TrainerType.HOOPSTER]: ["Bobby","John","Lamarcus","Derrick","Nicolas"], + [TrainerType.INFIELDER]: ["Alex","Connor","Todd"], + [TrainerType.JANITOR]: ["Caleb","Geoff","Brady","Felix","Orville","Melvin","Shawn"], + [TrainerType.LINEBACKER]: ["Bob","Dan","Jonah"], + [TrainerType.MAID]: ["Belinda","Sophie","Emily","Elena","Clare","Alica","Tanya","Tammy"], + [TrainerType.MUSICIAN]: ["Boris","Preston","Charles"], + [TrainerType.NURSERY_AIDE]: ["Autumn","Briana","Leah","Miho","Ethel","Hollie","Ilse","June","Kimya","Rosalyn"], + [TrainerType.OFFICER]: ["Dirk","Keith","Alex","Bobby","Caleb","Danny","Dylan","Thomas","Daniel","Jeff","Braven","Dell","Neagle","Haruki","Mitchell","Sheriff","Raymond"], + [TrainerType.PARASOL_LADY]: ["Angelica","Clarissa","Madeline","Akari","Annabell","Kayley","Rachel","Alexa","Sabrina","April","Gwyneth","Laura","Lumi","Mariah","Melita","Nicole","Tihana","Ingrid","Tyra"], + [TrainerType.PILOT]: ["Chase","Leonard","Ted","Elron","Ewing","Flynn","Winslow"], + [TrainerType.POKEFAN]: [["Alex","Allan","Brandon","Carter","Colin","Derek","Jeremy","Joshua","Rex","Robert","Trevor","William","Colton","Miguel","Francisco","Kaleb","Leonard","Boone","Elliot","Jude","Norbert","Corey","Gabe","Baxter"],["Beverly","Georgia","Jaime","Ruth","Isabel","Marissa","Vanessa","Annika","Bethany","Kimberly","Meredith","Rebekah","Eleanor","Darcy","Lydia","Sachiko","Abigail","Agnes","Lydie","Roisin","Tara","Carmen","Janet"]], + [TrainerType.PRESCHOOLER]: [["Billy","Doyle","Evan","Homer","Tully","Albert","Buster","Greg","Ike","Jojo","Tyrone","Adrian","Oliver","Hayden","Hunter","Kaleb","Liam","Dylan"],["Juliet","Mia","Sarah","Wendy","Winter","Chrissy","Eva","Lin","Samantha","Ella","Lily","Natalie","Ailey","Hannah","Malia","Kindra","Nancy"]], + [TrainerType.PSYCHIC]: [["Fidel","Franklin","Gilbert","Greg","Herman","Jared","Mark","Nathan","Norman","Phil","Richard","Rodney","Cameron","Edward","Fritz","Joshua","Preston","Virgil","William","Alvaro","Blake","Cedric","Keenan","Nicholas","Dario","Johan","Lorenzo","Tyron","Bryce","Corbin","Deandre","Elijah","Kody","Landon","Maxwell","Mitchell","Sterling","Eli","Nelson","Vernon","Gaven","Gerard","Low","Micki","Perry","Rudolf","Tommy","Al","Nandor","Tully","Arthur","Emanuel","Franz","Harry","Paschal","Robert","Sayid","Angelo","Anton","Arin","Avery","Danny","Frasier","Harrison","Jaime","Ross","Rui","Vlad","Mason"],["Alexis","Hannah","Jacki","Jaclyn","Kayla","Maura","Samantha","Alix","Brandi","Edie","Macey","Mariella","Marlene","Laura","Rodette","Abigail","Brittney","Chelsey","Daisy","Desiree","Kendra","Lindsey","Rachael","Valencia","Belle","Cybil","Doreen","Dua","Future","Lin","Madhu","Alia","Ena","Joyce","Lynette","Olesia","Sarah"]], + [TrainerType.RANGER]: [["Carlos","Jackson","Sebastian","Gav","Lorenzo","Logan","Nicolas","Trenton","Deshawn","Dwayne","Jeffery","Kyler","Taylor","Alain","Claude","Crofton","Forrest","Harry","Jaden","Keith","Lewis","Miguel","Pedro","Ralph","Richard","Bret","Daryl","Eddie","Johan","Leaf","Louis","Maxwell","Parker","Rick","Steve","Bjorn","Chaise","Dean","Lee","Maurice","Nash","Ralf","Reed","Shinobu","Silas"],["Catherine","Jenna","Sophia","Merdith","Nora","Beth","Chelsea","Katelyn","Madeline","Allison","Ashlee","Felicia","Krista","Annie","Audra","Brenda","Chloris","Eliza","Heidi","Irene","Mary","Mylene","Shanti","Shelly","Thalia","Anja","Briana","Dianna","Elaine","Elle","Hillary","Katie","Lena","Lois","Malory","Melita","Mikiko","Naoko","Serenity","Ambre","Brooke","Clementine","Melina","Petra","Twiggy"]], + [TrainerType.RICH]: [["Alfred","Edward","Gregory","Preston","Thomas","Tucker","Walter","Clifford","Everett","Micah","Nate","Pierre","Terrance","Arthur","Brooks","Emanuel","Lamar","Jeremy","Leonardo","Milton","Frederic","Renaud","Robert","Yan","Daniel","Sheldon","Stonewall","Gerald","Ronald","Smith","Stanley","Reginald","Orson","Wilco","Caden","Glenn"],["Rebecca","Reina","Cassandra","Emilia","Grace","Marian","Elizabeth","Kathleen","Sayuri","Caroline","Judy"]], + [TrainerType.RICH_KID]: [["Garret","Winston","Dawson","Enrique","Jason","Roman","Trey","Liam","Anthony","Brad","Cody","Manuel","Martin","Pierce","Rolan","Keenan","Filbert","Antoin","Cyus","Diek","Dugo","Flitz","Jurek","Lond","Perd","Quint","Basto","Benit","Brot","Denc","Guyit","Marcon","Perc","Puros","Roex","Sainz","Symin","Tark","Venak"],["Anette","Brianna","Cindy","Colleen","Daphne","Elizabeth","Naomi","Sarah","Charlotte","Gillian","Jacki","Lady","Melissa","Celeste","Colette","Elizandra","Isabel","Lynette","Magnolia","Sophie","Lina","Dulcie","Auro","Brin","Caril","Eloos","Gwin","Illa","Kowly","Rima","Ristin","Vesey","Brena","Deasy","Denslon","Kylet","Nemi","Rene","Sanol","Stouner","Sturk","Talmen","Zoila"]], + [TrainerType.ROUGHNECK]: ["Camron","Corey","Gabriel","Isaiah","Jamal","Koji","Luke","Paxton","Raul","Zeek","Kirby","Chance","Dave","Fletcher","Johnny","Reese","Joey","Roughneck","Ricky","Silvester","Martin"], + [TrainerType.SCIENTIST]: [["Jed","Marc","Mitch","Rich","Ross","Beau","Braydon","Connor","Ed","Ivan","Jerry","Jose","Joshua","Parker","Rodney","Taylor","Ted","Travis","Zackery","Darrius","Emilio","Fredrick","Shaun","Stefano","Travon","Daniel","Garett","Gregg","Linden","Lowell","Trenton","Dudley","Luke","Markus","Nathan","Orville","Randall","Ron","Ronald","Simon","Steve","William","Franklin","Clarke","Jacques","Terrance","Ernst","Justus","Ikaika","Jayson","Kyle","Reid","Tyrone","Adam","Albert","Alphonse","Cory","Donnie","Elton","Francis","Gordon","Herbert","Humphrey","Jordan","Julian","Keaton","Levi","Melvin","Murray","West","Craig","Coren","Dubik","Kotan","Lethco","Mante","Mort","Myron","Odlow","Ribek","Roeck","Vogi","Vonder","Zogo","Doimo","Doton","Durel","Hildon","Kukla","Messa","Nanot","Platen","Raburn","Reman","Acrod","Coffy","Elrok","Foss","Hardig","Hombol","Hospel","Kaller","Klots","Krilok","Limar","Loket","Mesak","Morbit","Newin","Orill","Tabor","Tekot"],["Blythe","Chan","Kathrine","Marie","Maria","Naoko","Samantha","Satomi","Shannon","Athena","Caroline","Lumi","Lumina","Marissa","Sonia"]], + [TrainerType.SMASHER]: ["Aspen","Elena","Mari","Amy","Lizzy"], + [TrainerType.SNOW_WORKER]: [["Braden","Brendon","Colin","Conrad","Dillan","Gary","Gerardo","Holden","Jackson","Mason","Quentin","Willy","Noel","Arnold","Brady","Brand","Cairn","Cliff","Don","Eddie","Felix","Filipe","Glenn","Gus","Heath","Matthew","Patton","Rich","Rob","Ryan","Scott","Shelby","Sterling","Tyler","Victor","Zack","Friedrich","Herman","Isaac","Leo","Maynard","Mitchell","Morgann","Nathan","Niel","Pasqual","Paul","Tavarius","Tibor","Dimitri","Narek","Yusif","Frank","Jeff","Vaclav","Ovid","Francis","Keith","Russel","Sangon","Toway","Bomber","Chean","Demit","Hubor","Kebile","Laber","Ordo","Retay","Ronix","Wagel","Dobit","Kaster","Lobel","Releo","Saken","Rustix"],["Georgia","Sandra","Yvonne"]], + [TrainerType.STRIKER]: ["Marco","Roberto","Tony"], + [TrainerType.STUDENT]: [["Alan","Billy","Chad","Danny","Dudley","Jack","Joe","Johnny","Kipp","Nate","Ricky","Tommy","Jerry","Paul","Ted","Chance","Esteban","Forrest","Harrison","Connor","Sherman","Torin","Travis","Al","Carter","Edgar","Jem","Sammy","Shane","Shayne","Alvin","Keston","Neil","Seymour","William","Carson","Clark","Nolan"],["Georgia","Karen","Meiko","Christine","Mackenzie","Tiera","Ann","Gina","Lydia","Marsha","Millie","Sally","Serena","Silvia","Alberta","Cassie","Mara","Rita","Georgie","Meena","Nitzel"]], + [TrainerType.SWIMMER]: [["Berke","Cameron","Charlie","George","Harold","Jerome","Kirk","Mathew","Parker","Randall","Seth","Simon","Tucker","Austin","Barry","Chad","Cody","Darrin","David","Dean","Douglas","Franklin","Gilbert","Herman","Jack","Luis","Matthew","Reed","Richard","Rodney","Roland","Spencer","Stan","Tony","Clarence","Declan","Dominik","Harrison","Kevin","Leonardo","Nolen","Pete","Santiago","Axle","Braden","Finn","Garrett","Mymo","Reece","Samir","Toby","Adrian","Colton","Dillon","Erik","Evan","Francisco","Glenn","Kurt","Oscar","Ricardo","Sam","Sheltin","Troy","Vincent","Wade","Wesley","Duane","Elmo","Esteban","Frankie","Ronald","Tyson","Bart","Matt","Tim","Wright","Jeffery","Kyle","Alessandro","Estaban","Kieran","Ramses","Casey","Dakota","Jared","Kalani","Keoni","Lawrence","Logan","Robert","Roddy","Yasu","Derek","Jacob","Bruce","Clayton"],["Briana","Dawn","Denise","Diana","Elaine","Kara","Kaylee","Lori","Nicole","Nikki","Paula","Susie","Wendy","Alice","Beth","Beverly","Brenda","Dana","Debra","Grace","Jenny","Katie","Laurel","Linda","Missy","Sharon","Tanya","Tara","Tisha","Carlee","Imani","Isabelle","Kyla","Sienna","Abigail","Amara","Anya","Connie","Maria","Melissa","Nora","Shirley","Shania","Tiffany","Aubree","Cassandra","Claire","Crystal","Erica","Gabrielle","Haley","Jessica","Joanna","Lydia","Mallory","Mary","Miranda","Paige","Sophia","Vanessa","Chelan","Debbie","Joy","Kendra","Leona","Mina","Caroline","Joyce","Larissa","Rebecca","Tyra","Dara","Desiree","Kaoru","Ruth","Coral","Genevieve","Isla","Marissa","Romy","Sheryl","Alexandria","Alicia","Chelsea","Jade","Kelsie","Laura","Portia","Shelby","Sara","Tiare","Kyra","Natasha","Layla","Scarlett","Cora"]], + [TrainerType.TWINS]: ["Amy & May","Jo & Zoe","Meg & Peg","Ann & Anne","Lea & Pia","Amy & Liv","Gina & Mia","Miu & Yuki","Tori & Tia","Eli & Anne","Jen & Kira","Joy & Meg","Kiri & Jan","Miu & Mia","Emma & Lil","Liv & Liz","Teri & Tia","Amy & Mimi","Clea & Gil","Day & Dani","Kay & Tia","Tori & Til","Saya & Aya","Emy & Lin","Kumi & Amy","Mayo & May","Ally & Amy","Lia & Lily","Rae & Ula","Sola & Ana","Tara & Val","Faith & Joy","Nana & Nina"], + [TrainerType.VETERAN]: [["Armando","Brenden","Brian","Clayton","Edgar","Emanuel","Grant","Harlan","Terrell","Arlen","Chester","Hugo","Martell","Ray","Shaun","Abraham","Carter","Claude","Jerry","Lucius","Murphy","Rayne","Ron","Sinan","Sterling","Vincent","Zach","Gerard","Gilles","Louis","Timeo","Akira","Don","Eric","Harry","Leon","Roger","Angus","Aristo","Brone","Johnny"],["Julia","Karla","Kim","Sayuri","Tiffany","Cathy","Cecile","Chloris","Denae","Gina","Maya","Oriana","Portia","Rhona","Rosaline","Catrina","Inga","Trisha","Heather","Lynn","Sheri","Alonsa","Ella","Leticia","Kiara"]], + [TrainerType.WAITER]: [["Bert","Clint","Maxwell","Lou"],["Kati","Aurora","Bonita","Flo","Tia","Jan","Olwen","Paget","Paula","Talia"]], + [TrainerType.WORKER]: [["Braden","Brendon","Colin","Conrad","Dillan","Gary","Gerardo","Holden","Jackson","Mason","Quentin","Willy","Noel","Arnold","Brady","Brand","Cairn","Cliff","Don","Eddie","Felix","Filipe","Glenn","Gus","Heath","Matthew","Patton","Rich","Rob","Ryan","Scott","Shelby","Sterling","Tyler","Victor","Zack","Friedrich","Herman","Isaac","Leo","Maynard","Mitchell","Morgann","Nathan","Niel","Pasqual","Paul","Tavarius","Tibor","Dimitri","Narek","Yusif","Frank","Jeff","Vaclav","Ovid","Francis","Keith","Russel","Sangon","Toway","Bomber","Chean","Demit","Hubor","Kebile","Laber","Ordo","Retay","Ronix","Wagel","Dobit","Kaster","Lobel","Releo","Saken","Rustix"],["Georgia","Sandra","Yvonne"]], + [TrainerType.YOUNGSTER]: [["Albert","Gordon","Ian","Jason","Jimmy","Mikey","Owen","Samuel","Warren","Allen","Ben","Billy","Calvin","Dillion","Eddie","Joey","Josh","Neal","Timmy","Tommy","Breyden","Deandre","Demetrius","Dillon","Jaylen","Johnson","Shigenobu","Chad","Cole","Cordell","Dan","Dave","Destin","Nash","Tyler","Yasu","Austin","Dallas","Darius","Donny","Jonathon","Logan","Michael","Oliver","Sebastian","Tristan","Wayne","Norman","Roland","Regis","Abe","Astor","Keita","Kenneth","Kevin","Kyle","Lester","Masao","Nicholas","Parker","Wes","Zachary","Cody","Henley","Jaye","Karl","Kenny","Masahiro","Pedro","Petey","Sinclair","Terrell","Waylon","Aidan","Anthony","David","Jacob","Jayden","Cutler","Ham","Caleb","Kai","Honus","Kenway","Bret","Chris","Cid","Dennis","Easton","Ken","Robby","Ronny","Shawn","Benjamin","Jake","Travis","Adan","Aday","Beltran","Elian","Hernan","Julen","Luka","Roi","Bernie","Dustin","Jonathan","Wyatt"],["Alice","Bridget","Carrie","Connie","Dana","Ellen","Krise","Laura","Linda","Michelle","Shannon","Andrea","Crissy","Janice","Robin","Sally","Tiana","Haley","Ali","Ann","Dalia","Dawn","Iris","Joana","Julia","Kay","Lisa","Megan","Mikaela","Miriam","Paige","Reli","Blythe","Briana","Caroline","Cassidy","Kaitlin","Madeline","Molly","Natalie","Samantha","Sarah","Cathy","Dye","Eri","Eva","Fey","Kara","Lurleen","Maki","Mali","Maya","Miki","Sibyl","Daya","Diana","Flo","Helia","Henrietta","Isabel","Mai","Persephone","Serena","Anna","Charlotte","Elin","Elsa","Lise","Sara","Suzette","Audrey","Emmy","Isabella","Madison","Rika","Rylee","Salla","Ellie","Alexandra","Amy","Lass","Brittany","Chel","Cindy","Dianne","Emily","Emma","Evelyn","Hana","Harleen","Hazel","Jocelyn","Katrina","Kimberly","Lina","Marge","Mila","Mizuki","Rena","Sal","Satoko","Summer","Tomoe","Vicky","Yue","Yumi","Lauren","Rei","Riley","Lois","Nancy","Tammy","Terry"]], + [TrainerType.HEX_MANIAC]: ["Kindra","Patricia","Tammy","Tasha","Valerie","Alaina","Kathleen","Leah","Makie","Sylvia","Anina","Arachna","Carrie","Desdemona","Josette","Luna","Melanie","Osanna","Raziah"], +}; + +function fetchAndPopulateTrainerNames(url: string, parser: DOMParser, trainerNames: Set, femaleTrainerNames: Set, forceFemale: boolean = false) { + return new Promise(resolve => { + fetch(`https://bulbapedia.bulbagarden.net/wiki/${url}_(Trainer_class)`) + .then(response => response.text()) + .then(html => { + console.log(url); + const htmlDoc = parser.parseFromString(html, 'text/html'); + const trainerListHeader = htmlDoc.querySelector('#Trainer_list').parentElement; + const elements = [...trainerListHeader.parentElement.childNodes]; + const startChildIndex = elements.indexOf(trainerListHeader); + const endChildIndex = elements.findIndex(h => h.nodeName === 'H2' && elements.indexOf(h) > startChildIndex); + const tables = elements.filter(t => { + if (t.nodeName !== 'TABLE' || t['className'] !== 'expandable') + return false; + const childIndex = elements.indexOf(t); + return childIndex > startChildIndex && childIndex < endChildIndex; + }).map(t => t as Element); + console.log(url, tables) + for (let table of tables) { + const trainerRows = [...table.querySelectorAll('tr:not(:first-child)')].filter(r => r.children.length === 9); + for (let row of trainerRows) { + const nameCell = row.firstElementChild; + const content = nameCell.innerHTML; + if (content.indexOf(' -1) { + const female = /♀/.test(content); + if (url === 'Twins') + console.log(content) + const nameMatch = />([a-z]+(?: & [a-z]+)?)<\/a>/i.exec(content); + if (nameMatch) + (female || forceFemale ? femaleTrainerNames : trainerNames).add(nameMatch[1].replace('&', '&')); + } + } + } + resolve(); + }); + }); +} + +/*export function scrapeTrainerNames() { + const parser = new DOMParser(); + const trainerTypeNames = {}; + const populateTrainerNamePromises: Promise[] = []; + for (let t of Object.keys(trainerNameConfigs)) { + populateTrainerNamePromises.push(new Promise(resolve => { + const trainerType = t; + trainerTypeNames[trainerType] = []; + + const config = trainerNameConfigs[t] as TrainerNameConfig; + const trainerNames = new Set(); + const femaleTrainerNames = new Set(); + console.log(config.urls, config.femaleUrls) + const trainerClassRequests = config.urls.map(u => fetchAndPopulateTrainerNames(u, parser, trainerNames, femaleTrainerNames)); + if (config.femaleUrls) + trainerClassRequests.push(...config.femaleUrls.map(u => fetchAndPopulateTrainerNames(u, parser, null, femaleTrainerNames, true))); + Promise.all(trainerClassRequests).then(() => { + console.log(trainerNames, femaleTrainerNames) + trainerTypeNames[trainerType] = !femaleTrainerNames.size ? Array.from(trainerNames) : [ Array.from(trainerNames), Array.from(femaleTrainerNames) ]; + resolve(); + }); + })); + } + Promise.all(populateTrainerNamePromises).then(() => { + let output = 'export const trainerNamePools = {'; + Object.keys(trainerTypeNames).forEach(t => { + output += `\n\t[TrainerType.${TrainerType[t]}]: ${JSON.stringify(trainerTypeNames[t])},`; + }); + output += `\n};`; + console.log(output); + }); +}*/ \ No newline at end of file diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 3ffac2504..6390aaed1 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -39,6 +39,7 @@ import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from '@material/material- import { Nature, getNatureStatMultiplier } from '../data/nature'; import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveUsedTrigger, SpeciesFormChangeStatusEffectTrigger } from '../data/pokemon-forms'; import { TerrainType } from '../data/terrain'; +import { TrainerSlot } from '../data/trainer-config'; export enum FieldPosition { CENTER, @@ -2193,16 +2194,16 @@ export class PlayerPokemon extends Pokemon { } export class EnemyPokemon extends Pokemon { - public trainer: boolean; + public trainerSlot: TrainerSlot; public aiType: AiType; public bossSegments: integer; public bossSegmentIndex: integer; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainer: boolean, boss: boolean, dataSource: PokemonData) { + constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource: PokemonData) { super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender, dataSource ? dataSource.shiny : false, null, dataSource ? dataSource.nature : undefined, dataSource); - this.trainer = trainer; + this.trainerSlot = trainerSlot; if (boss) this.setBoss(); @@ -2406,7 +2407,7 @@ export class EnemyPokemon extends Pokemon { } hasTrainer(): boolean { - return this.trainer; + return !!this.trainerSlot; } isBoss(): boolean { diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 9f112c5a3..732fafe94 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -1,30 +1,63 @@ import BattleScene from "../battle-scene"; import { pokemonPrevolutions } from "../data/pokemon-evolutions"; import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; -import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyMemberStrength, TrainerPartyTemplate, TrainerPoolTier, trainerConfigs, trainerPartyTemplates } from "../data/trainer-config"; +import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyMemberStrength, TrainerPartyTemplate, TrainerPoolTier, TrainerSlot, trainerConfigs, trainerPartyTemplates } from "../data/trainer-config"; import { TrainerType } from "../data/enums/trainer-type"; import { EnemyPokemon } from "./pokemon"; import * as Utils from "../utils"; import { PersistentModifier } from "../modifier/modifier"; +import { trainerNamePools } from "../data/trainer-names"; + +export enum TrainerVariant { + DEFAULT, + FEMALE, + DOUBLE +} export default class Trainer extends Phaser.GameObjects.Container { public config: TrainerConfig; - public female: boolean; + public variant: TrainerVariant; public partyTemplateIndex: integer; + public name: string; + public partnerName: string; - constructor(scene: BattleScene, trainerType: TrainerType, female?: boolean, partyTemplateIndex?: integer) { + constructor(scene: BattleScene, trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: integer, name?: string, partnerName?: string) { super(scene, -72, 80); this.config = trainerConfigs.hasOwnProperty(trainerType) ? trainerConfigs[trainerType] : trainerConfigs[TrainerType.ACE_TRAINER]; - this.female = female; + this.variant = variant; this.partyTemplateIndex = Math.min(partyTemplateIndex !== undefined ? partyTemplateIndex : Utils.randSeedWeightedItem(this.config.partyTemplates.map((_, i) => i)), this.config.partyTemplates.length - 1); + 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) { + if (partnerName) + this.partnerName = partnerName; + else + [ this.name, this.partnerName ] = this.name.split(' & '); + } else + this.partnerName = partnerName || Utils.randSeedItem(Array.isArray(namePool[0]) ? namePool[1] : namePool); + } + } + + switch (this.variant) { + case TrainerVariant.FEMALE: + if (!this.config.hasGenders) + variant = TrainerVariant.DEFAULT; + break; + case TrainerVariant.DOUBLE: + if (!this.config.hasDouble) + variant = TrainerVariant.DEFAULT; + break; + } console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]); - const getSprite = (hasShadow?: boolean) => { - const ret = this.scene.addFieldSprite(0, 0, this.getKey()); + const getSprite = (hasShadow?: boolean, forceFemale?: boolean) => { + const ret = this.scene.addFieldSprite(0, 0, this.config.getSpriteKey(variant === TrainerVariant.FEMALE || forceFemale)); ret.setOrigin(0.5, 1); ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow }); return ret; @@ -37,15 +70,45 @@ export default class Trainer extends Phaser.GameObjects.Container { this.add(sprite); this.add(tintSprite); + + if (variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) { + const partnerSprite = getSprite(true, true); + const partnerTintSprite = getSprite(false, true); + + partnerTintSprite.setVisible(false); + + sprite.x = -16; + tintSprite.x = -16; + partnerSprite.x = 16; + partnerTintSprite.x = 16; + + this.add(partnerSprite); + this.add(partnerTintSprite); + } } - getKey(): string { - return this.config.getKey(this.female); + getKey(forceFemale?: boolean): string { + return this.config.getSpriteKey(this.variant === TrainerVariant.FEMALE || forceFemale); } - getName(includeTitle: boolean = false): string { - let name = this.config.getName(this.female); - return includeTitle && this.config.title ? `${this.config.title} ${name}` : name; + getName(trainerSlot: TrainerSlot = TrainerSlot.NONE, includeTitle: boolean = false): string { + let name = this.config.getTitle(trainerSlot, this.variant); + let title = includeTitle && this.config.title ? this.config.title : null; + if (this.name) { + if (includeTitle) + title = name; + if (!trainerSlot) { + name = this.name; + if (this.partnerName) + name = `${name} & ${this.partnerName}`; + } else + name = trainerSlot === TrainerSlot.TRAINER ? this.name : this.partnerName || this.name; + } + return title ? `${title} ${name}` : name; + } + + isDouble(): boolean { + return this.config.doubleOnly || this.variant === TrainerVariant.DOUBLE; } getBattleBgm(): string { @@ -53,19 +116,19 @@ export default class Trainer extends Phaser.GameObjects.Container { } getEncounterBgm(): string { - return !this.female ? this.config.encounterBgm : this.config.femaleEncounterBgm || this.config.encounterBgm; + return !this.variant ? this.config.encounterBgm : (this.variant === TrainerVariant.DOUBLE ? this.config.doubleEncounterBgm : this.config.femaleEncounterBgm) || this.config.encounterBgm; } getEncounterMessages(): string[] { - return !this.female || !this.config.femaleEncounterMessages ? this.config.encounterMessages : this.config.femaleEncounterMessages; + return !this.variant ? this.config.encounterMessages : (this.variant === TrainerVariant.DOUBLE ? this.config.doubleEncounterMessages : this.config.femaleEncounterMessages) || this.config.encounterMessages; } getVictoryMessages(): string[] { - return !this.female || !this.config.femaleVictoryMessages ? this.config.victoryMessages : this.config.femaleVictoryMessages; + return !this.variant ? this.config.victoryMessages : (this.variant === TrainerVariant.DOUBLE ? this.config.doubleVictoryMessages : this.config.femaleVictoryMessages) || this.config.victoryMessages; } getDefeatMessages(): string[] { - return !this.female || !this.config.femaleDefeatMessages ? this.config.defeatMessages : this.config.femaleDefeatMessages; + return !this.variant ? this.config.defeatMessages : (this.variant === TrainerVariant.DOUBLE ? this.config.doubleDefeatMessages : this.config.femaleDefeatMessages) || this.config.defeatMessages; } getPartyTemplate(): TrainerPartyTemplate { @@ -150,7 +213,7 @@ export default class Trainer extends Phaser.GameObjects.Container { ? getPokemonSpecies(battle.enemyParty[offset].species.getSpeciesForLevel(level, false, true, this.config.isBoss)) : this.genNewPartyMemberSpecies(level); - ret = this.scene.addEnemyPokemon(species, level, true); + ret = this.scene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); }, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8)); return ret; @@ -207,9 +270,9 @@ export default class Trainer extends Phaser.GameObjects.Container { return ret; } - getPartyMemberMatchupScores(): [integer, integer][] { + getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE): [integer, integer][] { const party = this.scene.getEnemyParty(); - const nonFaintedPartyMembers = party.slice(this.scene.currentBattle.getBattlerCount()).filter(p => !p.isFainted()); + const nonFaintedPartyMembers = party.slice(this.scene.currentBattle.getBattlerCount()).filter(p => !p.isFainted()).filter(p => !trainerSlot || p.trainerSlot === trainerSlot); const partyMemberScores = nonFaintedPartyMembers.map(p => { const playerField = this.scene.getPlayerField(); let score = 0; @@ -238,7 +301,7 @@ export default class Trainer extends Phaser.GameObjects.Container { return sortedPartyMemberScores; } - getNextSummonIndex(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()): integer { + getNextSummonIndex(trainerSlot: TrainerSlot = TrainerSlot.NONE, partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores(trainerSlot)): integer { const sortedPartyMemberScores = this.getSortedPartyMemberMatchupScores(partyMemberScores); const maxScorePartyMemberIndexes = partyMemberScores.filter(pms => pms[1] === sortedPartyMemberScores[0][1]).map(pms => pms[0]); @@ -274,12 +337,12 @@ export default class Trainer extends Phaser.GameObjects.Container { } loadAssets(): Promise { - return this.config.loadAssets(this.scene, this.female); + return this.config.loadAssets(this.scene, this.variant); } initSprite(): void { - this.getSprite().setTexture(this.getKey()).setFrame(0); - this.getTintSprite().setTexture(this.getKey()).setFrame(0); + this.getSprites().map((sprite, i) => sprite.setTexture(this.getKey(!!i)).setFrame(0)); + this.getTintSprites().map((tintSprite, i) => tintSprite.setTexture(this.getKey(!!i)).setFrame(0)); } playAnim(): void { @@ -288,54 +351,78 @@ export default class Trainer extends Phaser.GameObjects.Container { repeat: 0, startFrame: 0 }; - this.getSprite().play(trainerAnimConfig); - this.getTintSprite().play(trainerAnimConfig); + const sprites = this.getSprites(); + const tintSprites = this.getTintSprites(); + sprites[0].play(trainerAnimConfig); + tintSprites[0].play(trainerAnimConfig); + if (this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) { + const partnerTrainerAnimConfig = { + key: this.getKey(true), + repeat: 0, + startFrame: 0 + }; + sprites[1].play(partnerTrainerAnimConfig); + tintSprites[1].play(partnerTrainerAnimConfig); + } } - getSprite(): Phaser.GameObjects.Sprite { - return this.getAt(0) as Phaser.GameObjects.Sprite; + getSprites(): Phaser.GameObjects.Sprite[] { + const ret: Phaser.GameObjects.Sprite[] = [ + this.getAt(0) + ]; + if (this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) + ret.push(this.getAt(2)); + return ret; } - getTintSprite(): Phaser.GameObjects.Sprite { - return this.getAt(1) as Phaser.GameObjects.Sprite; + getTintSprites(): Phaser.GameObjects.Sprite[] { + const ret: Phaser.GameObjects.Sprite[] = [ + this.getAt(1) + ]; + if (this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) + ret.push(this.getAt(3)); + return ret; } tint(color: number, alpha?: number, duration?: integer, ease?: string): void { - const tintSprite = this.getTintSprite(); - tintSprite.setTintFill(color); - tintSprite.setVisible(true); + const tintSprites = this.getTintSprites(); + tintSprites.map(tintSprite => { + tintSprite.setTintFill(color); + tintSprite.setVisible(true); - if (duration) { - tintSprite.setAlpha(0); + if (duration) { + tintSprite.setAlpha(0); - this.scene.tweens.add({ - targets: tintSprite, - alpha: alpha || 1, - duration: duration, - ease: ease || 'Linear' - }); - } else - tintSprite.setAlpha(alpha); + this.scene.tweens.add({ + targets: tintSprite, + alpha: alpha || 1, + duration: duration, + ease: ease || 'Linear' + }); + } else + tintSprite.setAlpha(alpha); + }); } untint(duration: integer, ease?: string): void { - const tintSprite = this.getTintSprite(); - - if (duration) { - this.scene.tweens.add({ - targets: tintSprite, - alpha: 0, - duration: duration, - ease: ease || 'Linear', - onComplete: () => { - tintSprite.setVisible(false); - tintSprite.setAlpha(1); - } - }); - } else { - tintSprite.setVisible(false); - tintSprite.setAlpha(1); - } + const tintSprites = this.getTintSprites(); + tintSprites.map(tintSprite => { + if (duration) { + this.scene.tweens.add({ + targets: tintSprite, + alpha: 0, + duration: duration, + ease: ease || 'Linear', + onComplete: () => { + tintSprite.setVisible(false); + tintSprite.setAlpha(1); + } + }); + } else { + tintSprite.setVisible(false); + tintSprite.setAlpha(1); + } + }); } } diff --git a/src/phases.ts b/src/phases.ts index b4f176cdd..6094c2bb8 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -37,7 +37,7 @@ import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleSpec } from "./enums/battle-spec"; import { Species } from "./data/enums/species"; import { HealAchv, LevelAchv, MoneyAchv, achvs } from "./system/achv"; -import { trainerConfigs } from "./data/trainer-config"; +import { TrainerSlot, trainerConfigs } from "./data/trainer-config"; import { TrainerType } from "./data/enums/trainer-type"; import { EggHatchPhase } from "./egg-hatch-phase"; import { Egg } from "./data/egg"; @@ -434,7 +434,22 @@ export class BattlePhase extends Phase { super(scene); } - showEnemyTrainer(): void { + showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { + const sprites = this.scene.currentBattle.trainer.getSprites(); + const tintSprites = this.scene.currentBattle.trainer.getTintSprites(); + for (let i = 0; i < sprites.length; i++) { + const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; + [ sprites[i], tintSprites[i] ].map(sprite => { + if (visible) + sprite.x = trainerSlot ? 0 : i ? 16 : -16; + sprite.setVisible(visible); + sprite.clearTint(); + }) + sprites[i].setVisible(visible); + tintSprites[i].setVisible(visible); + sprites[i].clearTint(); + tintSprites[i].clearTint(); + } this.scene.tweens.add({ targets: this.scene.currentBattle.trainer, x: '-=16', @@ -577,7 +592,7 @@ export class EncounterPhase extends BattlePhase { battle.enemyParty[e] = battle.trainer.genPartyMember(e); else { const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); - battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, false, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); + battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) battle.enemyParty[e].ivs = new Array(6).fill(31); this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { @@ -688,7 +703,7 @@ export class EncounterPhase extends BattlePhase { return `${enemyField[0].name} appeared.`; if (this.scene.currentBattle.battleType === BattleType.TRAINER) - return `${this.scene.currentBattle.trainer.getName(true)}\nwould like to battle!`; + return `${this.scene.currentBattle.trainer.getName(TrainerSlot.NONE, true)}\nwould like to battle!`; return enemyField.length === 1 ? `A wild ${enemyField[0].name} appeared!` @@ -1041,7 +1056,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { this.scene.time.delayedCall(750, () => this.summon()); } else { this.scene.pbTrayEnemy.hide(); - this.scene.ui.showText(`${this.scene.currentBattle.trainer.getName(true)} sent out\n${this.getPokemon().name}!`, null, () => this.summon()); + this.scene.ui.showText(`${this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER)} sent out\n${this.getPokemon().name}!`, null, () => this.summon()); } } @@ -1164,9 +1179,9 @@ export class SwitchSummonPhase extends SummonPhase { preSummon(): void { if (!this.player) { if (this.slotIndex === -1) - this.slotIndex = this.scene.currentBattle.trainer.getNextSummonIndex(); + this.slotIndex = this.scene.currentBattle.trainer.getNextSummonIndex(!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); if (this.slotIndex > -1) { - this.showEnemyTrainer(); + this.showEnemyTrainer(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); } } @@ -1187,7 +1202,7 @@ export class SwitchSummonPhase extends SummonPhase { applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, pokemon); - this.scene.ui.showText(this.player ? `Come back, ${pokemon.name}!` : `${this.scene.currentBattle.trainer.getName(true)}\nwithdrew ${pokemon.name}!`); + this.scene.ui.showText(this.player ? `Come back, ${pokemon.name}!` : `${this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER)}\nwithdrew ${pokemon.name}!`); this.scene.playSound('pb_rel'); pokemon.hideInfo(); pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, 'Sine.easeIn'); @@ -1222,7 +1237,7 @@ export class SwitchSummonPhase extends SummonPhase { party[this.slotIndex] = this.lastPokemon; party[this.fieldIndex] = switchedPokemon; const showTextAndSummon = () => { - this.scene.ui.showText(this.player ? `Go! ${switchedPokemon.name}!` : `${this.scene.currentBattle.trainer.getName(true)} sent out\n${this.getPokemon().name}!`); + this.scene.ui.showText(this.player ? `Go! ${switchedPokemon.name}!` : `${this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER)} sent out\n${this.getPokemon().name}!`); this.summon(); }; if (this.player) @@ -1677,7 +1692,7 @@ export class EnemyCommandPhase extends FieldPhase { const sortedPartyMemberScores = trainer.getSortedPartyMemberMatchupScores(partyMemberScores); if (sortedPartyMemberScores[0][1] >= matchupScore * (trainer.config.isBoss ? 2 : 3)) { - const index = trainer.getNextSummonIndex(partyMemberScores); + const index = trainer.getNextSummonIndex(undefined, partyMemberScores); this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = { command: Command.POKEMON, cursor: index, args: [ false ] }; @@ -2804,7 +2819,7 @@ export class FaintPhase extends PokemonPhase { } else { this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField()).length; + const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length; if (hasReservePartyMember) this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false)); } @@ -3000,7 +3015,7 @@ export class TrainerVictoryPhase extends BattlePhase { this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.VOUCHER)); } - this.scene.ui.showText(`You defeated\n${this.scene.currentBattle.trainer.getName(true)}!`, null, () => { + this.scene.ui.showText(`You defeated\n${this.scene.currentBattle.trainer.getName(TrainerSlot.NONE, true)}!`, null, () => { const victoryMessages = this.scene.currentBattle.trainer.getVictoryMessages(); const showMessage = () => { let message: string; diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index bd849cf85..f51d75412 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -346,9 +346,9 @@ export default class SpritePipeline extends FieldSpritePipeline { position[0] += field.x / field.scale; position[1] += field.y / field.scale; } - position[0] += -(sprite.width - (sprite.frame.width)) / 2 + sprite.frame.x; + position[0] += -(sprite.width - (sprite.frame.width)) / 2 + sprite.frame.x + (sprite.x - field.x); if (sprite.originY === 0.5) - position[1] += (sprite.height / 2) * ((isEntityObj ? sprite.parentContainer : sprite).scale - 1); + position[1] += (sprite.height / 2) * ((isEntityObj ? sprite.parentContainer : sprite).scale - 1) + (sprite.y - field.y); this.set1f('teraTime', (this.game.getTime() % 500000) / 500000); this.set3fv('teraColor', teraColor.map(c => c / 255)); this.set1i('hasShadow', hasShadow ? 1 : 0); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 9d2eb3f92..74fcdbff2 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -26,6 +26,7 @@ import { Tutorial } from "../tutorial"; import { Moves } from "../data/enums/moves"; import { speciesEggMoves } from "../data/egg-moves"; import { allMoves } from "../data/move"; +import { TrainerVariant } from "../field/trainer"; const saveKey = 'x0i2O7WRiANTqPmZ'; // Temporary; secure encryption is not yet necessary @@ -575,13 +576,14 @@ export class GameData { scene.updateScoreText(); const battleType = sessionData.battleType || 0; - const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfigs[sessionData.trainer.trainerType].isDouble : sessionData.enemyParty.length > 1); + const trainerConfig = trainerConfigs[sessionData.trainer.trainerType]; + const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig.doubleOnly || sessionData.trainer.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1); battle.enemyLevels = sessionData.enemyParty.map(p => p.level); scene.newArena(sessionData.arena.biome, true); sessionData.enemyParty.forEach((enemyData, e) => { - const enemyPokemon = enemyData.toPokemon(scene, battleType) as EnemyPokemon; + const enemyPokemon = enemyData.toPokemon(scene, battleType, e) as EnemyPokemon; battle.enemyParty[e] = enemyPokemon; if (battleType === BattleType.WILD) battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 4b20f9b6b..f37ae2eab 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -8,6 +8,7 @@ import { getPokemonSpecies } from "../data/pokemon-species"; import { Species } from "../data/enums/species"; import { Status } from "../data/status-effect"; import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon"; +import { TrainerSlot } from "../data/trainer-config"; export default class PokemonData { public id: integer; @@ -104,10 +105,10 @@ export default class PokemonData { } } - toPokemon(scene: BattleScene, battleType?: BattleType): Pokemon { + toPokemon(scene: BattleScene, battleType?: BattleType, partyMemberIndex: integer = 0): Pokemon { const species = getPokemonSpecies(this.species); if (this.player) return scene.addPlayerPokemon(species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.ivs, this.nature, this); - return scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER, this.boss, this); + return scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, this); } } \ No newline at end of file diff --git a/src/system/trainer-data.ts b/src/system/trainer-data.ts index 07e9c846a..752979d5d 100644 --- a/src/system/trainer-data.ts +++ b/src/system/trainer-data.ts @@ -1,20 +1,24 @@ import BattleScene from "../battle-scene"; import { TrainerType } from "../data/enums/trainer-type"; -import Trainer from "../field/trainer"; +import Trainer, { TrainerVariant } from "../field/trainer"; export default class TrainerData { public trainerType: TrainerType; - public female: boolean; + public variant: TrainerVariant; public partyTemplateIndex: integer; + public name: string; + public partnerName: string; constructor(source: Trainer | any) { const sourceTrainer = source instanceof Trainer ? source as Trainer : null; this.trainerType = sourceTrainer ? sourceTrainer.config.trainerType : source.trainerType; - this.female = source.female; + this.variant = source.hasOwnProperty('variant') ? source.variant : source.female ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT; this.partyTemplateIndex = source.partyMemberTemplateIndex; + this.name = source.name; + this.partnerName = source.partnerName; } toTrainer(scene: BattleScene): Trainer { - return new Trainer(scene, this.trainerType, this.female, this.partyTemplateIndex); + return new Trainer(scene, this.trainerType, this.variant, this.partyTemplateIndex); } } \ No newline at end of file diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 5ad7f798d..6ee111f34 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -10,6 +10,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { private levelUpStatsContainer: Phaser.GameObjects.Container; private levelUpStatsIncrContent: Phaser.GameObjects.Text; private levelUpStatsValuesContent: Phaser.GameObjects.Text; + private nameBox: Phaser.GameObjects.NineSlice; private nameText: Phaser.GameObjects.Text; public bg: Phaser.GameObjects.Image; @@ -71,12 +72,12 @@ export default class BattleMessageUiHandler extends MessageUiHandler { this.nameBoxContainer = this.scene.add.container(0, -16); this.nameBoxContainer.setVisible(false); - const nameBox = this.scene.add.nineslice(0, 0, 'namebox', null, 72, 16, 8, 8, 5, 5); - nameBox.setOrigin(0, 0); + this.nameBox = this.scene.add.nineslice(0, 0, 'namebox', null, 72, 16, 8, 8, 5, 5); + this.nameBox.setOrigin(0, 0); this.nameText = addTextObject(this.scene, 8, 0, 'Rival', TextStyle.MESSAGE, { maxLines: 1 }); - this.nameBoxContainer.add(nameBox); + this.nameBoxContainer.add(this.nameBox); this.nameBoxContainer.add(this.nameText); messageContainer.add(this.nameBoxContainer); @@ -229,6 +230,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { showNameText(name: string): void { this.nameBoxContainer.setVisible(true); this.nameText.setText(name); + this.nameBox.width = this.nameText.displayWidth + 16; } hideNameText(): void {