[Balance] Rival Fight Rework (#6603)

* Basic Implementation of new Rival Fights

* Set abilities for birds, Set levels for slots 3-5

* Left an accidental TODO

* Remove duplicate Paldea Tauros

* Allow species in getRandomPartyMemberFunc to be an array

* Use switch statement instead of if/else chain

* docs: add doc comments

* Misc cleanup

* Misc cleanup

* Implement superior rival fight teamgen

* Remove latent console logs from other PR

* Fix unrelated typo in pokemon-move-no-pp

* Tweak type overlap logic

* Fix off-by-one-error for limits

* Address Kev's comments from code review

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Fix typo

* Fix docs in `rival-party-config.ts`

* Add missing default in doc in `rival-team-gen.ts`

* Update src/ai/rival-team-gen.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update src/ai/rival-team-gen.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Address Kev's comments from code review

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

---------

Co-authored-by: Madmadness65 <blaze.the.fireman@gmail.com>
Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com>
Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
damocleas 2025-10-01 23:45:50 -04:00 committed by GitHub
parent 68f65da233
commit 0da202c26e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1100 additions and 506 deletions

View File

@ -1,7 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { SpeciesFormEvolution } from "#balance/pokemon-evolutions"; import type { SpeciesFormEvolution } from "#balance/pokemon-evolutions";
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
import { allSpecies } from "#data/data-lists";
import type { PokemonSpecies } from "#data/pokemon-species"; import type { PokemonSpecies } from "#data/pokemon-species";
import { EvoLevelThresholdKind } from "#enums/evo-level-threshold-kind"; import { EvoLevelThresholdKind } from "#enums/evo-level-threshold-kind";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
@ -37,11 +36,6 @@ function calcEvoChance(ev: SpeciesFormEvolution, level: number, encounterKind: E
const levelThreshold = Math.max(ev.level, ev.evoLevelThreshold?.[encounterKind] ?? 0); const levelThreshold = Math.max(ev.level, ev.evoLevelThreshold?.[encounterKind] ?? 0);
// Disallow evolution if the level is below its required threshold. // Disallow evolution if the level is below its required threshold.
if (level < ev.level || level < levelThreshold) { if (level < ev.level || level < levelThreshold) {
console.info(
"%cDisallowing evolution of %s to %s at level %d (needs %d)",
"color: blue",
allSpecies[ev.speciesId]?.name,
);
return 0; return 0;
} }
return levelThreshold; return levelThreshold;
@ -83,14 +77,6 @@ function getRequiredPrevo(
const threshold = evoThreshold?.[encounterKind] ?? levelReq; const threshold = evoThreshold?.[encounterKind] ?? levelReq;
const req = levelReq === 1 ? threshold : Math.min(levelReq, threshold); const req = levelReq === 1 ? threshold : Math.min(levelReq, threshold);
if (level < req) { if (level < req) {
console.info(
"%cForcing prevo %s for %s at level %d (needs %d)",
"color: orange",
prevoSpecies,
species.speciesId,
level,
req,
);
return prevoSpecies; return prevoSpecies;
} }
} }
@ -127,13 +113,6 @@ export function determineEnemySpecies(
encounterKind: EvoLevelThresholdKind = forTrainer ? EvoLevelThresholdKind.NORMAL : EvoLevelThresholdKind.WILD, encounterKind: EvoLevelThresholdKind = forTrainer ? EvoLevelThresholdKind.NORMAL : EvoLevelThresholdKind.WILD,
tryForcePrevo = true, tryForcePrevo = true,
): SpeciesId { ): SpeciesId {
console.info(
"%c Determining species for %s at level %d with encounter kind %s",
"color: blue",
species.name,
level,
encounterKind,
);
const requiredPrevo = const requiredPrevo =
tryForcePrevo tryForcePrevo
&& pokemonPrevolutions.hasOwnProperty(species.speciesId) && pokemonPrevolutions.hasOwnProperty(species.speciesId)
@ -162,7 +141,6 @@ export function determineEnemySpecies(
} }
} }
if (evoPool.length === 0) { if (evoPool.length === 0) {
console.log("%c No evolutions available, returning base species", "color: blue");
return species.speciesId; return species.speciesId;
} }
const [choice, evoSpecies] = randSeedItem(evoPool); const [choice, evoSpecies] = randSeedItem(evoPool);
@ -184,14 +162,7 @@ export function determineEnemySpecies(
break; break;
} }
console.info(
"%c Returning a random integer between %d and %d",
"color: blue",
choice,
Math.round(choice * multiplier),
);
const randomLevel = randSeedInt(choice, Math.round(choice * multiplier)); const randomLevel = randSeedInt(choice, Math.round(choice * multiplier));
console.info("%c Random level is %d", "color: blue", randomLevel);
if (randomLevel <= level) { if (randomLevel <= level) {
return determineEnemySpecies( return determineEnemySpecies(
getPokemonSpecies(evoSpecies), getPokemonSpecies(evoSpecies),

331
src/ai/rival-team-gen.ts Normal file
View File

@ -0,0 +1,331 @@
import { globalScene } from "#app/global-scene";
import type { PokemonSpecies } from "#data/pokemon-species";
import { getTypeDamageMultiplier } from "#data/type";
import { AbilityId } from "#enums/ability-id";
import { ChallengeType } from "#enums/challenge-type";
import type { PartyMemberStrength } from "#enums/party-member-strength";
import { PokemonType } from "#enums/pokemon-type";
import type { SpeciesId } from "#enums/species-id";
import { TrainerSlot } from "#enums/trainer-slot";
import type { EnemyPokemon } from "#field/pokemon";
import { RIVAL_6_POOL, type RivalPoolConfig } from "#trainers/rival-party-config";
import { applyChallenges } from "#utils/challenge-utils";
import { NumberHolder, randSeedItem } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { getPokemonSpecies } from "#utils/pokemon-utils";
/**
* The maximum number of shared weaknesses to tolerate when balancing weakness
*
* @remarks
* When generating a slot that has weakness balancing enabled, the pool will
* exclude any species that would cause a type to be a weakness for more than
* this number of party members.
* Note that it is assumed that slot 0 is always going to Terastallize to its primary type,
* so slot 0's secondary type is excluded from weakness calculations.
*/
const MAX_SHARED_WEAKNESSES = 2;
/**
* The maximum number of shared types to tolerate when balancing types
*
* @remarks
* When generating a slot that has type balancing enabled, the pool will
* exclude any species that would cause a type to be present in more than
* this number of party members.
*/
const MAX_SHARED_TYPES = 1;
/** Record of the chosen indices in the rival species pool, for type balancing based on the final fight */
const CHOSEN_RIVAL_ROLLS: (undefined | [number] | [number, number])[] = new Array(6);
/**
* Return the species from the rival species pool based on a previously chosen roll
* @param param0 - The chosen rolls for the rival species pool
* @param pool - The rival species pool for the slot
* @returns - A `SpeciesId` if found, or `undefined` if no species exists at the chosen roll
*/
function rivalRollToSpecies(
[roll1, roll2]: [number] | [number, number],
pool: readonly (SpeciesId | readonly SpeciesId[])[],
): SpeciesId | undefined {
const pull1 = pool[roll1];
if (typeof pull1 === "number") {
return pull1;
}
if (roll2 == null) {
return;
}
return pull1[roll2];
}
/**
* Calculates the types that the given species is weak to
*
* @remarks
* - Considers Levitate as a guaranteed ability if the species has it as all 3 possible abilities.
* - Accounts for type effectiveness challenges via {@linkcode applyChallenges}
*
* @privateRemarks
* Despite potentially being a useful utility method, this is intentionally *not*
* exported because it uses logic specific to this file, such as excluding the second type for Tera starters
*
* @param species - The species to calculate weaknesses for
* @param exclude2ndType - (Default `false`) Whether to exclude the second type when calculating weaknesses
* Intended to be used for starters since they will terastallize to their primary type.
* @returns The set of types that the species is weak to
*/
function getWeakTypes(species: PokemonSpecies, exclude2ndType = false): Set<PokemonType> {
const weaknesses = new Set<PokemonType>();
// If the species is always immune to ground, skip ground type checks
// Note that there are no other Pokémon with guaranteed immunities due to all 3 of their abilities providing
// an immunity.
// At this point, we do not have an ability to know which ability the Pokémon generated with, so we can only
// work with guaranteed immunities.
const groundImmunityAbilities: readonly AbilityId[] = [AbilityId.LEVITATE, AbilityId.EARTH_EATER];
const isAlwaysGroundImmune =
groundImmunityAbilities.includes(species.ability1)
&& (species.ability2 == null || groundImmunityAbilities.includes(species.ability2))
&& (species.abilityHidden == null || groundImmunityAbilities.includes(species.ability2));
for (const ty of getEnumValues(PokemonType)) {
if (
ty === PokemonType.UNKNOWN
|| ty === PokemonType.STELLAR
|| (ty === PokemonType.GROUND && isAlwaysGroundImmune)
) {
continue;
}
const multiplier = new NumberHolder(getTypeDamageMultiplier(ty, species.type1));
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (multiplier.value >= 2 && !exclude2ndType) {
const type2 = species.type2;
if (type2 != null) {
const multiplier2 = new NumberHolder(getTypeDamageMultiplier(ty, type2));
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier2);
multiplier.value *= multiplier2.value;
}
}
if (multiplier.value >= 2) {
weaknesses.add(ty);
}
}
return weaknesses;
}
/**
* Calculate the existing types and weaknesses in the party up to the target slot
* @remarks
* At least one of either `balanceTypes` or `balanceWeaknesses` should be `true`,
* otherwise the function does nothing.
* @param targetSlot - The slot we are calculating up to (exclusive)
* @param pokemonTypes - A map that will hold the types present in the party
* @param pokemonWeaknesses - A map that will hold the weaknesses present in the party, and their counts
* @param balanceTypes - (Default `false`) Whether to include type balancing
* @param balanceWeaknesses - (Default `false`) Whether to attempt to add the party's existing weaknesses for the purpose of weakness balancing.
* @param referenceConfig - (Default {@linkcode RIVAL_6_POOL}); The reference rival pool configuration to use for type considerations
*
* @see {@linkcode MAX_SHARED_WEAKNESSES}
*/
function calcPartyTypings(
targetSlot: number,
pokemonTypes: Map<PokemonType, number>,
pokemonWeaknesses: Map<PokemonType, number>,
balanceTypes = false,
balanceWeaknesses = false,
referenceConfig: RivalPoolConfig = RIVAL_6_POOL,
): void {
if (!balanceTypes && !balanceWeaknesses) {
return;
}
for (let i = 0; i < targetSlot; i++) {
const chosenRoll = CHOSEN_RIVAL_ROLLS[i];
const refConfig = referenceConfig[i];
// In case pokemon are somehow generating out of order, break early
if (chosenRoll == null || refConfig == null) {
break;
}
// Get the species from the roll
const refSpecies = rivalRollToSpecies(chosenRoll, refConfig.pool);
if (refSpecies == null) {
continue;
}
const refPokeSpecies = getPokemonSpecies(refSpecies);
const type1 = refPokeSpecies.type1;
const type2 = refPokeSpecies.type2;
if (balanceTypes) {
pokemonTypes.set(type1, (pokemonTypes.get(type1) ?? 0) + 1);
if (type2 != null) {
pokemonTypes.set(type2, (pokemonTypes.get(type2) ?? 0) + 1);
}
}
if (balanceWeaknesses) {
for (const weakType of getWeakTypes(refPokeSpecies)) {
pokemonWeaknesses.set(weakType, (pokemonWeaknesses.get(weakType) ?? 0) + 1);
}
}
}
}
/**
* Determine if the species can be added to the party without violating type or weakness constraints
* @param species - The species to check
* @param existingTypes - The existing types in the party
* @param existingWeaknesses - The existing weaknesses in the party
* @param balanceTypes - (Default `false`) Whether to include type balancing
* @param balanceWeaknesses - (Default `false`) Whether to include weakness balancing
* @returns Whether the species meets the constraints
*/
function checkTypingConstraints(
species: SpeciesId,
existingTypes: ReadonlyMap<PokemonType, number>,
existingWeaknesses: ReadonlyMap<PokemonType, number>,
balanceTypes = false,
balanceWeaknesses = false,
): boolean {
if (!balanceTypes && !balanceWeaknesses) {
return true;
}
const { type1, type2 } = getPokemonSpecies(species);
if (
balanceTypes
&& ((existingTypes.get(type1) ?? 0) >= MAX_SHARED_TYPES
|| (type2 != null && (existingTypes.get(type2) ?? 0) >= MAX_SHARED_TYPES))
) {
return false;
}
if (balanceWeaknesses) {
const weaknesses = getWeakTypes(getPokemonSpecies(species));
for (const weakType of weaknesses) {
if ((existingWeaknesses.get(weakType) ?? 0) >= MAX_SHARED_WEAKNESSES) {
return false;
}
}
}
return true;
}
/**
* Convert a species pool to a list of choices after filtering by type and weakness constraints
* @param pool - The pool to convert to choices
* @param existingTypes - The existing types in the party
* @param existingWeaknesses - The existing weaknesses in the party
* @param balanceTypes - (Default `false`) Whether to include type balancing
* @param balanceWeaknesses - (Default `false`) Whether to include weakness balancing
* @returns A list of choices, where each choice is either a single index or a tuple of indices for sub-pools
*/
function convertPoolToChoices(
pool: readonly (SpeciesId | readonly SpeciesId[])[],
existingTypes: ReadonlyMap<PokemonType, number>,
existingWeaknesses: ReadonlyMap<PokemonType, number>,
balanceTypes = false,
balanceWeaknesses = false,
): (number | [number, number])[] {
const choices: (number | [number, number])[] = [];
if (balanceTypes || balanceWeaknesses) {
for (const [i, entry] of pool.entries()) {
// Determine if there is a type overlap
if (
typeof entry === "number"
&& checkTypingConstraints(entry, existingTypes, existingWeaknesses, balanceTypes, balanceWeaknesses)
) {
choices.push(i);
} else if (typeof entry !== "number") {
for (const [j, subEntry] of entry.entries()) {
if (checkTypingConstraints(subEntry, existingTypes, existingWeaknesses, balanceTypes, balanceWeaknesses)) {
choices.push([i, j]);
}
}
}
}
}
if (choices.length === 0) {
for (const [i, entry] of pool.entries()) {
if (typeof entry === "number") {
choices.push(i);
} else {
for (const j of entry.keys()) {
choices.push([i, j]);
}
}
}
}
return choices;
}
/**
* Randomly selects one of the `Species` from `speciesPool`, determines its evolution, level, and strength.
* Then adds Pokemon to `globalScene`.
* @param config - The configuration for the rival pool fight
* @param slot - The slot being generated for (0-5)
* @param referenceConfig - (Default {@linkcode RIVAL_6_POOL}); The final rival pool configuration to use if `config` is `RIVAL_POOL_CONFIG.FINAL`
*
* @throws
* If no configuration is found for the specified slot.
*/
export function getRandomRivalPartyMemberFunc(
config: RivalPoolConfig,
slot: number,
referenceConfig: RivalPoolConfig = RIVAL_6_POOL,
): (level: number, strength: PartyMemberStrength) => EnemyPokemon {
// Protect against out of range slots.
// Only care about this in dev to be caught during development; it will be excluded in production builds.
if (import.meta.env.DEV && slot > config.length) {
throw new Error(`Slot ${slot} is out of range for the provided config of length ${config.length}`);
}
return (level: number, _strength: PartyMemberStrength) => {
const { pool, postProcess, balanceTypes, balanceWeaknesses } = config[slot];
const existingTypes = new Map<PokemonType, number>();
const existingWeaknesses = new Map<PokemonType, number>();
if (slot === 0) {
// Clear out the rolls from previous rival generations
CHOSEN_RIVAL_ROLLS.fill(undefined);
} else if (balanceTypes || balanceWeaknesses) {
calcPartyTypings(slot, existingTypes, existingWeaknesses, balanceTypes, balanceWeaknesses, referenceConfig);
}
// Filter the pool to its choices, or map it
let species: SpeciesId | SpeciesId[];
// When converting pool to choices, base off of the reference config
// to use for type balancing, as we only narrow based on what the slot
// will be in its final stage
const choices = convertPoolToChoices(
referenceConfig[slot].pool,
existingTypes,
existingWeaknesses,
balanceTypes,
balanceWeaknesses,
);
const choice = randSeedItem(choices);
if (typeof choice === "number") {
species = pool[choice] as SpeciesId;
CHOSEN_RIVAL_ROLLS[slot] = [choice];
} else {
species = pool[choice[0]][choice[1]];
CHOSEN_RIVAL_ROLLS[slot] = choice;
}
return globalScene.addEnemyPokemon(
getPokemonSpecies(species),
level,
TrainerSlot.TRAINER,
undefined,
false,
undefined,
postProcess,
);
};
}

View File

@ -59,7 +59,7 @@ export class PokemonMove {
} }
if (!ignorePp && move.pp !== -1 && this.ppUsed >= this.getMovePp()) { if (!ignorePp && move.pp !== -1 && this.ppUsed >= this.getMovePp()) {
return [false, i18next.t("battle:moveNoPP", { moveName: move.name })]; return [false, i18next.t("battle:moveNoPp", { moveName: move.name })];
} }
if (forSelection) { if (forSelection) {

View File

@ -0,0 +1,692 @@
import { timedEventManager } from "#app/global-event-manager";
import { PokeballType } from "#enums/pokeball";
import { SpeciesId } from "#enums/species-id";
import type { EnemyPokemon } from "#field/pokemon";
import { randSeedIntRange, randSeedItem } from "#utils/common";
//#region constants
// Levels for slots 1 and 2 do not need post-processing logic
// Fight 1 doesn't have slot 3
const SLOT_3_FIGHT_2_LEVEL = 16;
const SLOT_3_FIGHT_3_LEVEL = 36;
const SLOT_3_FIGHT_4_LEVEL = 71;
const SLOT_3_FIGHT_5_LEVEL = 125;
const SLOT_3_FIGHT_6_LEVEL = 189;
// Fights 1 and 2 don't have slot 4
const SLOT_4_FIGHT_3_LEVEL = 38;
const SLOT_4_FIGHT_4_LEVEL = 71;
const SLOT_4_FIGHT_5_LEVEL = 125;
const SLOT_4_FIGHT_6_LEVEL = 189;
// Fights 1-3 don't have slot 5
const SLOT_5_FIGHT_4_LEVEL = 69;
const SLOT_5_FIGHT_5_LEVEL = 127;
const SLOT_5_FIGHT_6_LEVEL = 189;
// Fights 1-4 don't have slot 6
const SLOT_6_FIGHT_5_LEVEL = 129;
const SLOT_6_FIGHT_6_LEVEL = 200;
//#endregion constants
//#region Slot 1
/**
* Set the abiltiy index to 0 and the tera type to the primary type
*
* @param pokemon - The pokemon to force traits for
* @param bars - (default `0`) The number of boss bar segments to set. If `zero`, the pokemon will not be a boss
*/
function forceRivalStarterTraits(pokemon: EnemyPokemon, bars = 0): void {
pokemon.abilityIndex = 0;
pokemon.teraType = pokemon.species.type1;
if (bars > 0) {
pokemon.setBoss(true, bars);
pokemon.generateAndPopulateMoveset();
}
}
/** Rival's slot 1 species pool for fight 1 */
const SLOT_1_FIGHT_1 = [
SpeciesId.BULBASAUR,
SpeciesId.CHARMANDER,
SpeciesId.SQUIRTLE,
SpeciesId.CHIKORITA,
SpeciesId.CYNDAQUIL,
SpeciesId.TOTODILE,
SpeciesId.TREECKO,
SpeciesId.TORCHIC,
SpeciesId.MUDKIP,
SpeciesId.TURTWIG,
SpeciesId.CHIMCHAR,
SpeciesId.PIPLUP,
SpeciesId.SNIVY,
SpeciesId.TEPIG,
SpeciesId.OSHAWOTT,
SpeciesId.CHESPIN,
SpeciesId.FENNEKIN,
SpeciesId.FROAKIE,
SpeciesId.ROWLET,
SpeciesId.LITTEN,
SpeciesId.POPPLIO,
SpeciesId.GROOKEY,
SpeciesId.SCORBUNNY,
SpeciesId.SOBBLE,
SpeciesId.SPRIGATITO,
SpeciesId.FUECOCO,
SpeciesId.QUAXLY,
];
/** Rival's slot 1 species pool for fight 2 */
const SLOT_1_FIGHT_2 = [
SpeciesId.IVYSAUR,
SpeciesId.CHARMELEON,
SpeciesId.WARTORTLE,
SpeciesId.BAYLEEF,
SpeciesId.QUILAVA,
SpeciesId.CROCONAW,
SpeciesId.GROVYLE,
SpeciesId.COMBUSKEN,
SpeciesId.MARSHTOMP,
SpeciesId.GROTLE,
SpeciesId.MONFERNO,
SpeciesId.PRINPLUP,
SpeciesId.SERVINE,
SpeciesId.PIGNITE,
SpeciesId.DEWOTT,
SpeciesId.QUILLADIN,
SpeciesId.BRAIXEN,
SpeciesId.FROGADIER,
SpeciesId.DARTRIX,
SpeciesId.TORRACAT,
SpeciesId.BRIONNE,
SpeciesId.THWACKEY,
SpeciesId.RABOOT,
SpeciesId.DRIZZILE,
SpeciesId.FLORAGATO,
SpeciesId.CROCALOR,
SpeciesId.QUAXWELL,
];
/** Rival's slot 1 species pool for fight 3 and beyond */
const SLOT_1_FINAL = [
SpeciesId.VENUSAUR,
SpeciesId.CHARIZARD,
SpeciesId.BLASTOISE,
SpeciesId.MEGANIUM,
SpeciesId.TYPHLOSION,
SpeciesId.FERALIGATR,
SpeciesId.SCEPTILE,
SpeciesId.BLAZIKEN,
SpeciesId.SWAMPERT,
SpeciesId.TORTERRA,
SpeciesId.INFERNAPE,
SpeciesId.EMPOLEON,
SpeciesId.SERPERIOR,
SpeciesId.EMBOAR,
SpeciesId.SAMUROTT,
SpeciesId.CHESNAUGHT,
SpeciesId.DELPHOX,
SpeciesId.GRENINJA,
SpeciesId.DECIDUEYE,
SpeciesId.INCINEROAR,
SpeciesId.PRIMARINA,
SpeciesId.RILLABOOM,
SpeciesId.CINDERACE,
SpeciesId.INTELEON,
SpeciesId.MEOWSCARADA,
SpeciesId.SKELEDIRGE,
SpeciesId.QUAQUAVAL,
];
//#endregion slot 1
//#region Slot 2
/**
* Post-process rival birds to override their sets
*
* @remarks
* Currently used to force ability indices
*
* @param pokemon - The rival bird pokemon to force an ability for
* @param bars - (default `0`) The number of boss bar segments to set. If `zero`, the pokemon will not be a boss
*/
function forceRivalBirdAbility(pokemon: EnemyPokemon, bars = 0): void {
switch (pokemon.species.speciesId) {
// Guts for Tailow line
case SpeciesId.TAILLOW:
case SpeciesId.SWELLOW:
// Intimidate for Starly line
case SpeciesId.STARLY:
case SpeciesId.STARAVIA:
case SpeciesId.STARAPTOR: {
pokemon.abilityIndex = 0;
break;
}
// Tangled Feet for Pidgey line
case SpeciesId.PIDGEY:
case SpeciesId.PIDGEOTTO:
case SpeciesId.PIDGEOT:
// Super Luck for pidove line
case SpeciesId.PIDOVE:
case SpeciesId.TRANQUILL:
case SpeciesId.UNFEZANT:
// Volt Absorb for Wattrel line
case SpeciesId.WATTREL:
case SpeciesId.KILOWATTREL: {
pokemon.abilityIndex = 1;
break;
}
// Tinted lens for Hoothoot line
case SpeciesId.HOOTHOOT:
case SpeciesId.NOCTOWL:
// Skill link for Pikipek line
case SpeciesId.PIKIPEK:
case SpeciesId.TRUMBEAK:
case SpeciesId.TOUCANNON:
// Gale Wings for Fletchling line
case SpeciesId.FLETCHLING:
case SpeciesId.FLETCHINDER:
case SpeciesId.TALONFLAME: {
pokemon.abilityIndex = 2;
break;
}
}
if (bars > 0) {
pokemon.setBoss(true, bars);
pokemon.generateAndPopulateMoveset();
}
}
/** Rival's slot 2 species pool for fight 1 */
const SLOT_2_FIGHT_1 = [
SpeciesId.PIDGEY,
SpeciesId.HOOTHOOT,
SpeciesId.TAILLOW,
SpeciesId.STARLY,
SpeciesId.PIDOVE,
SpeciesId.FLETCHLING,
SpeciesId.PIKIPEK,
SpeciesId.ROOKIDEE,
SpeciesId.WATTREL,
];
/** Rival's slot 2 species pool for fight 2 */
const SLOT_2_FIGHT_2 = [
SpeciesId.PIDGEOTTO,
SpeciesId.HOOTHOOT,
SpeciesId.TAILLOW,
SpeciesId.STARAVIA,
SpeciesId.TRANQUILL,
SpeciesId.FLETCHINDER,
SpeciesId.TRUMBEAK,
SpeciesId.CORVISQUIRE,
SpeciesId.WATTREL,
];
/** Rival's slot 2 species pool for fight 3 and beyond */
const SLOT_2_FINAL = [
SpeciesId.PIDGEOT,
SpeciesId.NOCTOWL,
SpeciesId.SWELLOW,
SpeciesId.STARAPTOR,
SpeciesId.UNFEZANT,
SpeciesId.TALONFLAME,
SpeciesId.TOUCANNON,
SpeciesId.CORVIKNIGHT,
SpeciesId.KILOWATTREL,
];
//#endregion Slot 2
//#region Slot 3
/** Rival's slot 3 species pool for fight 2 */
const SLOT_3_FIGHT_2 = [
SpeciesId.NIDORINA,
SpeciesId.NIDORINO,
SpeciesId.MANKEY,
SpeciesId.GROWLITHE,
SpeciesId.ABRA,
SpeciesId.MACHOP,
SpeciesId.GASTLY,
SpeciesId.MAGNEMITE,
SpeciesId.RHYDON,
SpeciesId.TANGELA,
SpeciesId.PORYGON,
SpeciesId.ELEKID,
SpeciesId.MAGBY,
SpeciesId.MARILL,
SpeciesId.TEDDIURSA,
SpeciesId.SWINUB,
SpeciesId.SLAKOTH,
SpeciesId.ARON,
SpeciesId.SPHEAL,
SpeciesId.FEEBAS,
SpeciesId.MUNCHLAX,
SpeciesId.ROGGENROLA,
SpeciesId.TIMBURR,
SpeciesId.TYMPOLE,
SpeciesId.SANDILE,
SpeciesId.YAMASK,
SpeciesId.SOLOSIS,
SpeciesId.VANILLITE,
SpeciesId.TYMPOLE,
SpeciesId.LITWICK,
SpeciesId.MUDBRAY,
SpeciesId.DEWPIDER,
SpeciesId.WIMPOD,
SpeciesId.HATENNA,
SpeciesId.IMPIDIMP,
SpeciesId.SMOLIV,
SpeciesId.NACLI,
[SpeciesId.CHARCADET, SpeciesId.CHARCADET],
SpeciesId.TINKATINK,
SpeciesId.GLIMMET,
];
/** Rival's slot 3 species pool for fight 3 */
const SLOT_3_FIGHT_3 = [
SpeciesId.NIDOQUEEN,
SpeciesId.NIDOKING,
SpeciesId.PRIMEAPE,
SpeciesId.ARCANINE,
SpeciesId.KADABRA,
SpeciesId.MACHOKE,
SpeciesId.HAUNTER,
SpeciesId.MAGNETON,
SpeciesId.RHYDON,
SpeciesId.TANGROWTH,
SpeciesId.PORYGON2,
SpeciesId.ELECTIVIRE,
SpeciesId.MAGMAR,
SpeciesId.AZUMARILL,
SpeciesId.URSARING,
SpeciesId.PILOSWINE,
SpeciesId.VIGOROTH,
SpeciesId.LAIRON,
SpeciesId.SEALEO,
SpeciesId.MILOTIC,
SpeciesId.SNORLAX,
SpeciesId.BOLDORE,
SpeciesId.GURDURR,
SpeciesId.PALPITOAD,
SpeciesId.KROKOROK,
SpeciesId.COFAGRIGUS,
SpeciesId.DUOSION,
SpeciesId.VANILLISH,
SpeciesId.EELEKTRIK,
SpeciesId.LAMPENT,
SpeciesId.MUDSDALE,
SpeciesId.ARAQUANID,
SpeciesId.GOLISOPOD,
SpeciesId.HATTREM,
SpeciesId.MORGREM,
SpeciesId.DOLLIV,
SpeciesId.NACLSTACK,
[SpeciesId.ARMAROUGE, SpeciesId.CERULEDGE],
SpeciesId.TINKATUFF,
SpeciesId.GLIMMORA,
];
/** Rival's slot 3 species pool for fight 4 and beyond */
const SLOT_3_FINAL = [
SpeciesId.NIDOQUEEN,
SpeciesId.NIDOKING,
SpeciesId.ANNIHILAPE,
SpeciesId.ARCANINE,
SpeciesId.ALAKAZAM,
SpeciesId.MACHAMP,
SpeciesId.GENGAR,
SpeciesId.MAGNEZONE,
SpeciesId.RHYPERIOR,
SpeciesId.TANGROWTH,
SpeciesId.PORYGON_Z,
SpeciesId.ELECTIVIRE,
SpeciesId.MAGMORTAR,
SpeciesId.AZUMARILL,
SpeciesId.URSALUNA,
SpeciesId.MAMOSWINE,
SpeciesId.SLAKING,
SpeciesId.AGGRON,
SpeciesId.WALREIN,
SpeciesId.MILOTIC,
SpeciesId.SNORLAX,
SpeciesId.GIGALITH,
SpeciesId.CONKELDURR,
SpeciesId.SEISMITOAD,
SpeciesId.KROOKODILE,
SpeciesId.COFAGRIGUS,
SpeciesId.REUNICLUS,
SpeciesId.VANILLUXE,
SpeciesId.EELEKTROSS,
SpeciesId.CHANDELURE,
SpeciesId.MUDSDALE,
SpeciesId.ARAQUANID,
SpeciesId.GOLISOPOD,
SpeciesId.HATTERENE,
SpeciesId.GRIMMSNARL,
SpeciesId.ARBOLIVA,
SpeciesId.GARGANACL,
[SpeciesId.ARMAROUGE, SpeciesId.CERULEDGE],
SpeciesId.TINKATON,
SpeciesId.GLIMMORA,
];
//#endregion Slot 3
//#region Slot 4
/**
* Post-process logic for rival slot 4, fight 4
* @remarks
* Set level to 38 and specific forms for certain species
* @param pokemon - The pokemon to post-process
*/
function postProcessSlot4Fight3(pokemon: EnemyPokemon): void {
pokemon.level = SLOT_4_FIGHT_3_LEVEL;
switch (pokemon.species.speciesId) {
case SpeciesId.BASCULIN:
pokemon.formIndex = 2; // White
return;
case SpeciesId.ROTOM:
// Heat, Wash, Mow
pokemon.formIndex = randSeedItem([1, 2, 5]);
return;
case SpeciesId.PALDEA_TAUROS:
pokemon.formIndex = randSeedIntRange(1, 2); // Blaze, Aqua
return;
}
}
/** Rival's slot 4 species pool for fight 3 */
const SLOT_4_FIGHT_3 = [
SpeciesId.CLEFABLE,
[SpeciesId.SLOWBRO, SpeciesId.SLOWKING],
SpeciesId.PINSIR,
SpeciesId.LAPRAS,
SpeciesId.SCIZOR,
SpeciesId.HERACROSS,
SpeciesId.SNEASEL,
SpeciesId.GARDEVOIR,
SpeciesId.ROSERADE,
SpeciesId.SPIRITOMB,
SpeciesId.LUCARIO,
SpeciesId.DRAPION,
SpeciesId.GALLADE,
SpeciesId.ROTOM,
SpeciesId.EXCADRILL,
[SpeciesId.ZOROARK, SpeciesId.HISUI_ZOROARK],
SpeciesId.FERROTHORN,
SpeciesId.DURANT,
SpeciesId.FLORGES,
SpeciesId.DOUBLADE,
SpeciesId.VIKAVOLT,
SpeciesId.MIMIKYU,
SpeciesId.DHELMISE,
SpeciesId.POLTEAGEIST,
SpeciesId.COPPERAJAH,
SpeciesId.KLEAVOR,
SpeciesId.BASCULIN,
SpeciesId.HISUI_SNEASEL,
SpeciesId.HISUI_QWILFISH,
SpeciesId.PAWMOT,
SpeciesId.CETITAN,
SpeciesId.DONDOZO,
SpeciesId.DUDUNSPARCE,
SpeciesId.GHOLDENGO,
SpeciesId.POLTCHAGEIST,
[SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWKING],
SpeciesId.HISUI_ARCANINE,
SpeciesId.PALDEA_TAUROS,
];
/**
* Set level and and specific forms for the species in slot 4
* @param pokemon - The pokemon to post-process
* @param level - (default {@linkcode SLOT_4_FIGHT_4_LEVEL}) The level to set the pokemon to
*/
function postProcessSlot4Fight4(pokemon: EnemyPokemon, level = SLOT_4_FIGHT_4_LEVEL): void {
pokemon.level = level;
switch (pokemon.species.speciesId) {
case SpeciesId.BASCULEGION:
pokemon.formIndex = randSeedIntRange(0, 1);
return;
case SpeciesId.ROTOM:
// Heat, Wash, Mow
pokemon.formIndex = randSeedItem([1, 2, 5]);
return;
case SpeciesId.PALDEA_TAUROS:
pokemon.formIndex = randSeedIntRange(1, 2); // Blaze, Aqua
return;
}
}
/** Rival's slot 4 species pool for fight 4 and beyond */
const SLOT_4_FINAL = [
SpeciesId.CLEFABLE,
[SpeciesId.SLOWBRO, SpeciesId.SLOWKING],
SpeciesId.PINSIR,
SpeciesId.LAPRAS,
SpeciesId.SCIZOR,
SpeciesId.HERACROSS,
SpeciesId.WEAVILE,
SpeciesId.GARDEVOIR,
SpeciesId.ROSERADE,
SpeciesId.SPIRITOMB,
SpeciesId.LUCARIO,
SpeciesId.DRAPION,
SpeciesId.GALLADE,
SpeciesId.ROTOM,
SpeciesId.EXCADRILL,
[SpeciesId.ZOROARK, SpeciesId.HISUI_ZOROARK],
SpeciesId.FERROTHORN,
SpeciesId.DURANT,
SpeciesId.FLORGES,
SpeciesId.AEGISLASH,
SpeciesId.VIKAVOLT,
SpeciesId.MIMIKYU,
SpeciesId.DHELMISE,
SpeciesId.POLTEAGEIST,
SpeciesId.COPPERAJAH,
SpeciesId.KLEAVOR,
SpeciesId.BASCULEGION, // Ensure gender does not change
SpeciesId.SNEASLER,
SpeciesId.OVERQWIL,
SpeciesId.PAWMOT,
SpeciesId.CETITAN,
SpeciesId.DONDOZO,
SpeciesId.DUDUNSPARCE,
SpeciesId.GHOLDENGO,
SpeciesId.POLTCHAGEIST,
[SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWKING],
SpeciesId.HISUI_ARCANINE,
SpeciesId.PALDEA_TAUROS,
];
//#endregion Slot 4
//#region Slot 5
/** Rival's slot 5 species pool for fight 4 and beyond */
const SLOT_5_FINAL = [
SpeciesId.DRAGONITE,
SpeciesId.KINGDRA,
SpeciesId.TYRANITAR,
SpeciesId.SALAMENCE,
SpeciesId.METAGROSS,
SpeciesId.GARCHOMP,
SpeciesId.HAXORUS,
SpeciesId.HYDREIGON,
SpeciesId.VOLCARONA,
SpeciesId.GOODRA,
SpeciesId.KOMMO_O,
SpeciesId.DRAGAPULT,
SpeciesId.KINGAMBIT,
SpeciesId.BAXCALIBUR,
SpeciesId.GHOLDENGO,
SpeciesId.ARCHALUDON,
SpeciesId.HYDRAPPLE,
SpeciesId.HISUI_GOODRA,
];
//#endregion Slot 5
//#region Slot 6
/**
* Post-process logic for rival slot 6, fight 5
*
* @remarks
* Sets the level to the provided argument, sets the Pokémon to be caught in a Master Ball, sets boss
* @param pokemon - The pokemon to post-process
* @param level - (default {@linkcode SLOT_6_FIGHT_5_LEVEL}) The level to set the pokemon to
* @param overrideSegments - If `true`, will force the pokemon to have 3 boss bar segments
*/
function postProcessSlot6Fight5(pokemon: EnemyPokemon, level = SLOT_6_FIGHT_5_LEVEL, overrideSegments = true): void {
pokemon.level = level;
pokemon.pokeball = PokeballType.MASTER_BALL;
if (timedEventManager.getClassicTrainerShinyChance() === 0) {
pokemon.shiny = true;
pokemon.variant = 1;
}
// When called for fight 5, uses 3 segments.
// For fight 6, uses the logic from `getEncounterBossSegments`
pokemon.setBoss(true, overrideSegments ? 3 : undefined);
}
/**
* Post-process logic for rival slot 6, fight 6
*
* @remarks
* Applies {@linkcode postProcessSlot6Fight5} with an updated level
* and also sets the `formIndex` to `1` for Mega Rayquaza
* @param pokemon
*/
function postProcessSlot6Fight6(pokemon: EnemyPokemon): void {
// Guard just in case species gets overridden
if (pokemon.species.speciesId === SpeciesId.RAYQUAZA) {
pokemon.formIndex = 1; // Mega
}
postProcessSlot6Fight5(pokemon, SLOT_6_FIGHT_6_LEVEL, false);
pokemon.generateName();
}
/** Rival's slot 6 species pool for fight 5 and beyond */
const SLOT_6_FINAL = [SpeciesId.RAYQUAZA];
//#endregion Slot 6
export interface RivalSlotConfig {
/**
* The pool of `SpeciesId`s to choose from
*
* @remarks
* An entry may be either a single `SpeciesId` or an array of `SpeciesId`s. An
* array entry indicates that another roll is required, and is used for split
* evolution lines such as Charcadet to Armarouge/Ceruledge.
*/
readonly pool: readonly (SpeciesId | readonly SpeciesId[])[];
/** A function that will post-process the Pokémon after it has fully generated */
readonly postProcess: (enemyPokemon: EnemyPokemon) => void;
/**
* Whether to try to balance types in this slot to avoid sharing types with previous slots
* @defaultValue `false`
*/
readonly balanceTypes?: boolean;
/**
* Whether to try to balance weaknesses in this slot to avoid adding too many weaknesses to the overall party
* @defaultValue `false`
*/
readonly balanceWeaknesses?: boolean;
}
export type RivalPoolConfig = RivalSlotConfig[];
/** Pools for the first rival fight */
export const RIVAL_1_POOL: RivalPoolConfig = [
{ pool: SLOT_1_FIGHT_1, postProcess: forceRivalStarterTraits },
{ pool: SLOT_2_FIGHT_1, postProcess: forceRivalBirdAbility },
];
/** Pools for the second rival fight */
export const RIVAL_2_POOL: RivalPoolConfig = [
{ pool: SLOT_1_FIGHT_2, postProcess: forceRivalStarterTraits },
{ pool: SLOT_2_FIGHT_2, postProcess: forceRivalBirdAbility },
{
pool: SLOT_3_FIGHT_2,
postProcess: p => (p.level = SLOT_3_FIGHT_2_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
];
/** Pools for the third rival fight */
export const RIVAL_3_POOL: RivalPoolConfig = [
{ pool: SLOT_1_FINAL, postProcess: forceRivalStarterTraits },
{ pool: SLOT_2_FINAL, postProcess: forceRivalBirdAbility },
{
pool: SLOT_3_FIGHT_3,
postProcess: p => (p.level = SLOT_3_FIGHT_3_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
{ pool: SLOT_4_FIGHT_3, postProcess: postProcessSlot4Fight3, balanceTypes: true, balanceWeaknesses: true },
];
/** Pools for the fourth rival fight */
export const RIVAL_4_POOL: RivalPoolConfig = [
{ pool: SLOT_1_FINAL, postProcess: forceRivalStarterTraits },
{ pool: SLOT_2_FINAL, postProcess: forceRivalBirdAbility },
{
pool: SLOT_3_FINAL,
postProcess: p => (p.level = SLOT_3_FIGHT_4_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
{
pool: SLOT_4_FINAL,
postProcess: p => postProcessSlot4Fight4(p, SLOT_4_FIGHT_4_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
{ pool: SLOT_5_FINAL, postProcess: p => (p.level = SLOT_5_FIGHT_4_LEVEL) },
];
/** Pools for the fifth rival fight */
export const RIVAL_5_POOL: RivalPoolConfig = [
{ pool: SLOT_1_FINAL, postProcess: p => forceRivalStarterTraits(p, 2) },
{ pool: SLOT_2_FINAL, postProcess: forceRivalBirdAbility },
{
pool: SLOT_3_FINAL,
postProcess: p => (p.level = SLOT_3_FIGHT_5_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
{
pool: SLOT_4_FINAL,
postProcess: p => postProcessSlot4Fight4(p, SLOT_4_FIGHT_5_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
{ pool: SLOT_5_FINAL, postProcess: p => (p.level = SLOT_5_FIGHT_5_LEVEL) },
{ pool: SLOT_6_FINAL, postProcess: postProcessSlot6Fight5 },
];
/** Pools for the sixth rival fight */
export const RIVAL_6_POOL: RivalPoolConfig = [
{ pool: SLOT_1_FINAL, postProcess: p => forceRivalStarterTraits(p, 3) },
{ pool: SLOT_2_FINAL, postProcess: p => forceRivalBirdAbility(p, 2) },
{
pool: SLOT_3_FINAL,
postProcess: p => (p.level = SLOT_3_FIGHT_6_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
{
pool: SLOT_4_FINAL,
postProcess: p => postProcessSlot4Fight4(p, SLOT_4_FIGHT_6_LEVEL),
balanceTypes: true,
balanceWeaknesses: true,
},
{ pool: SLOT_5_FINAL, postProcess: p => (p.level = SLOT_5_FIGHT_6_LEVEL) },
{ pool: SLOT_6_FINAL, postProcess: postProcessSlot6Fight6 },
];

View File

@ -1,10 +1,7 @@
import { timedEventManager } from "#app/global-event-manager"; import { getRandomRivalPartyMemberFunc } from "#app/ai/rival-team-gen";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { pokemonEvolutions, pokemonPrevolutions } from "#balance/pokemon-evolutions";
import { signatureSpecies } from "#balance/signature-species"; import { signatureSpecies } from "#balance/signature-species";
import { tmSpecies } from "#balance/tms"; import { tmSpecies } from "#balance/tms";
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { RARE_EGG_MOVE_LEVEL_REQUIREMENT } from "#data/balance/moveset-generation";
import { modifierTypes } from "#data/data-lists"; import { modifierTypes } from "#data/data-lists";
import { doubleBattleDialogue } from "#data/double-battle-dialogue"; import { doubleBattleDialogue } from "#data/double-battle-dialogue";
import { Gender } from "#data/gender"; import { Gender } from "#data/gender";
@ -25,6 +22,14 @@ import { PokemonMove } from "#moves/pokemon-move";
import { getIsInitialized, initI18n } from "#plugins/i18n"; import { getIsInitialized, initI18n } from "#plugins/i18n";
import type { EvilTeam } from "#trainers/evil-admin-trainer-pools"; import type { EvilTeam } from "#trainers/evil-admin-trainer-pools";
import { evilAdminTrainerPools } from "#trainers/evil-admin-trainer-pools"; import { evilAdminTrainerPools } from "#trainers/evil-admin-trainer-pools";
import {
RIVAL_1_POOL,
RIVAL_2_POOL,
RIVAL_3_POOL,
RIVAL_4_POOL,
RIVAL_5_POOL,
RIVAL_6_POOL,
} from "#trainers/rival-party-config";
import { import {
getEvilGruntPartyTemplate, getEvilGruntPartyTemplate,
getGymLeaderPartyTemplate, getGymLeaderPartyTemplate,
@ -988,32 +993,30 @@ export class TrainerConfig {
} }
} }
let t = 0;
/** /**
* Randomly selects one of the `Species` from `speciesPool`, determines its evolution, level, and strength. * Randomly selects one of the `Species` from `speciesPool`, determines its evolution, level, and strength.
* Then adds Pokemon to globalScene. * Then adds Pokemon to globalScene.
* @param speciesPool * @param speciesPool - The pool of species to choose from. Can be a list of `SpeciesId` or a list of lists of `SpeciesId`.
* @param trainerSlot * @param trainerSlot - (default {@linkcode TrainerSlot.TRAINER | TRAINER}); The trainer slot to generate for.
* @param ignoreEvolution * @param ignoreEvolution - (default `false`); Whether to ignore evolution when determining the species to use.
* @param postProcess * @param postProcess - An optional function to post-process the generated `EnemyPokemon`
*/ */
export function getRandomPartyMemberFunc( export function getRandomPartyMemberFunc(
speciesPool: SpeciesId[], speciesPool: (SpeciesId | SpeciesId[])[],
trainerSlot: TrainerSlot = TrainerSlot.TRAINER, trainerSlot: TrainerSlot = TrainerSlot.TRAINER,
ignoreEvolution = false, ignoreEvolution = false,
postProcess?: (enemyPokemon: EnemyPokemon) => void, postProcess?: (enemyPokemon: EnemyPokemon) => void,
) { ): (level: number, strength: PartyMemberStrength) => EnemyPokemon {
return (level: number, strength: PartyMemberStrength) => { return (level: number, strength: PartyMemberStrength) => {
let species = randSeedItem(speciesPool); let species: SpeciesId | SpeciesId[] | typeof speciesPool = speciesPool;
do {
species = randSeedItem(species);
} while (Array.isArray(species));
if (!ignoreEvolution) { if (!ignoreEvolution) {
species = getPokemonSpecies(species).getTrainerSpeciesForLevel( species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength);
level,
true,
strength,
// TODO: What EvoLevelThresholdKind to use here?
);
} }
return globalScene.addEnemyPokemon( return globalScene.addEnemyPokemon(
getPokemonSpecies(species), getPokemonSpecies(species),
level, level,
@ -1026,6 +1029,7 @@ export function getRandomPartyMemberFunc(
}; };
} }
// biome-ignore lint/correctness/noUnusedVariables: potentially useful
function getSpeciesFilterRandomPartyMemberFunc( function getSpeciesFilterRandomPartyMemberFunc(
originalSpeciesFilter: PokemonSpeciesFilter, originalSpeciesFilter: PokemonSpeciesFilter,
trainerSlot: TrainerSlot = TrainerSlot.TRAINER, trainerSlot: TrainerSlot = TrainerSlot.TRAINER,
@ -1050,6 +1054,7 @@ function getSpeciesFilterRandomPartyMemberFunc(
}; };
} }
let t = 0;
export const trainerConfigs: TrainerConfigs = { export const trainerConfigs: TrainerConfigs = {
[TrainerType.UNKNOWN]: new TrainerConfig(0).setHasGenders(), [TrainerType.UNKNOWN]: new TrainerConfig(0).setHasGenders(),
[TrainerType.ACE_TRAINER]: new TrainerConfig(++t) [TrainerType.ACE_TRAINER]: new TrainerConfig(++t)
@ -4548,61 +4553,8 @@ export const trainerConfigs: TrainerConfigs = {
() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.SUPER_EXP_CHARM,
() => modifierTypes.EXP_SHARE, () => modifierTypes.EXP_SHARE,
) )
.setPartyMemberFunc( .setPartyMemberFunc(0, getRandomRivalPartyMemberFunc(RIVAL_1_POOL, 0))
0, .setPartyMemberFunc(1, getRandomRivalPartyMemberFunc(RIVAL_1_POOL, 1)),
getRandomPartyMemberFunc(
[
SpeciesId.BULBASAUR,
SpeciesId.CHARMANDER,
SpeciesId.SQUIRTLE,
SpeciesId.CHIKORITA,
SpeciesId.CYNDAQUIL,
SpeciesId.TOTODILE,
SpeciesId.TREECKO,
SpeciesId.TORCHIC,
SpeciesId.MUDKIP,
SpeciesId.TURTWIG,
SpeciesId.CHIMCHAR,
SpeciesId.PIPLUP,
SpeciesId.SNIVY,
SpeciesId.TEPIG,
SpeciesId.OSHAWOTT,
SpeciesId.CHESPIN,
SpeciesId.FENNEKIN,
SpeciesId.FROAKIE,
SpeciesId.ROWLET,
SpeciesId.LITTEN,
SpeciesId.POPPLIO,
SpeciesId.GROOKEY,
SpeciesId.SCORBUNNY,
SpeciesId.SOBBLE,
SpeciesId.SPRIGATITO,
SpeciesId.FUECOCO,
SpeciesId.QUAXLY,
],
TrainerSlot.TRAINER,
true,
p => (p.abilityIndex = 0),
),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc(
[
SpeciesId.PIDGEY,
SpeciesId.HOOTHOOT,
SpeciesId.TAILLOW,
SpeciesId.STARLY,
SpeciesId.PIDOVE,
SpeciesId.FLETCHLING,
SpeciesId.PIKIPEK,
SpeciesId.ROOKIDEE,
SpeciesId.WATTREL,
],
TrainerSlot.TRAINER,
true,
),
),
[TrainerType.RIVAL_2]: new TrainerConfig(++t) [TrainerType.RIVAL_2]: new TrainerConfig(++t)
.setName("Finn") .setName("Finn")
.setHasGenders("Ivy") .setHasGenders("Ivy")
@ -4615,70 +4567,9 @@ export const trainerConfigs: TrainerConfigs = {
.setMixedBattleBgm("battle_rival") .setMixedBattleBgm("battle_rival")
.setPartyTemplates(trainerPartyTemplates.RIVAL_2) .setPartyTemplates(trainerPartyTemplates.RIVAL_2)
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE) .setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
.setPartyMemberFunc( .setPartyMemberFunc(0, getRandomRivalPartyMemberFunc(RIVAL_2_POOL, 0))
0, .setPartyMemberFunc(1, getRandomRivalPartyMemberFunc(RIVAL_2_POOL, 1))
getRandomPartyMemberFunc( .setPartyMemberFunc(2, getRandomRivalPartyMemberFunc(RIVAL_2_POOL, 2)),
[
SpeciesId.IVYSAUR,
SpeciesId.CHARMELEON,
SpeciesId.WARTORTLE,
SpeciesId.BAYLEEF,
SpeciesId.QUILAVA,
SpeciesId.CROCONAW,
SpeciesId.GROVYLE,
SpeciesId.COMBUSKEN,
SpeciesId.MARSHTOMP,
SpeciesId.GROTLE,
SpeciesId.MONFERNO,
SpeciesId.PRINPLUP,
SpeciesId.SERVINE,
SpeciesId.PIGNITE,
SpeciesId.DEWOTT,
SpeciesId.QUILLADIN,
SpeciesId.BRAIXEN,
SpeciesId.FROGADIER,
SpeciesId.DARTRIX,
SpeciesId.TORRACAT,
SpeciesId.BRIONNE,
SpeciesId.THWACKEY,
SpeciesId.RABOOT,
SpeciesId.DRIZZILE,
SpeciesId.FLORAGATO,
SpeciesId.CROCALOR,
SpeciesId.QUAXWELL,
],
TrainerSlot.TRAINER,
true,
p => (p.abilityIndex = 0),
),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc(
[
SpeciesId.PIDGEOTTO,
SpeciesId.HOOTHOOT,
SpeciesId.TAILLOW,
SpeciesId.STARAVIA,
SpeciesId.TRANQUILL,
SpeciesId.FLETCHINDER,
SpeciesId.TRUMBEAK,
SpeciesId.CORVISQUIRE,
SpeciesId.WATTREL,
],
TrainerSlot.TRAINER,
true,
),
)
.setPartyMemberFunc(
2,
getSpeciesFilterRandomPartyMemberFunc(
(species: PokemonSpecies) =>
!pokemonEvolutions.hasOwnProperty(species.speciesId)
&& !pokemonPrevolutions.hasOwnProperty(species.speciesId)
&& species.baseTotal >= 450,
),
),
[TrainerType.RIVAL_3]: new TrainerConfig(++t) [TrainerType.RIVAL_3]: new TrainerConfig(++t)
.setName("Finn") .setName("Finn")
.setHasGenders("Ivy") .setHasGenders("Ivy")
@ -4690,71 +4581,10 @@ export const trainerConfigs: TrainerConfigs = {
.setBattleBgm("battle_rival") .setBattleBgm("battle_rival")
.setMixedBattleBgm("battle_rival") .setMixedBattleBgm("battle_rival")
.setPartyTemplates(trainerPartyTemplates.RIVAL_3) .setPartyTemplates(trainerPartyTemplates.RIVAL_3)
.setPartyMemberFunc( .setPartyMemberFunc(0, getRandomRivalPartyMemberFunc(RIVAL_3_POOL, 0))
0, .setPartyMemberFunc(1, getRandomRivalPartyMemberFunc(RIVAL_3_POOL, 1))
getRandomPartyMemberFunc( .setPartyMemberFunc(2, getRandomRivalPartyMemberFunc(RIVAL_3_POOL, 2))
[ .setPartyMemberFunc(3, getRandomRivalPartyMemberFunc(RIVAL_3_POOL, 3)),
SpeciesId.VENUSAUR,
SpeciesId.CHARIZARD,
SpeciesId.BLASTOISE,
SpeciesId.MEGANIUM,
SpeciesId.TYPHLOSION,
SpeciesId.FERALIGATR,
SpeciesId.SCEPTILE,
SpeciesId.BLAZIKEN,
SpeciesId.SWAMPERT,
SpeciesId.TORTERRA,
SpeciesId.INFERNAPE,
SpeciesId.EMPOLEON,
SpeciesId.SERPERIOR,
SpeciesId.EMBOAR,
SpeciesId.SAMUROTT,
SpeciesId.CHESNAUGHT,
SpeciesId.DELPHOX,
SpeciesId.GRENINJA,
SpeciesId.DECIDUEYE,
SpeciesId.INCINEROAR,
SpeciesId.PRIMARINA,
SpeciesId.RILLABOOM,
SpeciesId.CINDERACE,
SpeciesId.INTELEON,
SpeciesId.MEOWSCARADA,
SpeciesId.SKELEDIRGE,
SpeciesId.QUAQUAVAL,
],
TrainerSlot.TRAINER,
true,
p => (p.abilityIndex = 0),
),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc(
[
SpeciesId.PIDGEOT,
SpeciesId.NOCTOWL,
SpeciesId.SWELLOW,
SpeciesId.STARAPTOR,
SpeciesId.UNFEZANT,
SpeciesId.TALONFLAME,
SpeciesId.TOUCANNON,
SpeciesId.CORVIKNIGHT,
SpeciesId.KILOWATTREL,
],
TrainerSlot.TRAINER,
true,
),
)
.setPartyMemberFunc(
2,
getSpeciesFilterRandomPartyMemberFunc(
(species: PokemonSpecies) =>
!pokemonEvolutions.hasOwnProperty(species.speciesId)
&& !pokemonPrevolutions.hasOwnProperty(species.speciesId)
&& species.baseTotal >= 450,
),
)
.setSpeciesFilter(species => species.baseTotal >= 540),
[TrainerType.RIVAL_4]: new TrainerConfig(++t) [TrainerType.RIVAL_4]: new TrainerConfig(++t)
.setName("Finn") .setName("Finn")
.setHasGenders("Ivy") .setHasGenders("Ivy")
@ -4768,74 +4598,11 @@ export const trainerConfigs: TrainerConfigs = {
.setMixedBattleBgm("battle_rival_2") .setMixedBattleBgm("battle_rival_2")
.setPartyTemplates(trainerPartyTemplates.RIVAL_4) .setPartyTemplates(trainerPartyTemplates.RIVAL_4)
.setModifierRewardFuncs(() => modifierTypes.TERA_ORB) .setModifierRewardFuncs(() => modifierTypes.TERA_ORB)
.setPartyMemberFunc( .setPartyMemberFunc(0, getRandomRivalPartyMemberFunc(RIVAL_4_POOL, 0))
0, .setPartyMemberFunc(1, getRandomRivalPartyMemberFunc(RIVAL_4_POOL, 1))
getRandomPartyMemberFunc( .setPartyMemberFunc(2, getRandomRivalPartyMemberFunc(RIVAL_4_POOL, 2))
[ .setPartyMemberFunc(3, getRandomRivalPartyMemberFunc(RIVAL_4_POOL, 3))
SpeciesId.VENUSAUR, .setPartyMemberFunc(4, getRandomRivalPartyMemberFunc(RIVAL_4_POOL, 4))
SpeciesId.CHARIZARD,
SpeciesId.BLASTOISE,
SpeciesId.MEGANIUM,
SpeciesId.TYPHLOSION,
SpeciesId.FERALIGATR,
SpeciesId.SCEPTILE,
SpeciesId.BLAZIKEN,
SpeciesId.SWAMPERT,
SpeciesId.TORTERRA,
SpeciesId.INFERNAPE,
SpeciesId.EMPOLEON,
SpeciesId.SERPERIOR,
SpeciesId.EMBOAR,
SpeciesId.SAMUROTT,
SpeciesId.CHESNAUGHT,
SpeciesId.DELPHOX,
SpeciesId.GRENINJA,
SpeciesId.DECIDUEYE,
SpeciesId.INCINEROAR,
SpeciesId.PRIMARINA,
SpeciesId.RILLABOOM,
SpeciesId.CINDERACE,
SpeciesId.INTELEON,
SpeciesId.MEOWSCARADA,
SpeciesId.SKELEDIRGE,
SpeciesId.QUAQUAVAL,
],
TrainerSlot.TRAINER,
true,
p => {
p.abilityIndex = 0;
p.teraType = p.species.type1;
},
),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc(
[
SpeciesId.PIDGEOT,
SpeciesId.NOCTOWL,
SpeciesId.SWELLOW,
SpeciesId.STARAPTOR,
SpeciesId.UNFEZANT,
SpeciesId.TALONFLAME,
SpeciesId.TOUCANNON,
SpeciesId.CORVIKNIGHT,
SpeciesId.KILOWATTREL,
],
TrainerSlot.TRAINER,
true,
),
)
.setPartyMemberFunc(
2,
getSpeciesFilterRandomPartyMemberFunc(
(species: PokemonSpecies) =>
!pokemonEvolutions.hasOwnProperty(species.speciesId)
&& !pokemonPrevolutions.hasOwnProperty(species.speciesId)
&& species.baseTotal >= 450,
),
)
.setSpeciesFilter(species => species.baseTotal >= 540)
.setInstantTera(0), // Tera starter to primary type .setInstantTera(0), // Tera starter to primary type
[TrainerType.RIVAL_5]: new TrainerConfig(++t) [TrainerType.RIVAL_5]: new TrainerConfig(++t)
.setName("Finn") .setName("Finn")
@ -4844,89 +4611,17 @@ export const trainerConfigs: TrainerConfigs = {
.setTitle("Rival") .setTitle("Rival")
.setBoss() .setBoss()
.setStaticParty() .setStaticParty()
.setMoneyMultiplier(2.25) .setMoneyMultiplier(2.5)
.setEncounterBgm(TrainerType.RIVAL) .setEncounterBgm(TrainerType.RIVAL)
.setBattleBgm("battle_rival_3") .setBattleBgm("battle_rival_3")
.setMixedBattleBgm("battle_rival_3") .setMixedBattleBgm("battle_rival_3")
.setPartyTemplates(trainerPartyTemplates.RIVAL_5) .setPartyTemplates(trainerPartyTemplates.RIVAL_5)
.setPartyMemberFunc( .setPartyMemberFunc(0, getRandomRivalPartyMemberFunc(RIVAL_5_POOL, 0))
0, .setPartyMemberFunc(1, getRandomRivalPartyMemberFunc(RIVAL_5_POOL, 1))
getRandomPartyMemberFunc( .setPartyMemberFunc(2, getRandomRivalPartyMemberFunc(RIVAL_5_POOL, 2))
[ .setPartyMemberFunc(3, getRandomRivalPartyMemberFunc(RIVAL_5_POOL, 3))
SpeciesId.VENUSAUR, .setPartyMemberFunc(4, getRandomRivalPartyMemberFunc(RIVAL_5_POOL, 4))
SpeciesId.CHARIZARD, .setPartyMemberFunc(5, getRandomRivalPartyMemberFunc(RIVAL_5_POOL, 5))
SpeciesId.BLASTOISE,
SpeciesId.MEGANIUM,
SpeciesId.TYPHLOSION,
SpeciesId.FERALIGATR,
SpeciesId.SCEPTILE,
SpeciesId.BLAZIKEN,
SpeciesId.SWAMPERT,
SpeciesId.TORTERRA,
SpeciesId.INFERNAPE,
SpeciesId.EMPOLEON,
SpeciesId.SERPERIOR,
SpeciesId.EMBOAR,
SpeciesId.SAMUROTT,
SpeciesId.CHESNAUGHT,
SpeciesId.DELPHOX,
SpeciesId.GRENINJA,
SpeciesId.DECIDUEYE,
SpeciesId.INCINEROAR,
SpeciesId.PRIMARINA,
SpeciesId.RILLABOOM,
SpeciesId.CINDERACE,
SpeciesId.INTELEON,
SpeciesId.MEOWSCARADA,
SpeciesId.SKELEDIRGE,
SpeciesId.QUAQUAVAL,
],
TrainerSlot.TRAINER,
true,
p => {
p.setBoss(true, 2);
p.abilityIndex = 0;
p.teraType = p.species.type1;
},
),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc(
[
SpeciesId.PIDGEOT,
SpeciesId.NOCTOWL,
SpeciesId.SWELLOW,
SpeciesId.STARAPTOR,
SpeciesId.UNFEZANT,
SpeciesId.TALONFLAME,
SpeciesId.TOUCANNON,
SpeciesId.CORVIKNIGHT,
SpeciesId.KILOWATTREL,
],
TrainerSlot.TRAINER,
true,
),
)
.setPartyMemberFunc(
2,
getSpeciesFilterRandomPartyMemberFunc(
(species: PokemonSpecies) =>
!pokemonEvolutions.hasOwnProperty(species.speciesId)
&& !pokemonPrevolutions.hasOwnProperty(species.speciesId)
&& species.baseTotal >= 450,
),
)
.setSpeciesFilter(species => species.baseTotal >= 540)
.setPartyMemberFunc(
5,
getRandomPartyMemberFunc([SpeciesId.RAYQUAZA], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 3);
p.pokeball = PokeballType.MASTER_BALL;
p.shiny = timedEventManager.getClassicTrainerShinyChance() === 0;
p.variant = 1;
}),
)
.setInstantTera(0), // Tera starter to primary type .setInstantTera(0), // Tera starter to primary type
[TrainerType.RIVAL_6]: new TrainerConfig(++t) [TrainerType.RIVAL_6]: new TrainerConfig(++t)
.setName("Finn") .setName("Finn")
@ -4940,94 +4635,13 @@ export const trainerConfigs: TrainerConfigs = {
.setBattleBgm("battle_rival_3") .setBattleBgm("battle_rival_3")
.setMixedBattleBgm("battle_rival_3") .setMixedBattleBgm("battle_rival_3")
.setPartyTemplates(trainerPartyTemplates.RIVAL_6) .setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc( .setPartyMemberFunc(0, getRandomRivalPartyMemberFunc(RIVAL_6_POOL, 0))
0, .setPartyMemberFunc(1, getRandomRivalPartyMemberFunc(RIVAL_6_POOL, 1))
getRandomPartyMemberFunc( .setPartyMemberFunc(2, getRandomRivalPartyMemberFunc(RIVAL_6_POOL, 2))
[ .setPartyMemberFunc(3, getRandomRivalPartyMemberFunc(RIVAL_6_POOL, 3))
SpeciesId.VENUSAUR, .setPartyMemberFunc(4, getRandomRivalPartyMemberFunc(RIVAL_6_POOL, 4))
SpeciesId.CHARIZARD, .setPartyMemberFunc(5, getRandomRivalPartyMemberFunc(RIVAL_6_POOL, 5))
SpeciesId.BLASTOISE,
SpeciesId.MEGANIUM,
SpeciesId.TYPHLOSION,
SpeciesId.FERALIGATR,
SpeciesId.SCEPTILE,
SpeciesId.BLAZIKEN,
SpeciesId.SWAMPERT,
SpeciesId.TORTERRA,
SpeciesId.INFERNAPE,
SpeciesId.EMPOLEON,
SpeciesId.SERPERIOR,
SpeciesId.EMBOAR,
SpeciesId.SAMUROTT,
SpeciesId.CHESNAUGHT,
SpeciesId.DELPHOX,
SpeciesId.GRENINJA,
SpeciesId.DECIDUEYE,
SpeciesId.INCINEROAR,
SpeciesId.PRIMARINA,
SpeciesId.RILLABOOM,
SpeciesId.CINDERACE,
SpeciesId.INTELEON,
SpeciesId.MEOWSCARADA,
SpeciesId.SKELEDIRGE,
SpeciesId.QUAQUAVAL,
],
TrainerSlot.TRAINER,
true,
p => {
p.setBoss(true, 3);
p.abilityIndex = 0;
p.teraType = p.species.type1;
p.generateAndPopulateMoveset();
},
),
)
.setPartyMemberFunc(
1,
getRandomPartyMemberFunc(
[
SpeciesId.PIDGEOT,
SpeciesId.NOCTOWL,
SpeciesId.SWELLOW,
SpeciesId.STARAPTOR,
SpeciesId.UNFEZANT,
SpeciesId.TALONFLAME,
SpeciesId.TOUCANNON,
SpeciesId.CORVIKNIGHT,
SpeciesId.KILOWATTREL,
],
TrainerSlot.TRAINER,
true,
p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
},
),
)
.setPartyMemberFunc(
2,
getSpeciesFilterRandomPartyMemberFunc(
(species: PokemonSpecies) =>
!pokemonEvolutions.hasOwnProperty(species.speciesId)
&& !pokemonPrevolutions.hasOwnProperty(species.speciesId)
&& species.baseTotal >= 450,
),
)
.setSpeciesFilter(species => species.baseTotal >= 540)
.setPartyMemberFunc(
5,
getRandomPartyMemberFunc([SpeciesId.RAYQUAZA], TrainerSlot.TRAINER, true, p => {
p.setBoss();
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
p.shiny = timedEventManager.getClassicTrainerShinyChance() === 0;
p.variant = 1;
p.formIndex = 1; // Mega Rayquaza
p.generateName();
}),
)
.setInstantTera(0), // Tera starter to primary type .setInstantTera(0), // Tera starter to primary type
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig((t = TrainerType.ROCKET_BOSS_GIOVANNI_1)) [TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig((t = TrainerType.ROCKET_BOSS_GIOVANNI_1))
.setName("Giovanni") .setName("Giovanni")
.initForEvilTeamLeader("Rocket Boss", []) .initForEvilTeamLeader("Rocket Boss", [])

View File

@ -1304,7 +1304,6 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
const variableTypeAttr = move.getAttrs("VariableMoveTypeAttr")[0]; const variableTypeAttr = move.getAttrs("VariableMoveTypeAttr")[0];
const types = variableTypeAttr?.getTypesForItemSpawn(p, move) ?? [move.type]; const types = variableTypeAttr?.getTypesForItemSpawn(p, move) ?? [move.type];
for (const type of types) { for (const type of types) {
console.info("%cConsidering type " + PokemonType[type], "color: orange");
const currentWeight = attackMoveTypeWeights.get(type) ?? 0; const currentWeight = attackMoveTypeWeights.get(type) ?? 0;
if (currentWeight < 3) { if (currentWeight < 3) {
attackMoveTypeWeights.set(type, currentWeight + 1); attackMoveTypeWeights.set(type, currentWeight + 1);
@ -1319,11 +1318,9 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
} }
const randInt = randSeedInt(totalWeight); const randInt = randSeedInt(totalWeight);
console.log("%cTotal weight " + totalWeight + ", rolled " + randInt, "color: orange");
let weight = 0; let weight = 0;
for (const [type, typeWeight] of attackMoveTypeWeights.entries()) { for (const [type, typeWeight] of attackMoveTypeWeights.entries()) {
console.log("%cWeighted type " + PokemonType[type] + " with weight " + typeWeight, "color: orange");
if (randInt < weight + typeWeight) { if (randInt < weight + typeWeight) {
return new AttackTypeBoosterModifierType(type, TYPE_BOOST_ITEM_BOOST_PERCENT); return new AttackTypeBoosterModifierType(type, TYPE_BOOST_ITEM_BOOST_PERCENT);
} }

View File

@ -292,7 +292,10 @@ export class EncounterPhase extends BattlePhase {
} }
globalScene.ui.setMode(UiMode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
if (!this.loaded) { if (this.loaded) {
this.doEncounter();
globalScene.resetSeed();
} else {
this.trySetWeatherIfNewBiome(); // Set weather before session gets saved this.trySetWeatherIfNewBiome(); // Set weather before session gets saved
// Game syncs to server on waves X1 and X6 (As of 1.2.0) // Game syncs to server on waves X1 and X6 (As of 1.2.0)
globalScene.gameData globalScene.gameData
@ -305,9 +308,6 @@ export class EncounterPhase extends BattlePhase {
this.doEncounter(); this.doEncounter();
globalScene.resetSeed(); globalScene.resetSeed();
}); });
} else {
this.doEncounter();
globalScene.resetSeed();
} }
}); });
}); });
@ -490,36 +490,25 @@ export class EncounterPhase extends BattlePhase {
this.end(); this.end();
}; };
if (showEncounterMessage) { const introDialogue = encounter.dialogue.intro;
const introDialogue = encounter.dialogue.intro; if (showEncounterMessage && introDialogue) {
if (!introDialogue) { const FIRST_DIALOGUE_PROMPT_DELAY = 750;
doShowEncounterOptions(); let i = 0;
} else { const showNextDialogue = () => {
const FIRST_DIALOGUE_PROMPT_DELAY = 750; const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue;
let i = 0; const dialogue = introDialogue[i];
const showNextDialogue = () => { const title = getEncounterText(dialogue?.speaker);
const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue; const text = getEncounterText(dialogue.text)!;
const dialogue = introDialogue[i]; i++;
const title = getEncounterText(dialogue?.speaker); if (title) {
const text = getEncounterText(dialogue.text)!; globalScene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0);
i++; } else {
if (title) { globalScene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
globalScene.ui.showDialogue(
text,
title,
null,
nextAction,
0,
i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0,
);
} else {
globalScene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
};
if (introDialogue.length > 0) {
showNextDialogue();
} }
};
if (introDialogue.length > 0) {
showNextDialogue();
} }
} else { } else {
doShowEncounterOptions(); doShowEncounterOptions();
@ -528,13 +517,13 @@ export class EncounterPhase extends BattlePhase {
const encounterMessage = i18next.t("battle:mysteryEncounterAppeared"); const encounterMessage = i18next.t("battle:mysteryEncounterAppeared");
if (!encounterMessage) { if (encounterMessage) {
doEncounter();
} else {
doTrainerExclamation(); doTrainerExclamation();
globalScene.ui.showDialogue(encounterMessage, "???", null, () => { globalScene.ui.showDialogue(encounterMessage, "???", null, () => {
globalScene.charSprite.hide().then(() => globalScene.hideFieldOverlay(250).then(() => doEncounter())); globalScene.charSprite.hide().then(() => globalScene.hideFieldOverlay(250).then(() => doEncounter()));
}); });
} else {
doEncounter();
} }
} }
} }