diff --git a/biome.jsonc b/biome.jsonc index 3ec4552d359..c5e1d713d86 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -50,7 +50,8 @@ "noUndeclaredVariables": "off", "noUnusedVariables": "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": { "noVar": "error", diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 443113daef6..5797fda5611 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -72,8 +72,8 @@ import { GameModes, getGameMode } from "#app/game-mode"; import FieldSpritePipeline from "#app/pipelines/field-sprite"; import SpritePipeline from "#app/pipelines/sprite"; import PartyExpBar from "#app/ui/party-exp-bar"; -import type { TrainerSlot } from "#app/data/trainer-config"; -import { trainerConfigs } from "#app/data/trainer-config"; +import type { TrainerSlot } from "./enums/trainer-slot"; +import { trainerConfigs } from "#app/data/trainers/trainer-config"; import Trainer, { TrainerVariant } from "#app/field/trainer"; import type TrainerData from "#app/system/trainer-data"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; diff --git a/src/battle.ts b/src/battle.ts index 5ada921bf5a..367c52568dc 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -5,7 +5,7 @@ import Trainer, { TrainerVariant } from "./field/trainer"; import type { GameMode } from "./game-mode"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; 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 type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/data/ability.ts b/src/data/ability.ts index edae5aedc35..d94cf26c4ff 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1734,6 +1734,62 @@ export class PostAttackAbAttr extends AbAttr { 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 * @extends PostAttackAbAttr @@ -5598,6 +5654,30 @@ export function applyStatMultiplierAbAttrs( 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, + pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, checkedPokemon: Pokemon, ignoreAbility: boolean, ...args: any[] +): void { + return applyAbAttrsInternal( + 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( attrType: Constructor, pokemon: Pokemon, @@ -5610,7 +5690,8 @@ export function applyPostSetStatusAbAttrs( attrType, pokemon, (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, ); } @@ -6441,12 +6522,13 @@ export function initAbilities() { 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.SPDEF, 1.5) - .uncopiable() - .unreplaceable() + .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.ATK, 1.5) + .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.SPDEF, 1.5) .attr(NoFusionAbilityAbAttr) .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 ]) - .partial() // Should also boosts stats of ally + .uncopiable() + .unreplaceable() .ignorable(), new Ability(Abilities.BAD_DREAMS, 4) .attr(PostTurnHurtIfSleepingAbAttr), @@ -6581,7 +6663,7 @@ export function initAbilities() { .bypassFaint(), new Ability(Abilities.VICTORY_STAR, 5) .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) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(MoveAbilityBypassAbAttr), diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index c4004e9c582..b139faaeb88 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1644,7 +1644,9 @@ export class ContactDamageProtectedTag extends ProtectedTag { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); 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; 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) => { if (value > highestValue) { highestStat = EFFECTIVE_STATS[i]; @@ -2149,6 +2151,21 @@ export class TypeBoostTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { 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 { @@ -2240,7 +2257,9 @@ export class SaltCuredTag extends BattlerTag { if (!cancelled.value) { 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( i18next.t("battlerTags:saltCuredLapse", { diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index 0e755d54e15..fa640e92b00 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -1,6 +1,6 @@ import { BattleSpec } from "#enums/battle-spec"; import { TrainerType } from "#enums/trainer-type"; -import { trainerConfigs } from "./trainer-config"; +import { trainerConfigs } from "./trainers/trainer-config"; export interface TrainerTypeMessages { encounter?: string | string[]; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index b0154a9295e..99d6e3fc3cf 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -902,7 +902,7 @@ export default class Move implements Localizable { SacrificialAttrOnHit ]; - // ...and cannot enhance these specific moves. + // ...and cannot enhance these specific moves const exceptMoves: Moves[] = [ Moves.FLING, Moves.UPROAR, @@ -911,10 +911,14 @@ export default class Move implements Localizable { 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) && !this.isChargingMove() && !exceptAttrs.some(attr => this.hasAttr(attr)) && !exceptMoves.some(id => this.id === id) + && !exceptPollenPuffAlly && this.category !== MoveCategory.STATUS; } } @@ -1577,10 +1581,6 @@ export class SurviveDamageAttr extends ModifiedDamageAttr { return Math.min(damage, target.hp - 1); } - getCondition(): MoveConditionFunc { - return (user, target, move) => target.hp > 1; - } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { 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 { const category = (args[0] as Utils.NumberHolder); - const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true); - const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true); + const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true, true, true); + const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true, true, true); if (predictedPhysDmg > predictedSpecDmg) { category.value = MoveCategory.PHYSICAL; diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 6cea85346b3..a49157f8e88 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -6,7 +6,7 @@ import { setEncounterRewards, transitionMysteryEncounterIntroVisuals, } 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 type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index ca44782691d..85f40a41e51 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -31,7 +31,7 @@ import { catchPokemon, getHighestLevelPlayerPokemon, } 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 type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type { BerryType } from "#enums/berry-type"; diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 15cba1fa103..1e4c9a3b957 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -9,13 +9,10 @@ import { setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { - getRandomPartyMemberFunc, - trainerConfigs, - TrainerPartyCompoundTemplate, - TrainerPartyTemplate, - TrainerSlot, -} from "#app/data/trainer-config"; +import { getRandomPartyMemberFunc, trainerConfigs } from "#app/data/trainers/trainer-config"; +import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; +import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; +import { TrainerSlot } from "#enums/trainer-slot"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 2b499d938cd..eca99fc0c13 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -8,7 +8,9 @@ import { setEncounterRewards, transitionMysteryEncounterIntroVisuals, } 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 type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 91f168371cf..75527e1f8c1 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -20,7 +20,7 @@ import { STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER, } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; 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 Pokemon from "#app/field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index c95810b94d0..282c6c149ff 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -10,7 +10,7 @@ import { globalScene } from "#app/global-scene"; import type MysteryEncounter 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 { TrainerSlot } from "#app/data/trainer-config"; +import { TrainerSlot } from "#enums/trainer-slot"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { FieldPosition } from "#app/field/pokemon"; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 4f10a657e4e..c13501c4511 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -3,7 +3,7 @@ import { selectPokemonForOption, setEncounterRewards, } 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 { MusicPreference } from "#app/system/settings/settings"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index bf60e982b15..11924f93df4 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -3,12 +3,10 @@ import { initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { - trainerConfigs, - TrainerPartyCompoundTemplate, - TrainerPartyTemplate, - trainerPartyTemplates, -} from "#app/data/trainer-config"; +import { trainerConfigs } from "#app/data/trainers/trainer-config"; +import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate"; +import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; +import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index f231e4abdb8..8c45fde3079 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -10,7 +10,7 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounterOption 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 type { EnemyPokemon } from "#app/field/pokemon"; import { PokeballType } from "#enums/pokeball"; diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index c7220192caa..806a89a7131 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -24,7 +24,7 @@ import { Biome } from "#enums/biome"; import { getBiomeKey } from "#app/field/arena"; import { PokemonType } from "#enums/pokemon-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 { getPokemonNameWithAffix } from "#app/messages"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 2b29046f738..c189e341089 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -4,7 +4,7 @@ import { initBattleWithEnemyConfig, setEncounterRewards, } 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 { globalScene } from "#app/global-scene"; 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 { PokemonType } from "#enums/pokemon-type"; import { getPokeballTintColor } from "#app/data/pokeball"; -import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theExpertPokemonBreeder"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 5d3f834ed75..22ec52e976c 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -41,7 +41,8 @@ import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; 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"; /** i18n namespace for encounter */ diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 354f69d0ca3..5dd952b2bce 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -332,7 +332,6 @@ export function initMysteryEncounters() { }); // Add ANY biome encounters to biome map - // eslint-disable-next-line let _encounterBiomeTableLog = ""; mysteryEncountersByBiome.forEach((biomeEncounters, biome) => { anyBiomeEncounters.forEach(encounter => { diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index d37ac340a7c..5c6acf43e26 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -43,8 +43,9 @@ import type { Moves } from "#enums/moves"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { Status } from "#app/data/status-effect"; -import type { TrainerConfig } from "#app/data/trainer-config"; -import { trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; +import type { TrainerConfig } from "#app/data/trainers/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 { IEggOptions } from "#app/data/egg"; import { Egg } from "#app/data/egg"; diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index eca7d65ac6a..15c60d28969 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -7,7 +7,7 @@ import i18next from "i18next"; import type { AnySound } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; 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 { uncatchableSpecies } from "#app/data/balance/biomes"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; diff --git a/src/data/splash-messages.ts b/src/data/splash-messages.ts index de14e913664..3223bbb019e 100644 --- a/src/data/splash-messages.ts +++ b/src/data/splash-messages.ts @@ -214,7 +214,7 @@ const commonSplashMessages = [ "bornToBeAWinner", "onARollout", "itsAlwaysNightDeepInTheAbyss", - "folksThisIsInsane" + "folksThisIsInsane", ]; //#region Seasonal Messages @@ -224,10 +224,7 @@ const seasonalSplashMessages: Season[] = [ name: "New Year's", start: "01-01", end: "01-15", - messages: [ - "newYears.happyNewYear", - "newYears.andAHappyNewYear" - ], + messages: ["newYears.happyNewYear", "newYears.andAHappyNewYear"], }, { name: "Valentines", @@ -239,7 +236,7 @@ const seasonalSplashMessages: Season[] = [ "valentines.applinForYou", "valentines.thePowerOfLoveIsThreeThirtyBST", "valentines.haveAHeartScale", - "valentines.i<3You" + "valentines.i<3You", ], }, { @@ -279,7 +276,7 @@ const seasonalSplashMessages: Season[] = [ "aprilFools.nowWithQuickTimeEncounters", "aprilFools.timeYourInputsForHigherCatchrate", "aprilFools.certifiedButtonSimulator", - "aprilFools.iHopeYouGetSuckerPunched" + "aprilFools.iHopeYouGetSuckerPunched", ], }, { @@ -293,7 +290,7 @@ const seasonalSplashMessages: Season[] = [ "halloween.mayContainSpiders", "halloween.spookyScarySkeledirge", "halloween.gourgeistUsedTrickOrTreat", - "halloween.letsSnuggleForever" + "halloween.letsSnuggleForever", ], }, { @@ -316,7 +313,7 @@ const seasonalSplashMessages: Season[] = [ "winterHoliday.tisTheSeasonToBeSpeSpa", "winterHoliday.deckTheHalls", "winterHoliday.saveScummingGetsYouOnTheNaughtyList", - "winterHoliday.badTrainersGetRolycoly" + "winterHoliday.badTrainersGetRolycoly", ], }, ]; diff --git a/src/data/trainers/TrainerPartyTemplate.ts b/src/data/trainers/TrainerPartyTemplate.ts new file mode 100644 index 00000000000..adbaacc6b55 --- /dev/null +++ b/src/data/trainers/TrainerPartyTemplate.ts @@ -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, + ); +} diff --git a/src/data/trainers/evil-admin-trainer-pools.ts b/src/data/trainers/evil-admin-trainer-pools.ts new file mode 100644 index 00000000000..ecbd5952143 --- /dev/null +++ b/src/data/trainers/evil-admin-trainer-pools.ts @@ -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 = { + 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, +}; diff --git a/src/data/trainer-config.ts b/src/data/trainers/trainer-config.ts similarity index 88% rename from src/data/trainer-config.ts rename to src/data/trainers/trainer-config.ts index 0417e7abc32..f23027ffee8 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -1,29 +1,53 @@ -import { startingWave } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; -import type { ModifierTypeFunc } 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 * as Utils from "#app/utils"; -import { PokeballType } from "#enums/pokeball"; 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 { tmSpecies } from "#app/data/balance/tms"; -import { PokemonType } from "#enums/pokemon-type"; import { doubleBattleDialogue } from "#app/data/dialogue"; -import type { PersistentModifier } from "#app/modifier/modifier"; import { TrainerVariant } from "#app/field/trainer"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; 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 { Species } from "#enums/species"; -import { TrainerType } from "#enums/trainer-type"; -import { Gender } from "#app/data/gender"; -import { signatureSpecies } from "./balance/signature-species"; +import { PokeballType } from "#enums/pokeball"; +import { PokemonType } from "#enums/pokemon-type"; +import { Moves } from "#enums/moves"; 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 */ 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*/ 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. */ @@ -759,430 +536,6 @@ export class TrainerConfig { 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. * @param title The title of the evil team admin. @@ -1193,7 +546,7 @@ export class TrainerConfig { * **/ initForEvilTeamAdmin( title: string, - poolName: string, + poolName: EvilTeam, signatureSpecies: (Species | Species[])[], specialtyType?: PokemonType, ): TrainerConfig { @@ -1208,7 +561,7 @@ export class TrainerConfig { this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); // Set the species pools for the evil team admin. - this.speciesPools = this.speciesPoolPerEvilTeamAdmin(poolName); + this.speciesPools = evilAdminTrainerPools[poolName]; signatureSpecies.forEach((speciesPool, s) => { if (!Array.isArray(speciesPool)) { @@ -1637,56 +990,6 @@ export class TrainerConfig { 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. * Then adds Pokemon to globalScene. @@ -2878,7 +2181,7 @@ export const trainerConfigs: TrainerConfigs = { }), [TrainerType.ZINZOLIN]: new TrainerConfig(++t) .setMoneyMultiplier(1.5) - .initForEvilTeamAdmin("plasma_sage", "plasma", [Species.CRYOGONAL]) + .initForEvilTeamAdmin("plasma_sage", "plasma_zinzolin", [Species.CRYOGONAL]) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_plasma_grunt") @@ -2886,7 +2189,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), [TrainerType.COLRESS]: new TrainerConfig(++t) .setMoneyMultiplier(1.5) - .initForEvilTeamAdmin("plasma_boss", "plasma_2", [Species.KLINKLANG]) + .initForEvilTeamAdmin("plasma_boss", "plasma_colress", [Species.KLINKLANG]) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_colress") .setMixedBattleBgm("battle_colress") @@ -3105,7 +2408,7 @@ export const trainerConfigs: TrainerConfigs = { }), [TrainerType.OLEANA]: new TrainerConfig(++t) .setMoneyMultiplier(1.5) - .initForEvilTeamAdmin("macro_admin", "macro", [Species.GARBODOR]) + .initForEvilTeamAdmin("macro_admin", "macro_cosmos", [Species.GARBODOR]) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_oleana") @@ -3172,7 +2475,7 @@ export const trainerConfigs: TrainerConfigs = { }), [TrainerType.GIACOMO]: new TrainerConfig(++t) .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) .setBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_star_admin") @@ -3192,7 +2495,7 @@ export const trainerConfigs: TrainerConfigs = { ), [TrainerType.MELA]: new TrainerConfig(++t) .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) .setBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_star_admin") @@ -3212,7 +2515,7 @@ export const trainerConfigs: TrainerConfigs = { ), [TrainerType.ATTICUS]: new TrainerConfig(++t) .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) .setBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_star_admin") @@ -3232,7 +2535,7 @@ export const trainerConfigs: TrainerConfigs = { ), [TrainerType.ORTEGA]: new TrainerConfig(++t) .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) .setBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_star_admin") @@ -3252,7 +2555,7 @@ export const trainerConfigs: TrainerConfigs = { ), [TrainerType.ERI]: new TrainerConfig(++t) .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) .setBattleBgm("battle_plasma_grunt") .setMixedBattleBgm("battle_star_admin") diff --git a/src/data/trainers/typedefs.ts b/src/data/trainers/typedefs.ts new file mode 100644 index 00000000000..c6d286e961e --- /dev/null +++ b/src/data/trainers/typedefs.ts @@ -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; +} diff --git a/src/enums/tera-ai-mode.ts b/src/enums/tera-ai-mode.ts new file mode 100644 index 00000000000..35d4e4f3420 --- /dev/null +++ b/src/enums/tera-ai-mode.ts @@ -0,0 +1,5 @@ +export enum TeraAIMode { + NO_TERA, + INSTANT_TERA, + SMART_TERA +} diff --git a/src/enums/trainer-pool-tier.ts b/src/enums/trainer-pool-tier.ts new file mode 100644 index 00000000000..da6355d021b --- /dev/null +++ b/src/enums/trainer-pool-tier.ts @@ -0,0 +1,7 @@ +export enum TrainerPoolTier { + COMMON, + UNCOMMON, + RARE, + SUPER_RARE, + ULTRA_RARE +} diff --git a/src/enums/trainer-slot.ts b/src/enums/trainer-slot.ts new file mode 100644 index 00000000000..2dfa468f74c --- /dev/null +++ b/src/enums/trainer-slot.ts @@ -0,0 +1,5 @@ +export enum TrainerSlot { + NONE, + TRAINER, + TRAINER_PARTNER +} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d5d5319a860..15da9ac6f2a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -191,6 +191,9 @@ import { applyPreLeaveFieldAbAttrs, applyOnLoseAbAttrs, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, + applyAllyStatMultiplierAbAttrs, + AllyStatMultiplierAbAttr, + MoveAbilityBypassAbAttr, } from "#app/data/ability"; import type PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; @@ -221,7 +224,7 @@ import { SpeciesFormChangeStatusEffectTrigger, } from "#app/data/pokemon-forms"; 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 i18next from "i18next"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; @@ -259,6 +262,7 @@ import { import { Nature } from "#enums/nature"; import { StatusEffect } from "#enums/status-effect"; import { doShinySparkleAnim } from "#app/field/anims"; +import { MoveFlags } from "#enums/MoveFlags"; export enum LearnMoveSituation { MISC, @@ -1388,6 +1392,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param move the {@linkcode Move} being used * @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 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 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` @@ -1399,6 +1404,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { move?: Move, ignoreAbility = false, ignoreOppAbility = false, + ignoreAllyAbility = false, isCritical = false, simulated = true, 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 = statValue.value * this.getStatStageMultiplier( @@ -3888,6 +3899,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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; } @@ -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 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 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 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. @@ -3909,6 +3929,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { moveCategory: MoveCategory, ignoreAbility = false, ignoreSourceAbility = false, + ignoreAllyAbility = false, + ignoreSourceAllyAbility = false, isCritical = false, simulated = true, ): number { @@ -3931,6 +3953,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { undefined, ignoreSourceAbility, ignoreAbility, + ignoreAllyAbility, isCritical, simulated, ), @@ -3948,6 +3971,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { move, ignoreAbility, ignoreSourceAbility, + ignoreSourceAllyAbility, isCritical, 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 ignoreAbility If `true`, ignores this Pokemon's defensive 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 simulated If `true`, suppresses changes to game state during the calculation. * @returns a {@linkcode DamageCalculationResult} object with three fields: @@ -3994,6 +4020,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { move: Move, ignoreAbility = false, ignoreSourceAbility = false, + ignoreAllyAbility = false, + ignoreSourceAllyAbility = false, isCritical = false, simulated = true, ): DamageCalculationResult { @@ -4103,6 +4131,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { moveCategory, ignoreAbility, ignoreSourceAbility, + ignoreAllyAbility, + ignoreSourceAllyAbility, isCritical, simulated, ); @@ -4428,7 +4458,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { cancelled, result, damage: dmg, - } = this.getAttackDamage(source, move, false, false, isCritical, false); + } = this.getAttackDamage(source, move, false, false, false, false, isCritical, false); const typeBoost = source.findTag( t => @@ -7113,6 +7143,8 @@ export class EnemyPokemon extends Pokemon { move, !p.battleData.abilityRevealed, false, + !p.getAlly()?.battleData.abilityRevealed, + false, isCritical, ).damage >= p.hp ); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 2af3e25050f..c52957eef75 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -2,15 +2,14 @@ import { globalScene } from "#app/global-scene"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; -import type { TrainerConfig, TrainerPartyTemplate } from "#app/data/trainer-config"; -import { - TrainerPartyCompoundTemplate, - TrainerPoolTier, - TrainerSlot, - trainerConfigs, - trainerPartyTemplates, - TeraAIMode, -} from "#app/data/trainer-config"; +import type { TrainerConfig } from "#app/data/trainers/trainer-config"; +import type { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; +import { trainerConfigs } from "#app/data/trainers/trainer-config"; +import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate"; +import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; +import { TrainerSlot } from "#enums/trainer-slot"; +import { TrainerPoolTier } from "#enums/trainer-pool-tier"; +import { TeraAIMode } from "#enums/tera-ai-mode"; import type { EnemyPokemon } from "#app/field/pokemon"; import * as Utils from "#app/utils"; import type { PersistentModifier } from "#app/modifier/modifier"; diff --git a/src/phases/battle-phase.ts b/src/phases/battle-phase.ts index 3fc2b9c0467..72bcc85bc62 100644 --- a/src/phases/battle-phase.ts +++ b/src/phases/battle-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { TrainerSlot } from "#app/data/trainer-config"; +import { TrainerSlot } from "#enums/trainer-slot"; import { Phase } from "#app/phase"; export class BattlePhase extends Phase { diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 7afd4176788..ad2bf689e38 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -7,7 +7,7 @@ import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { doTrainerExclamation } from "#app/data/mystery-encounters/utils/encounter-phase-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 { EncounterPhaseEvent } from "#app/events/battle-scene"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/phases/game-over-modifier-reward-phase.ts b/src/phases/game-over-modifier-reward-phase.ts index f3f2aebd67d..d0a39a4031a 100644 --- a/src/phases/game-over-modifier-reward-phase.ts +++ b/src/phases/game-over-modifier-reward-phase.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index af948ad0632..2090592367d 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -5,7 +5,7 @@ import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; import type PokemonSpecies 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 { modifierTypes } from "#app/modifier/modifier-type"; import { BattlePhase } from "#app/phases/battle-phase"; diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index 2a5bcf6b99c..cff7249fcfa 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -29,7 +29,7 @@ export class MessagePhase extends Phase { if (this.text.indexOf("$") > -1) { const pokename: string[] = []; - const repname = [ "#POKEMON1", "#POKEMON2" ]; + const repname = ["#POKEMON1", "#POKEMON2"]; for (let p = 0; p < globalScene.getPlayerField().length; p++) { pokename.push(globalScene.getPlayerField()[p].getNameToRender()); this.text = this.text.split(pokename[p]).join(repname[p]); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 4152fc243f0..995684f8c03 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -653,7 +653,7 @@ export class MoveEffectPhase extends PokemonPhase { this.applyOnHitEffects(user, target, firstHit, lastHit, firstTarget); this.applyOnGetHitAbEffects(user, target, 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); } } diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 4716370cc4e..46e266a32b7 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; import { BattlerTagLapseType } from "#app/data/battler-tags"; import { PokemonPhase } from "./pokemon-phase"; diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 26012df191d..eb187617e69 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -22,7 +22,7 @@ import { globalScene } from "#app/global-scene"; import { getCharVariantFromDialogue } from "../data/dialogue"; import type { OptionSelectSettings } 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 { Phase } from "../phase"; import { Mode } from "../ui/ui"; diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index a7aa9389505..45b0a0f65ce 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -1,5 +1,4 @@ import { globalScene } from "#app/global-scene"; -import type { BattlerIndex } from "#app/battle"; import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability"; import { ArenaTrapTag } from "#app/data/arena-tag"; import { StatusEffect } from "#app/enums/status-effect"; diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 42463e7edb0..621c8c8c2a9 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -1,7 +1,7 @@ import { BattleType } from "#app/battle"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; 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 { addPokeballOpenParticles } from "#app/field/anims"; import type Pokemon from "#app/field/pokemon"; diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 48bcd0c4ebd..16868bf9bc0 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -3,7 +3,7 @@ import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAt import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move"; import { getPokeballTintColor } from "#app/data/pokeball"; 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 { getPokemonNameWithAffix } from "#app/messages"; import { SwitchEffectTransferModifier } from "#app/modifier/modifier"; diff --git a/src/phases/trainer-message-test-phase.ts b/src/phases/trainer-message-test-phase.ts index fa3f553cdd6..23c2c86361c 100644 --- a/src/phases/trainer-message-test-phase.ts +++ b/src/phases/trainer-message-test-phase.ts @@ -1,5 +1,5 @@ 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 { BattlePhase } from "./battle-phase"; import { TestMessagePhase } from "./test-message-phase"; diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 024c1e3f837..f7b2eb2bb66 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -7,7 +7,7 @@ import * as Utils from "#app/utils"; import { BattlePhase } from "./battle-phase"; import { ModifierRewardPhase } from "./modifier-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 { Biome } from "#app/enums/biome"; import { achvs } from "#app/system/achv"; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 7845d50b2ad..3a098a55a56 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -17,7 +17,7 @@ import { Unlockables } from "#app/system/unlockables"; import { GameModes, getGameMode } from "#app/game-mode"; import { BattleType } from "#app/battle"; 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 { achvs } from "#app/system/achv"; import EggData from "#app/system/egg-data"; diff --git a/src/system/game-speed.ts b/src/system/game-speed.ts index e2156c03728..d9c48664f80 100644 --- a/src/system/game-speed.ts +++ b/src/system/game-speed.ts @@ -5,8 +5,8 @@ import type BattleScene from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import * as Utils from "../utils"; -type FadeIn = typeof FadeIn; -type FadeOut = typeof FadeOut; +type FadeInType = typeof FadeIn; +type FadeOutType = typeof FadeOut; export function initGameSpeed() { const thisArg = this as BattleScene; @@ -101,7 +101,7 @@ export function initGameSpeed() { const originalFadeOut = SoundFade.fadeOut; 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; SoundFade.fadeIn = (( @@ -110,5 +110,5 @@ export function initGameSpeed() { duration: number, endVolume?: number, startVolume?: number, - ) => originalFadeIn(globalScene, sound, transformValue(duration), endVolume, startVolume)) as FadeIn; + ) => originalFadeIn(globalScene, sound, transformValue(duration), endVolume, startVolume)) as FadeInType; } diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 3fa07e8b085..957d43797a1 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -6,7 +6,7 @@ import type { PokeballType } from "#enums/pokeball"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { Status } from "../data/status-effect"; 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 { loadBattlerTag } from "../data/battler-tags"; import type { Biome } from "#enums/biome"; @@ -14,7 +14,6 @@ import { Moves } from "#enums/moves"; import type { Species } from "#enums/species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import type { PokemonType } from "#enums/pokemon-type"; -import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms"; export default class PokemonData { public id: number; diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index b2b1d3eb298..2db72dfecda 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -950,7 +950,7 @@ export function setSetting(setting: string, value: number): boolean { }, { label: "Català", - handler: () => changeLocaleHandler("ca-ES") + handler: () => changeLocaleHandler("ca-ES"), }, { label: i18next.t("settings:back"), diff --git a/src/system/voucher.ts b/src/system/voucher.ts index 39294bccf13..ce10560c3e2 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -3,7 +3,7 @@ import { AchvTier, achvs, getAchievementDescription } from "./achv"; import type { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; 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 { REGULAR, diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index 230b951de59..e927793e0ab 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -77,7 +77,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { const actionPattern = /@(c|d|s|f)\{(.*?)\}/; let actionMatch: RegExpExecArray | null; const pokename: string[] = []; - const repname = [ "#POKEMON1", "#POKEMON2" ]; + const repname = ["#POKEMON1", "#POKEMON2"]; for (let p = 0; p < globalScene.getPlayerField().length; p++) { pokename.push(globalScene.getPlayerField()[p].getNameToRender()); text = text.split(pokename[p]).join(repname[p]); diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index b359c188e0c..24812f62044 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -1150,15 +1150,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler { }); this.blockInput = false; } else { - ui.revertMode() - .then(() => { - console.log("exitCallback", this.exitCallback); - if (this.exitCallback instanceof Function) { - const exitCallback = this.exitCallback; - this.exitCallback = null; - exitCallback(); - } - }); + ui.revertMode().then(() => { + console.log("exitCallback", this.exitCallback); + if (this.exitCallback instanceof Function) { + const exitCallback = this.exitCallback; + this.exitCallback = null; + exitCallback(); + } + }); success = true; } } else { diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index a98415f72d2..230b1bcb42b 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -12,7 +12,7 @@ import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUN import { catchableSpecies } from "#app/data/balance/biomes"; import { PokemonType } from "#enums/pokemon-type"; 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 PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { TextStyle, addTextObject } from "#app/ui/text"; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 1599c86aa87..c1e2b2ac568 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1,6 +1,6 @@ import type { CandyUpgradeNotificationChangedEvent } 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 { getVariantTint, getVariantIcon } from "#app/data/variant"; 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 { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; 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 { PokemonType } from "#enums/pokemon-type"; import { GameModes } from "#app/game-mode"; diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 026e42ccf46..6605e5ef730 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -329,7 +329,7 @@ export default class UI extends Phaser.GameObjects.Container { promptDelay?: number | null, ): void { const pokename: string[] = []; - const repname = [ "#POKEMON1", "#POKEMON2" ]; + const repname = ["#POKEMON1", "#POKEMON2"]; for (let p = 0; p < globalScene.getPlayerField().length; p++) { pokename.push(globalScene.getPlayerField()[p].getNameToRender()); text = text.split(pokename[p]).join(repname[p]); diff --git a/test/abilities/desolate-land.test.ts b/test/abilities/desolate-land.test.ts index 405aab873aa..bb0b152418d 100644 --- a/test/abilities/desolate-land.test.ts +++ b/test/abilities/desolate-land.test.ts @@ -135,10 +135,8 @@ describe("Abilities - Desolate Land", () => { }); it("should lift after fleeing from a wild pokemon", async () => { - game.override - .enemyAbility(Abilities.DESOLATE_LAND) - .ability(Abilities.BALL_FETCH); - await game.classicMode.startBattle([ Species.MAGIKARP ]); + game.override.enemyAbility(Abilities.DESOLATE_LAND).ability(Abilities.BALL_FETCH); + await game.classicMode.startBattle([Species.MAGIKARP]); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN); vi.spyOn(game.scene.getPlayerPokemon()!, "randSeedInt").mockReturnValue(0); diff --git a/test/abilities/flower_gift.test.ts b/test/abilities/flower_gift.test.ts index c265742ef4a..5da796539e5 100644 --- a/test/abilities/flower_gift.test.ts +++ b/test/abilities/flower_gift.test.ts @@ -1,12 +1,14 @@ import { BattlerIndex } from "#app/battle"; +import { allAbilities } from "#app/data/ability"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import { WeatherType } from "#app/enums/weather-type"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; 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"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - Flower Gift", () => { let phaserGame: Phaser.Game; @@ -28,6 +30,64 @@ describe("Abilities - Flower Gift", () => { 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(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -41,23 +101,24 @@ describe("Abilities - Flower Gift", () => { beforeEach(() => { game = new GameManager(phaserGame); 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) .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 () => { game.override.battleType("double"); 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 cherrimSpDefStat = cherrim.getEffectiveStat(Stat.SPDEF); - // const magikarpAtkStat = magikarp.getEffectiveStat(Stat.ATK);; - // const magikarpSpDefStat = magikarp.getEffectiveStat(Stat.SPDEF); + const magikarpAtkStat = magikarp.getEffectiveStat(Stat.ATK); + const magikarpSpDefStat = magikarp.getEffectiveStat(Stat.SPDEF); game.move.select(Moves.SUNNY_DAY, 0); game.move.select(Moves.SPLASH, 1); @@ -68,8 +129,34 @@ describe("Abilities - Flower Gift", () => { expect(cherrim.formIndex).toBe(SUNSHINE_FORM); expect(cherrim.getEffectiveStat(Stat.ATK)).toBe(Math.floor(cherrimAtkStat * 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.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5)); + expect(magikarp.getEffectiveStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 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 () => { diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index aa5a48d5e4e..a10a246d855 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -160,10 +160,8 @@ describe("Abilities - Neutralizing Gas", () => { }); it("should deactivate after fleeing from a wild pokemon", async () => { - game.override - .enemyAbility(Abilities.NEUTRALIZING_GAS) - .ability(Abilities.BALL_FETCH); - await game.classicMode.startBattle([ Species.MAGIKARP ]); + game.override.enemyAbility(Abilities.NEUTRALIZING_GAS).ability(Abilities.BALL_FETCH); + await game.classicMode.startBattle([Species.MAGIKARP]); expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); vi.spyOn(game.scene.getPlayerPokemon()!, "randSeedInt").mockReturnValue(0); diff --git a/test/abilities/parental_bond.test.ts b/test/abilities/parental_bond.test.ts index 2aa24e78d6e..d4bf544e8c7 100644 --- a/test/abilities/parental_bond.test.ts +++ b/test/abilities/parental_bond.test.ts @@ -9,6 +9,7 @@ import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { BattlerIndex } from "#app/battle"; describe("Abilities - Parental Bond", () => { 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 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); + }); }); diff --git a/test/abilities/protosynthesis.test.ts b/test/abilities/protosynthesis.test.ts index d0ae46cd951..882474b7cef 100644 --- a/test/abilities/protosynthesis.test.ts +++ b/test/abilities/protosynthesis.test.ts @@ -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. mew.setNature(Nature.HARDY); const enemy = game.scene.getEnemyPokemon()!; - const def_before_boost = mew.getEffectiveStat(Stat.DEF, undefined, undefined, false, undefined, false, false, true); - const atk_before_boost = mew.getEffectiveStat(Stat.ATK, undefined, undefined, false, undefined, false, false, true); + const def_before_boost = mew.getEffectiveStat( + 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; game.move.select(Moves.TACKLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); const unboosted_dmg = initialHp - enemy.hp; enemy.hp = initialHp; - const def_after_boost = mew.getEffectiveStat(Stat.DEF, undefined, undefined, false, undefined, false, false, true); - const atk_after_boost = mew.getEffectiveStat(Stat.ATK, undefined, undefined, false, undefined, false, false, true); + const def_after_boost = mew.getEffectiveStat( + 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); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); diff --git a/test/abilities/victory_star.test.ts b/test/abilities/victory_star.test.ts new file mode 100644 index 00000000000..92db522871a --- /dev/null +++ b/test/abilities/victory_star.test.ts @@ -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); + }); +}); diff --git a/test/abilities/wonder_skin.test.ts b/test/abilities/wonder_skin.test.ts index db746831753..18d5be36aef 100644 --- a/test/abilities/wonder_skin.test.ts +++ b/test/abilities/wonder_skin.test.ts @@ -1,4 +1,3 @@ -import { allAbilities } from "#app/data/ability"; import { allMoves } from "#app/data/moves/move"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; diff --git a/test/data/splash_messages.test.ts b/test/data/splash_messages.test.ts index bd29a17339a..773b2715825 100644 --- a/test/data/splash_messages.test.ts +++ b/test/data/splash_messages.test.ts @@ -9,7 +9,7 @@ describe("Data - Splash Messages", () => { // Make sure to adjust this test if the weight is changed! 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); }); diff --git a/test/items/grip_claw.test.ts b/test/items/grip_claw.test.ts index 1d169006116..aa7c23ca43d 100644 --- a/test/items/grip_claw.test.ts +++ b/test/items/grip_claw.test.ts @@ -98,6 +98,31 @@ describe("Items - Grip Claw", () => { expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); 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); + }); }); /* diff --git a/test/items/multi_lens.test.ts b/test/items/multi_lens.test.ts index 90e73ae88ea..176e8213f55 100644 --- a/test/items/multi_lens.test.ts +++ b/test/items/multi_lens.test.ts @@ -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 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); + }); }); diff --git a/test/items/reviver_seed.test.ts b/test/items/reviver_seed.test.ts index ab249d48a23..c06f354a94a 100644 --- a/test/items/reviver_seed.test.ts +++ b/test/items/reviver_seed.test.ts @@ -26,7 +26,7 @@ describe("Items - Reviver Seed", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([ Moves.SPLASH, Moves.TACKLE, Moves.ENDURE ]) + .moveset([Moves.SPLASH, Moves.TACKLE, Moves.ENDURE]) .ability(Abilities.BALL_FETCH) .battleType("single") .disableCrits() @@ -47,13 +47,10 @@ describe("Items - Reviver Seed", () => { { moveType: "Fixed Damage Move", move: Moves.SEISMIC_TOSS }, { moveType: "Final Gambit", move: Moves.FINAL_GAMBIT }, { 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 }) => { - game.override - .enemyLevel(100) - .startingLevel(1) - .enemyMoveset(move); - await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + game.override.enemyLevel(100).startingLevel(1).enemyMoveset(move); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); const player = game.scene.getPlayerPokemon()!; player.damageAndUpdate(player.hp - 1); @@ -67,11 +64,8 @@ describe("Items - Reviver Seed", () => { }); it("should activate the holder's reviver seed from confusion self-hit", async () => { - game.override - .enemyLevel(1) - .startingLevel(100) - .enemyMoveset(Moves.SPLASH); - await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + game.override.enemyLevel(1).startingLevel(100).enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); const player = game.scene.getPlayerPokemon()!; player.damageAndUpdate(player.hp - 1); player.addTag(BattlerTagType.CONFUSED, 3); @@ -100,7 +94,7 @@ describe("Items - Reviver Seed", () => { .enemySpecies(Species.MAGIKARP) .moveset(move) .enemyMoveset(Moves.ENDURE); - await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); const enemy = game.scene.getEnemyPokemon()!; enemy.damageAndUpdate(enemy.hp - 1); @@ -124,7 +118,7 @@ describe("Items - Reviver Seed", () => { .moveset(move) .enemyAbility(Abilities.LIQUID_OOZE) .enemyMoveset(Moves.SPLASH); - await game.classicMode.startBattle([ Species.GASTLY, Species.FEEBAS ]); + await game.classicMode.startBattle([Species.GASTLY, Species.FEEBAS]); const player = game.scene.getPlayerPokemon()!; player.damageAndUpdate(player.hp - 1); @@ -145,13 +139,13 @@ describe("Items - Reviver Seed", () => { .moveset(Moves.DESTINY_BOND) .startingHeldItems([]) // reset held items to nothing so user doesn't revive and not trigger Destiny Bond .enemyMoveset(Moves.TACKLE); - await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); const player = game.scene.getPlayerPokemon()!; player.damageAndUpdate(player.hp - 1); const enemy = game.scene.getEnemyPokemon()!; game.move.select(Moves.DESTINY_BOND); - await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("TurnEndPhase"); expect(enemy.isFainted()).toBeTruthy(); diff --git a/test/moves/effectiveness.test.ts b/test/moves/effectiveness.test.ts index dc55392f8bf..fb03f1c10a0 100644 --- a/test/moves/effectiveness.test.ts +++ b/test/moves/effectiveness.test.ts @@ -1,6 +1,6 @@ import { allMoves } from "#app/data/moves/move"; 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 { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; diff --git a/test/moves/endure.test.ts b/test/moves/endure.test.ts index d706d5d9581..8fbb2272ece 100644 --- a/test/moves/endure.test.ts +++ b/test/moves/endure.test.ts @@ -22,7 +22,7 @@ describe("Moves - Endure", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([ Moves.THUNDER, Moves.BULLET_SEED, Moves.TOXIC, Moves.SHEER_COLD ]) + .moveset([Moves.THUNDER, Moves.BULLET_SEED, Moves.TOXIC, Moves.SHEER_COLD]) .ability(Abilities.SKILL_LINK) .startingLevel(100) .battleType("single") @@ -51,7 +51,7 @@ describe("Moves - Endure", () => { }); it("should let the pokemon survive against OHKO moves", async () => { - await game.classicMode.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([Species.MAGIKARP]); const enemy = game.scene.getEnemyPokemon()!; game.move.select(Moves.SHEER_COLD); @@ -74,7 +74,7 @@ describe("Moves - Endure", () => { .enemySpecies(Species.MAGIKARP) .moveset(move) .enemyMoveset(Moves.ENDURE); - await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); const enemy = game.scene.getEnemyPokemon()!; enemy.damageAndUpdate(enemy.hp - 1); diff --git a/test/moves/false_swipe.test.ts b/test/moves/false_swipe.test.ts new file mode 100644 index 00000000000..4fb5b81ef67 --- /dev/null +++ b/test/moves/false_swipe.test.ts @@ -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); + }); +}); diff --git a/test/moves/revival_blessing.test.ts b/test/moves/revival_blessing.test.ts index 1ceb850edea..87be20f60ad 100644 --- a/test/moves/revival_blessing.test.ts +++ b/test/moves/revival_blessing.test.ts @@ -116,12 +116,8 @@ describe("Moves - Revival Blessing", () => { }); it("should not summon multiple pokemon to the same slot when reviving the enemy ally in doubles", async () => { - game.override - .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 ]); + game.override.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]); const enemyFainting = game.scene.getEnemyField()[0]; diff --git a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 8e825dc2c9b..f620cbd6c36 100644 --- a/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -16,7 +16,9 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; import { ModifierTier } from "#app/modifier/modifier-tier"; 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 { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; diff --git a/test/testUtils/gameWrapper.ts b/test/testUtils/gameWrapper.ts index 28c7c4af80f..388861e01c4 100644 --- a/test/testUtils/gameWrapper.ts +++ b/test/testUtils/gameWrapper.ts @@ -1,31 +1,25 @@ -/* eslint-disable */ -// @ts-nocheck +// @ts-nocheck - TODO: remove this import BattleScene, * as battleScene from "#app/battle-scene"; import { MoveAnim } from "#app/data/battle-anims"; import Pokemon from "#app/field/pokemon"; import * as Utils from "#app/utils"; import { blobToString } from "#test/testUtils/gameManagerUtils"; import { MockClock } from "#test/testUtils/mocks/mockClock"; -import { MockConsoleLog } from "#test/testUtils/mocks/mockConsoleLog"; import { MockFetch } from "#test/testUtils/mocks/mockFetch"; 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 fs from "node:fs"; 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 { version } from "../../package.json"; import { MockGameObjectCreator } from "./mocks/mockGameObjectCreator"; +import { MockTimedEventManager } from "./mocks/mockTimedEventManager"; import InputManager = Phaser.Input.InputManager; import KeyboardManager = Phaser.Input.Keyboard.KeyboardManager; import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin; import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin; import EventEmitter = Phaser.Events.EventEmitter; import UpdateList = Phaser.GameObjects.UpdateList; -import { version } from "../../package.json"; -import { MockTimedEventManager } from "./mocks/mockTimedEventManager"; window.URL.createObjectURL = (blob: Blob) => { blobToString(blob).then((data: string) => { diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 2d56ae35fce..9bb0369a31a 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -1,9 +1,6 @@ import type { Variant } from "#app/data/variant"; import { Weather } from "#app/data/weather"; 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 { BattleStyle } from "#app/overrides"; import Overrides, { defaultOverrides } from "#app/overrides"; diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index 854baed5915..7bdf343759d 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -1,4 +1,3 @@ -import { off } from "process"; import type { MockGameObject } from "../mockGameObject"; export default class MockRectangle implements MockGameObject { diff --git a/test/vitest.setup.ts b/test/vitest.setup.ts index b9da0850306..93b439e540f 100644 --- a/test/vitest.setup.ts +++ b/test/vitest.setup.ts @@ -1,17 +1,4 @@ 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 { 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. */ vi.mock("#app/overrides", async importOriginal => { - // eslint-disable-next-line @typescript-eslint/consistent-type-imports const { defaultOverrides } = await importOriginal(); return { default: defaultOverrides, defaultOverrides, - // eslint-disable-next-line @typescript-eslint/consistent-type-imports } satisfies typeof import("#app/overrides"); });