[Balance] Make sure trainers are using fully evolved Pokémon by gym 3 (#3499)

* Make sure trainers are using fully evolved Pokemon by gym 3

* Expand comment info

Co-authored-by: Mumble <kimjoanne@protonmail.com>

* Implement suggestions

* Update `getPokemonSpecies()` to throw an error if passed `undefined`

---------

Co-authored-by: Mumble <kimjoanne@protonmail.com>
This commit is contained in:
NightKev 2024-08-26 14:05:16 -07:00 committed by GitHub
parent f5757f0a3a
commit d234466d61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 39 additions and 28 deletions

View File

@ -49,7 +49,8 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[]
const costSpecies = Object.keys(speciesStarters) const costSpecies = Object.keys(speciesStarters)
.map(s => parseInt(s) as Species) .map(s => parseInt(s) as Species)
.filter(s => speciesStarters[s] === cost); .filter(s => speciesStarters[s] === cost);
const starterSpecies = getPokemonSpecies(getPokemonSpecies(Utils.randSeedItem(costSpecies)).getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER)); const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies));
const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER));
starters.push(getDailyRunStarter(scene, starterSpecies, startingLevel)); starters.push(getDailyRunStarter(scene, starterSpecies, startingLevel));
} }
}, 0, seed); }, 0, seed);

View File

@ -1,24 +1,21 @@
import { Localizable } from "#app/interfaces/locales";
import { Abilities } from "#enums/abilities";
import { PartyMemberStrength } from "#enums/party-member-strength";
import { Species } from "#enums/species";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import i18next from "i18next";
import BattleScene, { AnySound } from "../battle-scene"; import BattleScene, { AnySound } from "../battle-scene";
import { Variant, variantColorCache } from "./variant"; import { GameMode } from "../game-mode";
import { variantData } from "./variant"; import { StarterMoveset } from "../system/game-data";
import * as Utils from "../utils";
import { uncatchableSpecies } from "./biomes";
import { speciesEggMoves } from "./egg-moves";
import { GrowthRate } from "./exp"; import { GrowthRate } from "./exp";
import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions"; import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions";
import { Type } from "./type"; import { Type } from "./type";
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "./pokemon-level-moves"; import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "./pokemon-level-moves";
import { uncatchableSpecies } from "./biomes";
import * as Utils from "../utils";
import { StarterMoveset } from "../system/game-data";
import { speciesEggMoves } from "./egg-moves";
import { GameMode } from "../game-mode";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { VariantSet } from "./variant";
import i18next from "i18next";
import { Localizable } from "#app/interfaces/locales";
import { Stat } from "./pokemon-stat"; import { Stat } from "./pokemon-stat";
import { Abilities } from "#enums/abilities"; import { Variant, VariantSet, variantColorCache, variantData } from "./variant";
import { PartyMemberStrength } from "#enums/party-member-strength";
import { Species } from "#enums/species";
export enum Region { export enum Region {
NORMAL, NORMAL,
@ -28,7 +25,15 @@ export enum Region {
PALDEA PALDEA
} }
export function getPokemonSpecies(species: Species | Species[]): PokemonSpecies { /**
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode Species} enum given
* @param species The species to fetch
* @returns The associated {@linkcode PokemonSpecies} object
*/
export function getPokemonSpecies(species: Species | Species[] | undefined): PokemonSpecies {
if (!species) {
throw new Error("`species` must not be undefined in `getPokemonSpecies()`");
}
// If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block // If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block
if (Array.isArray(species)) { if (Array.isArray(species)) {
// Pick a random species from the list // Pick a random species from the list
@ -648,8 +653,8 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
return this.getSpeciesForLevel(level, allowEvolving, false, (isBoss ? PartyMemberStrength.WEAKER : PartyMemberStrength.AVERAGE) + (gameMode?.isEndless ? 1 : 0)); return this.getSpeciesForLevel(level, allowEvolving, false, (isBoss ? PartyMemberStrength.WEAKER : PartyMemberStrength.AVERAGE) + (gameMode?.isEndless ? 1 : 0));
} }
getTrainerSpeciesForLevel(level: integer, allowEvolving: boolean = false, strength: PartyMemberStrength): Species { getTrainerSpeciesForLevel(level: integer, allowEvolving: boolean = false, strength: PartyMemberStrength, currentWave: number = 0): Species {
return this.getSpeciesForLevel(level, allowEvolving, true, strength); return this.getSpeciesForLevel(level, allowEvolving, true, strength, currentWave);
} }
private getStrengthLevelDiff(strength: PartyMemberStrength): integer { private getStrengthLevelDiff(strength: PartyMemberStrength): integer {
@ -669,7 +674,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
} }
getSpeciesForLevel(level: integer, allowEvolving: boolean = false, forTrainer: boolean = false, strength: PartyMemberStrength = PartyMemberStrength.WEAKER): Species { getSpeciesForLevel(level: integer, allowEvolving: boolean = false, forTrainer: boolean = false, strength: PartyMemberStrength = PartyMemberStrength.WEAKER, currentWave: number = 0): Species {
const prevolutionLevels = this.getPrevolutionLevels(); const prevolutionLevels = this.getPrevolutionLevels();
if (prevolutionLevels.length) { if (prevolutionLevels.length) {
@ -730,6 +735,11 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
evolutionChance = Math.min(0.65 * easeInFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel) / preferredMinLevel) + 0.35 * easeOutFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel * 2.5) / (preferredMinLevel * 2.5)), 1); evolutionChance = Math.min(0.65 * easeInFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel) / preferredMinLevel) + 0.35 * easeOutFunc(Math.min(Math.max(level - evolutionLevel, 0), preferredMinLevel * 2.5) / (preferredMinLevel * 2.5)), 1);
} }
} }
/* (Most) Trainers shouldn't be using unevolved Pokemon by the third gym leader / wave 80. Exceptions to this include Breeders, whose large teams are balanced by the use of weaker pokemon */
if (currentWave >= 80 && forTrainer && strength > PartyMemberStrength.WEAKER) {
evolutionChance = 1;
noEvolutionChance = 0;
}
if (evolutionChance > 0) { if (evolutionChance > 0) {
if (isRegionalEvolution) { if (isRegionalEvolution) {
@ -754,7 +764,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
for (const weight of evolutionPool.keys()) { for (const weight of evolutionPool.keys()) {
if (randValue < weight) { if (randValue < weight) {
return getPokemonSpecies(evolutionPool.get(weight)!).getSpeciesForLevel(level, true, forTrainer, strength); // TODO: is the bang correct? return getPokemonSpecies(evolutionPool.get(weight)).getSpeciesForLevel(level, true, forTrainer, strength, currentWave);
} }
} }

View File

@ -142,7 +142,7 @@ export const trainerPartyTemplates = {
FIVE_WEAK_BALANCED: new TrainerPartyTemplate(5, PartyMemberStrength.WEAK, false, true), FIVE_WEAK_BALANCED: new TrainerPartyTemplate(5, PartyMemberStrength.WEAK, false, true),
SIX_WEAKER: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER), SIX_WEAKER: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER),
SIX_WEAKER_SAME: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER, true), SIX_WEAKER_SAME: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER, true),
SIX_WEAK_SAME: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER, true), SIX_WEAK_SAME: new TrainerPartyTemplate(6, PartyMemberStrength.WEAK, true),
SIX_WEAK_BALANCED: new TrainerPartyTemplate(6, PartyMemberStrength.WEAK, false, true), SIX_WEAK_BALANCED: new TrainerPartyTemplate(6, PartyMemberStrength.WEAK, false, true),
GYM_LEADER_1: new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)), GYM_LEADER_1: new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)),
@ -965,7 +965,7 @@ function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSl
return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => { return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => {
let species = Utils.randSeedItem(speciesPool); let species = Utils.randSeedItem(speciesPool);
if (!ignoreEvolution) { if (!ignoreEvolution) {
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength); species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex);
} }
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess); return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess);
}; };
@ -975,7 +975,7 @@ function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilt
const originalSpeciesFilter = speciesFilter; const originalSpeciesFilter = speciesFilter;
speciesFilter = (species: PokemonSpecies) => (allowLegendaries || (!species.legendary && !species.subLegendary && !species.mythical)) && !species.isTrainerForbidden() && originalSpeciesFilter(species); speciesFilter = (species: PokemonSpecies) => (allowLegendaries || (!species.legendary && !species.subLegendary && !species.mythical)) && !species.isTrainerForbidden() && originalSpeciesFilter(species);
return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => { return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => {
const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getTrainerSpeciesForLevel(level, true, strength)), level, trainerSlot, undefined, undefined, postProcess); const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex)), level, trainerSlot, undefined, undefined, postProcess);
return ret; return ret;
}; };
} }

View File

@ -359,12 +359,12 @@ export default class Trainer extends Phaser.GameObjects.Container {
let species = useNewSpeciesPool let species = useNewSpeciesPool
? getPokemonSpecies(newSpeciesPool[Math.floor(Math.random() * newSpeciesPool.length)]) ? getPokemonSpecies(newSpeciesPool[Math.floor(Math.random() * newSpeciesPool.length)])
: template.isSameSpecies(index) && index > offset : template.isSameSpecies(index) && index > offset
? getPokemonSpecies(battle.enemyParty[offset].species.getTrainerSpeciesForLevel(level, false, template.getStrength(offset))) ? getPokemonSpecies(battle.enemyParty[offset].species.getTrainerSpeciesForLevel(level, false, template.getStrength(offset), this.scene.currentBattle.waveIndex))
: this.genNewPartyMemberSpecies(level, strength); : this.genNewPartyMemberSpecies(level, strength);
// If the species is from newSpeciesPool, we need to adjust it based on the level and strength // If the species is from newSpeciesPool, we need to adjust it based on the level and strength
if (newSpeciesPool) { if (newSpeciesPool) {
species = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, strength)); species = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, strength, this.scene.currentBattle.waveIndex));
} }
ret = this.scene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); ret = this.scene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
@ -393,7 +393,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
species = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); species = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter);
} }
let ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength)); let ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex));
let retry = false; let retry = false;
console.log(ret.getName()); console.log(ret.getName());
@ -412,7 +412,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
console.log("Attempting reroll of species evolution to fit specialty type..."); console.log("Attempting reroll of species evolution to fit specialty type...");
let evoAttempt = 0; let evoAttempt = 0;
while (retry && evoAttempt++ < 10) { while (retry && evoAttempt++ < 10) {
ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength)); ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex));
console.log(ret.name); console.log(ret.name);
if (this.config.specialtyTypes.find(t => ret.isOfType(t))) { if (this.config.specialtyTypes.find(t => ret.isOfType(t))) {
retry = false; retry = false;