Merge branch beta into ability-copy

This commit is contained in:
Dean 2025-03-29 16:18:13 -07:00
commit 503afd66b0
75 changed files with 1342 additions and 925 deletions

View File

@ -50,7 +50,8 @@
"noUndeclaredVariables": "off", "noUndeclaredVariables": "off",
"noUnusedVariables": "error", "noUnusedVariables": "error",
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error "noSwitchDeclarations": "warn", // TODO: refactor and make this an error
"noVoidTypeReturn": "warn" // TODO: Refactor and make this an error "noVoidTypeReturn": "warn", // TODO: Refactor and make this an error
"noUnusedImports": "error"
}, },
"style": { "style": {
"noVar": "error", "noVar": "error",

View File

@ -72,8 +72,8 @@ import { GameModes, getGameMode } from "#app/game-mode";
import FieldSpritePipeline from "#app/pipelines/field-sprite"; import FieldSpritePipeline from "#app/pipelines/field-sprite";
import SpritePipeline from "#app/pipelines/sprite"; import SpritePipeline from "#app/pipelines/sprite";
import PartyExpBar from "#app/ui/party-exp-bar"; import PartyExpBar from "#app/ui/party-exp-bar";
import type { TrainerSlot } from "#app/data/trainer-config"; import type { TrainerSlot } from "./enums/trainer-slot";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer, { TrainerVariant } from "#app/field/trainer";
import type TrainerData from "#app/system/trainer-data"; import type TrainerData from "#app/system/trainer-data";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";

View File

@ -5,7 +5,7 @@ import Trainer, { TrainerVariant } from "./field/trainer";
import type { GameMode } from "./game-mode"; import type { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";

View File

@ -1734,6 +1734,62 @@ export class PostAttackAbAttr extends AbAttr {
args: any[]): void {} args: any[]): void {}
} }
/**
* Multiplies a Stat from an ally pokemon's ability.
* @see {@link applyAllyStatMultiplierAbAttrs}
* @see {@link applyAllyStat}
*/
export class AllyStatMultiplierAbAttr extends AbAttr {
private stat: BattleStat;
private multiplier: number;
private ignorable: boolean;
/**
* @param stat - The stat being modified
* @param multipler - The multiplier to apply to the stat
* @param ignorable - Whether the multiplier can be ignored by mold breaker-like moves and abilities
*/
constructor(stat: BattleStat, multiplier: number, ignorable: boolean = true) {
super(false);
this.stat = stat;
this.multiplier = multiplier;
this.ignorable = ignorable;
}
/**
* Multiply a Pokemon's Stat due to an Ally's ability.
* @param _pokemon - The ally {@linkcode Pokemon} with the ability (unused)
* @param passive - unused
* @param _simulated - Whether the ability is being simulated (unused)
* @param _stat - The type of the checked {@linkcode Stat} (unused)
* @param statValue - {@linkcode Utils.NumberHolder} containing the value of the checked stat
* @param _checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused)
* @param _ignoreAbility - Whether the ability should be ignored if possible
* @param _args - unused
* @returns `true` if this changed the checked stat, `false` otherwise.
*/
applyAllyStat(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _stat: BattleStat, statValue: Utils.NumberHolder, _checkedPokemon: Pokemon, _ignoreAbility: boolean, _args: any[]) {
statValue.value *= this.multiplier;
}
/**
* Check if this ability can apply to the checked stat.
* @param pokemon - The ally {@linkcode Pokemon} with the ability (unused)
* @param passive - unused
* @param simulated - Whether the ability is being simulated (unused)
* @param stat - The type of the checked {@linkcode Stat}
* @param statValue - {@linkcode Utils.NumberHolder} containing the value of the checked stat
* @param checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused)
* @param ignoreAbility - Whether the ability should be ignored if possible
* @param args - unused
* @returns `true` if this can apply to the checked stat, `false` otherwise.
*/
canApplyAllyStat(pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, ignoreAbility: boolean, args: any[]): boolean {
return stat === this.stat && !(ignoreAbility && this.ignorable);
}
}
/** /**
* Ability attribute for Gorilla Tactics * Ability attribute for Gorilla Tactics
* @extends PostAttackAbAttr * @extends PostAttackAbAttr
@ -5598,6 +5654,30 @@ export function applyStatMultiplierAbAttrs(
args, args,
); );
} }
/**
* Applies an ally's Stat multiplier attribute
* @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being
* @param pokemon - The {@linkcode Pokemon} with the ability
* @param stat - The type of the checked {@linkcode Stat}
* @param statValue - {@linkcode Utils.NumberHolder} containing the value of the checked stat
* @param checkedPokemon - The {@linkcode Pokemon} with the checked stat
* @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move.
* @param args - unused
*/
export function applyAllyStatMultiplierAbAttrs(attrType: Constructor<AllyStatMultiplierAbAttr>,
pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, checkedPokemon: Pokemon, ignoreAbility: boolean, ...args: any[]
): void {
return applyAbAttrsInternal<AllyStatMultiplierAbAttr>(
attrType,
pokemon,
(attr, passive) => attr.applyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args),
(attr, passive) => attr.canApplyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignoreAbility, args),
args,
simulated,
);
}
export function applyPostSetStatusAbAttrs( export function applyPostSetStatusAbAttrs(
attrType: Constructor<PostSetStatusAbAttr>, attrType: Constructor<PostSetStatusAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
@ -5610,7 +5690,8 @@ export function applyPostSetStatusAbAttrs(
attrType, attrType,
pokemon, pokemon,
(attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
(attr, passive) => attr.canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, (attr, passive) => attr.canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
args,
simulated, simulated,
); );
} }
@ -6441,12 +6522,13 @@ export function initAbilities() {
new Ability(Abilities.FLOWER_GIFT, 4) new Ability(Abilities.FLOWER_GIFT, 4)
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 1.5) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 1.5)
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.SPDEF, 1.5) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.SPDEF, 1.5)
.uncopiable() .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.ATK, 1.5)
.unreplaceable() .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.SPDEF, 1.5)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FLOWER_GIFT) .attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FLOWER_GIFT)
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ]) .attr(PostWeatherChangeFormChangeAbAttr, Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
.partial() // Should also boosts stats of ally .uncopiable()
.unreplaceable()
.ignorable(), .ignorable(),
new Ability(Abilities.BAD_DREAMS, 4) new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr), .attr(PostTurnHurtIfSleepingAbAttr),
@ -6581,7 +6663,7 @@ export function initAbilities() {
.bypassFaint(), .bypassFaint(),
new Ability(Abilities.VICTORY_STAR, 5) new Ability(Abilities.VICTORY_STAR, 5)
.attr(StatMultiplierAbAttr, Stat.ACC, 1.1) .attr(StatMultiplierAbAttr, Stat.ACC, 1.1)
.partial(), // Does not boost ally's accuracy .attr(AllyStatMultiplierAbAttr, Stat.ACC, 1.1, false),
new Ability(Abilities.TURBOBLAZE, 5) new Ability(Abilities.TURBOBLAZE, 5)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(MoveAbilityBypassAbAttr), .attr(MoveAbilityBypassAbAttr),

View File

@ -1644,7 +1644,9 @@ export class ContactDamageProtectedTag extends ProtectedTag {
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon(); const attacker = effectPhase.getPokemon();
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT,
});
} }
} }
} }
@ -1970,7 +1972,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
let highestStat: EffectiveStat; let highestStat: EffectiveStat;
EFFECTIVE_STATS.map(s => EFFECTIVE_STATS.map(s =>
pokemon.getEffectiveStat(s, undefined, undefined, undefined, undefined, undefined, undefined, true), pokemon.getEffectiveStat(s, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true),
).reduce((highestValue: number, value: number, i: number) => { ).reduce((highestValue: number, value: number, i: number) => {
if (value > highestValue) { if (value > highestValue) {
highestStat = EFFECTIVE_STATS[i]; highestStat = EFFECTIVE_STATS[i];
@ -2149,6 +2151,21 @@ export class TypeBoostTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
return lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); return lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
} }
override onAdd(pokemon: Pokemon): void {
globalScene.queueMessage(
i18next.t("abilityTriggers:typeImmunityPowerBoost", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: i18next.t(`pokemonInfo:Type.${PokemonType[this.boostedType]}`),
}),
);
}
override onOverlap(pokemon: Pokemon): void {
globalScene.queueMessage(
i18next.t("abilityTriggers:moveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }),
);
}
} }
export class CritBoostTag extends BattlerTag { export class CritBoostTag extends BattlerTag {
@ -2240,7 +2257,9 @@ export class SaltCuredTag extends BattlerTag {
if (!cancelled.value) { if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER); const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
pokemon.damageAndUpdate(toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8), {
result: HitResult.INDIRECT,
});
globalScene.queueMessage( globalScene.queueMessage(
i18next.t("battlerTags:saltCuredLapse", { i18next.t("battlerTags:saltCuredLapse", {

View File

@ -1,6 +1,6 @@
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { trainerConfigs } from "./trainer-config"; import { trainerConfigs } from "./trainers/trainer-config";
export interface TrainerTypeMessages { export interface TrainerTypeMessages {
encounter?: string | string[]; encounter?: string | string[];

View File

@ -902,7 +902,7 @@ export default class Move implements Localizable {
SacrificialAttrOnHit SacrificialAttrOnHit
]; ];
// ...and cannot enhance these specific moves. // ...and cannot enhance these specific moves
const exceptMoves: Moves[] = [ const exceptMoves: Moves[] = [
Moves.FLING, Moves.FLING,
Moves.UPROAR, Moves.UPROAR,
@ -911,10 +911,14 @@ export default class Move implements Localizable {
Moves.ENDEAVOR Moves.ENDEAVOR
]; ];
// ...and cannot enhance Pollen Puff when targeting an ally.
const exceptPollenPuffAlly: boolean = this.id === Moves.POLLEN_PUFF && targets.includes(user.getAlly().getBattlerIndex())
return (!restrictSpread || !isMultiTarget) return (!restrictSpread || !isMultiTarget)
&& !this.isChargingMove() && !this.isChargingMove()
&& !exceptAttrs.some(attr => this.hasAttr(attr)) && !exceptAttrs.some(attr => this.hasAttr(attr))
&& !exceptMoves.some(id => this.id === id) && !exceptMoves.some(id => this.id === id)
&& !exceptPollenPuffAlly
&& this.category !== MoveCategory.STATUS; && this.category !== MoveCategory.STATUS;
} }
} }
@ -1577,10 +1581,6 @@ export class SurviveDamageAttr extends ModifiedDamageAttr {
return Math.min(damage, target.hp - 1); return Math.min(damage, target.hp - 1);
} }
getCondition(): MoveConditionFunc {
return (user, target, move) => target.hp > 1;
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return target.hp > 1 ? 0 : -20; return target.hp > 1 ? 0 : -20;
} }
@ -4795,8 +4795,8 @@ export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.NumberHolder); const category = (args[0] as Utils.NumberHolder);
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true); const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true, true, true);
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true); const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true, true, true);
if (predictedPhysDmg > predictedSpecDmg) { if (predictedPhysDmg > predictedSpecDmg) {
category.value = MoveCategory.PHYSICAL; category.value = MoveCategory.PHYSICAL;

View File

@ -6,7 +6,7 @@ import {
setEncounterRewards, setEncounterRewards,
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";

View File

@ -31,7 +31,7 @@ import {
catchPokemon, catchPokemon,
getHighestLevelPlayerPokemon, getHighestLevelPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type { BerryType } from "#enums/berry-type"; import type { BerryType } from "#enums/berry-type";

View File

@ -9,13 +9,10 @@ import {
setEncounterRewards, setEncounterRewards,
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import { getRandomPartyMemberFunc, trainerConfigs } from "#app/data/trainers/trainer-config";
getRandomPartyMemberFunc, import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
trainerConfigs, import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
TrainerPartyCompoundTemplate, import { TrainerSlot } from "#enums/trainer-slot";
TrainerPartyTemplate,
TrainerSlot,
} from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -8,7 +8,9 @@ import {
setEncounterRewards, setEncounterRewards,
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";

View File

@ -20,7 +20,7 @@ import {
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";

View File

@ -10,7 +10,7 @@ import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#app/field/pokemon"; import { FieldPosition } from "#app/field/pokemon";

View File

@ -3,7 +3,7 @@ import {
selectPokemonForOption, selectPokemonForOption,
setEncounterRewards, setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings"; import { MusicPreference } from "#app/system/settings/settings";
import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import type { ModifierTypeOption } from "#app/modifier/modifier-type";

View File

@ -3,12 +3,10 @@ import {
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
setEncounterRewards, setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import { trainerConfigs } from "#app/data/trainers/trainer-config";
trainerConfigs, import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate";
TrainerPartyCompoundTemplate, import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
TrainerPartyTemplate, import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
trainerPartyTemplates,
} from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -10,7 +10,7 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";

View File

@ -24,7 +24,7 @@ import { Biome } from "#enums/biome";
import { getBiomeKey } from "#app/field/arena"; import { getBiomeKey } from "#app/field/arena";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type"; import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";

View File

@ -4,7 +4,7 @@ import {
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
setEncounterRewards, setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { randSeedShuffle } from "#app/utils"; import { randSeedShuffle } from "#app/utils";
@ -30,7 +30,6 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theExpertPokemonBreeder"; const namespace = "mysteryEncounters/theExpertPokemonBreeder";

View File

@ -41,7 +41,8 @@ import { TrainerType } from "#enums/trainer-type";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { trainerConfigs, TrainerPartyTemplate } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
/** i18n namespace for encounter */ /** i18n namespace for encounter */

View File

@ -332,7 +332,6 @@ export function initMysteryEncounters() {
}); });
// Add ANY biome encounters to biome map // Add ANY biome encounters to biome map
// eslint-disable-next-line
let _encounterBiomeTableLog = ""; let _encounterBiomeTableLog = "";
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => { mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
anyBiomeEncounters.forEach(encounter => { anyBiomeEncounters.forEach(encounter => {

View File

@ -43,8 +43,9 @@ import type { Moves } from "#enums/moves";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { Status } from "#app/data/status-effect"; import { Status } from "#app/data/status-effect";
import type { TrainerConfig } from "#app/data/trainer-config"; import type { TrainerConfig } from "#app/data/trainers/trainer-config";
import { trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { TrainerSlot } from "#enums/trainer-slot";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import type { IEggOptions } from "#app/data/egg"; import type { IEggOptions } from "#app/data/egg";
import { Egg } from "#app/data/egg"; import { Egg } from "#app/data/egg";

View File

@ -7,7 +7,7 @@ import i18next from "i18next";
import type { AnySound } from "#app/battle-scene"; import type { AnySound } from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { DexAttr, DexEntry, type StarterMoveset } from "#app/system/game-data"; import { DexAttr, type StarterMoveset } from "#app/system/game-data";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import { uncatchableSpecies } from "#app/data/balance/biomes"; import { uncatchableSpecies } from "#app/data/balance/biomes";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";

View File

@ -214,7 +214,7 @@ const commonSplashMessages = [
"bornToBeAWinner", "bornToBeAWinner",
"onARollout", "onARollout",
"itsAlwaysNightDeepInTheAbyss", "itsAlwaysNightDeepInTheAbyss",
"folksThisIsInsane" "folksThisIsInsane",
]; ];
//#region Seasonal Messages //#region Seasonal Messages
@ -224,10 +224,7 @@ const seasonalSplashMessages: Season[] = [
name: "New Year's", name: "New Year's",
start: "01-01", start: "01-01",
end: "01-15", end: "01-15",
messages: [ messages: ["newYears.happyNewYear", "newYears.andAHappyNewYear"],
"newYears.happyNewYear",
"newYears.andAHappyNewYear"
],
}, },
{ {
name: "Valentines", name: "Valentines",
@ -239,7 +236,7 @@ const seasonalSplashMessages: Season[] = [
"valentines.applinForYou", "valentines.applinForYou",
"valentines.thePowerOfLoveIsThreeThirtyBST", "valentines.thePowerOfLoveIsThreeThirtyBST",
"valentines.haveAHeartScale", "valentines.haveAHeartScale",
"valentines.i<3You" "valentines.i<3You",
], ],
}, },
{ {
@ -279,7 +276,7 @@ const seasonalSplashMessages: Season[] = [
"aprilFools.nowWithQuickTimeEncounters", "aprilFools.nowWithQuickTimeEncounters",
"aprilFools.timeYourInputsForHigherCatchrate", "aprilFools.timeYourInputsForHigherCatchrate",
"aprilFools.certifiedButtonSimulator", "aprilFools.certifiedButtonSimulator",
"aprilFools.iHopeYouGetSuckerPunched" "aprilFools.iHopeYouGetSuckerPunched",
], ],
}, },
{ {
@ -293,7 +290,7 @@ const seasonalSplashMessages: Season[] = [
"halloween.mayContainSpiders", "halloween.mayContainSpiders",
"halloween.spookyScarySkeledirge", "halloween.spookyScarySkeledirge",
"halloween.gourgeistUsedTrickOrTreat", "halloween.gourgeistUsedTrickOrTreat",
"halloween.letsSnuggleForever" "halloween.letsSnuggleForever",
], ],
}, },
{ {
@ -316,7 +313,7 @@ const seasonalSplashMessages: Season[] = [
"winterHoliday.tisTheSeasonToBeSpeSpa", "winterHoliday.tisTheSeasonToBeSpeSpa",
"winterHoliday.deckTheHalls", "winterHoliday.deckTheHalls",
"winterHoliday.saveScummingGetsYouOnTheNaughtyList", "winterHoliday.saveScummingGetsYouOnTheNaughtyList",
"winterHoliday.badTrainersGetRolycoly" "winterHoliday.badTrainersGetRolycoly",
], ],
}, },
]; ];

View File

@ -0,0 +1,255 @@
import { startingWave } from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import { PartyMemberStrength } from "#enums/party-member-strength";
export class TrainerPartyTemplate {
public size: number;
public strength: PartyMemberStrength;
public sameSpecies: boolean;
public balanced: boolean;
constructor(size: number, strength: PartyMemberStrength, sameSpecies?: boolean, balanced?: boolean) {
this.size = size;
this.strength = strength;
this.sameSpecies = !!sameSpecies;
this.balanced = !!balanced;
}
getStrength(_index: number): PartyMemberStrength {
return this.strength;
}
isSameSpecies(_index: number): boolean {
return this.sameSpecies;
}
isBalanced(_index: number): boolean {
return this.balanced;
}
}
export class TrainerPartyCompoundTemplate extends TrainerPartyTemplate {
public templates: TrainerPartyTemplate[];
constructor(...templates: TrainerPartyTemplate[]) {
super(
templates.reduce((total: number, template: TrainerPartyTemplate) => {
total += template.size;
return total;
}, 0),
PartyMemberStrength.AVERAGE,
);
this.templates = templates;
}
getStrength(index: number): PartyMemberStrength {
let t = 0;
for (const template of this.templates) {
if (t + template.size > index) {
return template.getStrength(index - t);
}
t += template.size;
}
return super.getStrength(index);
}
isSameSpecies(index: number): boolean {
let t = 0;
for (const template of this.templates) {
if (t + template.size > index) {
return template.isSameSpecies(index - t);
}
t += template.size;
}
return super.isSameSpecies(index);
}
isBalanced(index: number): boolean {
let t = 0;
for (const template of this.templates) {
if (t + template.size > index) {
return template.isBalanced(index - t);
}
t += template.size;
}
return super.isBalanced(index);
}
}
export const trainerPartyTemplates = {
ONE_WEAK_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
ONE_AVG: new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
ONE_AVG_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
ONE_STRONG: new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
ONE_STRONGER: new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
TWO_WEAKER: new TrainerPartyTemplate(2, PartyMemberStrength.WEAKER),
TWO_WEAK: new TrainerPartyTemplate(2, PartyMemberStrength.WEAK),
TWO_WEAK_ONE_AVG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
TWO_WEAK_SAME_ONE_AVG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK, true),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
TWO_WEAK_SAME_TWO_WEAK_SAME: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK, true),
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK, true),
),
TWO_WEAK_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
TWO_AVG: new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
TWO_AVG_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
TWO_AVG_SAME_ONE_AVG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
TWO_AVG_SAME_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
TWO_AVG_SAME_TWO_AVG_SAME: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
),
TWO_STRONG: new TrainerPartyTemplate(2, PartyMemberStrength.STRONG),
THREE_WEAK: new TrainerPartyTemplate(3, PartyMemberStrength.WEAK),
THREE_WEAK_SAME: new TrainerPartyTemplate(3, PartyMemberStrength.WEAK, true),
THREE_AVG: new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE),
THREE_AVG_SAME: new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, true),
THREE_WEAK_BALANCED: new TrainerPartyTemplate(3, PartyMemberStrength.WEAK, false, true),
FOUR_WEAKER: new TrainerPartyTemplate(4, PartyMemberStrength.WEAKER),
FOUR_WEAKER_SAME: new TrainerPartyTemplate(4, PartyMemberStrength.WEAKER, true),
FOUR_WEAK: new TrainerPartyTemplate(4, PartyMemberStrength.WEAK),
FOUR_WEAK_SAME: new TrainerPartyTemplate(4, PartyMemberStrength.WEAK, true),
FOUR_WEAK_BALANCED: new TrainerPartyTemplate(4, PartyMemberStrength.WEAK, false, true),
FIVE_WEAKER: new TrainerPartyTemplate(5, PartyMemberStrength.WEAKER),
FIVE_WEAK: new TrainerPartyTemplate(5, PartyMemberStrength.WEAK),
FIVE_WEAK_BALANCED: new TrainerPartyTemplate(5, PartyMemberStrength.WEAK, false, true),
SIX_WEAKER: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER),
SIX_WEAKER_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),
GYM_LEADER_1: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
GYM_LEADER_2: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
GYM_LEADER_3: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
GYM_LEADER_4: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
GYM_LEADER_5: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(2, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
ELITE_FOUR: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(3, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
CHAMPION: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(4, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(2, PartyMemberStrength.STRONGER, false, true),
),
RIVAL: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
RIVAL_2: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK, false, true),
),
RIVAL_3: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK, false, true),
),
RIVAL_4: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK, false, true),
),
RIVAL_5: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
RIVAL_6: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
};
/**
* The function to get variable strength grunts
* @returns the correct TrainerPartyTemplate
*/
export function getEvilGruntPartyTemplate(): TrainerPartyTemplate {
const waveIndex = globalScene.currentBattle?.waveIndex;
if (waveIndex < 40) {
return trainerPartyTemplates.TWO_AVG;
}
if (waveIndex < 63) {
return trainerPartyTemplates.THREE_AVG;
}
if (waveIndex < 65) {
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
}
if (waveIndex < 112) {
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
}
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
}
export function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) {
const { currentBattle, gameMode } = globalScene;
const wave = gameMode.getWaveForDifficulty(currentBattle?.waveIndex || startingWave, true);
const templateIndex = Math.ceil((wave - 20) / 30);
return templates[Phaser.Math.Clamp(templateIndex, 0, templates.length - 1)];
}
export function getGymLeaderPartyTemplate() {
return getWavePartyTemplate(
trainerPartyTemplates.GYM_LEADER_1,
trainerPartyTemplates.GYM_LEADER_2,
trainerPartyTemplates.GYM_LEADER_3,
trainerPartyTemplates.GYM_LEADER_4,
trainerPartyTemplates.GYM_LEADER_5,
);
}

View File

@ -0,0 +1,436 @@
import type { TrainerTierPools } from "#app/data/trainers/typedefs";
import { TrainerPoolTier } from "#enums/trainer-pool-tier";
import { Species } from "#enums/species";
/** Team Rocket's admin trainer pool. */
const ROCKET: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.RATICATE,
Species.ARBOK,
Species.VILEPLUME,
Species.ARCANINE,
Species.GENGAR,
Species.HYPNO,
Species.ELECTRODE,
Species.EXEGGUTOR,
Species.CUBONE,
Species.KOFFING,
Species.GYARADOS,
Species.CROBAT,
Species.STEELIX,
Species.HOUNDOOM,
Species.HONCHKROW,
],
[TrainerPoolTier.UNCOMMON]: [
Species.OMASTAR,
Species.KABUTOPS,
Species.MAGNEZONE,
Species.ELECTIVIRE,
Species.MAGMORTAR,
Species.PORYGON_Z,
Species.ANNIHILAPE,
Species.ALOLA_SANDSLASH,
Species.ALOLA_PERSIAN,
Species.ALOLA_GOLEM,
Species.ALOLA_MUK,
Species.PALDEA_TAUROS,
],
[TrainerPoolTier.RARE]: [Species.DRAGONITE, Species.TYRANITAR],
};
/** Team Magma's admin trainer pool */
const MAGMA: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.ARCANINE,
Species.MAGCARGO,
Species.HOUNDOOM,
Species.TORKOAL,
Species.SOLROCK,
Species.CLAYDOL,
Species.HIPPOWDON,
Species.MAGMORTAR,
Species.GLISCOR,
Species.COALOSSAL,
],
[TrainerPoolTier.UNCOMMON]: [
Species.AGGRON,
Species.FLYGON,
Species.CRADILY,
Species.ARMALDO,
Species.RHYPERIOR,
Species.TURTONATOR,
Species.SANDACONDA,
Species.TOEDSCRUEL,
Species.HISUI_ARCANINE,
],
[TrainerPoolTier.RARE]: [Species.CHARCADET, Species.SCOVILLAIN],
};
const AQUA: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.TENTACRUEL,
Species.LANTURN,
Species.AZUMARILL,
Species.QUAGSIRE,
Species.OCTILLERY,
Species.LUDICOLO,
Species.PELIPPER,
Species.WAILORD,
Species.WHISCASH,
Species.CRAWDAUNT,
Species.WALREIN,
Species.CLAMPERL,
],
[TrainerPoolTier.UNCOMMON]: [
Species.QUAGSIRE,
Species.MANTINE,
Species.KINGDRA,
Species.MILOTIC,
Species.DRAGALGE,
Species.DHELMISE,
Species.BARRASKEWDA,
Species.GRAPPLOCT,
Species.OVERQWIL,
],
[TrainerPoolTier.RARE]: [Species.BASCULEGION, Species.DONDOZO],
};
const GALACTIC: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.ELECTRODE,
Species.GYARADOS,
Species.CROBAT,
Species.HONCHKROW,
Species.BRONZONG,
Species.DRAPION,
Species.LICKILICKY,
Species.TANGROWTH,
Species.ELECTIVIRE,
Species.MAGMORTAR,
Species.YANMEGA,
Species.MAMOSWINE,
],
[TrainerPoolTier.UNCOMMON]: [
Species.ALAKAZAM,
Species.WEAVILE,
Species.GLISCOR,
Species.DUSKNOIR,
Species.ROTOM,
Species.OVERQWIL,
Species.HISUI_ARCANINE,
Species.HISUI_ELECTRODE,
],
[TrainerPoolTier.RARE]: [Species.SPIRITOMB, Species.URSALUNA, Species.SNEASLER, Species.HISUI_LILLIGANT],
};
const PLASMA_ZINZOLIN: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.GIGALITH,
Species.CONKELDURR,
Species.SEISMITOAD,
Species.KROOKODILE,
Species.DARMANITAN,
Species.COFAGRIGUS,
Species.VANILLUXE,
Species.AMOONGUSS,
Species.JELLICENT,
Species.GALVANTULA,
Species.FERROTHORN,
Species.BEARTIC,
],
[TrainerPoolTier.UNCOMMON]: [
Species.EXCADRILL,
Species.SIGILYPH,
Species.ZOROARK,
Species.KLINKLANG,
Species.EELEKTROSS,
Species.MIENSHAO,
Species.GOLURK,
Species.BISHARP,
Species.MANDIBUZZ,
Species.DURANT,
Species.GALAR_DARMANITAN,
],
[TrainerPoolTier.RARE]: [Species.HAXORUS, Species.HYDREIGON, Species.HISUI_ZOROARK, Species.HISUI_BRAVIARY],
};
const PLASMA_COLRESS: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.MUK,
Species.ELECTRODE,
Species.BRONZONG,
Species.MAGNEZONE,
Species.PORYGON_Z,
Species.MUSHARNA,
Species.REUNICLUS,
Species.GALVANTULA,
Species.FERROTHORN,
Species.EELEKTROSS,
Species.BEHEEYEM,
],
[TrainerPoolTier.UNCOMMON]: [
Species.METAGROSS,
Species.ROTOM,
Species.CARRACOSTA,
Species.ARCHEOPS,
Species.GOLURK,
Species.DURANT,
Species.VIKAVOLT,
Species.ORBEETLE,
Species.REVAVROOM,
Species.ALOLA_MUK,
Species.HISUI_ELECTRODE,
],
[TrainerPoolTier.RARE]: [Species.ELECTIVIRE, Species.MAGMORTAR, Species.BISHARP, Species.ARCHALUDON],
};
const FLARE: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.MANECTRIC,
Species.DRAPION,
Species.LIEPARD,
Species.AMOONGUSS,
Species.DIGGERSBY,
Species.TALONFLAME,
Species.PYROAR,
Species.PANGORO,
Species.MEOWSTIC,
Species.MALAMAR,
Species.CLAWITZER,
Species.HELIOLISK,
],
[TrainerPoolTier.UNCOMMON]: [
Species.HOUNDOOM,
Species.WEAVILE,
Species.CHANDELURE,
Species.AEGISLASH,
Species.BARBARACLE,
Species.DRAGALGE,
Species.GOODRA,
Species.TREVENANT,
Species.GOURGEIST,
],
[TrainerPoolTier.RARE]: [Species.NOIVERN, Species.HISUI_GOODRA, Species.HISUI_AVALUGG],
};
const AETHER: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.ALAKAZAM,
Species.SLOWBRO,
Species.EXEGGUTOR,
Species.XATU,
Species.CLAYDOL,
Species.BEHEEYEM,
Species.ORANGURU,
Species.BRUXISH,
Species.ORBEETLE,
Species.FARIGIRAF,
Species.ALOLA_RAICHU,
],
[TrainerPoolTier.UNCOMMON]: [
Species.KIRLIA,
Species.MEDICHAM,
Species.METAGROSS,
Species.MALAMAR,
Species.HATTERENE,
Species.MR_RIME,
Species.GALAR_SLOWKING,
],
[TrainerPoolTier.RARE]: [Species.PORYGON_Z, Species.ARMAROUGE, Species.HISUI_BRAVIARY],
};
const SKULL: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.NIDOQUEEN,
Species.GENGAR,
Species.KOFFING,
Species.CROBAT,
Species.ROSERADE,
Species.SKUNTANK,
Species.TOXICROAK,
Species.SCOLIPEDE,
Species.TOXAPEX,
Species.LURANTIS,
Species.ALOLA_MUK,
],
[TrainerPoolTier.UNCOMMON]: [
Species.DRAPION,
Species.MANDIBUZZ,
Species.OVERQWIL,
Species.GLIMMORA,
Species.CLODSIRE,
Species.GALAR_SLOWBRO,
],
[TrainerPoolTier.RARE]: [Species.DRAGALGE, Species.SNEASLER],
};
const MACRO_COSMOS: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.NINETALES,
Species.BELLOSSOM,
Species.MILOTIC,
Species.FROSLASS,
Species.GOTHITELLE,
Species.JELLICENT,
Species.SALAZZLE,
Species.TSAREENA,
Species.POLTEAGEIST,
Species.HATTERENE,
Species.GALAR_RAPIDASH,
],
[TrainerPoolTier.UNCOMMON]: [
Species.TOGEKISS,
Species.MANDIBUZZ,
Species.TOXAPEX,
Species.APPLETUN,
Species.CURSOLA,
Species.ALOLA_NINETALES,
],
[TrainerPoolTier.RARE]: [Species.TINKATON, Species.HISUI_LILLIGANT],
};
const STAR_DARK: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.SHIFTRY,
Species.CACTURNE,
Species.HONCHKROW,
Species.SKUNTANK,
Species.KROOKODILE,
Species.OBSTAGOON,
Species.LOKIX,
Species.MABOSSTIFF,
],
[TrainerPoolTier.UNCOMMON]: [
Species.UMBREON,
Species.CRAWDAUNT,
Species.WEAVILE,
Species.ZOROARK,
Species.MALAMAR,
Species.BOMBIRDIER,
],
[TrainerPoolTier.RARE]: [Species.HYDREIGON, Species.MEOWSCARADA],
};
const STAR_FIRE: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.ARCANINE,
Species.HOUNDOOM,
Species.CAMERUPT,
Species.CHANDELURE,
Species.TALONFLAME,
Species.PYROAR,
Species.COALOSSAL,
Species.SCOVILLAIN,
],
[TrainerPoolTier.UNCOMMON]: [
Species.RAPIDASH,
Species.FLAREON,
Species.TORKOAL,
Species.MAGMORTAR,
Species.SALAZZLE,
Species.TURTONATOR,
],
[TrainerPoolTier.RARE]: [Species.VOLCARONA, Species.SKELEDIRGE],
};
const STAR_POISON: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.MUK,
Species.CROBAT,
Species.SKUNTANK,
Species.AMOONGUSS,
Species.TOXAPEX,
Species.TOXTRICITY,
Species.GRAFAIAI,
Species.CLODSIRE,
],
[TrainerPoolTier.UNCOMMON]: [
Species.GENGAR,
Species.SEVIPER,
Species.DRAGALGE,
Species.OVERQWIL,
Species.ALOLA_MUK,
Species.GALAR_SLOWBRO,
],
[TrainerPoolTier.RARE]: [Species.GLIMMORA, Species.VENUSAUR],
};
const STAR_FAIRY: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.CLEFABLE,
Species.WIGGLYTUFF,
Species.AZUMARILL,
Species.WHIMSICOTT,
Species.FLORGES,
Species.HATTERENE,
Species.GRIMMSNARL,
Species.TINKATON,
],
[TrainerPoolTier.UNCOMMON]: [
Species.TOGEKISS,
Species.GARDEVOIR,
Species.SYLVEON,
Species.KLEFKI,
Species.MIMIKYU,
Species.ALOLA_NINETALES,
],
[TrainerPoolTier.RARE]: [Species.GALAR_RAPIDASH, Species.PRIMARINA],
};
const STAR_FIGHTING: TrainerTierPools = {
[TrainerPoolTier.COMMON]: [
Species.BRELOOM,
Species.HARIYAMA,
Species.MEDICHAM,
Species.TOXICROAK,
Species.SCRAFTY,
Species.MIENSHAO,
Species.PAWMOT,
Species.PALDEA_TAUROS,
],
[TrainerPoolTier.UNCOMMON]: [
Species.LUCARIO,
Species.CONKELDURR,
Species.HAWLUCHA,
Species.PASSIMIAN,
Species.FALINKS,
Species.FLAMIGO,
],
[TrainerPoolTier.RARE]: [Species.KOMMO_O, Species.QUAQUAVAL],
};
export type EvilTeam =
| "rocket"
| "magma"
| "aqua"
| "galactic"
| "plasma_zinzolin"
| "plasma_colress"
| "flare"
| "aether"
| "skull"
| "macro_cosmos"
| "star_dark"
| "star_fire"
| "star_poison"
| "star_fairy"
| "star_fighting";
/** Trainer pools for each evil admin team */
export const evilAdminTrainerPools: Record<EvilTeam, TrainerTierPools> = {
rocket: ROCKET,
magma: MAGMA,
aqua: AQUA,
galactic: GALACTIC,
plasma_zinzolin: PLASMA_ZINZOLIN,
plasma_colress: PLASMA_COLRESS,
flare: FLARE,
aether: AETHER,
macro_cosmos: MACRO_COSMOS,
skull: SKULL,
star_dark: STAR_DARK,
star_fire: STAR_FIRE,
star_poison: STAR_POISON,
star_fairy: STAR_FAIRY,
star_fighting: STAR_FIGHTING,
};

View File

@ -1,29 +1,53 @@
import { startingWave } from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import type { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import { PokeballType } from "#enums/pokeball";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms"; import { tmSpecies } from "#app/data/balance/tms";
import { PokemonType } from "#enums/pokemon-type";
import { doubleBattleDialogue } from "#app/data/dialogue"; import { doubleBattleDialogue } from "#app/data/dialogue";
import type { PersistentModifier } from "#app/modifier/modifier";
import { TrainerVariant } from "#app/field/trainer"; import { TrainerVariant } from "#app/field/trainer";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";
import { Moves } from "#enums/moves"; import { Gender } from "#app/data/gender";
import { signatureSpecies } from "../balance/signature-species";
import {
getEvilGruntPartyTemplate,
getGymLeaderPartyTemplate,
getWavePartyTemplate,
TrainerPartyCompoundTemplate,
TrainerPartyTemplate,
trainerPartyTemplates,
} from "./TrainerPartyTemplate";
import { evilAdminTrainerPools } from "./evil-admin-trainer-pools";
// Enum imports
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { PokeballType } from "#enums/pokeball";
import { Gender } from "#app/data/gender"; import { PokemonType } from "#enums/pokemon-type";
import { signatureSpecies } from "./balance/signature-species"; import { Moves } from "#enums/moves";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { TeraAIMode } from "#enums/tera-ai-mode";
import { TrainerPoolTier } from "#enums/trainer-pool-tier";
import { TrainerSlot } from "#enums/trainer-slot";
import { TrainerType } from "#enums/trainer-type";
// Type imports
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
import type { EnemyPokemon } from "#app/field/pokemon";
import type { EvilTeam } from "./evil-admin-trainer-pools";
import type {
PartyMemberFunc,
GenModifiersFunc,
GenAIFunc,
PartyTemplateFunc,
TrainerTierPools,
TrainerConfigs,
PartyMemberFuncs,
} from "./typedefs";
/** Minimum BST for Pokemon generated onto the Elite Four's teams */ /** Minimum BST for Pokemon generated onto the Elite Four's teams */
const ELITE_FOUR_MINIMUM_BST = 460; const ELITE_FOUR_MINIMUM_BST = 460;
@ -31,253 +55,6 @@ const ELITE_FOUR_MINIMUM_BST = 460;
/** The wave at which (non-Paldean) Gym Leaders start having Tera mons*/ /** The wave at which (non-Paldean) Gym Leaders start having Tera mons*/
const GYM_LEADER_TERA_WAVE = 100; const GYM_LEADER_TERA_WAVE = 100;
export enum TrainerPoolTier {
COMMON,
UNCOMMON,
RARE,
SUPER_RARE,
ULTRA_RARE,
}
export interface TrainerTierPools {
[key: number]: Species[];
}
export enum TrainerSlot {
NONE,
TRAINER,
TRAINER_PARTNER,
}
export class TrainerPartyTemplate {
public size: number;
public strength: PartyMemberStrength;
public sameSpecies: boolean;
public balanced: boolean;
constructor(size: number, strength: PartyMemberStrength, sameSpecies?: boolean, balanced?: boolean) {
this.size = size;
this.strength = strength;
this.sameSpecies = !!sameSpecies;
this.balanced = !!balanced;
}
getStrength(_index: number): PartyMemberStrength {
return this.strength;
}
isSameSpecies(_index: number): boolean {
return this.sameSpecies;
}
isBalanced(_index: number): boolean {
return this.balanced;
}
}
export class TrainerPartyCompoundTemplate extends TrainerPartyTemplate {
public templates: TrainerPartyTemplate[];
constructor(...templates: TrainerPartyTemplate[]) {
super(
templates.reduce((total: number, template: TrainerPartyTemplate) => {
total += template.size;
return total;
}, 0),
PartyMemberStrength.AVERAGE,
);
this.templates = templates;
}
getStrength(index: number): PartyMemberStrength {
let t = 0;
for (const template of this.templates) {
if (t + template.size > index) {
return template.getStrength(index - t);
}
t += template.size;
}
return super.getStrength(index);
}
isSameSpecies(index: number): boolean {
let t = 0;
for (const template of this.templates) {
if (t + template.size > index) {
return template.isSameSpecies(index - t);
}
t += template.size;
}
return super.isSameSpecies(index);
}
isBalanced(index: number): boolean {
let t = 0;
for (const template of this.templates) {
if (t + template.size > index) {
return template.isBalanced(index - t);
}
t += template.size;
}
return super.isBalanced(index);
}
}
export const trainerPartyTemplates = {
ONE_WEAK_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
ONE_AVG: new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
ONE_AVG_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
ONE_STRONG: new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
ONE_STRONGER: new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
TWO_WEAKER: new TrainerPartyTemplate(2, PartyMemberStrength.WEAKER),
TWO_WEAK: new TrainerPartyTemplate(2, PartyMemberStrength.WEAK),
TWO_WEAK_ONE_AVG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
TWO_WEAK_SAME_ONE_AVG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK, true),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
TWO_WEAK_SAME_TWO_WEAK_SAME: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK, true),
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK, true),
),
TWO_WEAK_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.WEAK),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
TWO_AVG: new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
TWO_AVG_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
TWO_AVG_SAME_ONE_AVG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
TWO_AVG_SAME_ONE_STRONG: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
TWO_AVG_SAME_TWO_AVG_SAME: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, true),
),
TWO_STRONG: new TrainerPartyTemplate(2, PartyMemberStrength.STRONG),
THREE_WEAK: new TrainerPartyTemplate(3, PartyMemberStrength.WEAK),
THREE_WEAK_SAME: new TrainerPartyTemplate(3, PartyMemberStrength.WEAK, true),
THREE_AVG: new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE),
THREE_AVG_SAME: new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, true),
THREE_WEAK_BALANCED: new TrainerPartyTemplate(3, PartyMemberStrength.WEAK, false, true),
FOUR_WEAKER: new TrainerPartyTemplate(4, PartyMemberStrength.WEAKER),
FOUR_WEAKER_SAME: new TrainerPartyTemplate(4, PartyMemberStrength.WEAKER, true),
FOUR_WEAK: new TrainerPartyTemplate(4, PartyMemberStrength.WEAK),
FOUR_WEAK_SAME: new TrainerPartyTemplate(4, PartyMemberStrength.WEAK, true),
FOUR_WEAK_BALANCED: new TrainerPartyTemplate(4, PartyMemberStrength.WEAK, false, true),
FIVE_WEAKER: new TrainerPartyTemplate(5, PartyMemberStrength.WEAKER),
FIVE_WEAK: new TrainerPartyTemplate(5, PartyMemberStrength.WEAK),
FIVE_WEAK_BALANCED: new TrainerPartyTemplate(5, PartyMemberStrength.WEAK, false, true),
SIX_WEAKER: new TrainerPartyTemplate(6, PartyMemberStrength.WEAKER),
SIX_WEAKER_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),
GYM_LEADER_1: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
GYM_LEADER_2: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
GYM_LEADER_3: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
GYM_LEADER_4: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
GYM_LEADER_5: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(2, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
ELITE_FOUR: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(3, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
CHAMPION: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(4, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(2, PartyMemberStrength.STRONGER, false, true),
),
RIVAL: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
),
RIVAL_2: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK, false, true),
),
RIVAL_3: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK, false, true),
),
RIVAL_4: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.WEAK, false, true),
),
RIVAL_5: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
),
RIVAL_6: new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE),
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, false, true),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
),
};
type PartyTemplateFunc = () => TrainerPartyTemplate;
type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
type GenAIFunc = (party: EnemyPokemon[]) => void;
export interface PartyMemberFuncs {
[key: number]: PartyMemberFunc;
}
export enum TeraAIMode {
NO_TERA,
INSTANT_TERA,
SMART_TERA,
}
/** /**
* Stores data and helper functions about a trainers AI options. * Stores data and helper functions about a trainers AI options.
*/ */
@ -759,430 +536,6 @@ export class TrainerConfig {
return this; return this;
} }
/**
* Returns the pool of species for an evil team admin
* @param team - The evil team the admin belongs to.
* @returns {TrainerTierPools}
*/
speciesPoolPerEvilTeamAdmin(team): TrainerTierPools {
team = team.toLowerCase();
switch (team) {
case "rocket": {
return {
[TrainerPoolTier.COMMON]: [
Species.RATICATE,
Species.ARBOK,
Species.VILEPLUME,
Species.ARCANINE,
Species.GENGAR,
Species.HYPNO,
Species.ELECTRODE,
Species.EXEGGUTOR,
Species.CUBONE,
Species.KOFFING,
Species.GYARADOS,
Species.CROBAT,
Species.STEELIX,
Species.HOUNDOOM,
Species.HONCHKROW,
],
[TrainerPoolTier.UNCOMMON]: [
Species.OMASTAR,
Species.KABUTOPS,
Species.MAGNEZONE,
Species.ELECTIVIRE,
Species.MAGMORTAR,
Species.PORYGON_Z,
Species.ANNIHILAPE,
Species.ALOLA_SANDSLASH,
Species.ALOLA_PERSIAN,
Species.ALOLA_GOLEM,
Species.ALOLA_MUK,
Species.PALDEA_TAUROS,
],
[TrainerPoolTier.RARE]: [Species.DRAGONITE, Species.TYRANITAR],
};
}
case "magma": {
return {
[TrainerPoolTier.COMMON]: [
Species.ARCANINE,
Species.MAGCARGO,
Species.HOUNDOOM,
Species.TORKOAL,
Species.SOLROCK,
Species.CLAYDOL,
Species.HIPPOWDON,
Species.MAGMORTAR,
Species.GLISCOR,
Species.COALOSSAL,
],
[TrainerPoolTier.UNCOMMON]: [
Species.AGGRON,
Species.FLYGON,
Species.CRADILY,
Species.ARMALDO,
Species.RHYPERIOR,
Species.TURTONATOR,
Species.SANDACONDA,
Species.TOEDSCRUEL,
Species.HISUI_ARCANINE,
],
[TrainerPoolTier.RARE]: [Species.CHARCADET, Species.SCOVILLAIN],
};
}
case "aqua": {
return {
[TrainerPoolTier.COMMON]: [
Species.TENTACRUEL,
Species.LANTURN,
Species.AZUMARILL,
Species.QUAGSIRE,
Species.OCTILLERY,
Species.LUDICOLO,
Species.PELIPPER,
Species.WAILORD,
Species.WHISCASH,
Species.CRAWDAUNT,
Species.WALREIN,
Species.CLAMPERL,
],
[TrainerPoolTier.UNCOMMON]: [
Species.QUAGSIRE,
Species.MANTINE,
Species.KINGDRA,
Species.MILOTIC,
Species.DRAGALGE,
Species.DHELMISE,
Species.BARRASKEWDA,
Species.GRAPPLOCT,
Species.OVERQWIL,
],
[TrainerPoolTier.RARE]: [Species.BASCULEGION, Species.DONDOZO],
};
}
case "galactic": {
return {
[TrainerPoolTier.COMMON]: [
Species.ELECTRODE,
Species.GYARADOS,
Species.CROBAT,
Species.HONCHKROW,
Species.BRONZONG,
Species.DRAPION,
Species.LICKILICKY,
Species.TANGROWTH,
Species.ELECTIVIRE,
Species.MAGMORTAR,
Species.YANMEGA,
Species.MAMOSWINE,
],
[TrainerPoolTier.UNCOMMON]: [
Species.ALAKAZAM,
Species.WEAVILE,
Species.GLISCOR,
Species.DUSKNOIR,
Species.ROTOM,
Species.OVERQWIL,
Species.HISUI_ARCANINE,
Species.HISUI_ELECTRODE,
],
[TrainerPoolTier.RARE]: [Species.SPIRITOMB, Species.URSALUNA, Species.SNEASLER, Species.HISUI_LILLIGANT],
};
}
case "plasma": {
return {
[TrainerPoolTier.COMMON]: [
Species.GIGALITH,
Species.CONKELDURR,
Species.SEISMITOAD,
Species.KROOKODILE,
Species.DARMANITAN,
Species.COFAGRIGUS,
Species.VANILLUXE,
Species.AMOONGUSS,
Species.JELLICENT,
Species.GALVANTULA,
Species.FERROTHORN,
Species.BEARTIC,
],
[TrainerPoolTier.UNCOMMON]: [
Species.EXCADRILL,
Species.SIGILYPH,
Species.ZOROARK,
Species.KLINKLANG,
Species.EELEKTROSS,
Species.MIENSHAO,
Species.GOLURK,
Species.BISHARP,
Species.MANDIBUZZ,
Species.DURANT,
Species.GALAR_DARMANITAN,
],
[TrainerPoolTier.RARE]: [Species.HAXORUS, Species.HYDREIGON, Species.HISUI_ZOROARK, Species.HISUI_BRAVIARY],
};
}
case "plasma_2": {
return {
[TrainerPoolTier.COMMON]: [
Species.MUK,
Species.ELECTRODE,
Species.BRONZONG,
Species.MAGNEZONE,
Species.PORYGON_Z,
Species.MUSHARNA,
Species.REUNICLUS,
Species.GALVANTULA,
Species.FERROTHORN,
Species.EELEKTROSS,
Species.BEHEEYEM,
],
[TrainerPoolTier.UNCOMMON]: [
Species.METAGROSS,
Species.ROTOM,
Species.CARRACOSTA,
Species.ARCHEOPS,
Species.GOLURK,
Species.DURANT,
Species.VIKAVOLT,
Species.ORBEETLE,
Species.REVAVROOM,
Species.ALOLA_MUK,
Species.HISUI_ELECTRODE,
],
[TrainerPoolTier.RARE]: [Species.ELECTIVIRE, Species.MAGMORTAR, Species.BISHARP, Species.ARCHALUDON],
};
}
case "flare": {
return {
[TrainerPoolTier.COMMON]: [
Species.MANECTRIC,
Species.DRAPION,
Species.LIEPARD,
Species.AMOONGUSS,
Species.DIGGERSBY,
Species.TALONFLAME,
Species.PYROAR,
Species.PANGORO,
Species.MEOWSTIC,
Species.MALAMAR,
Species.CLAWITZER,
Species.HELIOLISK,
],
[TrainerPoolTier.UNCOMMON]: [
Species.HOUNDOOM,
Species.WEAVILE,
Species.CHANDELURE,
Species.AEGISLASH,
Species.BARBARACLE,
Species.DRAGALGE,
Species.GOODRA,
Species.TREVENANT,
Species.GOURGEIST,
],
[TrainerPoolTier.RARE]: [Species.NOIVERN, Species.HISUI_GOODRA, Species.HISUI_AVALUGG],
};
}
case "aether": {
return {
[TrainerPoolTier.COMMON]: [
Species.ALAKAZAM,
Species.SLOWBRO,
Species.EXEGGUTOR,
Species.XATU,
Species.CLAYDOL,
Species.BEHEEYEM,
Species.ORANGURU,
Species.BRUXISH,
Species.ORBEETLE,
Species.FARIGIRAF,
Species.ALOLA_RAICHU,
],
[TrainerPoolTier.UNCOMMON]: [
Species.KIRLIA,
Species.MEDICHAM,
Species.METAGROSS,
Species.MALAMAR,
Species.HATTERENE,
Species.MR_RIME,
Species.GALAR_SLOWKING,
],
[TrainerPoolTier.RARE]: [Species.PORYGON_Z, Species.ARMAROUGE, Species.HISUI_BRAVIARY],
};
}
case "skull": {
return {
[TrainerPoolTier.COMMON]: [
Species.NIDOQUEEN,
Species.GENGAR,
Species.KOFFING,
Species.CROBAT,
Species.ROSERADE,
Species.SKUNTANK,
Species.TOXICROAK,
Species.SCOLIPEDE,
Species.TOXAPEX,
Species.LURANTIS,
Species.ALOLA_MUK,
],
[TrainerPoolTier.UNCOMMON]: [
Species.DRAPION,
Species.MANDIBUZZ,
Species.OVERQWIL,
Species.GLIMMORA,
Species.CLODSIRE,
Species.GALAR_SLOWBRO,
],
[TrainerPoolTier.RARE]: [Species.DRAGALGE, Species.SNEASLER],
};
}
case "macro": {
return {
[TrainerPoolTier.COMMON]: [
Species.NINETALES,
Species.BELLOSSOM,
Species.MILOTIC,
Species.FROSLASS,
Species.GOTHITELLE,
Species.JELLICENT,
Species.SALAZZLE,
Species.TSAREENA,
Species.POLTEAGEIST,
Species.HATTERENE,
Species.GALAR_RAPIDASH,
],
[TrainerPoolTier.UNCOMMON]: [
Species.TOGEKISS,
Species.MANDIBUZZ,
Species.TOXAPEX,
Species.APPLETUN,
Species.CURSOLA,
Species.ALOLA_NINETALES,
],
[TrainerPoolTier.RARE]: [Species.TINKATON, Species.HISUI_LILLIGANT],
};
}
case "star_1": {
return {
[TrainerPoolTier.COMMON]: [
Species.SHIFTRY,
Species.CACTURNE,
Species.HONCHKROW,
Species.SKUNTANK,
Species.KROOKODILE,
Species.OBSTAGOON,
Species.LOKIX,
Species.MABOSSTIFF,
],
[TrainerPoolTier.UNCOMMON]: [
Species.UMBREON,
Species.CRAWDAUNT,
Species.WEAVILE,
Species.ZOROARK,
Species.MALAMAR,
Species.BOMBIRDIER,
],
[TrainerPoolTier.RARE]: [Species.HYDREIGON, Species.MEOWSCARADA],
};
}
case "star_2": {
return {
[TrainerPoolTier.COMMON]: [
Species.ARCANINE,
Species.HOUNDOOM,
Species.CAMERUPT,
Species.CHANDELURE,
Species.TALONFLAME,
Species.PYROAR,
Species.COALOSSAL,
Species.SCOVILLAIN,
],
[TrainerPoolTier.UNCOMMON]: [
Species.RAPIDASH,
Species.FLAREON,
Species.TORKOAL,
Species.MAGMORTAR,
Species.SALAZZLE,
Species.TURTONATOR,
],
[TrainerPoolTier.RARE]: [Species.VOLCARONA, Species.SKELEDIRGE],
};
}
case "star_3": {
return {
[TrainerPoolTier.COMMON]: [
Species.MUK,
Species.CROBAT,
Species.SKUNTANK,
Species.AMOONGUSS,
Species.TOXAPEX,
Species.TOXTRICITY,
Species.GRAFAIAI,
Species.CLODSIRE,
],
[TrainerPoolTier.UNCOMMON]: [
Species.GENGAR,
Species.SEVIPER,
Species.DRAGALGE,
Species.OVERQWIL,
Species.ALOLA_MUK,
Species.GALAR_SLOWBRO,
],
[TrainerPoolTier.RARE]: [Species.GLIMMORA, Species.VENUSAUR],
};
}
case "star_4": {
return {
[TrainerPoolTier.COMMON]: [
Species.CLEFABLE,
Species.WIGGLYTUFF,
Species.AZUMARILL,
Species.WHIMSICOTT,
Species.FLORGES,
Species.HATTERENE,
Species.GRIMMSNARL,
Species.TINKATON,
],
[TrainerPoolTier.UNCOMMON]: [
Species.TOGEKISS,
Species.GARDEVOIR,
Species.SYLVEON,
Species.KLEFKI,
Species.MIMIKYU,
Species.ALOLA_NINETALES,
],
[TrainerPoolTier.RARE]: [Species.GALAR_RAPIDASH, Species.PRIMARINA],
};
}
case "star_5": {
return {
[TrainerPoolTier.COMMON]: [
Species.BRELOOM,
Species.HARIYAMA,
Species.MEDICHAM,
Species.TOXICROAK,
Species.SCRAFTY,
Species.MIENSHAO,
Species.PAWMOT,
Species.PALDEA_TAUROS,
],
[TrainerPoolTier.UNCOMMON]: [
Species.LUCARIO,
Species.CONKELDURR,
Species.HAWLUCHA,
Species.PASSIMIAN,
Species.FALINKS,
Species.FLAMIGO,
],
[TrainerPoolTier.RARE]: [Species.KOMMO_O, Species.QUAQUAVAL],
};
}
}
console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`);
return [];
}
/** /**
* Initializes the trainer configuration for an evil team admin. * Initializes the trainer configuration for an evil team admin.
* @param title The title of the evil team admin. * @param title The title of the evil team admin.
@ -1193,7 +546,7 @@ export class TrainerConfig {
* **/ * **/
initForEvilTeamAdmin( initForEvilTeamAdmin(
title: string, title: string,
poolName: string, poolName: EvilTeam,
signatureSpecies: (Species | Species[])[], signatureSpecies: (Species | Species[])[],
specialtyType?: PokemonType, specialtyType?: PokemonType,
): TrainerConfig { ): TrainerConfig {
@ -1208,7 +561,7 @@ export class TrainerConfig {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
// Set the species pools for the evil team admin. // Set the species pools for the evil team admin.
this.speciesPools = this.speciesPoolPerEvilTeamAdmin(poolName); this.speciesPools = evilAdminTrainerPools[poolName];
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { if (!Array.isArray(speciesPool)) {
@ -1637,56 +990,6 @@ export class TrainerConfig {
let t = 0; let t = 0;
interface TrainerConfigs {
[key: number]: TrainerConfig;
}
/**
* The function to get variable strength grunts
* @returns the correct TrainerPartyTemplate
*/
function getEvilGruntPartyTemplate(): TrainerPartyTemplate {
const waveIndex = globalScene.currentBattle?.waveIndex;
if (waveIndex < 40) {
return trainerPartyTemplates.TWO_AVG;
}
if (waveIndex < 63) {
return trainerPartyTemplates.THREE_AVG;
}
if (waveIndex < 65) {
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
}
if (waveIndex < 112) {
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
}
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
}
function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) {
return templates[
Math.min(
Math.max(
Math.ceil(
(globalScene.gameMode.getWaveForDifficulty(globalScene.currentBattle?.waveIndex || startingWave, true) - 20) /
30,
),
0,
),
templates.length - 1,
)
];
}
function getGymLeaderPartyTemplate() {
return getWavePartyTemplate(
trainerPartyTemplates.GYM_LEADER_1,
trainerPartyTemplates.GYM_LEADER_2,
trainerPartyTemplates.GYM_LEADER_3,
trainerPartyTemplates.GYM_LEADER_4,
trainerPartyTemplates.GYM_LEADER_5,
);
}
/** /**
* 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.
@ -2878,7 +2181,7 @@ export const trainerConfigs: TrainerConfigs = {
}), }),
[TrainerType.ZINZOLIN]: new TrainerConfig(++t) [TrainerType.ZINZOLIN]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("plasma_sage", "plasma", [Species.CRYOGONAL]) .initForEvilTeamAdmin("plasma_sage", "plasma_zinzolin", [Species.CRYOGONAL])
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_plasma_grunt") .setBattleBgm("battle_plasma_grunt")
.setMixedBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_plasma_grunt")
@ -2886,7 +2189,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), .setPartyTemplateFunc(() => getEvilGruntPartyTemplate()),
[TrainerType.COLRESS]: new TrainerConfig(++t) [TrainerType.COLRESS]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("plasma_boss", "plasma_2", [Species.KLINKLANG]) .initForEvilTeamAdmin("plasma_boss", "plasma_colress", [Species.KLINKLANG])
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_colress") .setBattleBgm("battle_colress")
.setMixedBattleBgm("battle_colress") .setMixedBattleBgm("battle_colress")
@ -3105,7 +2408,7 @@ export const trainerConfigs: TrainerConfigs = {
}), }),
[TrainerType.OLEANA]: new TrainerConfig(++t) [TrainerType.OLEANA]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("macro_admin", "macro", [Species.GARBODOR]) .initForEvilTeamAdmin("macro_admin", "macro_cosmos", [Species.GARBODOR])
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_plasma_grunt") .setBattleBgm("battle_plasma_grunt")
.setMixedBattleBgm("battle_oleana") .setMixedBattleBgm("battle_oleana")
@ -3172,7 +2475,7 @@ export const trainerConfigs: TrainerConfigs = {
}), }),
[TrainerType.GIACOMO]: new TrainerConfig(++t) [TrainerType.GIACOMO]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("star_admin", "star_1", [Species.KINGAMBIT], PokemonType.DARK) .initForEvilTeamAdmin("star_admin", "star_dark", [Species.KINGAMBIT], PokemonType.DARK)
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_plasma_grunt") .setBattleBgm("battle_plasma_grunt")
.setMixedBattleBgm("battle_star_admin") .setMixedBattleBgm("battle_star_admin")
@ -3192,7 +2495,7 @@ export const trainerConfigs: TrainerConfigs = {
), ),
[TrainerType.MELA]: new TrainerConfig(++t) [TrainerType.MELA]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("star_admin", "star_2", [Species.ARMAROUGE], PokemonType.FIRE) .initForEvilTeamAdmin("star_admin", "star_fire", [Species.ARMAROUGE], PokemonType.FIRE)
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_plasma_grunt") .setBattleBgm("battle_plasma_grunt")
.setMixedBattleBgm("battle_star_admin") .setMixedBattleBgm("battle_star_admin")
@ -3212,7 +2515,7 @@ export const trainerConfigs: TrainerConfigs = {
), ),
[TrainerType.ATTICUS]: new TrainerConfig(++t) [TrainerType.ATTICUS]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("star_admin", "star_3", [Species.REVAVROOM], PokemonType.POISON) .initForEvilTeamAdmin("star_admin", "star_poison", [Species.REVAVROOM], PokemonType.POISON)
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_plasma_grunt") .setBattleBgm("battle_plasma_grunt")
.setMixedBattleBgm("battle_star_admin") .setMixedBattleBgm("battle_star_admin")
@ -3232,7 +2535,7 @@ export const trainerConfigs: TrainerConfigs = {
), ),
[TrainerType.ORTEGA]: new TrainerConfig(++t) [TrainerType.ORTEGA]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("star_admin", "star_4", [Species.DACHSBUN], PokemonType.FAIRY) .initForEvilTeamAdmin("star_admin", "star_fairy", [Species.DACHSBUN], PokemonType.FAIRY)
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_plasma_grunt") .setBattleBgm("battle_plasma_grunt")
.setMixedBattleBgm("battle_star_admin") .setMixedBattleBgm("battle_star_admin")
@ -3252,7 +2555,7 @@ export const trainerConfigs: TrainerConfigs = {
), ),
[TrainerType.ERI]: new TrainerConfig(++t) [TrainerType.ERI]: new TrainerConfig(++t)
.setMoneyMultiplier(1.5) .setMoneyMultiplier(1.5)
.initForEvilTeamAdmin("star_admin", "star_5", [Species.ANNIHILAPE], PokemonType.FIGHTING) .initForEvilTeamAdmin("star_admin", "star_fighting", [Species.ANNIHILAPE], PokemonType.FIGHTING)
.setEncounterBgm(TrainerType.PLASMA_GRUNT) .setEncounterBgm(TrainerType.PLASMA_GRUNT)
.setBattleBgm("battle_plasma_grunt") .setBattleBgm("battle_plasma_grunt")
.setMixedBattleBgm("battle_star_admin") .setMixedBattleBgm("battle_star_admin")

View File

@ -0,0 +1,22 @@
import type { EnemyPokemon } from "#app/field/pokemon";
import type { PersistentModifier } from "#app/modifier/modifier";
import type { PartyMemberStrength } from "#enums/party-member-strength";
import type { Species } from "#enums/species";
import type { TrainerConfig } from "./trainer-config";
import type { TrainerPartyTemplate } from "./TrainerPartyTemplate";
export type PartyTemplateFunc = () => TrainerPartyTemplate;
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;
export type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
export type GenAIFunc = (party: EnemyPokemon[]) => void;
export interface TrainerTierPools {
[key: number]: Species[];
}
export interface TrainerConfigs {
[key: number]: TrainerConfig;
}
export interface PartyMemberFuncs {
[key: number]: PartyMemberFunc;
}

View File

@ -0,0 +1,5 @@
export enum TeraAIMode {
NO_TERA,
INSTANT_TERA,
SMART_TERA
}

View File

@ -0,0 +1,7 @@
export enum TrainerPoolTier {
COMMON,
UNCOMMON,
RARE,
SUPER_RARE,
ULTRA_RARE
}

View File

@ -0,0 +1,5 @@
export enum TrainerSlot {
NONE,
TRAINER,
TRAINER_PARTNER
}

View File

@ -191,6 +191,9 @@ import {
applyPreLeaveFieldAbAttrs, applyPreLeaveFieldAbAttrs,
applyOnLoseAbAttrs, applyOnLoseAbAttrs,
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
applyAllyStatMultiplierAbAttrs,
AllyStatMultiplierAbAttr,
MoveAbilityBypassAbAttr,
} from "#app/data/ability"; } from "#app/data/ability";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
@ -221,7 +224,7 @@ import {
SpeciesFormChangeStatusEffectTrigger, SpeciesFormChangeStatusEffectTrigger,
} from "#app/data/pokemon-forms"; } from "#app/data/pokemon-forms";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import type { TrainerSlot } from "#app/data/trainer-config"; import type { TrainerSlot } from "#enums/trainer-slot";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import i18next from "i18next"; import i18next from "i18next";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
@ -259,6 +262,7 @@ import {
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { doShinySparkleAnim } from "#app/field/anims"; import { doShinySparkleAnim } from "#app/field/anims";
import { MoveFlags } from "#enums/MoveFlags";
export enum LearnMoveSituation { export enum LearnMoveSituation {
MISC, MISC,
@ -1388,6 +1392,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param move the {@linkcode Move} being used * @param move the {@linkcode Move} being used
* @param ignoreAbility determines whether this Pokemon's abilities should be ignored during the stat calculation * @param ignoreAbility determines whether this Pokemon's abilities should be ignored during the stat calculation
* @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation. * @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation.
* @param ignoreAllyAbility during an attack, determines whether the ally Pokemon's abilities should be ignored during the stat calculation.
* @param isCritical determines whether a critical hit has occurred or not (`false` by default) * @param isCritical determines whether a critical hit has occurred or not (`false` by default)
* @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering * @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false` * @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false`
@ -1399,6 +1404,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
move?: Move, move?: Move,
ignoreAbility = false, ignoreAbility = false,
ignoreOppAbility = false, ignoreOppAbility = false,
ignoreAllyAbility = false,
isCritical = false, isCritical = false,
simulated = true, simulated = true,
ignoreHeldItems = false, ignoreHeldItems = false,
@ -1440,6 +1446,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
); );
} }
const ally = this.getAlly();
if (ally) {
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, stat, statValue, simulated, this, move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility);
}
let ret = let ret =
statValue.value * statValue.value *
this.getStatStageMultiplier( this.getStatStageMultiplier(
@ -3888,6 +3899,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
evasionMultiplier, evasionMultiplier,
); );
const ally = this.getAlly();
if (ally) {
const ignore = this.hasAbilityWithAttr(MoveAbilityBypassAbAttr) || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.ACC, accuracyMultiplier, false, this, ignore);
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.EVA, evasionMultiplier, false, this, ignore);
}
return accuracyMultiplier.value / evasionMultiplier.value; return accuracyMultiplier.value / evasionMultiplier.value;
} }
@ -3899,6 +3917,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param moveCategory the move's {@linkcode MoveCategory} after variable-category effects are applied. * @param moveCategory the move's {@linkcode MoveCategory} after variable-category effects are applied.
* @param ignoreAbility if `true`, ignores this Pokemon's defensive ability effects (defaults to `false`). * @param ignoreAbility if `true`, ignores this Pokemon's defensive ability effects (defaults to `false`).
* @param ignoreSourceAbility if `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`). * @param ignoreSourceAbility if `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`).
* @param ignoreAllyAbility if `true`, ignores the ally Pokemon's ability effects (defaults to `false`).
* @param ignoreSourceAllyAbility if `true`, ignores the attacking Pokemon's ally's ability effects (defaults to `false`).
* @param isCritical if `true`, calculates effective stats as if the hit were critical (defaults to `false`). * @param isCritical if `true`, calculates effective stats as if the hit were critical (defaults to `false`).
* @param simulated if `true`, suppresses changes to game state during calculation (defaults to `true`). * @param simulated if `true`, suppresses changes to game state during calculation (defaults to `true`).
* @returns The move's base damage against this Pokemon when used by the source Pokemon. * @returns The move's base damage against this Pokemon when used by the source Pokemon.
@ -3909,6 +3929,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
moveCategory: MoveCategory, moveCategory: MoveCategory,
ignoreAbility = false, ignoreAbility = false,
ignoreSourceAbility = false, ignoreSourceAbility = false,
ignoreAllyAbility = false,
ignoreSourceAllyAbility = false,
isCritical = false, isCritical = false,
simulated = true, simulated = true,
): number { ): number {
@ -3931,6 +3953,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
undefined, undefined,
ignoreSourceAbility, ignoreSourceAbility,
ignoreAbility, ignoreAbility,
ignoreAllyAbility,
isCritical, isCritical,
simulated, simulated,
), ),
@ -3948,6 +3971,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
move, move,
ignoreAbility, ignoreAbility,
ignoreSourceAbility, ignoreSourceAbility,
ignoreSourceAllyAbility,
isCritical, isCritical,
simulated, simulated,
), ),
@ -3982,6 +4006,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param move {@linkcode Pokemon} the move used in the attack * @param move {@linkcode Pokemon} the move used in the attack
* @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects * @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects
* @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects * @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects
* @param ignoreAllyAbility If `true`, ignores the ally Pokemon's ability effects
* @param ignoreSourceAllyAbility If `true`, ignores the ability effects of the attacking pokemon's ally
* @param isCritical If `true`, calculates damage for a critical hit. * @param isCritical If `true`, calculates damage for a critical hit.
* @param simulated If `true`, suppresses changes to game state during the calculation. * @param simulated If `true`, suppresses changes to game state during the calculation.
* @returns a {@linkcode DamageCalculationResult} object with three fields: * @returns a {@linkcode DamageCalculationResult} object with three fields:
@ -3994,6 +4020,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
move: Move, move: Move,
ignoreAbility = false, ignoreAbility = false,
ignoreSourceAbility = false, ignoreSourceAbility = false,
ignoreAllyAbility = false,
ignoreSourceAllyAbility = false,
isCritical = false, isCritical = false,
simulated = true, simulated = true,
): DamageCalculationResult { ): DamageCalculationResult {
@ -4103,6 +4131,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
moveCategory, moveCategory,
ignoreAbility, ignoreAbility,
ignoreSourceAbility, ignoreSourceAbility,
ignoreAllyAbility,
ignoreSourceAllyAbility,
isCritical, isCritical,
simulated, simulated,
); );
@ -4428,7 +4458,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
cancelled, cancelled,
result, result,
damage: dmg, damage: dmg,
} = this.getAttackDamage(source, move, false, false, isCritical, false); } = this.getAttackDamage(source, move, false, false, false, false, isCritical, false);
const typeBoost = source.findTag( const typeBoost = source.findTag(
t => t =>
@ -7113,6 +7143,8 @@ export class EnemyPokemon extends Pokemon {
move, move,
!p.battleData.abilityRevealed, !p.battleData.abilityRevealed,
false, false,
!p.getAlly()?.battleData.abilityRevealed,
false,
isCritical, isCritical,
).damage >= p.hp ).damage >= p.hp
); );

View File

@ -2,15 +2,14 @@ import { globalScene } from "#app/global-scene";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import type { TrainerConfig, TrainerPartyTemplate } from "#app/data/trainer-config"; import type { TrainerConfig } from "#app/data/trainers/trainer-config";
import { import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
TrainerPartyCompoundTemplate, import { trainerConfigs } from "#app/data/trainers/trainer-config";
TrainerPoolTier, import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate";
TrainerSlot, import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
trainerConfigs, import { TrainerSlot } from "#enums/trainer-slot";
trainerPartyTemplates, import { TrainerPoolTier } from "#enums/trainer-pool-tier";
TeraAIMode, import { TeraAIMode } from "#enums/tera-ai-mode";
} from "#app/data/trainer-config";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import type { PersistentModifier } from "#app/modifier/modifier"; import type { PersistentModifier } from "#app/modifier/modifier";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
export class BattlePhase extends Phase { export class BattlePhase extends Phase {

View File

@ -7,7 +7,7 @@ import { getCharVariantFromDialogue } from "#app/data/dialogue";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { doTrainerExclamation } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { doTrainerExclamation } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { getRandomWeatherType } from "#app/data/weather"; import { getRandomWeatherType } from "#app/data/weather";
import { EncounterPhaseEvent } from "#app/events/battle-scene"; import { EncounterPhaseEvent } from "#app/events/battle-scene";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";

View File

@ -1,5 +1,4 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import i18next from "i18next"; import i18next from "i18next";
import { ModifierRewardPhase } from "./modifier-reward-phase"; import { ModifierRewardPhase } from "./modifier-reward-phase";

View File

@ -5,7 +5,7 @@ import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getCharVariantFromDialogue } from "#app/data/dialogue";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";

View File

@ -653,7 +653,7 @@ export class MoveEffectPhase extends PokemonPhase {
this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget); this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget);
this.applyOnGetHitAbEffects(user, target, hitResult); this.applyOnGetHitAbEffects(user, target, hitResult);
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult); applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult);
if (this.move.getMove() instanceof AttackMove) { if (this.move.getMove() instanceof AttackMove && hitResult !== HitResult.STATUS) {
globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
} }
} }

View File

@ -1,5 +1,4 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle";
import { BattlerTagLapseType } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#app/data/battler-tags";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";

View File

@ -22,7 +22,7 @@ import { globalScene } from "#app/global-scene";
import { getCharVariantFromDialogue } from "../data/dialogue"; import { getCharVariantFromDialogue } from "../data/dialogue";
import type { OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils"; import type { OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
import { transitionMysteryEncounterIntroVisuals } from "../data/mystery-encounters/utils/encounter-phase-utils"; import { transitionMysteryEncounterIntroVisuals } from "../data/mystery-encounters/utils/encounter-phase-utils";
import { TrainerSlot } from "../data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { IvScannerModifier } from "../modifier/modifier"; import { IvScannerModifier } from "../modifier/modifier";
import { Phase } from "../phase"; import { Phase } from "../phase";
import { Mode } from "../ui/ui"; import { Mode } from "../ui/ui";

View File

@ -1,5 +1,4 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle";
import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability"; import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability";
import { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTrapTag } from "#app/data/arena-tag";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";

View File

@ -1,7 +1,7 @@
import { BattleType } from "#app/battle"; import { BattleType } from "#app/battle";
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { PlayerGender } from "#app/enums/player-gender"; import { PlayerGender } from "#app/enums/player-gender";
import { addPokeballOpenParticles } from "#app/field/anims"; import { addPokeballOpenParticles } from "#app/field/anims";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";

View File

@ -3,7 +3,7 @@ import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAt
import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move"; import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { SwitchEffectTransferModifier } from "#app/modifier/modifier";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import type { TrainerType } from "#app/enums/trainer-type"; import type { TrainerType } from "#app/enums/trainer-type";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import { TestMessagePhase } from "./test-message-phase"; import { TestMessagePhase } from "./test-message-phase";

View File

@ -7,7 +7,7 @@ import * as Utils from "#app/utils";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import { ModifierRewardPhase } from "./modifier-reward-phase"; import { ModifierRewardPhase } from "./modifier-reward-phase";
import { MoneyRewardPhase } from "./money-reward-phase"; import { MoneyRewardPhase } from "./money-reward-phase";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { Biome } from "#app/enums/biome"; import { Biome } from "#app/enums/biome";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";

View File

@ -17,7 +17,7 @@ import { Unlockables } from "#app/system/unlockables";
import { GameModes, getGameMode } from "#app/game-mode"; import { GameModes, getGameMode } from "#app/game-mode";
import { BattleType } from "#app/battle"; import { BattleType } from "#app/battle";
import TrainerData from "#app/system/trainer-data"; import TrainerData from "#app/system/trainer-data";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { resetSettings, setSetting, SettingKeys } from "#app/system/settings/settings"; import { resetSettings, setSetting, SettingKeys } from "#app/system/settings/settings";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import EggData from "#app/system/egg-data"; import EggData from "#app/system/egg-data";

View File

@ -5,8 +5,8 @@ import type BattleScene from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import * as Utils from "../utils"; import * as Utils from "../utils";
type FadeIn = typeof FadeIn; type FadeInType = typeof FadeIn;
type FadeOut = typeof FadeOut; type FadeOutType = typeof FadeOut;
export function initGameSpeed() { export function initGameSpeed() {
const thisArg = this as BattleScene; const thisArg = this as BattleScene;
@ -101,7 +101,7 @@ export function initGameSpeed() {
const originalFadeOut = SoundFade.fadeOut; const originalFadeOut = SoundFade.fadeOut;
SoundFade.fadeOut = ((_scene: Phaser.Scene, sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean) => SoundFade.fadeOut = ((_scene: Phaser.Scene, sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean) =>
originalFadeOut(globalScene, sound, transformValue(duration), destroy)) as FadeOut; originalFadeOut(globalScene, sound, transformValue(duration), destroy)) as FadeOutType;
const originalFadeIn = SoundFade.fadeIn; const originalFadeIn = SoundFade.fadeIn;
SoundFade.fadeIn = (( SoundFade.fadeIn = ((
@ -110,5 +110,5 @@ export function initGameSpeed() {
duration: number, duration: number,
endVolume?: number, endVolume?: number,
startVolume?: number, startVolume?: number,
) => originalFadeIn(globalScene, sound, transformValue(duration), endVolume, startVolume)) as FadeIn; ) => originalFadeIn(globalScene, sound, transformValue(duration), endVolume, startVolume)) as FadeInType;
} }

View File

@ -6,7 +6,7 @@ import type { PokeballType } from "#enums/pokeball";
import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species";
import { Status } from "../data/status-effect"; import { Status } from "../data/status-effect";
import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon"; import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon";
import { TrainerSlot } from "../data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import type { Variant } from "#app/data/variant"; import type { Variant } from "#app/data/variant";
import { loadBattlerTag } from "../data/battler-tags"; import { loadBattlerTag } from "../data/battler-tags";
import type { Biome } from "#enums/biome"; import type { Biome } from "#enums/biome";
@ -14,7 +14,6 @@ import { Moves } from "#enums/moves";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms";
export default class PokemonData { export default class PokemonData {
public id: number; public id: number;

View File

@ -950,7 +950,7 @@ export function setSetting(setting: string, value: number): boolean {
}, },
{ {
label: "Català", label: "Català",
handler: () => changeLocaleHandler("ca-ES") handler: () => changeLocaleHandler("ca-ES"),
}, },
{ {
label: i18next.t("settings:back"), label: i18next.t("settings:back"),

View File

@ -3,7 +3,7 @@ import { AchvTier, achvs, getAchievementDescription } from "./achv";
import type { PlayerGender } from "#enums/player-gender"; import type { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import type { ConditionFn } from "#app/@types/common"; import type { ConditionFn } from "#app/@types/common";
import { trainerConfigs } from "#app/data/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
export enum VoucherType { export enum VoucherType {
REGULAR, REGULAR,

View File

@ -1150,8 +1150,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
}); });
this.blockInput = false; this.blockInput = false;
} else { } else {
ui.revertMode() ui.revertMode().then(() => {
.then(() => {
console.log("exitCallback", this.exitCallback); console.log("exitCallback", this.exitCallback);
if (this.exitCallback instanceof Function) { if (this.exitCallback instanceof Function) {
const exitCallback = this.exitCallback; const exitCallback = this.exitCallback;

View File

@ -12,7 +12,7 @@ import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUN
import { catchableSpecies } from "#app/data/balance/biomes"; import { catchableSpecies } from "#app/data/balance/biomes";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type { DexAttrProps, DexEntry, StarterAttributes, StarterPreferences } from "#app/system/game-data"; import type { DexAttrProps, DexEntry, StarterAttributes, StarterPreferences } from "#app/system/game-data";
import { AbilityAttr, DexAttr, loadStarterPreferences, saveStarterPreferences } from "#app/system/game-data"; import { AbilityAttr, DexAttr, loadStarterPreferences } from "#app/system/game-data";
import MessageUiHandler from "#app/ui/message-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler";
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler";
import { TextStyle, addTextObject } from "#app/ui/text"; import { TextStyle, addTextObject } from "#app/ui/text";

View File

@ -1,6 +1,6 @@
import type { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; import type { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene";
import { BattleSceneEventType } from "#app/events/battle-scene"; import { BattleSceneEventType } from "#app/events/battle-scene";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type { Variant } from "#app/data/variant"; import type { Variant } from "#app/data/variant";
import { getVariantTint, getVariantIcon } from "#app/data/variant"; import { getVariantTint, getVariantIcon } from "#app/data/variant";
import { argbFromRgba } from "@material/material-color-utilities"; import { argbFromRgba } from "@material/material-color-utilities";
@ -19,7 +19,7 @@ import { pokemonFormChanges } from "#app/data/pokemon-forms";
import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species";
import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { GameModes } from "#app/game-mode"; import { GameModes } from "#app/game-mode";

View File

@ -135,9 +135,7 @@ describe("Abilities - Desolate Land", () => {
}); });
it("should lift after fleeing from a wild pokemon", async () => { it("should lift after fleeing from a wild pokemon", async () => {
game.override game.override.enemyAbility(Abilities.DESOLATE_LAND).ability(Abilities.BALL_FETCH);
.enemyAbility(Abilities.DESOLATE_LAND)
.ability(Abilities.BALL_FETCH);
await game.classicMode.startBattle([Species.MAGIKARP]); await game.classicMode.startBattle([Species.MAGIKARP]);
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);

View File

@ -1,12 +1,14 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { allAbilities } from "#app/data/ability";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#app/enums/abilities";
import { Stat } from "#app/enums/stat"; import { Stat } from "#app/enums/stat";
import { WeatherType } from "#app/enums/weather-type"; import { WeatherType } from "#app/enums/weather-type";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager"; import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Abilities - Flower Gift", () => { describe("Abilities - Flower Gift", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -28,6 +30,64 @@ describe("Abilities - Flower Gift", () => {
expect(game.scene.getPlayerPokemon()?.formIndex).toBe(OVERCAST_FORM); expect(game.scene.getPlayerPokemon()?.formIndex).toBe(OVERCAST_FORM);
}; };
/**
* Tests damage dealt by a move used against a target before and after Flower Gift is activated.
* @param game The game manager instance
* @param move The move that should be used
* @param allyAttacker True if the ally is attacking the enemy, false if the enemy is attacking the ally
* @param ability The ability that the ally pokemon should have
* @param enemyAbility The ability that the enemy pokemon should have
*
* @returns Two numbers, the first being the damage done to the target without flower gift active, the second being the damage done with flower gift active
*/
const testDamageDealt = async (
game: GameManager,
move: Moves,
allyAttacker: boolean,
allyAbility = Abilities.BALL_FETCH,
enemyAbility = Abilities.BALL_FETCH,
): Promise<[number, number]> => {
game.override.battleType("double");
game.override.moveset([Moves.SPLASH, Moves.SUNNY_DAY, move, Moves.HEAL_PULSE]);
game.override.enemyMoveset([Moves.SPLASH, Moves.HEAL_PULSE]);
const target_index = allyAttacker ? BattlerIndex.ENEMY : BattlerIndex.PLAYER_2;
const attacker_index = allyAttacker ? BattlerIndex.PLAYER_2 : BattlerIndex.ENEMY;
const ally_move = allyAttacker ? move : Moves.SPLASH;
const enemy_move = allyAttacker ? Moves.SPLASH : move;
const ally_target = allyAttacker ? BattlerIndex.ENEMY : null;
await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
const target = allyAttacker ? game.scene.getEnemyField()[0] : game.scene.getPlayerField()[1];
const initialHp = target.getMaxHp();
// Override the ability for the target and attacker only
vi.spyOn(game.scene.getPlayerField()[1], "getAbility").mockReturnValue(allAbilities[allyAbility]);
vi.spyOn(game.scene.getEnemyField()[0], "getAbility").mockReturnValue(allAbilities[enemyAbility]);
// turn 1
game.move.select(Moves.SUNNY_DAY, 0);
game.move.select(ally_move, 1, ally_target);
await game.forceEnemyMove(enemy_move, BattlerIndex.PLAYER_2);
await game.forceEnemyMove(Moves.SPLASH);
// Ensure sunny day is used last.
await game.setTurnOrder([attacker_index, target_index, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to(TurnEndPhase);
const damageWithoutGift = initialHp - target.hp;
target.hp = initialHp;
// turn 2. Make target use recover to reset hp calculation.
game.move.select(Moves.SPLASH, 0, target_index);
game.move.select(ally_move, 1, ally_target);
await game.forceEnemyMove(enemy_move, BattlerIndex.PLAYER_2);
await game.forceEnemyMove(Moves.SPLASH);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, target_index, attacker_index]);
await game.phaseInterceptor.to(TurnEndPhase);
const damageWithGift = initialHp - target.hp;
return [damageWithoutGift, damageWithGift];
};
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
@ -41,23 +101,24 @@ describe("Abilities - Flower Gift", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.moveset([Moves.SPLASH, Moves.RAIN_DANCE, Moves.SUNNY_DAY, Moves.SKILL_SWAP]) .moveset([Moves.SPLASH, Moves.SUNSTEEL_STRIKE, Moves.SUNNY_DAY, Moves.MUD_SLAP])
.enemySpecies(Species.MAGIKARP) .enemySpecies(Species.MAGIKARP)
.enemyMoveset(Moves.SPLASH) .enemyMoveset(Moves.SPLASH)
.enemyAbility(Abilities.BALL_FETCH); .enemyAbility(Abilities.BALL_FETCH)
.enemyLevel(100)
.startingLevel(100);
}); });
// TODO: Uncomment expect statements when the ability is implemented - currently does not increase stats of allies
it("increases the ATK and SPDEF stat stages of the Pokémon with this Ability and its allies by 1.5× during Harsh Sunlight", async () => { it("increases the ATK and SPDEF stat stages of the Pokémon with this Ability and its allies by 1.5× during Harsh Sunlight", async () => {
game.override.battleType("double"); game.override.battleType("double");
await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]); await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
const [cherrim] = game.scene.getPlayerField(); const [cherrim, magikarp] = game.scene.getPlayerField();
const cherrimAtkStat = cherrim.getEffectiveStat(Stat.ATK); const cherrimAtkStat = cherrim.getEffectiveStat(Stat.ATK);
const cherrimSpDefStat = cherrim.getEffectiveStat(Stat.SPDEF); const cherrimSpDefStat = cherrim.getEffectiveStat(Stat.SPDEF);
// const magikarpAtkStat = magikarp.getEffectiveStat(Stat.ATK);; const magikarpAtkStat = magikarp.getEffectiveStat(Stat.ATK);
// const magikarpSpDefStat = magikarp.getEffectiveStat(Stat.SPDEF); const magikarpSpDefStat = magikarp.getEffectiveStat(Stat.SPDEF);
game.move.select(Moves.SUNNY_DAY, 0); game.move.select(Moves.SUNNY_DAY, 0);
game.move.select(Moves.SPLASH, 1); game.move.select(Moves.SPLASH, 1);
@ -68,8 +129,34 @@ describe("Abilities - Flower Gift", () => {
expect(cherrim.formIndex).toBe(SUNSHINE_FORM); expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
expect(cherrim.getEffectiveStat(Stat.ATK)).toBe(Math.floor(cherrimAtkStat * 1.5)); expect(cherrim.getEffectiveStat(Stat.ATK)).toBe(Math.floor(cherrimAtkStat * 1.5));
expect(cherrim.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(cherrimSpDefStat * 1.5)); expect(cherrim.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(cherrimSpDefStat * 1.5));
// expect(magikarp.getEffectiveStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 1.5)); expect(magikarp.getEffectiveStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 1.5));
// expect(magikarp.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5)); expect(magikarp.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5));
});
it("should not increase the damage of an ally using an ability ignoring move", async () => {
const [damageWithGift, damageWithoutGift] = await testDamageDealt(game, Moves.SUNSTEEL_STRIKE, true);
expect(damageWithGift).toBe(damageWithoutGift);
});
it("should not increase the damage of a mold breaker ally", async () => {
const [damageWithGift, damageWithoutGift] = await testDamageDealt(game, Moves.TACKLE, true, Abilities.MOLD_BREAKER);
expect(damageWithGift).toBe(damageWithoutGift);
});
it("should decrease the damage an ally takes from a special attack", async () => {
const [damageWithoutGift, damageWithGift] = await testDamageDealt(game, Moves.MUD_SLAP, false);
expect(damageWithGift).toBeLessThan(damageWithoutGift);
});
it("should not decrease the damage an ally takes from a mold breaker enemy using a special attack", async () => {
const [damageWithoutGift, damageWithGift] = await testDamageDealt(
game,
Moves.MUD_SLAP,
false,
Abilities.BALL_FETCH,
Abilities.MOLD_BREAKER,
);
expect(damageWithGift).toBe(damageWithoutGift);
}); });
it("changes the Pokemon's form during Harsh Sunlight", async () => { it("changes the Pokemon's form during Harsh Sunlight", async () => {

View File

@ -160,9 +160,7 @@ describe("Abilities - Neutralizing Gas", () => {
}); });
it("should deactivate after fleeing from a wild pokemon", async () => { it("should deactivate after fleeing from a wild pokemon", async () => {
game.override game.override.enemyAbility(Abilities.NEUTRALIZING_GAS).ability(Abilities.BALL_FETCH);
.enemyAbility(Abilities.NEUTRALIZING_GAS)
.ability(Abilities.BALL_FETCH);
await game.classicMode.startBattle([Species.MAGIKARP]); await game.classicMode.startBattle([Species.MAGIKARP]);
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();

View File

@ -9,6 +9,7 @@ import { StatusEffect } from "#enums/status-effect";
import GameManager from "#test/testUtils/gameManager"; import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { BattlerIndex } from "#app/battle";
describe("Abilities - Parental Bond", () => { describe("Abilities - Parental Bond", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -426,4 +427,21 @@ describe("Abilities - Parental Bond", () => {
// TODO: Update hit count to 1 once Future Sight is fixed to not activate abilities if user is off the field // TODO: Update hit count to 1 once Future Sight is fixed to not activate abilities if user is off the field
expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2); expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2);
}); });
it("should not allow Pollen Puff to heal ally more than once", async () => {
game.override.battleType("double").moveset([Moves.POLLEN_PUFF, Moves.ENDURE]);
await game.classicMode.startBattle([Species.BULBASAUR, Species.OMANYTE]);
const [, rightPokemon] = game.scene.getPlayerField();
rightPokemon.damageAndUpdate(rightPokemon.hp - 1);
game.move.select(Moves.POLLEN_PUFF, 0, BattlerIndex.PLAYER_2);
game.move.select(Moves.ENDURE, 1);
await game.toNextTurn();
// Pollen Puff heals with a ratio of 0.5, as long as Pollen Puff triggers only once the pokemon will always be <= (0.5 * Max HP) + 1
expect(rightPokemon.hp).toBeLessThanOrEqual(0.5 * rightPokemon.getMaxHp() + 1);
});
}); });

View File

@ -46,16 +46,56 @@ describe("Abilities - Protosynthesis", () => {
// Nature of starting mon is randomized. We need to fix it to a neutral nature for the automated test. // Nature of starting mon is randomized. We need to fix it to a neutral nature for the automated test.
mew.setNature(Nature.HARDY); mew.setNature(Nature.HARDY);
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
const def_before_boost = mew.getEffectiveStat(Stat.DEF, undefined, undefined, false, undefined, false, false, true); const def_before_boost = mew.getEffectiveStat(
const atk_before_boost = mew.getEffectiveStat(Stat.ATK, undefined, undefined, false, undefined, false, false, true); Stat.DEF,
undefined,
undefined,
false,
undefined,
undefined,
false,
false,
true,
);
const atk_before_boost = mew.getEffectiveStat(
Stat.ATK,
undefined,
undefined,
false,
undefined,
undefined,
false,
false,
true,
);
const initialHp = enemy.hp; const initialHp = enemy.hp;
game.move.select(Moves.TACKLE); game.move.select(Moves.TACKLE);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toNextTurn(); await game.toNextTurn();
const unboosted_dmg = initialHp - enemy.hp; const unboosted_dmg = initialHp - enemy.hp;
enemy.hp = initialHp; enemy.hp = initialHp;
const def_after_boost = mew.getEffectiveStat(Stat.DEF, undefined, undefined, false, undefined, false, false, true); const def_after_boost = mew.getEffectiveStat(
const atk_after_boost = mew.getEffectiveStat(Stat.ATK, undefined, undefined, false, undefined, false, false, true); Stat.DEF,
undefined,
undefined,
false,
undefined,
undefined,
false,
false,
true,
);
const atk_after_boost = mew.getEffectiveStat(
Stat.ATK,
undefined,
undefined,
false,
undefined,
undefined,
false,
false,
true,
);
game.move.select(Moves.TACKLE); game.move.select(Moves.TACKLE);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toNextTurn(); await game.toNextTurn();

View File

@ -0,0 +1,60 @@
import { BattlerIndex } from "#app/battle";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Abilities - Victory Star", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([Moves.TACKLE, Moves.SPLASH])
.battleType("double")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should increase the accuracy of its user", async () => {
await game.classicMode.startBattle([Species.VICTINI, Species.MAGIKARP]);
const user = game.scene.getPlayerField()[0];
vi.spyOn(user, "getAccuracyMultiplier");
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(TurnEndPhase);
expect(user.getAccuracyMultiplier).toHaveReturnedWith(1.1);
});
it("should increase the accuracy of its user's ally", async () => {
await game.classicMode.startBattle([Species.MAGIKARP, Species.VICTINI]);
const ally = game.scene.getPlayerField()[0];
vi.spyOn(ally, "getAccuracyMultiplier");
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(TurnEndPhase);
expect(ally.getAccuracyMultiplier).toHaveReturnedWith(1.1);
});
});

View File

@ -1,4 +1,3 @@
import { allAbilities } from "#app/data/ability";
import { allMoves } from "#app/data/moves/move"; import { allMoves } from "#app/data/moves/move";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";

View File

@ -9,7 +9,7 @@ describe("Data - Splash Messages", () => {
// Make sure to adjust this test if the weight is changed! // Make sure to adjust this test if the weight is changed!
it("should add contain 15 `battlesWon` splash messages", () => { it("should add contain 15 `battlesWon` splash messages", () => {
const battlesWonMessages = getSplashMessages().filter((message) => message === "splashMessages:battlesWon"); const battlesWonMessages = getSplashMessages().filter(message => message === "splashMessages:battlesWon");
expect(battlesWonMessages).toHaveLength(15); expect(battlesWonMessages).toHaveLength(15);
}); });

View File

@ -98,6 +98,31 @@ describe("Items - Grip Claw", () => {
expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount);
expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount); expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount);
}); });
it("should not allow Pollen Puff to steal items when healing ally", async () => {
game.override
.battleType("double")
.moveset([Moves.POLLEN_PUFF, Moves.ENDURE])
.startingHeldItems([
{ name: "GRIP_CLAW", count: 1 },
{ name: "BERRY", type: BerryType.LUM, count: 1 },
]);
await game.classicMode.startBattle([Species.BULBASAUR, Species.OMANYTE]);
const [leftPokemon, rightPokemon] = game.scene.getPlayerField();
const gripClaw = leftPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier;
vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100);
const heldItemCountBefore = getHeldItemCount(rightPokemon);
game.move.select(Moves.POLLEN_PUFF, 0, BattlerIndex.PLAYER_2);
game.move.select(Moves.ENDURE, 1);
await game.toNextTurn();
expect(getHeldItemCount(rightPokemon)).toBe(heldItemCountBefore);
});
}); });
/* /*

View File

@ -211,4 +211,21 @@ describe("Items - Multi Lens", () => {
// TODO: Update hit count to 1 once Future Sight is fixed to not activate held items if user is off the field // TODO: Update hit count to 1 once Future Sight is fixed to not activate held items if user is off the field
expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2); expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2);
}); });
it("should not allow Pollen Puff to heal ally more than once", async () => {
game.override.battleType("double").moveset([Moves.POLLEN_PUFF, Moves.ENDURE]);
await game.classicMode.startBattle([Species.BULBASAUR, Species.OMANYTE]);
const [, rightPokemon] = game.scene.getPlayerField();
rightPokemon.damageAndUpdate(rightPokemon.hp - 1);
game.move.select(Moves.POLLEN_PUFF, 0, BattlerIndex.PLAYER_2);
game.move.select(Moves.ENDURE, 1);
await game.toNextTurn();
// Pollen Puff heals with a ratio of 0.5, as long as Pollen Puff triggers only once the pokemon will always be <= (0.5 * Max HP) + 1
expect(rightPokemon.hp).toBeLessThanOrEqual(0.5 * rightPokemon.getMaxHp() + 1);
});
}); });

View File

@ -47,12 +47,9 @@ describe("Items - Reviver Seed", () => {
{ moveType: "Fixed Damage Move", move: Moves.SEISMIC_TOSS }, { moveType: "Fixed Damage Move", move: Moves.SEISMIC_TOSS },
{ moveType: "Final Gambit", move: Moves.FINAL_GAMBIT }, { moveType: "Final Gambit", move: Moves.FINAL_GAMBIT },
{ moveType: "Counter", move: Moves.COUNTER }, { moveType: "Counter", move: Moves.COUNTER },
{ moveType: "OHKO", move: Moves.SHEER_COLD } { moveType: "OHKO", move: Moves.SHEER_COLD },
])("should activate the holder's reviver seed from a $moveType", async ({ move }) => { ])("should activate the holder's reviver seed from a $moveType", async ({ move }) => {
game.override game.override.enemyLevel(100).startingLevel(1).enemyMoveset(move);
.enemyLevel(100)
.startingLevel(1)
.enemyMoveset(move);
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
const player = game.scene.getPlayerPokemon()!; const player = game.scene.getPlayerPokemon()!;
player.damageAndUpdate(player.hp - 1); player.damageAndUpdate(player.hp - 1);
@ -67,10 +64,7 @@ describe("Items - Reviver Seed", () => {
}); });
it("should activate the holder's reviver seed from confusion self-hit", async () => { it("should activate the holder's reviver seed from confusion self-hit", async () => {
game.override game.override.enemyLevel(1).startingLevel(100).enemyMoveset(Moves.SPLASH);
.enemyLevel(1)
.startingLevel(100)
.enemyMoveset(Moves.SPLASH);
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
const player = game.scene.getPlayerPokemon()!; const player = game.scene.getPlayerPokemon()!;
player.damageAndUpdate(player.hp - 1); player.damageAndUpdate(player.hp - 1);

View File

@ -1,6 +1,6 @@
import { allMoves } from "#app/data/moves/move"; import { allMoves } from "#app/data/moves/move";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#enums/trainer-slot";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves"; import { Moves } from "#app/enums/moves";

View File

@ -0,0 +1,53 @@
import { MoveResult } from "#app/field/pokemon";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - False Swipe", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([Moves.FALSE_SWIPE])
.ability(Abilities.BALL_FETCH)
.startingLevel(1000)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should reduce the target to 1 HP", async () => {
await game.classicMode.startBattle([Species.MILOTIC]);
const player = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.FALSE_SWIPE);
await game.toNextTurn();
game.move.select(Moves.FALSE_SWIPE);
await game.phaseInterceptor.to("BerryPhase");
expect(enemy.hp).toBe(1);
const falseSwipeHistory = player
.getMoveHistory()
.every(turnMove => turnMove.move === Moves.FALSE_SWIPE && turnMove.result === MoveResult.SUCCESS);
expect(falseSwipeHistory).toBe(true);
});
});

View File

@ -116,11 +116,7 @@ describe("Moves - Revival Blessing", () => {
}); });
it("should not summon multiple pokemon to the same slot when reviving the enemy ally in doubles", async () => { it("should not summon multiple pokemon to the same slot when reviving the enemy ally in doubles", async () => {
game.override game.override.battleType("double").enemyMoveset([Moves.REVIVAL_BLESSING]).moveset([Moves.SPLASH]).startingWave(25); // 2nd rival battle - must have 3+ pokemon
.battleType("double")
.enemyMoveset([ Moves.REVIVAL_BLESSING ])
.moveset([ Moves.SPLASH ])
.startingWave(25); // 2nd rival battle - must have 3+ pokemon
await game.classicMode.startBattle([Species.ARCEUS, Species.GIRATINA]); await game.classicMode.startBattle([Species.ARCEUS, Species.GIRATINA]);
const enemyFainting = game.scene.getEnemyField()[0]; const enemyFainting = game.scene.getEnemyField()[0];

View File

@ -16,7 +16,9 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { MysteriousChallengersEncounter } from "#app/data/mystery-encounters/encounters/mysterious-challengers-encounter"; import { MysteriousChallengersEncounter } from "#app/data/mystery-encounters/encounters/mysterious-challengers-encounter";
import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#app/data/trainer-config"; import { TrainerConfig } from "#app/data/trainers/trainer-config";
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";

View File

@ -1,31 +1,25 @@
/* eslint-disable */ // @ts-nocheck - TODO: remove this
// @ts-nocheck
import BattleScene, * as battleScene from "#app/battle-scene"; import BattleScene, * as battleScene from "#app/battle-scene";
import { MoveAnim } from "#app/data/battle-anims"; import { MoveAnim } from "#app/data/battle-anims";
import Pokemon from "#app/field/pokemon"; import Pokemon from "#app/field/pokemon";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import { blobToString } from "#test/testUtils/gameManagerUtils"; import { blobToString } from "#test/testUtils/gameManagerUtils";
import { MockClock } from "#test/testUtils/mocks/mockClock"; import { MockClock } from "#test/testUtils/mocks/mockClock";
import { MockConsoleLog } from "#test/testUtils/mocks/mockConsoleLog";
import { MockFetch } from "#test/testUtils/mocks/mockFetch"; import { MockFetch } from "#test/testUtils/mocks/mockFetch";
import MockLoader from "#test/testUtils/mocks/mockLoader"; import MockLoader from "#test/testUtils/mocks/mockLoader";
import { mockLocalStorage } from "#test/testUtils/mocks/mockLocalStorage";
import { MockImage } from "#test/testUtils/mocks/mocksContainer/mockImage";
import MockTextureManager from "#test/testUtils/mocks/mockTextureManager"; import MockTextureManager from "#test/testUtils/mocks/mockTextureManager";
import fs from "node:fs"; import fs from "node:fs";
import Phaser from "phaser"; import Phaser from "phaser";
import InputText from "phaser3-rex-plugins/plugins/inputtext";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import { vi } from "vitest"; import { vi } from "vitest";
import { version } from "../../package.json";
import { MockGameObjectCreator } from "./mocks/mockGameObjectCreator"; import { MockGameObjectCreator } from "./mocks/mockGameObjectCreator";
import { MockTimedEventManager } from "./mocks/mockTimedEventManager";
import InputManager = Phaser.Input.InputManager; import InputManager = Phaser.Input.InputManager;
import KeyboardManager = Phaser.Input.Keyboard.KeyboardManager; import KeyboardManager = Phaser.Input.Keyboard.KeyboardManager;
import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin; import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin;
import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin; import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin;
import EventEmitter = Phaser.Events.EventEmitter; import EventEmitter = Phaser.Events.EventEmitter;
import UpdateList = Phaser.GameObjects.UpdateList; import UpdateList = Phaser.GameObjects.UpdateList;
import { version } from "../../package.json";
import { MockTimedEventManager } from "./mocks/mockTimedEventManager";
window.URL.createObjectURL = (blob: Blob) => { window.URL.createObjectURL = (blob: Blob) => {
blobToString(blob).then((data: string) => { blobToString(blob).then((data: string) => {

View File

@ -1,9 +1,6 @@
import type { Variant } from "#app/data/variant"; import type { Variant } from "#app/data/variant";
import { Weather } from "#app/data/weather"; import { Weather } from "#app/data/weather";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#app/enums/abilities";
import * as GameMode from "#app/game-mode";
import type { GameModes } from "#app/game-mode";
import { getGameMode } from "#app/game-mode";
import type { ModifierOverride } from "#app/modifier/modifier-type"; import type { ModifierOverride } from "#app/modifier/modifier-type";
import type { BattleStyle } from "#app/overrides"; import type { BattleStyle } from "#app/overrides";
import Overrides, { defaultOverrides } from "#app/overrides"; import Overrides, { defaultOverrides } from "#app/overrides";

View File

@ -1,4 +1,3 @@
import { off } from "process";
import type { MockGameObject } from "../mockGameObject"; import type { MockGameObject } from "../mockGameObject";
export default class MockRectangle implements MockGameObject { export default class MockRectangle implements MockGameObject {

View File

@ -1,17 +1,4 @@
import "vitest-canvas-mock"; import "vitest-canvas-mock";
import { initLoggedInUser } from "#app/account";
import { initAbilities } from "#app/data/ability";
import { initBiomes } from "#app/data/balance/biomes";
import { initEggMoves } from "#app/data/balance/egg-moves";
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { initMoves } from "#app/data/moves/move";
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
import { initPokemonForms } from "#app/data/pokemon-forms";
import { initSpecies } from "#app/data/pokemon-species";
import { initAchievements } from "#app/system/achv";
import { initVouchers } from "#app/system/voucher";
import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
import { afterAll, beforeAll, vi } from "vitest"; import { afterAll, beforeAll, vi } from "vitest";
import { initTestFile } from "./testUtils/testFileInitialization"; import { initTestFile } from "./testUtils/testFileInitialization";
@ -20,13 +7,11 @@ import { initTestFile } from "./testUtils/testFileInitialization";
/** Mock the override import to always return default values, ignoring any custom overrides. */ /** Mock the override import to always return default values, ignoring any custom overrides. */
vi.mock("#app/overrides", async importOriginal => { vi.mock("#app/overrides", async importOriginal => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const { defaultOverrides } = await importOriginal<typeof import("#app/overrides")>(); const { defaultOverrides } = await importOriginal<typeof import("#app/overrides")>();
return { return {
default: defaultOverrides, default: defaultOverrides,
defaultOverrides, defaultOverrides,
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
} satisfies typeof import("#app/overrides"); } satisfies typeof import("#app/overrides");
}); });