2023-10-07 16:08:33 -04:00
|
|
|
import BattleScene from "./battle-scene";
|
2023-12-07 23:10:45 -05:00
|
|
|
import { pokemonPrevolutions } from "./data/pokemon-evolutions";
|
2023-10-09 20:20:02 -04:00
|
|
|
import PokemonSpecies, { getPokemonSpecies } from "./data/pokemon-species";
|
2024-01-13 12:24:24 -05:00
|
|
|
import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyMemberStrength, TrainerPartyTemplate, TrainerPoolTier, trainerConfigs, trainerPartyTemplates } from "./data/trainer-config";
|
|
|
|
import { TrainerType } from "./data/enums/trainer-type";
|
2023-10-18 18:01:15 -04:00
|
|
|
import { EnemyPokemon } from "./pokemon";
|
2023-10-07 16:08:33 -04:00
|
|
|
import * as Utils from "./utils";
|
|
|
|
|
|
|
|
export default class Trainer extends Phaser.GameObjects.Container {
|
|
|
|
public config: TrainerConfig;
|
|
|
|
public female: boolean;
|
2023-10-18 18:01:15 -04:00
|
|
|
public partyTemplateIndex: integer;
|
2023-10-07 16:08:33 -04:00
|
|
|
|
2023-10-18 18:01:15 -04:00
|
|
|
constructor(scene: BattleScene, trainerType: TrainerType, female?: boolean, partyTemplateIndex?: integer) {
|
2023-10-07 16:08:33 -04:00
|
|
|
super(scene, -72, 80);
|
2023-12-14 00:41:35 -05:00
|
|
|
this.config = trainerConfigs.hasOwnProperty(trainerType)
|
|
|
|
? trainerConfigs[trainerType]
|
|
|
|
: trainerConfigs[TrainerType.ACE_TRAINER];
|
2023-10-07 16:08:33 -04:00
|
|
|
this.female = female;
|
2023-11-10 16:41:02 -05:00
|
|
|
this.partyTemplateIndex = Math.min(partyTemplateIndex !== undefined ? partyTemplateIndex : Phaser.Math.RND.weightedPick(this.config.partyTemplates.map((_, i) => i)),
|
|
|
|
this.config.partyTemplates.length - 1);
|
|
|
|
|
|
|
|
// TODO: Remove when Phaser weightedPick bug is fixed
|
|
|
|
if (isNaN(this.partyTemplateIndex))
|
|
|
|
this.partyTemplateIndex = this.config.partyTemplates.length - 1;
|
2023-10-18 18:01:15 -04:00
|
|
|
|
|
|
|
console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]);
|
2023-10-07 16:08:33 -04:00
|
|
|
|
|
|
|
const getSprite = (hasShadow?: boolean) => {
|
2023-12-29 21:04:40 -05:00
|
|
|
const ret = this.scene.addFieldSprite(0, 0, this.getKey());
|
2023-10-07 16:08:33 -04:00
|
|
|
ret.setOrigin(0.5, 1);
|
|
|
|
ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow });
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
|
|
|
const sprite = getSprite(true);
|
|
|
|
const tintSprite = getSprite();
|
|
|
|
|
|
|
|
tintSprite.setVisible(false);
|
|
|
|
|
|
|
|
this.add(sprite);
|
|
|
|
this.add(tintSprite);
|
|
|
|
}
|
|
|
|
|
|
|
|
getKey(): string {
|
|
|
|
return this.config.getKey(this.female);
|
|
|
|
}
|
|
|
|
|
2024-02-05 23:05:56 -05:00
|
|
|
getName(includeTitle: boolean = false): string {
|
|
|
|
let name = this.config.getName(this.female);
|
2024-02-05 23:56:21 -05:00
|
|
|
return includeTitle && this.config.title ? `${this.config.title} ${name}` : name;
|
2023-10-09 20:20:02 -04:00
|
|
|
}
|
|
|
|
|
2023-10-18 18:01:15 -04:00
|
|
|
getBattleBgm(): string {
|
|
|
|
return this.config.battleBgm;
|
|
|
|
}
|
|
|
|
|
|
|
|
getEncounterBgm(): string {
|
|
|
|
return !this.female ? this.config.encounterBgm : this.config.femaleEncounterBgm || this.config.encounterBgm;
|
|
|
|
}
|
|
|
|
|
2024-02-14 14:41:39 -05:00
|
|
|
getEncounterMessages(): string[] {
|
|
|
|
return !this.female ? this.config.encounterMessages : this.config.femaleEncounterMessages;
|
|
|
|
}
|
|
|
|
|
|
|
|
getVictoryMessages(): string[] {
|
|
|
|
return !this.female ? this.config.victoryMessages : this.config.femaleVictoryMessages;
|
|
|
|
}
|
|
|
|
|
|
|
|
getDefeatMessages(): string[] {
|
|
|
|
return !this.female ? this.config.defeatMessages : this.config.femaleDefeatMessages;
|
|
|
|
}
|
|
|
|
|
2023-10-18 18:01:15 -04:00
|
|
|
getPartyTemplate(): TrainerPartyTemplate {
|
2023-10-20 11:38:41 -04:00
|
|
|
if (this.config.partyTemplateFunc)
|
|
|
|
return this.config.partyTemplateFunc(this.scene);
|
2023-10-18 18:01:15 -04:00
|
|
|
return this.config.partyTemplates[this.partyTemplateIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
getPartyLevels(waveIndex: integer): integer[] {
|
|
|
|
const ret = [];
|
|
|
|
const partyTemplate = this.getPartyTemplate();
|
|
|
|
|
|
|
|
let baseLevel = 1 + waveIndex / 2 + Math.pow(waveIndex / 25, 2);
|
|
|
|
|
|
|
|
for (let i = 0; i < partyTemplate.size; i++) {
|
|
|
|
let multiplier = 1;
|
|
|
|
|
|
|
|
const strength = partyTemplate.getStrength(i)
|
|
|
|
|
|
|
|
switch (strength) {
|
2024-01-09 00:06:11 -05:00
|
|
|
case TrainerPartyMemberStrength.WEAKEST:
|
|
|
|
multiplier = 0.85;
|
|
|
|
break;
|
2023-10-18 18:01:15 -04:00
|
|
|
case TrainerPartyMemberStrength.WEAKER:
|
|
|
|
multiplier = 0.95;
|
|
|
|
break;
|
|
|
|
case TrainerPartyMemberStrength.WEAK:
|
|
|
|
multiplier = 1.0;
|
|
|
|
break;
|
|
|
|
case TrainerPartyMemberStrength.AVERAGE:
|
|
|
|
multiplier = 1.1;
|
|
|
|
break;
|
|
|
|
case TrainerPartyMemberStrength.STRONG:
|
|
|
|
multiplier = 1.2;
|
|
|
|
break;
|
|
|
|
case TrainerPartyMemberStrength.STRONGER:
|
|
|
|
multiplier = 1.25;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let level = Math.ceil(baseLevel * multiplier);
|
|
|
|
if (strength < TrainerPartyMemberStrength.STRONG) {
|
|
|
|
const minLevel = Math.ceil(baseLevel * 1.2) - Math.floor(waveIndex / 25);
|
|
|
|
if (level < minLevel)
|
|
|
|
level = minLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.push(level);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
genPartyMember(index: integer): EnemyPokemon {
|
2023-10-09 20:20:02 -04:00
|
|
|
const battle = this.scene.currentBattle;
|
2023-10-18 18:01:15 -04:00
|
|
|
const level = battle.enemyLevels[index];
|
|
|
|
|
|
|
|
let ret: EnemyPokemon;
|
|
|
|
|
|
|
|
this.scene.executeWithSeedOffset(() => {
|
2023-10-20 11:38:41 -04:00
|
|
|
const template = this.getPartyTemplate();
|
|
|
|
|
2023-10-18 18:01:15 -04:00
|
|
|
if (this.config.partyMemberFuncs.hasOwnProperty(index)) {
|
|
|
|
ret = this.config.partyMemberFuncs[index](this.scene, level);
|
|
|
|
return;
|
|
|
|
}
|
2024-01-04 19:37:07 -05:00
|
|
|
if (this.config.partyMemberFuncs.hasOwnProperty(index - template.size)) {
|
|
|
|
ret = this.config.partyMemberFuncs[index - template.size](this.scene, level);
|
2023-10-20 11:38:41 -04:00
|
|
|
return;
|
|
|
|
}
|
2023-10-18 18:01:15 -04:00
|
|
|
|
|
|
|
let offset = 0;
|
|
|
|
|
|
|
|
if (template instanceof TrainerPartyCompoundTemplate) {
|
|
|
|
for (let innerTemplate of template.templates) {
|
|
|
|
if (offset + innerTemplate.size > index)
|
|
|
|
break;
|
|
|
|
offset += innerTemplate.size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const species = template.isSameSpecies(index) && index > offset
|
2024-01-01 15:27:47 -05:00
|
|
|
? getPokemonSpecies(battle.enemyParty[offset].species.getSpeciesForLevel(level, false, true, this.config.isBoss))
|
2023-10-18 18:01:15 -04:00
|
|
|
: this.genNewPartyMemberSpecies(level);
|
|
|
|
|
2024-01-07 23:17:24 -05:00
|
|
|
ret = this.scene.addEnemyPokemon(species, level, true);
|
2023-12-26 12:29:18 -05:00
|
|
|
}, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8));
|
2023-10-18 18:01:15 -04:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
genNewPartyMemberSpecies(level: integer, attempt?: integer): PokemonSpecies {
|
|
|
|
const battle = this.scene.currentBattle;
|
|
|
|
const template = this.getPartyTemplate();
|
|
|
|
|
2023-12-07 23:10:45 -05:00
|
|
|
let species: PokemonSpecies;
|
2023-10-18 18:01:15 -04:00
|
|
|
if (this.config.speciesPools) {
|
|
|
|
const tierValue = Utils.randSeedInt(512);
|
|
|
|
let tier = tierValue >= 156 ? TrainerPoolTier.COMMON : tierValue >= 32 ? TrainerPoolTier.UNCOMMON : tierValue >= 6 ? TrainerPoolTier.RARE : tierValue >= 1 ? TrainerPoolTier.SUPER_RARE : TrainerPoolTier.ULTRA_RARE
|
|
|
|
console.log(TrainerPoolTier[tier]);
|
2023-10-20 19:07:47 -04:00
|
|
|
while (!this.config.speciesPools.hasOwnProperty(tier) || !this.config.speciesPools[tier].length) {
|
2023-10-18 18:01:15 -04:00
|
|
|
console.log(`Downgraded trainer Pokemon rarity tier from ${TrainerPoolTier[tier]} to ${TrainerPoolTier[tier - 1]}`);
|
|
|
|
tier--;
|
|
|
|
}
|
|
|
|
const tierPool = this.config.speciesPools[tier];
|
2023-12-07 23:10:45 -05:00
|
|
|
species = getPokemonSpecies(Phaser.Math.RND.pick(tierPool));
|
2023-10-18 18:01:15 -04:00
|
|
|
} else
|
2023-12-07 23:10:45 -05:00
|
|
|
species = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter);
|
2023-10-18 18:01:15 -04:00
|
|
|
|
2024-01-01 15:27:47 -05:00
|
|
|
let ret = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, this.config.isBoss));
|
2023-12-07 23:10:45 -05:00
|
|
|
let retry = false;
|
|
|
|
|
|
|
|
console.log(ret.getName());
|
|
|
|
|
|
|
|
if (pokemonPrevolutions.hasOwnProperty(species.speciesId) && ret.speciesId !== species.speciesId)
|
|
|
|
retry = true;
|
|
|
|
else if (template.isBalanced(battle.enemyParty.length)) {
|
2023-10-18 18:01:15 -04:00
|
|
|
const partyMemberTypes = battle.enemyParty.map(p => p.getTypes()).flat();
|
2023-12-07 23:10:45 -05:00
|
|
|
if (partyMemberTypes.indexOf(ret.type1) > -1 || (ret.type2 !== null && partyMemberTypes.indexOf(ret.type2) > -1))
|
|
|
|
retry = true;
|
|
|
|
}
|
|
|
|
|
2023-12-30 23:31:26 -05:00
|
|
|
if (!retry && this.config.specialtyTypes.length && !this.config.specialtyTypes.find(t => ret.isOfType(t))) {
|
|
|
|
retry = true;
|
|
|
|
console.log('Attempting reroll of species evolution to fit specialty type...');
|
|
|
|
let evoAttempt = 0;
|
|
|
|
while (retry && evoAttempt++ < 10) {
|
2024-01-01 15:27:47 -05:00
|
|
|
ret = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, this.config.isBoss));
|
2023-12-30 23:31:26 -05:00
|
|
|
console.log(ret.name);
|
|
|
|
if (this.config.specialtyTypes.find(t => ret.isOfType(t)))
|
|
|
|
retry = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-07 23:10:45 -05:00
|
|
|
if (retry && (attempt || 0) < 10) {
|
|
|
|
console.log('Rerolling party member...')
|
|
|
|
ret = this.genNewPartyMemberSpecies(level, (attempt || 0) + 1);
|
2023-10-09 20:20:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2023-10-07 16:08:33 -04:00
|
|
|
}
|
|
|
|
|
2024-01-15 00:20:26 -05:00
|
|
|
getPartyMemberMatchupScores(): [integer, integer][] {
|
2023-10-07 16:08:33 -04:00
|
|
|
const party = this.scene.getEnemyParty();
|
|
|
|
const nonFaintedPartyMembers = party.slice(this.scene.currentBattle.getBattlerCount()).filter(p => !p.isFainted());
|
|
|
|
const partyMemberScores = nonFaintedPartyMembers.map(p => {
|
|
|
|
const playerField = this.scene.getPlayerField();
|
|
|
|
let score = 0;
|
2024-01-15 00:20:26 -05:00
|
|
|
let ret: [integer, integer];
|
2023-10-18 18:01:15 -04:00
|
|
|
for (let playerPokemon of playerField) {
|
2023-10-07 16:08:33 -04:00
|
|
|
score += p.getMatchupScore(playerPokemon);
|
2023-10-18 18:01:15 -04:00
|
|
|
if (playerPokemon.species.legendary)
|
|
|
|
score /= 2;
|
|
|
|
}
|
2023-10-07 16:08:33 -04:00
|
|
|
score /= playerField.length;
|
2024-01-15 00:20:26 -05:00
|
|
|
ret = [ party.indexOf(p), score ];
|
|
|
|
return ret;
|
2023-10-07 16:08:33 -04:00
|
|
|
});
|
|
|
|
|
2024-01-15 00:20:26 -05:00
|
|
|
return partyMemberScores;
|
|
|
|
}
|
|
|
|
|
|
|
|
getSortedPartyMemberMatchupScores(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()) {
|
2023-10-07 16:08:33 -04:00
|
|
|
const sortedPartyMemberScores = partyMemberScores.slice(0);
|
|
|
|
sortedPartyMemberScores.sort((a, b) => {
|
|
|
|
const scoreA = a[1];
|
|
|
|
const scoreB = b[1];
|
|
|
|
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
|
|
|
|
});
|
|
|
|
|
2024-01-15 00:20:26 -05:00
|
|
|
return sortedPartyMemberScores;
|
|
|
|
}
|
|
|
|
|
|
|
|
getNextSummonIndex(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()): integer {
|
|
|
|
const sortedPartyMemberScores = this.getSortedPartyMemberMatchupScores(partyMemberScores);
|
|
|
|
|
2023-10-07 16:08:33 -04:00
|
|
|
const maxScorePartyMemberIndexes = partyMemberScores.filter(pms => pms[1] === sortedPartyMemberScores[0][1]).map(pms => pms[0]);
|
2024-01-15 00:20:26 -05:00
|
|
|
|
|
|
|
if (maxScorePartyMemberIndexes.length > 1) {
|
|
|
|
let rand: integer;
|
|
|
|
this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2);
|
|
|
|
return maxScorePartyMemberIndexes[rand];
|
|
|
|
}
|
|
|
|
|
|
|
|
return maxScorePartyMemberIndexes[0];
|
2023-10-07 16:08:33 -04:00
|
|
|
}
|
2023-10-24 10:05:07 -04:00
|
|
|
|
|
|
|
getPartyMemberModifierChanceMultiplier(index: integer): number {
|
|
|
|
switch (this.getPartyTemplate().getStrength(index)) {
|
|
|
|
case TrainerPartyMemberStrength.WEAKER:
|
|
|
|
return 0.75;
|
|
|
|
case TrainerPartyMemberStrength.WEAK:
|
|
|
|
return 0.675;
|
|
|
|
case TrainerPartyMemberStrength.AVERAGE:
|
|
|
|
return 0.5625;
|
|
|
|
case TrainerPartyMemberStrength.STRONG:
|
|
|
|
return 0.45;
|
|
|
|
case TrainerPartyMemberStrength.STRONGER:
|
|
|
|
return 0.375;
|
|
|
|
}
|
|
|
|
}
|
2023-10-07 16:08:33 -04:00
|
|
|
|
|
|
|
loadAssets(): Promise<void> {
|
|
|
|
return this.config.loadAssets(this.scene, this.female);
|
|
|
|
}
|
|
|
|
|
2023-10-18 18:01:15 -04:00
|
|
|
initSprite(): void {
|
2023-10-20 11:38:41 -04:00
|
|
|
this.getSprite().setTexture(this.getKey()).setFrame(0);
|
|
|
|
this.getTintSprite().setTexture(this.getKey()).setFrame(0);
|
2023-10-18 18:01:15 -04:00
|
|
|
}
|
|
|
|
|
2023-10-07 16:08:33 -04:00
|
|
|
playAnim(): void {
|
|
|
|
const trainerAnimConfig = {
|
2023-10-18 18:01:15 -04:00
|
|
|
key: this.getKey(),
|
2023-10-20 11:38:41 -04:00
|
|
|
repeat: 0,
|
|
|
|
startFrame: 0
|
2023-10-07 16:08:33 -04:00
|
|
|
};
|
|
|
|
this.getSprite().play(trainerAnimConfig);
|
|
|
|
this.getTintSprite().play(trainerAnimConfig);
|
|
|
|
}
|
|
|
|
|
|
|
|
getSprite(): Phaser.GameObjects.Sprite {
|
|
|
|
return this.getAt(0) as Phaser.GameObjects.Sprite;
|
|
|
|
}
|
|
|
|
|
|
|
|
getTintSprite(): Phaser.GameObjects.Sprite {
|
|
|
|
return this.getAt(1) as Phaser.GameObjects.Sprite;
|
|
|
|
}
|
|
|
|
|
|
|
|
tint(color: number, alpha?: number, duration?: integer, ease?: string): void {
|
|
|
|
const tintSprite = this.getTintSprite();
|
|
|
|
tintSprite.setTintFill(color);
|
|
|
|
tintSprite.setVisible(true);
|
|
|
|
|
|
|
|
if (duration) {
|
|
|
|
tintSprite.setAlpha(0);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default interface Trainer {
|
|
|
|
scene: BattleScene
|
|
|
|
}
|