Merge branch 'beta' into deep-sea-items

This commit is contained in:
Madmadness65 2025-03-03 15:21:52 -06:00 committed by GitHub
commit 7d0ff7d6fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 926 additions and 386 deletions

17
.github/CODEOWNERS vendored
View File

@ -4,4 +4,19 @@
* @pagefaultgames/junior-dev-team
# github actions/templates etc. - Dev Leads
/.github @pagefaultgames/dev-leads
/.github @pagefaultgames/senior-dev-team
# Art Team
/public/**/*.png @pagefaultgames/art-team
/public/**/*.json @pagefaultgames/art-team
/public/images @pagefaultgames/art-team
/public/battle-anims @pagefaultgames/art-team
# Audio files
*.mp3 @pagefaultgames/composer-team
*.wav @pagefaultgames/composer-team
*.ogg @pagefaultgames/composer-team
/public/audio @pagefaultgames/composer-team
# Balance Files; contain actual code logic and must also be owned by dev team
/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team

View File

@ -133,7 +133,7 @@
<span class="apad-label">V</span>
</div>
<!-- buttons to display battle-specific information -->
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="V">
<div id="apadInfo" class="apad-button apad-rectangle apad-small" data-key="CYCLE_TERA">
<span class="apad-label">V</span>
</div>
<div id="apadStats" class="apad-button apad-rectangle apad-small" data-key="STATS">

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "pokemon-rogue-battle",
"version": "1.7.0",
"version": "1.7.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pokemon-rogue-battle",
"version": "1.7.0",
"version": "1.7.6",
"hasInstallScript": true,
"dependencies": {
"@material/material-color-utilities": "^0.2.7",

View File

@ -1,7 +1,7 @@
{
"name": "pokemon-rogue-battle",
"private": true,
"version": "1.7.0",
"version": "1.7.6",
"type": "module",
"scripts": {
"start": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 528 B

View File

@ -3,12 +3,12 @@
"838394": "4d7dc5",
"62ace6": "8363af",
"7bcdff": "9c75c2",
"ffec8c": "ddfff9",
"fdea88": "ddfff9",
"a1a1c4": "7ab7ec",
"c9b241": "97d6e2",
"dfcf77": "bae7e8",
"ccbd70": "bae7e8",
"174592": "37408c",
"fdfdfd": "b1e5ff",
"f8f8f8": "b1e5ff",
"9c9cc5": "5385c7",
"cdcde6": "7eb7e8",
"396a83": "362864",
@ -18,12 +18,12 @@
"838394": "cc6845",
"62ace6": "c44848",
"7bcdff": "dd6155",
"ffec8c": "ddfff9",
"fdea88": "ddfff9",
"a1a1c4": "f7c685",
"c9b241": "97d6e2",
"dfcf77": "bae7e8",
"ccbd70": "bae7e8",
"174592": "198158",
"fdfdfd": "fff4bd",
"f8f8f8": "fff4bd",
"9c9cc5": "c96a48",
"cdcde6": "f7b785",
"396a83": "5c0d33",

View File

@ -1,17 +1,17 @@
{
"1": {
"838394": "4d7dc5",
"848496": "4d7dc5",
"7bcdff": "9c75c2",
"62ace6": "8363af",
"ffffff": "b1e5ff",
"396a83": "362864",
"9c9cc5": "5385c7",
"cdcde6": "7eb7e8",
"174592": "198158",
"174592": "37408c",
"5a94cd": "7054a4"
},
"2": {
"838394": "cc6845",
"848496": "cc6845",
"7bcdff": "dd6155",
"62ace6": "c44848",
"ffffff": "fff4bd",

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

@ -1 +1 @@
Subproject commit ef43efffe5fe454862c350f1b9393c3ad755bcc2
Subproject commit 0e5c6096ba26f6b87aed1aab3fe9b0b23f6cbb7b

View File

@ -1865,6 +1865,58 @@ export default class BattleScene extends SceneBase {
this.getCurrentPhase()?.constructor.name ?? "",
);
if ( // Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros.
!isEggPhase &&
this.currentBattle?.battleType === BattleType.TRAINER && !isNullOrUndefined(this.currentBattle.trainer) &&
this.currentBattle.trainer.config.hasSpecialtyType()
) {
if (species.speciesId === Species.WORMADAM) {
switch (this.currentBattle.trainer.config.specialtyType) {
case Type.GROUND:
return 1; // Sandy Cloak
case Type.STEEL:
return 2; // Trash Cloak
case Type.GRASS:
return 0; // Plant Cloak
}
} else if (species.speciesId === Species.ROTOM) {
switch (this.currentBattle.trainer.config.specialtyType) {
case Type.FLYING:
return 4; // Fan Rotom
case Type.GHOST:
return 0; // Lightbulb Rotom
case Type.FIRE:
return 1; // Heat Rotom
case Type.GRASS:
return 5; // Mow Rotom
case Type.WATER:
return 2; // Wash Rotom
case Type.ICE:
return 3; // Frost Rotom
}
} else if (species.speciesId === Species.ORICORIO) {
switch (this.currentBattle.trainer.config.specialtyType) {
case Type.GHOST:
return 3; // Sensu Style
case Type.FIRE:
return 0; // Baile Style
case Type.ELECTRIC:
return 1; // Pom-Pom Style
case Type.PSYCHIC:
return 2; // Pa'u Style
}
} else if (species.speciesId === Species.PALDEA_TAUROS) {
switch (this.currentBattle.trainer.config.specialtyType) {
case Type.FIRE:
return 1; // Blaze Breed
case Type.WATER:
return 2; // Aqua Breed
}
} else if (species.speciesId === Species.SILVALLY || species.speciesId === Species.ARCEUS) { // Would probably never happen, but might as well
return this.currentBattle.trainer.config.specialtyType;
}
}
switch (species.speciesId) {
case Species.UNOWN:
case Species.SHELLOS:
@ -1872,8 +1924,6 @@ export default class BattleScene extends SceneBase {
case Species.BASCULIN:
case Species.DEERLING:
case Species.SAWSBUCK:
case Species.FROAKIE:
case Species.FROGADIER:
case Species.SCATTERBUG:
case Species.SPEWPA:
case Species.VIVILLON:
@ -1907,9 +1957,14 @@ export default class BattleScene extends SceneBase {
return 0; // No Partner Eevee for Wave 12 Preschoolers
}
return Utils.randSeedInt(2);
case Species.FROAKIE:
case Species.FROGADIER:
case Species.GRENINJA:
if (this.currentBattle?.battleType === BattleType.TRAINER) {
return 0; // Don't give trainers Battle Bond Greninja
if (
this.currentBattle?.battleType === BattleType.TRAINER &&
!isEggPhase
) {
return 0; // Don't give trainers Battle Bond Greninja, Froakie or Frogadier
}
return Utils.randSeedInt(2);
case Species.URSHIFU:
@ -3448,8 +3503,6 @@ export default class BattleScene extends SceneBase {
for (const modifier of modifiers) {
this.addEnemyModifier(modifier, true, true);
}
this.currentBattle.trainer.genAI(party);
}
party.forEach((enemyPokemon: EnemyPokemon, i: number) => {

View File

@ -12,7 +12,7 @@ import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffect
import { Gender } from "./gender";
import type Move from "./move";
import { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move";
import type { ArenaTrapTag } from "./arena-tag";
import type { ArenaTrapTag, SuppressAbilitiesTag } from "./arena-tag";
import { ArenaTagSide } from "./arena-tag";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier";
import { TerrainType } from "./terrain";
@ -2197,6 +2197,34 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr {
}
}
/**
* Generic class to add an arena tag upon switching in
*/
export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr {
private readonly tagType: ArenaTagType;
private readonly turnCount: number;
private readonly side?: ArenaTagSide;
private readonly quiet?: boolean;
private sourceId: number;
constructor(tagType: ArenaTagType, turnCount: number, side?: ArenaTagSide, quiet?: boolean) {
super(false);
this.tagType = tagType;
this.turnCount = turnCount;
this.side = side;
this.quiet = quiet;
}
public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
this.sourceId = pokemon.id;
if (!simulated) {
globalScene.arena.addTag(this.tagType, this.turnCount, undefined, this.sourceId, this.side, this.quiet);
}
return true;
}
}
export class PostSummonMessageAbAttr extends PostSummonAbAttr {
private messageFunc: (pokemon: Pokemon) => string;
@ -2941,6 +2969,26 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr {
}
}
/**
* Updates the active {@linkcode SuppressAbilitiesTag} when a pokemon with {@linkcode Abilities.NEUTRALIZING_GAS} leaves the field
*/
export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFieldAbAttr {
constructor() {
super(false);
}
public override applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
if (!simulated) {
const suppressTag = globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
if (suppressTag) {
suppressTag.onSourceLeave(globalScene.arena);
return true;
}
}
return simulated;
}
}
export class PreStatStageChangeAbAttr extends AbAttr {
applyPreStatStageChange(
pokemon: Pokemon | null,
@ -4692,21 +4740,6 @@ export class MoveAbilityBypassAbAttr extends AbAttr {
}
}
export class SuppressFieldAbilitiesAbAttr extends AbAttr {
constructor() {
super(false);
}
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ability = (args[0] as Ability);
if (!ability.hasAttr(UnsuppressableAbilityAbAttr) && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) {
cancelled.value = true;
return true;
}
return false;
}
}
export class AlwaysHitAbAttr extends AbAttr { }
/** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */
@ -5021,7 +5054,7 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
const isCommandFight = turnCommand?.command === Command.FIGHT;
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
if (this.condition(pokemon, move!) && isCommandFight) {
if (isCommandFight && this.condition(pokemon, move!)) {
bypassSpeed.value = false;
canCheckHeldItems.value = false;
return false;
@ -5119,6 +5152,10 @@ function applySingleAbAttrs<TAttr extends AbAttr>(
showAbilityInstant: boolean = false,
messages: string[] = []
) {
if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) {
return;
}
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
if (gainedMidTurn && ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain())) {
return;
@ -5412,12 +5449,10 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(
gainedMidTurn: boolean = false
) {
for (const passive of [ false, true ]) {
if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) {
continue;
if (pokemon) {
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages);
globalScene.clearPhaseQueueSplice();
}
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages);
globalScene.clearPhaseQueueSplice();
}
}
@ -5946,10 +5981,10 @@ export function applyOnGainAbAttrs(pokemon: Pokemon, passive: boolean = false, s
}
/**
* Clears primal weather during the turn if {@linkcode pokemon}'s ability corresponds to one
* Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one
*/
export function applyOnLoseClearWeatherAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void {
applySingleAbAttrs<PreLeaveFieldClearWeatherAbAttr>(pokemon, passive, PreLeaveFieldClearWeatherAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated);
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void {
applySingleAbAttrs<PreLeaveFieldAbAttr>(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated);
}
function queueShowAbility(pokemon: Pokemon, passive: boolean): void {
globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive));
@ -6838,12 +6873,12 @@ export function initAbilities() {
new Ability(Abilities.GORILLA_TACTICS, 8)
.attr(GorillaTacticsAbAttr),
new Ability(Abilities.NEUTRALIZING_GAS, 8)
.attr(SuppressFieldAbilitiesAbAttr)
.attr(PostSummonAddArenaTagAbAttr, ArenaTagType.NEUTRALIZING_GAS, 0)
.attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.partial(), // A bunch of weird interactions with other abilities being suppressed then unsuppressed
.bypassFaint(),
new Ability(Abilities.PASTEL_VEIL, 8)
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)

View File

@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon";
import { HitResult, PokemonMove } from "#app/field/pokemon";
import { StatusEffect } from "#enums/status-effect";
import type { BattlerIndex } from "#app/battle";
import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, ProtectStatAbAttr, applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#app/data/ability";
import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
import i18next from "i18next";
@ -1221,6 +1221,76 @@ export class FairyLockTag extends ArenaTag {
}
/**
* Arena tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(Ability) Neutralizing Gas}
*
* Keeps track of the number of pokemon on the field with Neutralizing Gas - If it drops to zero, the effect is ended and abilities are reactivated
*
* Additionally ends onLose abilities when it is activated
*/
export class SuppressAbilitiesTag extends ArenaTag {
private sourceCount: number;
private beingRemoved: boolean;
constructor(sourceId: number) {
super(ArenaTagType.NEUTRALIZING_GAS, 0, undefined, sourceId);
this.sourceCount = 1;
this.beingRemoved = false;
}
public override onAdd(arena: Arena): void {
const pokemon = this.getSourcePokemon();
if (pokemon) {
globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
for (const fieldPokemon of globalScene.getField()) {
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
[ true, false ].forEach((passive) => applyOnLoseAbAttrs(fieldPokemon, passive));
}
}
}
}
public override onOverlap(arena: Arena): void {
this.sourceCount++;
}
public onSourceLeave(arena: Arena): void {
this.sourceCount--;
if (this.sourceCount <= 0) {
arena.removeTag(ArenaTagType.NEUTRALIZING_GAS);
} else if (this.sourceCount === 1) {
// With 1 source left, that pokemon's other abilities should reactivate
// This may be confusing for players but would be the most accurate gameplay-wise
// Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all
const setter = globalScene.getField().filter((p) => p && p.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0];
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr));
}
}
public override onRemove(arena: Arena, quiet: boolean = false) {
this.beingRemoved = true;
if (!quiet) {
globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove"));
}
for (const pokemon of globalScene.getField()) {
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) {
[ true, false ].forEach((passive) => applyOnGainAbAttrs(pokemon, passive));
}
}
}
public shouldApplyToSelf(): boolean {
return this.sourceCount > 1;
}
public isBeingRemoved() {
return this.beingRemoved;
}
}
// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter
export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
switch (tagType) {
@ -1281,6 +1351,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove
return new GrassWaterPledgeTag(sourceId, side);
case ArenaTagType.FAIRY_LOCK:
return new FairyLockTag(turnCount, sourceId);
case ArenaTagType.NEUTRALIZING_GAS:
return new SuppressAbilitiesTag(sourceId);
default:
return null;
}

View File

@ -46,7 +46,7 @@ export const speciesEggMoves = {
[Species.DROWZEE]: [ Moves.BADDY_BAD, Moves.STRENGTH_SAP, Moves.LUMINA_CRASH, Moves.DARK_VOID ],
[Species.KRABBY]: [ Moves.DIRE_CLAW, Moves.DRAGON_HAMMER, Moves.IVY_CUDGEL, Moves.JET_PUNCH ],
[Species.VOLTORB]: [ Moves.NASTY_PLOT, Moves.FUSION_FLARE, Moves.FROST_BREATH, Moves.ELECTRO_DRIFT ],
[Species.EXEGGCUTE]: [ Moves.FICKLE_BEAM, Moves.APPLE_ACID, Moves.TRICK_ROOM, Moves.LUMINA_CRASH ],
[Species.EXEGGCUTE]: [ Moves.FICKLE_BEAM, Moves.APPLE_ACID, Moves.HEAT_WAVE, Moves.LUMINA_CRASH ],
[Species.CUBONE]: [ Moves.HEAD_SMASH, Moves.WOOD_HAMMER, Moves.SHADOW_SNEAK, Moves.BITTER_BLADE ],
[Species.LICKITUNG]: [ Moves.CRUSH_GRIP, Moves.FIRE_LASH, Moves.SLACK_OFF, Moves.MAGICAL_TORQUE ],
[Species.KOFFING]: [ Moves.SCALD, Moves.RECOVER, Moves.BODY_PRESS, Moves.MALIGNANT_CHAIN ],
@ -59,7 +59,7 @@ export const speciesEggMoves = {
[Species.SCYTHER]: [ Moves.MIGHTY_CLEAVE, Moves.GEAR_GRIND, Moves.STORM_THROW, Moves.BITTER_BLADE ],
[Species.PINSIR]: [ Moves.HEADLONG_RUSH, Moves.LEECH_LIFE, Moves.CRUSH_GRIP, Moves.EXTREME_SPEED ],
[Species.TAUROS]: [ Moves.SWORDS_DANCE, Moves.FIRE_LASH, Moves.WICKED_TORQUE, Moves.COLLISION_COURSE ],
[Species.MAGIKARP]: [ Moves.FLIP_TURN, Moves.ICE_SPINNER, Moves.DRAGON_ASCENT, Moves.SURGING_STRIKES ],
[Species.MAGIKARP]: [ Moves.FLIP_TURN, Moves.ICE_SPINNER, Moves.KNOCK_OFF, Moves.DRAGON_ASCENT ],
[Species.LAPRAS]: [ Moves.RECOVER, Moves.FREEZE_DRY, Moves.SCALD, Moves.SHELL_SMASH ],
[Species.DITTO]: [ Moves.MIMIC, Moves.SKETCH, Moves.METRONOME, Moves.IMPRISON ],
[Species.EEVEE]: [ Moves.WISH, Moves.NO_RETREAT, Moves.ZIPPY_ZAP, Moves.BOOMBURST ],
@ -76,7 +76,7 @@ export const speciesEggMoves = {
[Species.CHIKORITA]: [ Moves.SAPPY_SEED, Moves.STONE_AXE, Moves.DRAGON_DANCE, Moves.SPORE ],
[Species.CYNDAQUIL]: [ Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.FIERY_DANCE, Moves.ELECTRO_DRIFT ],
[Species.TOTODILE]: [ Moves.THUNDER_PUNCH, Moves.DRAGON_DANCE, Moves.TRIPLE_AXEL, Moves.SURGING_STRIKES ],
[Species.TOTODILE]: [ Moves.THUNDER_PUNCH, Moves.DRAGON_DANCE, Moves.PLAY_ROUGH, Moves.SURGING_STRIKES ],
[Species.SENTRET]: [ Moves.TIDY_UP, Moves.FAKE_OUT, Moves.NUZZLE, Moves.EXTREME_SPEED ],
[Species.HOOTHOOT]: [ Moves.CALM_MIND, Moves.ESPER_WING, Moves.AEROBLAST, Moves.BOOMBURST ],
[Species.LEDYBA]: [ Moves.POLLEN_PUFF, Moves.MAT_BLOCK, Moves.PARTING_SHOT, Moves.SPORE ],
@ -159,7 +159,7 @@ export const speciesEggMoves = {
[Species.VOLBEAT]: [ Moves.BATON_PASS, Moves.STICKY_WEB, Moves.DECORATE, Moves.VICTORY_DANCE ],
[Species.ILLUMISE]: [ Moves.PARTING_SHOT, Moves.GLITZY_GLOW, Moves.POWDER, Moves.QUIVER_DANCE ],
[Species.GULPIN]: [ Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.CALM_MIND, Moves.STRENGTH_SAP ],
[Species.CARVANHA]: [ Moves.THUNDER_FANG, Moves.SWORDS_DANCE, Moves.OBSTRUCT, Moves.SURGING_STRIKES ],
[Species.CARVANHA]: [ Moves.THUNDER_FANG, Moves.GUNK_SHOT, Moves.OBSTRUCT, Moves.SURGING_STRIKES ],
[Species.WAILMER]: [ Moves.TAKE_HEART, Moves.COMEUPPANCE, Moves.SLACK_OFF, Moves.STEAM_ERUPTION ],
[Species.NUMEL]: [ Moves.TRICK_ROOM, Moves.ENERGY_BALL, Moves.SLACK_OFF, Moves.BLUE_FLARE ],
[Species.TORKOAL]: [ Moves.MORNING_SUN, Moves.BURNING_BULWARK, Moves.BODY_PRESS, Moves.HYDRO_STEAM ],
@ -235,7 +235,7 @@ export const speciesEggMoves = {
[Species.RIOLU]: [ Moves.THUNDEROUS_KICK, Moves.TACHYON_CUTTER, Moves.TRIPLE_AXEL, Moves.SUNSTEEL_STRIKE ],
[Species.HIPPOPOTAS]: [ Moves.SHORE_UP, Moves.STONE_AXE, Moves.BULK_UP, Moves.SALT_CURE ],
[Species.SKORUPI]: [ Moves.COIL, Moves.DIRE_CLAW, Moves.CRABHAMMER, Moves.WICKED_BLOW ],
[Species.CROAGUNK]: [ Moves.DIRE_CLAW, Moves.ICE_SPINNER, Moves.THUNDEROUS_KICK, Moves.VICTORY_DANCE ],
[Species.CROAGUNK]: [ Moves.DIRE_CLAW, Moves.TRIPLE_AXEL, Moves.THUNDEROUS_KICK, Moves.VICTORY_DANCE ],
[Species.CARNIVINE]: [ Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.COIL, Moves.SAPPY_SEED ],
[Species.FINNEON]: [ Moves.QUIVER_DANCE, Moves.SPLISHY_SPLASH, Moves.FREEZE_DRY, Moves.OBLIVION_WING ],
[Species.MANTYKE]: [ Moves.SPLISHY_SPLASH, Moves.FREEZY_FROST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ],
@ -267,7 +267,7 @@ export const speciesEggMoves = {
[Species.PANSEAR]: [ Moves.NASTY_PLOT, Moves.HYDRO_STEAM, Moves.EARTH_POWER, Moves.ERUPTION ],
[Species.PANPOUR]: [ Moves.NASTY_PLOT, Moves.ENERGY_BALL, Moves.EARTH_POWER, Moves.WATER_SPOUT ],
[Species.MUNNA]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.LUNAR_BLESSING, Moves.MYSTICAL_POWER ],
[Species.PIDOVE]: [ Moves.GUNK_SHOT, Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ],
[Species.PIDOVE]: [ Moves.SLASH, Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ],
[Species.BLITZLE]: [ Moves.HORN_LEECH, Moves.SWORDS_DANCE, Moves.FLARE_BLITZ, Moves.BOLT_STRIKE ],
[Species.ROGGENROLA]: [ Moves.BODY_PRESS, Moves.CURSE, Moves.SHORE_UP, Moves.DIAMOND_STORM ],
[Species.WOOBAT]: [ Moves.ESPER_WING, Moves.STORED_POWER, Moves.MYSTICAL_FIRE, Moves.OBLIVION_WING ],
@ -282,7 +282,7 @@ export const speciesEggMoves = {
[Species.COTTONEE]: [ Moves.POLLEN_PUFF, Moves.PARTING_SHOT, Moves.SLEEP_POWDER, Moves.SEED_FLARE ],
[Species.PETILIL]: [ Moves.THUNDEROUS_KICK, Moves.SPARKLING_ARIA, Moves.FIERY_DANCE, Moves.FLOWER_TRICK ],
[Species.BASCULIN]: [ Moves.LAST_RESPECTS, Moves.CLOSE_COMBAT, Moves.SPLISHY_SPLASH, Moves.NO_RETREAT ],
[Species.SANDILE]: [ Moves.DIRE_CLAW, Moves.HEADLONG_RUSH, Moves.FIRE_LASH, Moves.WICKED_BLOW ],
[Species.SANDILE]: [ Moves.DIRE_CLAW, Moves.SUCKER_PUNCH, Moves.FIRE_LASH, Moves.HEADLONG_RUSH ],
[Species.DARUMAKA]: [ Moves.DRAIN_PUNCH, Moves.ZIPPY_ZAP, Moves.HEADLONG_RUSH, Moves.PYRO_BALL ],
[Species.MARACTUS]: [ Moves.EARTH_POWER, Moves.QUIVER_DANCE, Moves.FIERY_DANCE, Moves.SEED_FLARE ],
[Species.DWEBBLE]: [ Moves.CRABHAMMER, Moves.STONE_AXE, Moves.LEECH_LIFE, Moves.MIGHTY_CLEAVE ],
@ -327,8 +327,8 @@ export const speciesEggMoves = {
[Species.DEINO]: [ Moves.FIERY_WRATH, Moves.ESPER_WING, Moves.SLUDGE_BOMB, Moves.FICKLE_BEAM ],
[Species.LARVESTA]: [ Moves.THUNDERBOLT, Moves.DAZZLING_GLEAM, Moves.EARTH_POWER, Moves.HYDRO_STEAM ],
[Species.COBALION]: [ Moves.BEHEMOTH_BLADE, Moves.MIGHTY_CLEAVE, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ],
[Species.TERRAKION]: [ Moves.MIGHTY_CLEAVE, Moves.HEADLONG_RUSH, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ],
[Species.VIRIZION]: [ Moves.SAPPY_SEED, Moves.PSYBLADE, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ],
[Species.TERRAKION]: [ Moves.MIGHTY_CLEAVE, Moves.HEADLONG_RUSH, Moves.KNOCK_OFF, Moves.VICTORY_DANCE ],
[Species.VIRIZION]: [ Moves.SAPPY_SEED, Moves.PSYBLADE, Moves.STONE_AXE, Moves.VICTORY_DANCE ],
[Species.TORNADUS]: [ Moves.SANDSEAR_STORM, Moves.PARTING_SHOT, Moves.SPLISHY_SPLASH, Moves.OBLIVION_WING ],
[Species.THUNDURUS]: [ Moves.SANDSEAR_STORM, Moves.HURRICANE, Moves.FROST_BREATH, Moves.ELECTRO_SHOT ],
[Species.RESHIRAM]: [ Moves.ENERGY_BALL, Moves.TAKE_HEART, Moves.FICKLE_BEAM, Moves.ERUPTION ],
@ -342,7 +342,7 @@ export const speciesEggMoves = {
[Species.CHESPIN]: [ Moves.COMBAT_TORQUE, Moves.SYNTHESIS, Moves.CEASELESS_EDGE, Moves.SAPPY_SEED ],
[Species.FENNEKIN]: [ Moves.TWIN_BEAM, Moves.FIERY_DANCE, Moves.THUNDERBOLT, Moves.SPARKLY_SWIRL ],
[Species.FROAKIE]: [ Moves.MOONBLAST, Moves.SHELL_SIDE_ARM, Moves.FIERY_WRATH, Moves.STEAM_ERUPTION ],
[Species.BUNNELBY]: [ Moves.DRAIN_PUNCH, Moves.TIDY_UP, Moves.FACADE, Moves.EXTREME_SPEED ],
[Species.BUNNELBY]: [ Moves.DRAIN_PUNCH, Moves.TIDY_UP, Moves.LANDS_WRATH, Moves.EXTREME_SPEED ],
[Species.FLETCHLING]: [ Moves.DRILL_RUN, Moves.BURNING_BULWARK, Moves.HEAD_SMASH, Moves.VOLT_TACKLE ],
[Species.SCATTERBUG]: [ Moves.FOCUS_BLAST, Moves.AFTER_YOU, Moves.DECORATE, Moves.BLIZZARD ],
[Species.LITLEO]: [ Moves.EARTH_POWER, Moves.NASTY_PLOT, Moves.BURNING_BULWARK, Moves.BLUE_FLARE ],
@ -372,7 +372,7 @@ export const speciesEggMoves = {
[Species.NOIBAT]: [ Moves.AEROBLAST, Moves.OVERDRIVE, Moves.NASTY_PLOT, Moves.CLANGING_SCALES ],
[Species.XERNEAS]: [ Moves.EARTH_POWER, Moves.SPRINGTIDE_STORM, Moves.STRENGTH_SAP, Moves.TAIL_GLOW ],
[Species.YVELTAL]: [ Moves.SLUDGE_WAVE, Moves.POWER_TRIP, Moves.FIERY_WRATH, Moves.CLANGOROUS_SOUL ],
[Species.ZYGARDE]: [ Moves.DRAGON_DARTS, Moves.HEAL_ORDER, Moves.CLANGOROUS_SOUL, Moves.DOUBLE_IRON_BASH ],
[Species.ZYGARDE]: [ Moves.DRAGON_DARTS, Moves.V_CREATE, Moves.CLANGOROUS_SOUL, Moves.HEAL_ORDER ],
[Species.DIANCIE]: [ Moves.MAGICAL_TORQUE, Moves.FIERY_DANCE, Moves.SHORE_UP, Moves.GEOMANCY ],
[Species.HOOPA]: [ Moves.PHOTON_GEYSER, Moves.SECRET_SWORD, Moves.FIERY_WRATH, Moves.SHELL_SMASH ],
[Species.VOLCANION]: [ Moves.HYDRO_STEAM, Moves.CALM_MIND, Moves.SEARING_SHOT, Moves.THUNDERCLAP ],
@ -415,7 +415,7 @@ export const speciesEggMoves = {
[Species.JANGMO_O]: [ Moves.BODY_PRESS, Moves.SHELL_SIDE_ARM, Moves.SECRET_SWORD, Moves.GLAIVE_RUSH ],
[Species.TAPU_KOKO]: [ Moves.MAGICAL_TORQUE, Moves.TRIPLE_AXEL, Moves.SWORDS_DANCE, Moves.BOLT_STRIKE ],
[Species.TAPU_LELE]: [ Moves.MOONLIGHT, Moves.NASTY_PLOT, Moves.HEAT_WAVE, Moves.EXPANDING_FORCE ],
[Species.TAPU_BULU]: [ Moves.SAPPY_SEED, Moves.DRAIN_PUNCH, Moves.MAGICAL_TORQUE, Moves.VICTORY_DANCE ],
[Species.TAPU_BULU]: [ Moves.SAPPY_SEED, Moves.LANDS_WRATH, Moves.MAGICAL_TORQUE, Moves.VICTORY_DANCE ],
[Species.TAPU_FINI]: [ Moves.SPRINGTIDE_STORM, Moves.EARTH_POWER, Moves.RECOVER, Moves.QUIVER_DANCE ],
[Species.COSMOG]: [ Moves.PHOTON_GEYSER, Moves.PRECIPICE_BLADES, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE ],
[Species.NIHILEGO]: [ Moves.STRENGTH_SAP, Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.QUIVER_DANCE ],
@ -505,7 +505,7 @@ export const speciesEggMoves = {
[Species.HISUI_VOLTORB]: [ Moves.FROST_BREATH, Moves.NASTY_PLOT, Moves.APPLE_ACID, Moves.ELECTRO_DRIFT ],
[Species.HISUI_QWILFISH]: [ Moves.CEASELESS_EDGE, Moves.KNOCK_OFF, Moves.RECOVER, Moves.FISHIOUS_REND ],
[Species.HISUI_SNEASEL]: [ Moves.DRAIN_PUNCH, Moves.KNOCK_OFF, Moves.PSYCHIC_FANGS, Moves.TRIPLE_AXEL ],
[Species.HISUI_ZORUA]: [ Moves.MOONBLAST, Moves.HYPER_VOICE, Moves.PARTING_SHOT, Moves.BLOOD_MOON ],
[Species.HISUI_ZORUA]: [ Moves.MOONBLAST, Moves.SECRET_SWORD, Moves.PARTING_SHOT, Moves.BLOOD_MOON ],
[Species.SPRIGATITO]: [ Moves.FIRE_LASH, Moves.TRIPLE_AXEL, Moves.SUCKER_PUNCH, Moves.WICKED_BLOW ],
[Species.FUECOCO]: [ Moves.ALLURING_VOICE, Moves.SLACK_OFF, Moves.OVERDRIVE, Moves.MOONGEIST_BEAM ],
@ -538,7 +538,7 @@ export const speciesEggMoves = {
[Species.CYCLIZAR]: [ Moves.PARTING_SHOT, Moves.FIRE_LASH, Moves.MAGICAL_TORQUE, Moves.GLAIVE_RUSH ],
[Species.ORTHWORM]: [ Moves.SIZZLY_SLIDE, Moves.COIL, Moves.BODY_PRESS, Moves.SHORE_UP ],
[Species.GLIMMET]: [ Moves.CALM_MIND, Moves.GIGA_DRAIN, Moves.FIERY_DANCE, Moves.MALIGNANT_CHAIN ],
[Species.GREAVARD]: [ Moves.SHADOW_BONE, Moves.YAWN, Moves.SHORE_UP, Moves.COLLISION_COURSE ],
[Species.GREAVARD]: [ Moves.SHADOW_BONE, Moves.SIZZLY_SLIDE, Moves.SHORE_UP, Moves.COLLISION_COURSE ],
[Species.FLAMIGO]: [ Moves.THUNDEROUS_KICK, Moves.TRIPLE_AXEL, Moves.FLOATY_FALL, Moves.VICTORY_DANCE ],
[Species.CETODDLE]: [ Moves.ZING_ZAP, Moves.HIGH_HORSEPOWER, Moves.SLACK_OFF, Moves.DRAGON_DANCE ],
[Species.VELUZA]: [ Moves.PSYBLADE, Moves.LEAF_BLADE, Moves.CEASELESS_EDGE, Moves.BITTER_BLADE ],
@ -550,7 +550,7 @@ export const speciesEggMoves = {
[Species.FLUTTER_MANE]: [ Moves.MOONLIGHT, Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.MOONGEIST_BEAM ],
[Species.SLITHER_WING]: [ Moves.MIGHTY_CLEAVE, Moves.THUNDEROUS_KICK, Moves.FIRE_LASH, Moves.VICTORY_DANCE ],
[Species.SANDY_SHOCKS]: [ Moves.MORNING_SUN, Moves.ICE_BEAM, Moves.NASTY_PLOT, Moves.THUNDERCLAP ],
[Species.IRON_TREADS]: [ Moves.FUSION_BOLT, Moves.BULK_UP, Moves.SHORE_UP, Moves.SUNSTEEL_STRIKE ],
[Species.IRON_TREADS]: [ Moves.FUSION_BOLT, Moves.SHIFT_GEAR, Moves.SHORE_UP, Moves.SUNSTEEL_STRIKE ],
[Species.IRON_BUNDLE]: [ Moves.EARTH_POWER, Moves.SPLISHY_SPLASH, Moves.VOLT_SWITCH, Moves.NASTY_PLOT ],
[Species.IRON_HANDS]: [ Moves.DRAIN_PUNCH, Moves.BULK_UP, Moves.PLASMA_FISTS, Moves.ICE_HAMMER ],
[Species.IRON_JUGULIS]: [ Moves.FIERY_WRATH, Moves.ROOST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ],
@ -562,7 +562,7 @@ export const speciesEggMoves = {
[Species.CHIEN_PAO]: [ Moves.KNOCK_OFF, Moves.PARTING_SHOT, Moves.TRIPLE_AXEL, Moves.BITTER_BLADE ],
[Species.TING_LU]: [ Moves.SHORE_UP, Moves.CEASELESS_EDGE, Moves.SAPPY_SEED, Moves.PRECIPICE_BLADES ],
[Species.CHI_YU]: [ Moves.FIERY_WRATH, Moves.HYDRO_STEAM, Moves.MORNING_SUN, Moves.BLUE_FLARE ],
[Species.ROARING_MOON]: [ Moves.FIRE_LASH, Moves.DRAGON_HAMMER, Moves.SUCKER_PUNCH, Moves.WICKED_BLOW ],
[Species.ROARING_MOON]: [ Moves.FIRE_LASH, Moves.DRAGON_HAMMER, Moves.METEOR_MASH, Moves.DRAGON_ASCENT ],
[Species.IRON_VALIANT]: [ Moves.PLASMA_FISTS, Moves.NO_RETREAT, Moves.SECRET_SWORD, Moves.MAGICAL_TORQUE ],
[Species.KORAIDON]: [ Moves.SUNSTEEL_STRIKE, Moves.SOLAR_BLADE, Moves.DRAGON_DARTS, Moves.BITTER_BLADE ],
[Species.MIRAIDON]: [ Moves.ICE_BEAM, Moves.CLANGOROUS_SOUL, Moves.CORE_ENFORCER, Moves.RISING_VOLTAGE ],
@ -577,7 +577,7 @@ export const speciesEggMoves = {
[Species.RAGING_BOLT]: [ Moves.NASTY_PLOT, Moves.FLAMETHROWER, Moves.MORNING_SUN, Moves.ELECTRO_DRIFT ],
[Species.IRON_BOULDER]: [ Moves.PSYBLADE, Moves.KOWTOW_CLEAVE, Moves.STONE_AXE, Moves.BITTER_BLADE ],
[Species.IRON_CROWN]: [ Moves.NASTY_PLOT, Moves.SECRET_SWORD, Moves.PSYSTRIKE, Moves.ELECTRO_DRIFT ],
[Species.TERAPAGOS]: [ Moves.MOONBLAST, Moves.RECOVER, Moves.ICE_BEAM, Moves.SHELL_SMASH ],
[Species.TERAPAGOS]: [ Moves.MOONBLAST, Moves.NASTY_PLOT, Moves.ASTRAL_BARRAGE, Moves.RECOVER ],
[Species.PECHARUNT]: [ Moves.TAKE_HEART, Moves.BODY_PRESS, Moves.SAPPY_SEED, Moves.ASTRAL_BARRAGE ],
[Species.PALDEA_TAUROS]: [ Moves.NO_RETREAT, Moves.BLAZING_TORQUE, Moves.AQUA_STEP, Moves.THUNDEROUS_KICK ],
[Species.PALDEA_WOOPER]: [ Moves.STONE_AXE, Moves.RECOVER, Moves.BANEFUL_BUNKER, Moves.BARB_BARRAGE ],

View File

@ -555,7 +555,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[Species.FLUTTER_MANE]: { 0: Abilities.DAZZLING },
[Species.SLITHER_WING]: { 0: Abilities.SCRAPPY },
[Species.SANDY_SHOCKS]: { 0: Abilities.ELECTRIC_SURGE },
[Species.IRON_TREADS]: { 0: Abilities.STEELY_SPIRIT },
[Species.IRON_TREADS]: { 0: Abilities.DAUNTLESS_SHIELD },
[Species.IRON_BUNDLE]: { 0: Abilities.SNOW_WARNING },
[Species.IRON_HANDS]: { 0: Abilities.IRON_FIST },
[Species.IRON_JUGULIS]: { 0: Abilities.LIGHTNING_ROD },
@ -567,7 +567,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[Species.CHIEN_PAO]: { 0: Abilities.INTIMIDATE },
[Species.TING_LU]: { 0: Abilities.STAMINA },
[Species.CHI_YU]: { 0: Abilities.BERSERK },
[Species.ROARING_MOON]: { 0: Abilities.TOUGH_CLAWS },
[Species.ROARING_MOON]: { 0: Abilities.INTIMIDATE },
[Species.IRON_VALIANT]: { 0: Abilities.NEUROFORCE },
[Species.KORAIDON]: { 0: Abilities.OPPORTUNIST },
[Species.MIRAIDON]: { 0: Abilities.OPPORTUNIST },
@ -582,7 +582,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
[Species.RAGING_BOLT]: { 0: Abilities.BEAST_BOOST },
[Species.IRON_BOULDER]: { 0: Abilities.SHARPNESS },
[Species.IRON_CROWN]: { 0: Abilities.SHARPNESS },
[Species.TERAPAGOS]: { 0: Abilities.SOUL_HEART },
[Species.TERAPAGOS]: { 0: Abilities.SHIELD_DUST },
[Species.PECHARUNT]: { 0: Abilities.TOXIC_CHAIN },
[Species.PALDEA_TAUROS]: { 0: Abilities.ADAPTABILITY },
[Species.PALDEA_WOOPER]: { 0: Abilities.THICK_FAT },

View File

@ -290,7 +290,7 @@ export const speciesStarterCosts = {
[Species.SHAYMIN]: 6,
[Species.ARCEUS]: 9,
[Species.VICTINI]: 7,
[Species.VICTINI]: 6,
[Species.SNIVY]: 3,
[Species.TEPIG]: 3,
[Species.OSHAWOTT]: 3,
@ -461,7 +461,7 @@ export const speciesStarterCosts = {
[Species.GUZZLORD]: 6,
[Species.NECROZMA]: 8,
[Species.MAGEARNA]: 7,
[Species.MARSHADOW]: 7,
[Species.MARSHADOW]: 8,
[Species.POIPOLE]: 8,
[Species.STAKATAKA]: 6,
[Species.BLACEPHALON]: 7,
@ -611,7 +611,7 @@ export const speciesStarterCosts = {
[Species.RAGING_BOLT]: 7,
[Species.IRON_BOULDER]: 7,
[Species.IRON_CROWN]: 7,
[Species.TERAPAGOS]: 8,
[Species.TERAPAGOS]: 9,
[Species.PECHARUNT]: 6,
[Species.PALDEA_TAUROS]: 5,
[Species.PALDEA_WOOPER]: 3,

View File

@ -1,7 +1,20 @@
import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move";
import {
AttackMove,
BeakBlastHeaderAttr,
DelayedAttackAttr,
MoveFlags,
SelfStatusMove,
allMoves,
} from "./move";
import type Pokemon from "../field/pokemon";
import * as Utils from "../utils";
import {
type nil,
getFrameMs,
getEnumKeys,
getEnumValues,
animationFileName,
} from "../utils";
import type { BattlerIndex } from "../battle";
import type { Element } from "json-stable-stringify";
import { Moves } from "#enums/moves";
@ -401,7 +414,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
if (Object.keys(tweenProps).length) {
globalScene.tweens.add(Object.assign({
targets: moveAnim.bgSprite,
duration: Utils.getFrameMs(this.duration * 3)
duration: getFrameMs(this.duration * 3)
}, tweenProps));
}
return this.duration * 2;
@ -437,7 +450,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
globalScene.tweens.add({
targets: moveAnim.bgSprite,
duration: Utils.getFrameMs(this.duration * 3)
duration: getFrameMs(this.duration * 3)
});
return this.duration * 2;
@ -455,8 +468,8 @@ export const encounterAnims = new Map<EncounterAnim, AnimConfig>();
export function initCommonAnims(): Promise<void> {
return new Promise(resolve => {
const commonAnimNames = Utils.getEnumKeys(CommonAnim);
const commonAnimIds = Utils.getEnumValues(CommonAnim);
const commonAnimNames = getEnumKeys(CommonAnim);
const commonAnimIds = getEnumValues(CommonAnim);
const commonAnimFetches: Promise<Map<CommonAnim, AnimConfig>>[] = [];
for (let ca = 0; ca < commonAnimIds.length; ca++) {
const commonAnimId = commonAnimIds[ca];
@ -493,7 +506,7 @@ export function initMoveAnim(move: Moves): Promise<void> {
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
const fetchAnimAndResolve = (move: Moves) => {
globalScene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
globalScene.cachedFetch(`./battle-anims/${animationFileName(move)}.json`)
.then(response => {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
@ -550,7 +563,7 @@ function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) {
* @remarks use {@linkcode useDefaultAnim} to use a default animation
*/
function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
const moveName = Utils.animationFileName(move);
const moveName = animationFileName(move);
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
}
@ -560,7 +573,7 @@ function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
*/
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [ encounterAnim ];
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
const encounterAnimNames = getEnumKeys(EncounterAnim);
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) {
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
@ -922,7 +935,7 @@ export abstract class BattleAnim {
let f = 0;
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(3),
duration: getFrameMs(3),
repeat: anim?.frames.length ?? 0,
onRepeat: () => {
if (!f) {
@ -994,47 +1007,39 @@ export abstract class BattleAnim {
const moveSprite = sprites[graphicIndex];
if (spritePriorities[graphicIndex] !== frame.priority) {
spritePriorities[graphicIndex] = frame.priority;
/** Move the position that the moveSprite is rendered in based on the priority.
* @param priority The priority level to draw the sprite.
* - 0: Draw the sprite in front of the pokemon on the field.
* - 1: Draw the sprite in front of the user pokemon.
* - 2: Draw the sprite in front of its `bgSprite` (if it has one), or its
* `AnimFocus` (if that is user/target), otherwise behind everything.
* - 3: Draw the sprite behind its `AnimFocus` (if that is user/target), otherwise in front of everything.
*/
const setSpritePriority = (priority: number) => {
switch (priority) {
case 0:
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false)!); // TODO: is this bang correct?
break;
case 1:
globalScene.field.moveTo(moveSprite, globalScene.field.getAll().length - 1);
break;
case 2:
switch (frame.focus) {
case AnimFocus.USER:
if (this.bgSprite) {
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite);
} else {
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
}
break;
case AnimFocus.TARGET:
globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
break;
}
break;
case 3:
switch (frame.focus) {
case AnimFocus.USER:
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct?
break;
case AnimFocus.TARGET:
globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct?
break;
default:
setSpritePriority(1);
break;
}
break;
default:
setSpritePriority(1);
/** The sprite we are moving the moveSprite in relation to */
let targetSprite: Phaser.GameObjects.GameObject | nil;
/** The method that is being used to move the sprite.*/
let moveFunc: ((sprite: Phaser.GameObjects.GameObject, target: Phaser.GameObjects.GameObject) => void) |
((sprite: Phaser.GameObjects.GameObject) => void) = globalScene.field.bringToTop;
if (priority === 0) { // Place the sprite in front of the pokemon on the field.
targetSprite = globalScene.getEnemyField().find(p => p) ?? globalScene.getPlayerField().find(p => p);
console.log(typeof targetSprite);
moveFunc = globalScene.field.moveBelow;
} else if (priority === 2 && this.bgSprite) {
moveFunc = globalScene.field.moveAbove;
targetSprite = this.bgSprite;
} else if (priority === 2 || priority === 3) {
moveFunc = priority === 2 ? globalScene.field.moveBelow : globalScene.field.moveAbove;
if (frame.focus === AnimFocus.USER) {
targetSprite = this.user;
} else if (frame.focus === AnimFocus.TARGET) {
targetSprite = this.target;
}
}
// If target sprite is not undefined and exists in the field container, then move the sprite using the moveFunc.
// Otherwise, default to just bringing it to the top.
targetSprite && globalScene.field.exists(targetSprite) ? moveFunc.bind(globalScene.field)(moveSprite as Phaser.GameObjects.GameObject, targetSprite) : globalScene.field.bringToTop(moveSprite as Phaser.GameObjects.GameObject);
};
setSpritePriority(frame.priority);
}
@ -1052,11 +1057,13 @@ export abstract class BattleAnim {
}
}
if (anim?.frameTimedEvents.has(f)) {
for (const event of anim.frameTimedEvents.get(f)!) { // TODO: is this bang correct?
r = Math.max((anim.frames.length - f) + event.execute(this), r);
const base = anim.frames.length - f;
// Bang is correct due to `has` check above, which cannot return true for an undefined / null `f`
for (const event of anim.frameTimedEvents.get(f)!) {
r = Math.max(base + event.execute(this), r);
}
}
const targets = Utils.getEnumValues(AnimFrameTarget);
const targets = getEnumValues(AnimFrameTarget);
for (const i of targets) {
const count = i === AnimFrameTarget.GRAPHIC ? g : i === AnimFrameTarget.USER ? u : t;
if (count < spriteCache[i].length) {
@ -1084,7 +1091,7 @@ export abstract class BattleAnim {
}
if (r) {
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(r),
duration: getFrameMs(r),
onComplete: () => cleanUpAndComplete()
});
} else {
@ -1166,7 +1173,7 @@ export abstract class BattleAnim {
let existingFieldSprites = globalScene.field.getAll().slice(0);
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(3) * frameTimeMult,
duration: getFrameMs(3) * frameTimeMult,
repeat: anim!.frames.length,
onRepeat: () => {
existingFieldSprites = globalScene.field.getAll().slice(0);
@ -1215,11 +1222,12 @@ export abstract class BattleAnim {
}
}
if (anim?.frameTimedEvents.get(frameCount)) {
const base = anim.frames.length - frameCount;
for (const event of anim.frameTimedEvents.get(frameCount)!) {
totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(this, frameTimedEventPriority), totalFrames);
totalFrames = Math.max(base + event.execute(this, frameTimedEventPriority), totalFrames);
}
}
const targets = Utils.getEnumValues(AnimFrameTarget);
const targets = getEnumValues(AnimFrameTarget);
for (const i of targets) {
const count = graphicFrameCount;
if (count < spriteCache[i].length) {
@ -1244,7 +1252,7 @@ export abstract class BattleAnim {
}
if (totalFrames) {
globalScene.tweens.addCounter({
duration: Utils.getFrameMs(totalFrames),
duration: getFrameMs(totalFrames),
onComplete: () => cleanUpAndComplete()
});
} else {
@ -1342,15 +1350,15 @@ export class EncounterBattleAnim extends BattleAnim {
}
export async function populateAnims() {
const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase());
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, ""));
const commonAnimIds = Utils.getEnumValues(CommonAnim) as CommonAnim[];
const chargeAnimNames = Utils.getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
const commonAnimIds = getEnumValues(CommonAnim) as CommonAnim[];
const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/\_/g, " "));
const chargeAnimIds = Utils.getEnumValues(ChargeAnim) as ChargeAnim[];
const chargeAnimIds = getEnumValues(ChargeAnim) as ChargeAnim[];
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
const moveNameToId = {};
for (const move of Utils.getEnumValues(Moves).slice(1)) {
for (const move of getEnumValues(Moves).slice(1)) {
const moveName = Moves[move].toUpperCase().replace(/\_/g, "");
moveNameToId[moveName] = move;
}

View File

@ -1866,11 +1866,14 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
export class AddSubstituteAttr extends MoveEffectAttr {
/** The ratio of the user's max HP that is required to apply this effect */
private hpCost: number;
/** Whether the damage taken should be rounded up (Shed Tail rounds up) */
private roundUp: boolean;
constructor(hpCost: number = 0.25) {
constructor(hpCost: number, roundUp: boolean) {
super(true);
this.hpCost = hpCost;
this.roundUp = roundUp;
}
/**
@ -1886,7 +1889,8 @@ export class AddSubstituteAttr extends MoveEffectAttr {
return false;
}
user.damageAndUpdate(Math.floor(user.getMaxHp() * this.hpCost), HitResult.OTHER, false, true, true);
const damageTaken = this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost);
user.damageAndUpdate(damageTaken, HitResult.OTHER, false, true, true);
user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id);
return true;
}
@ -1899,7 +1903,7 @@ export class AddSubstituteAttr extends MoveEffectAttr {
}
getCondition(): MoveConditionFunc {
return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > Math.floor(user.getMaxHp() * this.hpCost) && user.getMaxHp() > 1;
return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > (this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost)) && user.getMaxHp() > 1;
}
/**
@ -5232,7 +5236,7 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr {
return false;
}
const combinedPledgeMove = user.turnData.combiningPledge;
const combinedPledgeMove = user?.turnData?.combiningPledge;
if (!combinedPledgeMove) {
return false;
}
@ -7811,11 +7815,12 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr {
return false;
}
target.summonData.abilitySuppressed = true;
globalScene.arena.triggerWeatherBasedFormChangesToNormal();
globalScene.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) }));
target.suppressAbility();
globalScene.arena.triggerWeatherBasedFormChangesToNormal();
return true;
}
@ -9035,7 +9040,7 @@ export function initMoves() {
.attr(HighCritAttr)
.slicingMove(),
new SelfStatusMove(Moves.SUBSTITUTE, Type.NORMAL, -1, 10, -1, 0, 1)
.attr(AddSubstituteAttr),
.attr(AddSubstituteAttr, 0.25, false),
new AttackMove(Moves.STRUGGLE, Type.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1)
.attr(RecoilAttr, true, 0.25, true)
.attr(TypelessAttr)
@ -9379,7 +9384,7 @@ export function initMoves() {
.attr(BypassBurnDamageReductionAttr),
new AttackMove(Moves.FOCUS_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3)
.attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) }))
.attr(PreUseInterruptAttr, i18next.t("moveTriggers:lostFocus"), user => !!user.turnData.attacksReceived.find(r => r.damage))
.attr(PreUseInterruptAttr, (user, target, move) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => !!user.turnData.attacksReceived.find(r => r.damage))
.punchingMove(),
new AttackMove(Moves.SMELLING_SALTS, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1)
@ -11381,7 +11386,7 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461 / 4096 : 1)
.makesContact(),
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
.attr(AddSubstituteAttr, 0.5)
.attr(AddSubstituteAttr, 0.5, true)
.attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL)
.condition(failIfLastInPartyCondition),
new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)

View File

@ -749,7 +749,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
if (this.speciesId === Species.ARCEUS) {
ret = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`);
} else if ([ SpeciesFormKey.MEGA, SpeciesFormKey.MEGA_X, SpeciesFormKey.MEGA_Y, SpeciesFormKey.PRIMAL, SpeciesFormKey.GIGANTAMAX, SpeciesFormKey.GIGANTAMAX_RAPID, SpeciesFormKey.GIGANTAMAX_SINGLE, SpeciesFormKey.ETERNAMAX ].includes(formKey as SpeciesFormKey)) {
return i18next.t(`battlePokemonForm:${formKey}`, { pokemonName: (append ? this.name : "") });
return append ? i18next.t(`battlePokemonForm:${formKey}`, { pokemonName: this.name }) : i18next.t(`pokemonForm:battleForm.${formKey}`);
} else if (region === Region.NORMAL || (this.speciesId === Species.GALAR_DARMANITAN && formIndex > 0) || this.speciesId === Species.PALDEA_TAUROS) { // More special cases can be added here
const i18key = `pokemonForm:${speciesName}${formText}`;
if (i18next.exists(i18key)) {

View File

@ -26,6 +26,9 @@ import { Gender } from "#app/data/gender";
/** Minimum BST for Pokemon generated onto the Elite Four's teams */
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,
@ -211,8 +214,8 @@ export class TrainerAI {
}
/**
* Sets a pokemon on this AI to just instantly tera on first move used
* @param index The index of the pokemon to instantly tera
* Sets a pokemon on this AI to just instantly Tera on first move used
* @param index The index of the pokemon to instantly tera.
*/
public setInstantTera(index: number) {
this.teraMode = TeraAIMode.INSTANT_TERA;
@ -251,7 +254,7 @@ export class TrainerConfig {
public partyMemberFuncs: PartyMemberFuncs = {};
public speciesPools: TrainerTierPools;
public speciesFilter: PokemonSpeciesFilter;
public specialtyTypes: Type[] = [];
public specialtyType: Type;
public hasVoucher: boolean = false;
public trainerAI: TrainerAI;
@ -583,8 +586,8 @@ export class TrainerConfig {
return this;
}
setSpecialtyTypes(...specialtyTypes: Type[]): TrainerConfig {
this.specialtyTypes = specialtyTypes;
setSpecialtyType(specialtyType: Type): TrainerConfig {
this.specialtyType = specialtyType;
return this;
}
@ -594,18 +597,22 @@ export class TrainerConfig {
}
/**
* Sets random pokemon from the trainers team to instant tera. Uses their specialty types is they have one.
* @param count The amount of pokemon to have instant tera
* Sets random pokemon from the trainer's team to instant tera. Also sets Tera type to specialty type and checks for Shedinja as appropriate.
* @param count A callback (yucky) to see how many teras should be used
* @param slot Optional, a specified slot that should be terastallized. Wraps to match party size (-1 will get the last slot and so on).
* @returns this
*/
setRandomTeraModifiers(count: () => integer): TrainerConfig {
setRandomTeraModifiers(count: () => number, slot?: number): TrainerConfig {
this.genAIFuncs.push((party: EnemyPokemon[]) => {
const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
const shedinjaCanTera = !this.hasSpecialtyType() || this.specialtyType === Type.BUG; // Better to check one time than 6
const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i)
.filter(i => shedinjaCanTera || party[i].species.speciesId !== Species.SHEDINJA); // Shedinja can only Tera on Bug specialty type (or no specialty type)
const setPartySlot = !Utils.isNullOrUndefined(slot) ? Phaser.Math.Wrap(slot, 0, party.length) : -1; // If we have a tera slot defined, wrap it to party size.
for (let t = 0; t < Math.min(count(), party.length); t++) {
const randomIndex = Utils.randSeedItem(partyMemberIndexes);
const randomIndex = partyMemberIndexes.indexOf(setPartySlot) > -1 ? setPartySlot : Utils.randSeedItem(partyMemberIndexes);
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
if (this.specialtyTypes?.length) {
party[randomIndex].teraType = Utils.randSeedItem(this.specialtyTypes);
if (this.hasSpecialtyType()) {
party[randomIndex].teraType = this.specialtyType;
}
this.trainerAI.setInstantTera(randomIndex);
}
@ -614,8 +621,8 @@ export class TrainerConfig {
}
/**
* Sets a specific pokemon to instant tera
* @param index The index within the team to have instant tera
* Sets a specific pokemon to instantly Tera
* @param index The index within the team to have instant Tera.
* @returns this
*/
setInstantTera(index: number): TrainerConfig {
@ -779,12 +786,18 @@ export class TrainerConfig {
* @param title The title of the evil team admin.
* @param poolName The evil team the admin belongs to.
* @param {Species | Species[]} signatureSpecies The signature species for the evil team leader.
* @param specialtyType The specialty Type of the admin, if they have one
* @returns {TrainerConfig} The updated TrainerConfig instance.
* **/
initForEvilTeamAdmin(title: string, poolName: string, signatureSpecies: (Species | Species[])[],): TrainerConfig {
initForEvilTeamAdmin(title: string, poolName: string, signatureSpecies: (Species | Species[])[], specialtyType?: Type): TrainerConfig {
if (!getIsInitialized()) {
initI18n();
}
if (!Utils.isNullOrUndefined(specialtyType)) {
this.setSpecialtyType(specialtyType);
}
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
// Set the species pools for the evil team admin.
@ -812,28 +825,16 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for a Stat Trainer, as part of the Trainer's Test Mystery Encounter.
* @param {Species | Species[]} signatureSpecies The signature species for the Elite Four member.
* @param {Type[]} specialtyTypes The specialty types for the Stat Trainer.
* @param isMale Whether the Elite Four Member is Male or Female (for localization of the title).
* @param isMale Whether the stat trainer is Male or Female (for localization of the title).
* @returns {TrainerConfig} The updated TrainerConfig instance.
**/
initForStatTrainer(signatureSpecies: (Species | Species[])[], isMale: boolean, ...specialtyTypes: Type[]): TrainerConfig {
initForStatTrainer(isMale: boolean = false): TrainerConfig {
if (!getIsInitialized()) {
initI18n();
}
this.setPartyTemplates(trainerPartyTemplates.ELITE_FOUR);
signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) {
speciesPool = [ speciesPool ];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
});
if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.find(t => p.isOfType(t)) !== undefined);
this.setSpecialtyTypes(...specialtyTypes);
}
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
this.name = i18next.t(`trainerNames:${nameForCall}`);
this.setMoneyMultiplier(2);
@ -850,11 +851,11 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for an evil team leader. Temporarily hardcoding evil leader teams though.
* @param {Species | Species[]} signatureSpecies The signature species for the evil team leader.
* @param {Type[]} specialtyTypes The specialty types for the evil team Leader.
* @param {Type} specialtyType The specialty type for the evil team Leader.
* @param boolean Whether or not this is the rematch fight
* @returns {TrainerConfig} The updated TrainerConfig instance.
* **/
initForEvilTeamLeader(title: string, signatureSpecies: (Species | Species[])[], rematch: boolean = false, ...specialtyTypes: Type[]): TrainerConfig {
initForEvilTeamLeader(title: string, signatureSpecies: (Species | Species[])[], rematch: boolean = false, specialtyType?: Type): TrainerConfig {
if (!getIsInitialized()) {
initI18n();
}
@ -869,9 +870,9 @@ export class TrainerConfig {
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
});
if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.find(t => p.isOfType(t)) !== undefined);
this.setSpecialtyTypes(...specialtyTypes);
if (!Utils.isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType));
this.setSpecialtyType(specialtyType);
}
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
this.name = i18next.t(`trainerNames:${nameForCall}`);
@ -888,12 +889,14 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for a Gym Leader.
* @param {Species | Species[]} signatureSpecies The signature species for the Gym Leader.
* @param {Type[]} specialtyTypes The specialty types for the Gym Leader.
* @param {Species | Species[]} signatureSpecies The signature species for the Gym Leader. Added to party in reverse order.
* @param isMale Whether the Gym Leader is Male or Not (for localization of the title).
* @param {Type} specialtyType The specialty type for the Gym Leader.
* @param ignoreMinTeraWave Whether the Gym Leader always uses Tera (true), or only Teras after {@linkcode GYM_LEADER_TERA_WAVE} (false). Defaults to false.
* @param teraSlot Optional, sets the party member in this slot to Terastallize. Wraps based on party size.
* @returns {TrainerConfig} The updated TrainerConfig instance.
* **/
initForGymLeader(signatureSpecies: (Species | Species[])[], isMale: boolean, ...specialtyTypes: Type[]): TrainerConfig {
initForGymLeader(signatureSpecies: (Species | Species[])[], isMale: boolean, specialtyType: Type, ignoreMinTeraWave: boolean = false, teraSlot?: number): TrainerConfig {
// Check if the internationalization (i18n) system is initialized.
if (!getIsInitialized()) {
initI18n();
@ -912,11 +915,9 @@ export class TrainerConfig {
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
});
// If specialty types are provided, set species filter and specialty types.
if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.find(t => p.isOfType(t)) !== undefined);
this.setSpecialtyTypes(...specialtyTypes);
}
// If specialty type is provided, set species filter and specialty type.
this.setSpeciesFilter(p => p.isOfType(specialtyType));
this.setSpecialtyType(specialtyType);
// Localize the trainer's name by converting it to lowercase and replacing spaces with underscores.
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
@ -935,7 +936,7 @@ export class TrainerConfig {
this.setHasVoucher(true);
this.setBattleBgm("battle_unova_gym");
this.setVictoryBgm("victory_gym");
this.setRandomTeraModifiers(() => globalScene.currentBattle.waveIndex >= 100 ? 1 : 0);
this.setRandomTeraModifiers(() => (ignoreMinTeraWave || globalScene.currentBattle.waveIndex >= GYM_LEADER_TERA_WAVE) ? 1 : 0, teraSlot);
return this;
}
@ -943,11 +944,12 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for an Elite Four member.
* @param {Species | Species[]} signatureSpecies The signature species for the Elite Four member.
* @param {Type[]} specialtyTypes The specialty types for the Elite Four member.
* @param isMale Whether the Elite Four Member is Male or Female (for localization of the title).
* @param specialtyType {Type} The specialty type for the Elite Four member.
* @param teraSlot Optional, sets the party member in this slot to Terastallize.
* @returns {TrainerConfig} The updated TrainerConfig instance.
**/
initForEliteFour(signatureSpecies: (Species | Species[])[], isMale: boolean, ...specialtyTypes: Type[]): TrainerConfig {
initForEliteFour(signatureSpecies: (Species | Species[])[], isMale: boolean, specialtyType?: Type, teraSlot?: number): TrainerConfig {
// Check if the internationalization (i18n) system is initialized.
if (!getIsInitialized()) {
initI18n();
@ -966,10 +968,10 @@ export class TrainerConfig {
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
});
// Set species filter and specialty types if provided, otherwise filter by base total.
if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.some(t => p.isOfType(t)) && p.baseTotal >= ELITE_FOUR_MINIMUM_BST);
this.setSpecialtyTypes(...specialtyTypes);
// Set species filter and specialty type if provided, otherwise filter by base total.
if (!Utils.isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType) && p.baseTotal >= ELITE_FOUR_MINIMUM_BST);
this.setSpecialtyType(specialtyType);
} else {
this.setSpeciesFilter(p => p.baseTotal >= ELITE_FOUR_MINIMUM_BST);
}
@ -991,7 +993,7 @@ export class TrainerConfig {
this.setHasVoucher(true);
this.setBattleBgm("battle_unova_elite");
this.setVictoryBgm("victory_gym");
this.setRandomTeraModifiers(() => 1);
this.setRandomTeraModifiers(() => 1, teraSlot);
return this;
}
@ -1142,6 +1144,14 @@ export class TrainerConfig {
});
}
/**
* Helper function to check if a specialty type is set
* @returns true if specialtyType is defined and not Type.UNKNOWN
*/
hasSpecialtyType(): boolean {
return !Utils.isNullOrUndefined(this.specialtyType) && this.specialtyType !== Type.UNKNOWN;
}
/**
* Creates a shallow copy of a trainer config so that it can be modified without affecting the {@link trainerConfigs} source map
*/
@ -1183,9 +1193,7 @@ export class TrainerConfig {
clone = this.speciesPools ? clone.setSpeciesPools(this.speciesPools) : clone;
clone = this.speciesFilter ? clone.setSpeciesFilter(this.speciesFilter) : clone;
if (this.specialtyTypes) {
clone.specialtyTypes = this.specialtyTypes.slice(0);
}
clone.specialtyType = this.specialtyType;
clone.encounterMessages = this.encounterMessages?.slice(0);
clone.victoryMessages = this.victoryMessages?.slice(0);
@ -1352,28 +1360,28 @@ export const signatureSpecies: SignatureSpecies = {
PIERS: [ Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.INKAY ],
MARNIE: [ Species.IMPIDIMP, Species.PURRLOIN, Species.MORPEKO ],
RAIHAN: [ Species.DURALUDON, Species.TURTONATOR, Species.GOOMY ],
KATY: [ Species.NYMBLE, Species.TAROUNTULA, Species.HERACROSS ],
BRASSIUS: [ Species.SMOLIV, Species.SHROOMISH, Species.ODDISH ],
IONO: [ Species.TADBULB, Species.WATTREL, Species.VOLTORB ],
KOFU: [ Species.VELUZA, Species.WIGLETT, Species.WINGULL ],
LARRY: [ Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA ],
RYME: [ Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU ],
TULIP: [ Species.GIRAFARIG, Species.FLITTLE, Species.RALTS ],
GRUSHA: [ Species.CETODDLE, Species.ALOLA_VULPIX, Species.CUBCHOO ],
LORELEI: [ Species.JYNX, [ Species.SLOWBRO, Species.GALAR_SLOWBRO ], Species.LAPRAS, [ Species.ALOLA_SANDSLASH, Species.CLOYSTER ]],
BRUNO: [ Species.MACHAMP, Species.HITMONCHAN, Species.HITMONLEE, [ Species.ALOLA_GOLEM, Species.GOLEM ]],
KATY: [ Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA ], // Tera Bug Teddiursa
BRASSIUS: [ Species.SUDOWOODO, Species.BRAMBLIN, Species.SMOLIV ], // Tera Grass Sudowoodo
IONO: [ Species.MISDREAVUS, Species.TADBULB, Species.WATTREL ], // Tera Ghost Misdreavus
KOFU: [ Species.CRABRAWLER, Species.VELUZA, Species.WIGLETT, Species.WINGULL ], // Tera Water Crabrawler
LARRY: [ Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA ], // Tera Normal Starly
RYME: [ Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU ], // Tera Ghost Toxel
TULIP: [ Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG ], // Tera Psychic Flabebe
GRUSHA: [ Species.SWABLU, Species.CETODDLE, Species.CUBCHOO, Species.ALOLA_VULPIX ], // Tera Ice Swablu
LORELEI: [ Species.JYNX, [ Species.SLOWBRO, Species.GALAR_SLOWBRO ], Species.LAPRAS, [ Species.CLOYSTER, Species.ALOLA_SANDSLASH ]],
BRUNO: [ Species.MACHAMP, Species.HITMONCHAN, Species.HITMONLEE, [ Species.GOLEM, Species.ALOLA_GOLEM ]],
AGATHA: [ Species.GENGAR, [ Species.ARBOK, Species.WEEZING ], Species.CROBAT, Species.ALOLA_MAROWAK ],
LANCE: [ Species.DRAGONITE, Species.GYARADOS, Species.AERODACTYL, Species.ALOLA_EXEGGUTOR ],
WILL: [ Species.XATU, Species.JYNX, [ Species.SLOWBRO, Species.SLOWKING ], Species.EXEGGUTOR ],
KOGA: [[ Species.WEEZING, Species.MUK ], [ Species.VENOMOTH, Species.ARIADOS ], Species.CROBAT, Species.TENTACRUEL ],
KOGA: [[ Species.MUK, Species.WEEZING ], [ Species.VENOMOTH, Species.ARIADOS ], Species.CROBAT, Species.TENTACRUEL ],
KAREN: [ Species.UMBREON, Species.HONCHKROW, Species.HOUNDOOM, Species.WEAVILE ],
SIDNEY: [[ Species.SHIFTRY, Species.CACTURNE ], [ Species.SHARPEDO, Species.CRAWDAUNT ], Species.ABSOL, Species.MIGHTYENA ],
PHOEBE: [ Species.SABLEYE, Species.DUSKNOIR, Species.BANETTE, [ Species.MISMAGIUS, Species.DRIFBLIM ]],
PHOEBE: [ Species.SABLEYE, Species.DUSKNOIR, Species.BANETTE, [ Species.DRIFBLIM, Species.MISMAGIUS ]],
GLACIA: [ Species.GLALIE, Species.WALREIN, Species.FROSLASS, Species.ABOMASNOW ],
DRAKE: [ Species.ALTARIA, Species.SALAMENCE, Species.FLYGON, Species.KINGDRA ],
AARON: [[ Species.SCIZOR, Species.KLEAVOR ], Species.HERACROSS, [ Species.VESPIQUEN, Species.YANMEGA ], Species.DRAPION ],
BERTHA: [ Species.WHISCASH, Species.HIPPOWDON, Species.GLISCOR, Species.RHYPERIOR ],
FLINT: [[ Species.FLAREON, Species.RAPIDASH ], Species.MAGMORTAR, [ Species.STEELIX, Species.LOPUNNY ], Species.INFERNAPE ],
FLINT: [[ Species.RAPIDASH, Species.FLAREON ], Species.MAGMORTAR, [ Species.STEELIX, Species.LOPUNNY ], Species.INFERNAPE ], // Tera Fire Steelix or Lopunny
LUCIAN: [ Species.MR_MIME, Species.GALLADE, Species.BRONZONG, [ Species.ALAKAZAM, Species.ESPEON ]],
SHAUNTAL: [ Species.COFAGRIGUS, Species.CHANDELURE, Species.GOLURK, Species.JELLICENT ],
MARSHAL: [ Species.CONKELDURR, Species.MIENSHAO, Species.THROH, Species.SAWK ],
@ -1389,18 +1397,18 @@ export const signatureSpecies: SignatureSpecies = {
ACEROLA: [[ Species.BANETTE, Species.DRIFBLIM ], Species.MIMIKYU, Species.DHELMISE, Species.PALOSSAND ],
KAHILI: [[ Species.BRAVIARY, Species.MANDIBUZZ ], Species.HAWLUCHA, Species.ORICORIO, Species.TOUCANNON ],
MARNIE_ELITE: [ Species.MORPEKO, Species.LIEPARD, [ Species.TOXICROAK, Species.SCRAFTY ], Species.GRIMMSNARL ],
NESSA_ELITE: [ Species.GOLISOPOD, [ Species.PELIPPER, Species.QUAGSIRE ], Species.TOXAPEX, Species.DREDNAW ],
NESSA_ELITE: [ Species.GOLISOPOD, [ Species.QUAGSIRE, Species.PELIPPER ], Species.TOXAPEX, Species.DREDNAW ],
BEA_ELITE: [ Species.HAWLUCHA, [ Species.GRAPPLOCT, Species.SIRFETCHD ], Species.FALINKS, Species.MACHAMP ],
ALLISTER_ELITE: [ Species.DUSKNOIR, [ Species.POLTEAGEIST, Species.RUNERIGUS ], Species.CURSOLA, Species.GENGAR ],
RAIHAN_ELITE: [ Species.GOODRA, [ Species.TORKOAL, Species.TURTONATOR ], Species.FLYGON, Species.ARCHALUDON ],
RIKA: [ Species.WHISCASH, [ Species.DONPHAN, Species.DUGTRIO ], Species.CAMERUPT, Species.CLODSIRE ],
POPPY: [ Species.COPPERAJAH, Species.BRONZONG, Species.CORVIKNIGHT, Species.TINKATON ],
LARRY_ELITE: [ Species.STARAPTOR, Species.FLAMIGO, Species.ALTARIA, Species.TROPIUS ],
HASSEL: [ Species.NOIVERN, [ Species.FLAPPLE, Species.APPLETUN ], Species.DRAGALGE, Species.BAXCALIBUR ],
CRISPIN: [ Species.TALONFLAME, Species.CAMERUPT, Species.MAGMORTAR, Species.BLAZIKEN ],
AMARYS: [ Species.SKARMORY, Species.EMPOLEON, Species.SCIZOR, Species.METAGROSS ],
LACEY: [ Species.EXCADRILL, Species.PRIMARINA, [ Species.ALCREMIE, Species.GRANBULL ], Species.WHIMSICOTT ],
DRAYTON: [ Species.DRAGONITE, Species.ARCHALUDON, Species.HAXORUS, Species.SCEPTILE ],
RIKA: [ Species.CLODSIRE, [ Species.DUGTRIO, Species.DONPHAN ], Species.CAMERUPT, Species.WHISCASH ], // Tera Ground Clodsire
POPPY: [ Species.TINKATON, Species.BRONZONG, Species.CORVIKNIGHT, Species.COPPERAJAH ], // Tera Steel Tinkaton
LARRY_ELITE: [ Species.FLAMIGO, Species.STARAPTOR, [ Species.ALTARIA, Species.TROPIUS ], Species.ORICORIO ], // Tera Flying Flamigo; random Oricorio
HASSEL: [ Species.BAXCALIBUR, [ Species.FLAPPLE, Species.APPLETUN ], Species.DRAGALGE, Species.NOIVERN ], // Tera Dragon Baxcalibur
CRISPIN: [ Species.BLAZIKEN, Species.MAGMORTAR, [ Species.CAMERUPT, Species.TALONFLAME ], Species.ROTOM ], // Tera Fire Blaziken; Heat Rotom
AMARYS: [ Species.METAGROSS, Species.SCIZOR, Species.EMPOLEON, Species.SKARMORY ], // Tera Steel Metagross
LACEY: [ Species.EXCADRILL, Species.PRIMARINA, [ Species.WHIMSICOTT, Species.ALCREMIE ], Species.GRANBULL ], // Tera Fairy Excadrill
DRAYTON: [ Species.ARCHALUDON, Species.DRAGONITE, Species.HAXORUS, Species.SCEPTILE ], // Tera Dragon Archaludon
};
export const trainerConfigs: TrainerConfigs = {
@ -1421,7 +1429,7 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.BAKER]: new TrainerConfig(++t).setEncounterBgm(TrainerType.CLERK).setMoneyMultiplier(1.35).setSpeciesFilter(s => s.isOfType(Type.GRASS) || s.isOfType(Type.FIRE)),
[TrainerType.BEAUTY]: new TrainerConfig(++t).setMoneyMultiplier(1.55).setEncounterBgm(TrainerType.PARASOL_LADY),
[TrainerType.BIKER]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.ROUGHNECK).setSpeciesFilter(s => s.isOfType(Type.POISON)),
[TrainerType.BLACK_BELT]: new TrainerConfig(++t).setHasGenders("Battle Girl", TrainerType.PSYCHIC).setHasDouble("Crush Kin").setEncounterBgm(TrainerType.ROUGHNECK).setSpecialtyTypes(Type.FIGHTING)
[TrainerType.BLACK_BELT]: new TrainerConfig(++t).setHasGenders("Battle Girl", TrainerType.PSYCHIC).setHasDouble("Crush Kin").setEncounterBgm(TrainerType.ROUGHNECK).setSpecialtyType(Type.FIGHTING)
.setPartyTemplates(trainerPartyTemplates.TWO_WEAK_ONE_AVG, trainerPartyTemplates.TWO_WEAK_ONE_AVG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_WEAK_ONE_STRONG, trainerPartyTemplates.THREE_AVG, trainerPartyTemplates.TWO_AVG_ONE_STRONG)
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.NIDORAN_F, Species.NIDORAN_M, Species.MACHOP, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.TIMBURR ],
@ -1461,7 +1469,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesFilter(s => !!s.getLevelMoves().find(plm => plm[1] === Moves.HEAL_PULSE)),
[TrainerType.FIREBREATHER]: new TrainerConfig(++t).setMoneyMultiplier(1.4).setEncounterBgm(TrainerType.ROUGHNECK)
.setSpeciesFilter(s => !!s.getLevelMoves().find(plm => plm[1] === Moves.SMOG) || s.isOfType(Type.FIRE)),
[TrainerType.FISHERMAN]: new TrainerConfig(++t).setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.BACKPACKER).setSpecialtyTypes(Type.WATER)
[TrainerType.FISHERMAN]: new TrainerConfig(++t).setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.BACKPACKER).setSpecialtyType(Type.WATER)
.setPartyTemplates(trainerPartyTemplates.TWO_WEAK_SAME_ONE_AVG, trainerPartyTemplates.ONE_AVG, trainerPartyTemplates.THREE_WEAK_SAME, trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.SIX_WEAKER)
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.TENTACOOL, Species.MAGIKARP, Species.GOLDEEN, Species.STARYU, Species.REMORAID, Species.SKRELP, Species.CLAUNCHER, Species.ARROKUDA ],
@ -1469,7 +1477,7 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [ Species.CHINCHOU, Species.CORSOLA, Species.WAILMER, Species.BARBOACH, Species.CLAMPERL, Species.LUVDISC, Species.MANTYKE, Species.ALOMOMOLA, Species.TATSUGIRI, Species.VELUZA ],
[TrainerPoolTier.SUPER_RARE]: [ Species.LAPRAS, Species.FEEBAS, Species.RELICANTH, Species.DONDOZO ]
}),
[TrainerType.GUITARIST]: new TrainerConfig(++t).setMoneyMultiplier(1.2).setEncounterBgm(TrainerType.ROUGHNECK).setSpecialtyTypes(Type.ELECTRIC).setSpeciesFilter(s => s.isOfType(Type.ELECTRIC)),
[TrainerType.GUITARIST]: new TrainerConfig(++t).setMoneyMultiplier(1.2).setEncounterBgm(TrainerType.ROUGHNECK).setSpecialtyType(Type.ELECTRIC).setSpeciesFilter(s => s.isOfType(Type.ELECTRIC)),
[TrainerType.HARLEQUIN]: new TrainerConfig(++t).setEncounterBgm(TrainerType.PSYCHIC).setSpeciesFilter(s => tmSpecies[Moves.TRICK_ROOM].indexOf(s.speciesId) > -1),
[TrainerType.HIKER]: new TrainerConfig(++t).setEncounterBgm(TrainerType.BACKPACKER)
.setPartyTemplates(trainerPartyTemplates.TWO_AVG_SAME_ONE_AVG, trainerPartyTemplates.TWO_AVG_SAME_ONE_STRONG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.FOUR_WEAK, trainerPartyTemplates.ONE_STRONG)
@ -1548,7 +1556,7 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [ Species.TANGELA, Species.EEVEE, Species.YANMA ],
[TrainerPoolTier.SUPER_RARE]: [ Species.TADBULB ]
}),
[TrainerType.SWIMMER]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setEncounterBgm(TrainerType.PARASOL_LADY).setHasGenders("Swimmer Female").setHasDouble("Swimmers").setSpecialtyTypes(Type.WATER).setSpeciesFilter(s => s.isOfType(Type.WATER)),
[TrainerType.SWIMMER]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setEncounterBgm(TrainerType.PARASOL_LADY).setHasGenders("Swimmer Female").setHasDouble("Swimmers").setSpecialtyType(Type.WATER).setSpeciesFilter(s => s.isOfType(Type.WATER)),
[TrainerType.TWINS]: new TrainerConfig(++t).setDoubleOnly().setMoneyMultiplier(0.65).setUseSameSeedForAllMembers()
.setPartyTemplateFunc(() => getWavePartyTemplate(trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_STRONG))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PLUSLE, Species.VOLBEAT, Species.PACHIRISU, Species.SILCOON, Species.METAPOD, Species.IGGLYBUFF, Species.PETILIL, Species.EEVEE ]))
@ -1654,27 +1662,27 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [ Species.MANKEY, Species.PAWNIARD, Species.CHARCADET, Species.FLITTLE, Species.VAROOM, Species.ORTHWORM ],
[TrainerPoolTier.SUPER_RARE]: [ Species.DONDOZO, Species.GIMMIGHOUL ]
}),
[TrainerType.GIACOMO]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_1", [ Species.KINGAMBIT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
[TrainerType.GIACOMO]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_1", [ Species.KINGAMBIT ], Type.DARK).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1; // Segin Starmobile
p.moveset = [ new PokemonMove(Moves.WICKED_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ];
})),
[TrainerType.MELA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_2", [ Species.ARMAROUGE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
[TrainerType.MELA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_2", [ Species.ARMAROUGE ], Type.FIRE).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 2; // Schedar Starmobile
p.moveset = [ new PokemonMove(Moves.BLAZING_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ];
})),
[TrainerType.ATTICUS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_3", [ Species.REVAVROOM ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
[TrainerType.ATTICUS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_3", [ Species.REVAVROOM ], Type.POISON).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 3; // Navi Starmobile
p.moveset = [ new PokemonMove(Moves.NOXIOUS_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ];
})),
[TrainerType.ORTEGA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_4", [ Species.DACHSBUN ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
[TrainerType.ORTEGA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_4", [ Species.DACHSBUN ], Type.FAIRY).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 4; // Ruchbah Starmobile
p.moveset = [ new PokemonMove(Moves.MAGICAL_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ];
})),
[TrainerType.ERI]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_5", [ Species.ANNIHILAPE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
[TrainerType.ERI]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_5", [ Species.ANNIHILAPE ], Type.FIGHTING).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate())
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 5; // Caph Starmobile
p.moveset = [ new PokemonMove(Moves.COMBAT_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ];
@ -1746,14 +1754,14 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.PIERS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["PIERS"], true, Type.DARK).setHasDouble("piers_marnie_double").setDoubleTrainerType(TrainerType.MARNIE).setDoubleTitle("gym_leader_double").setMixedBattleBgm("battle_galar_gym"),
[TrainerType.MARNIE]: new TrainerConfig(++t).setName("Marnie").initForGymLeader(signatureSpecies["MARNIE"], false, Type.DARK).setHasDouble("marnie_piers_double").setDoubleTrainerType(TrainerType.PIERS).setDoubleTitle("gym_leader_double").setMixedBattleBgm("battle_galar_gym"),
[TrainerType.RAIHAN]: new TrainerConfig(++t).setName("Raihan").initForGymLeader(signatureSpecies["RAIHAN"], true, Type.DRAGON).setMixedBattleBgm("battle_galar_gym"),
[TrainerType.KATY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KATY"], false, Type.BUG).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.BRASSIUS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["BRASSIUS"], true, Type.GRASS).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.IONO]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["IONO"], false, Type.ELECTRIC).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.KOFU]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KOFU"], true, Type.WATER).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.LARRY]: new TrainerConfig(++t).setName("Larry").initForGymLeader(signatureSpecies["LARRY"], true, Type.NORMAL).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.RYME]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["RYME"], false, Type.GHOST).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.TULIP]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["TULIP"], false, Type.PSYCHIC).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.GRUSHA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GRUSHA"], true, Type.ICE).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.KATY]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KATY"], false, Type.BUG, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.BRASSIUS]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["BRASSIUS"], true, Type.GRASS, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.IONO]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["IONO"], false, Type.ELECTRIC, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.KOFU]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["KOFU"], true, Type.WATER, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.LARRY]: new TrainerConfig(++t).setName("Larry").initForGymLeader(signatureSpecies["LARRY"], true, Type.NORMAL, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.RYME]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["RYME"], false, Type.GHOST, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.TULIP]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["TULIP"], false, Type.PSYCHIC, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.GRUSHA]: new TrainerConfig(++t).initForGymLeader(signatureSpecies["GRUSHA"], true, Type.ICE, true, -1).setMixedBattleBgm("battle_paldea_gym"),
[TrainerType.LORELEI]: new TrainerConfig((t = TrainerType.LORELEI)).initForEliteFour(signatureSpecies["LORELEI"], false, Type.ICE).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.BRUNO]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["BRUNO"], true, Type.FIGHTING).setBattleBgm("battle_kanto_gym").setMixedBattleBgm("battle_kanto_gym"),
@ -1768,7 +1776,7 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.DRAKE]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAKE"], true, Type.DRAGON).setMixedBattleBgm("battle_hoenn_elite"),
[TrainerType.AARON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AARON"], true, Type.BUG).setBattleBgm("battle_sinnoh_gym").setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.BERTHA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["BERTHA"], false, Type.GROUND).setBattleBgm("battle_sinnoh_gym").setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.FLINT]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["FLINT"], true, Type.FIRE).setBattleBgm("battle_sinnoh_gym").setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.FLINT]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["FLINT"], true, Type.FIRE, 3).setBattleBgm("battle_sinnoh_gym").setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.LUCIAN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LUCIAN"], true, Type.PSYCHIC).setBattleBgm("battle_sinnoh_gym").setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.SHAUNTAL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["SHAUNTAL"], false, Type.GHOST).setMixedBattleBgm("battle_unova_elite"),
[TrainerType.MARSHAL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["MARSHAL"], true, Type.FIGHTING).setMixedBattleBgm("battle_unova_elite"),
@ -1788,14 +1796,14 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.BEA_ELITE]: new TrainerConfig(++t).setName("Bea").initForEliteFour(signatureSpecies["BEA_ELITE"], false, Type.FIGHTING).setMixedBattleBgm("battle_galar_elite"),
[TrainerType.ALLISTER_ELITE]: new TrainerConfig(++t).setName("Allister").initForEliteFour(signatureSpecies["ALLISTER_ELITE"], true, Type.GHOST).setMixedBattleBgm("battle_galar_elite"),
[TrainerType.RAIHAN_ELITE]: new TrainerConfig(++t).setName("Raihan").initForEliteFour(signatureSpecies["RAIHAN_ELITE"], true, Type.DRAGON).setMixedBattleBgm("battle_galar_elite"),
[TrainerType.RIKA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["RIKA"], false, Type.GROUND).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.POPPY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["POPPY"], false, Type.STEEL).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.LARRY_ELITE]: new TrainerConfig(++t).setName("Larry").initForEliteFour(signatureSpecies["LARRY_ELITE"], true, Type.NORMAL, Type.FLYING).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.HASSEL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["HASSEL"], true, Type.DRAGON).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.CRISPIN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["CRISPIN"], true, Type.FIRE).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.AMARYS]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AMARYS"], false, Type.STEEL).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.LACEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LACEY"], false, Type.FAIRY).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.DRAYTON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAYTON"], true, Type.DRAGON).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.RIKA]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["RIKA"], false, Type.GROUND, 5).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.POPPY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["POPPY"], false, Type.STEEL, 5).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.LARRY_ELITE]: new TrainerConfig(++t).setName("Larry").initForEliteFour(signatureSpecies["LARRY_ELITE"], true, Type.FLYING, 5).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.HASSEL]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["HASSEL"], true, Type.DRAGON, 5).setMixedBattleBgm("battle_paldea_elite"),
[TrainerType.CRISPIN]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["CRISPIN"], true, Type.FIRE, 5).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.AMARYS]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["AMARYS"], false, Type.STEEL, 5).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.LACEY]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["LACEY"], false, Type.FAIRY, 5).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.DRAYTON]: new TrainerConfig(++t).initForEliteFour(signatureSpecies["DRAYTON"], true, Type.DRAGON, 5).setMixedBattleBgm("battle_bb_elite"),
[TrainerType.BLUE]: new TrainerConfig((t = TrainerType.BLUE)).initForChampion(true).setBattleBgm("battle_kanto_champion").setMixedBattleBgm("battle_kanto_champion").setHasDouble("blue_red_double").setDoubleTrainerType(TrainerType.RED).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ALAKAZAM ]))
@ -1815,7 +1823,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName();
p.gender = Gender.MALE;
}))
.setInstantTera(3),
.setInstantTera(3), // Tera Ground or Rock Rhyperior / Electric Electivire / Fire Magmortar
[TrainerType.RED]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion").setHasDouble("red_blue_double").setDoubleTrainerType(TrainerType.BLUE).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PIKACHU ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 8; // G-Max Pikachu
@ -1839,7 +1847,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName();
p.gender = Gender.MALE;
}))
.setInstantTera(3),
.setInstantTera(3), // Tera Grass Meganium / Fire Typhlosion / Water Feraligatr
[TrainerType.LANCE_CHAMPION]: new TrainerConfig(++t).setName("Lance").initForChampion(true).setBattleBgm("battle_johto_champion").setMixedBattleBgm("battle_johto_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GYARADOS, Species.KINGDRA ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AERODACTYL ]))
@ -1850,14 +1858,15 @@ export const trainerConfigs: TrainerConfigs = {
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.CHARIZARD ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.TYRANITAR, Species.GARCHOMP, Species.KOMMO_O ], TrainerSlot.TRAINER, true, p => {
p.teraType = p.species.type1;
p.teraType = Type.DRAGON;
p.abilityIndex = p.species.speciesId === Species.KOMMO_O ? 1 : 2; // Soundproof Kommo-o, Unnerve Tyranitar, Rough Skin Garchomp
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DRAGONITE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
p.setBoss(true, 2);
}))
.setInstantTera(4),
.setInstantTera(4), // Tera Dragon Tyranitar / Garchomp / Kommo-o
[TrainerType.STEVEN]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("steven_wallace_double").setDoubleTrainerType(TrainerType.WALLACE).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SKARMORY ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.CRADILY, Species.ARMALDO ]))
@ -1875,7 +1884,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
p.generateName();
}))
.setInstantTera(4),
.setInstantTera(4), // Tera Rock Regirock / Ice Regice / Steel Registeel
[TrainerType.WALLACE]: new TrainerConfig(++t).initForChampion(true).setBattleBgm("battle_hoenn_champion_g5").setMixedBattleBgm("battle_hoenn_champion_g6").setHasDouble("wallace_steven_double").setDoubleTrainerType(TrainerType.STEVEN).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PELIPPER ], TrainerSlot.TRAINER, true, p => {
p.abilityIndex = 1; // Drizzle
@ -1898,7 +1907,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.FEMALE;
p.setBoss(true, 2);
}))
.setInstantTera(4),
.setInstantTera(4), // Tera Electric Regieleki / Dragon Regidrago
[TrainerType.CYNTHIA]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_sinnoh_champion").setMixedBattleBgm("battle_sinnoh_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SPIRITOMB ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -1921,7 +1930,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName();
p.gender = Gender.FEMALE;
}))
.setInstantTera(3),
.setInstantTera(3), // Tera Water Milotic / Grass Roserade / Fire Hisuian Arcanine
[TrainerType.ALDER]: new TrainerConfig(++t).initForChampion(true).setHasDouble("alder_iris_double").setDoubleTrainerType(TrainerType.IRIS).setDoubleTitle("champion_double").setBattleBgm("battle_champion_alder").setMixedBattleBgm("battle_champion_alder")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BOUFFALANT, Species.BRAVIARY ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HISUI_LILLIGANT, Species.HISUI_ZOROARK, Species.BASCULEGION ], TrainerSlot.TRAINER, true, p => {
@ -1944,7 +1953,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.MALE;
p.setBoss(true, 2);
}))
.setInstantTera(4),
.setInstantTera(4), // Tera Ghost Chandelure / Dark Krookodile / Psychic Reuniclus / Fighting Conkeldurr
[TrainerType.IRIS]: new TrainerConfig(++t).initForChampion(false).setBattleBgm("battle_champion_iris").setMixedBattleBgm("battle_champion_iris").setHasDouble("iris_alder_double").setDoubleTrainerType(TrainerType.ALDER).setDoubleTitle("champion_double")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.DRUDDIGON ]))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ARCHEOPS ]))
@ -1966,7 +1975,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.FEMALE;
p.setBoss(true, 2);
}))
.setInstantTera(3),
.setInstantTera(3), // Tera Dragon Salamence / Hydreigon / Archaludon
[TrainerType.DIANTHA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_kalos_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.HAWLUCHA ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -1979,7 +1988,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.TYRANTRUM, Species.AURORUS ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Rock Head Tyrantrum, Snow Warning Aurorus
p.teraType = p.species.speciesId === Species.TYRANTRUM ? Type.DRAGON : Type.ICE;
p.teraType = p.species.type2!;
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GOODRA ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -1991,7 +2000,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName();
p.gender = Gender.FEMALE;
}))
.setInstantTera(3),
.setInstantTera(3), // Tera Dragon Tyrantrum / Ice Aurorus
[TrainerType.KUKUI]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kukui")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -1999,7 +2008,7 @@ export const trainerConfigs: TrainerConfigs = {
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MAGNEZONE, Species.ALOLA_NINETALES ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.TORNADUS, Species.THUNDURUS, Species.LANDORUS ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 1; // Therian Forms
p.formIndex = 1; // Therian Formes
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
@ -2015,9 +2024,9 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.INCINEROAR, Species.HISUI_DECIDUEYE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
p.teraType = p.species.speciesId === Species.INCINEROAR ? Type.DARK : Type.FIGHTING;
p.teraType = p.species.type2!;
}))
.setInstantTera(5),
.setInstantTera(5), // Tera Dark Incineroar / Fighting Hisuian Decidueye
[TrainerType.HAU]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_alola_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ALOLA_RAICHU ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -2042,7 +2051,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.gender = p.species.speciesId === Species.PRIMARINA ? Gender.FEMALE : Gender.MALE;
}))
.setInstantTera(3),
.setInstantTera(3), // Tera Psychic Tapu Lele / Grass Tapu Bulu
[TrainerType.LEON]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_galar_champion")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AEGISLASH ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -2063,7 +2072,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName();
p.gender = Gender.MALE;
}))
.setInstantTera(3),
.setInstantTera(3), // Tera Dragapult to Ghost or Dragon
[TrainerType.MUSTARD]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_mustard")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CORVIKNIGHT ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -2094,7 +2103,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setInstantTera(2),
.setInstantTera(2), // Tera Psychic Galar-Slowbro / Galar-Slowking
[TrainerType.GEETA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_geeta")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GLIMMORA ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -2113,7 +2122,7 @@ export const trainerConfigs: TrainerConfigs = {
p.abilityIndex = 1; // Supreme Overlord
p.teraType = Type.FLYING;
}))
.setInstantTera(5),
.setInstantTera(5), // Tera Flying Kingambit
[TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion(false).setMixedBattleBgm("battle_champion_nemona")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LYCANROC ], TrainerSlot.TRAINER, true, p => {
p.formIndex = 0; // Midday form
@ -2126,25 +2135,21 @@ export const trainerConfigs: TrainerConfigs = {
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GHOLDENGO ]))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.ARMAROUGE, Species.CERULEDGE ], TrainerSlot.TRAINER, true, p => {
p.teraType = p.species.speciesId === Species.ARMAROUGE ? Type.PSYCHIC : Type.GHOST;
p.teraType = p.species.type2!;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
p.setBoss(true, 2);
}))
.setInstantTera(4),
.setInstantTera(4), // Tera Psychic Armarouge / Ghost Ceruledge
[TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion(true).setMixedBattleBgm("battle_champion_kieran")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.POLIWRATH, Species.POLITOED ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.INCINEROAR, Species.GRIMMSNARL ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
if (p.species.speciesId === Species.INCINEROAR) {
p.abilityIndex = 2; // Intimidate
} else if (p.species.speciesId === Species.GRIMMSNARL) {
p.abilityIndex = 0; // Prankster
}
p.abilityIndex = p.species.speciesId === Species.INCINEROAR ? 2 : 0; // Intimidate Incineroar, Prankster Grimmsnarl
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.TERAPAGOS ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -2167,7 +2172,7 @@ export const trainerConfigs: TrainerConfigs = {
p.gender = Gender.MALE;
p.setBoss(true, 2);
}))
.setInstantTera(4),
.setInstantTera(4), // Tera Ogerpon
[TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL)
.setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE)
@ -2198,7 +2203,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540)
.setInstantTera(0),
.setInstantTera(0), // Tera starter to primary type
[TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
p => {
@ -2215,7 +2220,7 @@ export const trainerConfigs: TrainerConfigs = {
p.shiny = true;
p.variant = 1;
}))
.setInstantTera(0),
.setInstantTera(0), // Tera starter to primary type
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
p => {
@ -2240,7 +2245,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // Mega Rayquaza
p.generateName();
}))
.setInstantTera(0),
.setInstantTera(0), // Tera starter to primary type
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN ], TrainerSlot.TRAINER, true, p => {
@ -2638,7 +2643,7 @@ export const trainerConfigs: TrainerConfigs = {
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CRAWDAUNT, Species.HISUI_SAMUROTT ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Sharpness Hisui Samurott, Adaptability Crawdaunt
p.abilityIndex = 2; // Sharpness Hisuian Samurott, Adaptability Crawdaunt
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.XURKITREE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
@ -2738,7 +2743,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.generateName();
}))
.setInstantTera(4),
.setInstantTera(4), // Tera Fairy Sylveon
[TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2771,13 +2776,8 @@ export const trainerConfigs: TrainerConfigs = {
p.generateName();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.ZAMAZENTA ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
}))
.setInstantTera(0),
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true)
.setInstantTera(0), // Tera Fairy Sylveon
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer(true)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 3);
p.generateAndPopulateMoveset();
@ -2809,7 +2809,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.CHERYL]: new TrainerConfig(++t).setName("Cheryl").initForStatTrainer([], false)
[TrainerType.CHERYL]: new TrainerConfig(++t).setName("Cheryl").initForStatTrainer()
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BLISSEY ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 3);
p.generateAndPopulateMoveset();
@ -2837,7 +2837,7 @@ export const trainerConfigs: TrainerConfigs = {
}
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.MARLEY]: new TrainerConfig(++t).setName("Marley").initForStatTrainer([], false)
[TrainerType.MARLEY]: new TrainerConfig(++t).setName("Marley").initForStatTrainer()
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCANINE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 3);
p.generateAndPopulateMoveset();
@ -2861,7 +2861,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.MIRA]: new TrainerConfig(++t).setName("Mira").initForStatTrainer([], false)
[TrainerType.MIRA]: new TrainerConfig(++t).setName("Mira").initForStatTrainer()
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ALAKAZAM ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
@ -2883,7 +2883,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.RILEY]: new TrainerConfig(++t).setName("Riley").initForStatTrainer([], true)
[TrainerType.RILEY]: new TrainerConfig(++t).setName("Riley").initForStatTrainer(true)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LUCARIO ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();

View File

@ -29,4 +29,5 @@ export enum ArenaTagType {
WATER_FIRE_PLEDGE = "WATER_FIRE_PLEDGE",
GRASS_WATER_PLEDGE = "GRASS_WATER_PLEDGE",
FAIRY_LOCK = "FAIRY_LOCK",
NEUTRALIZING_GAS = "NEUTRALIZING_GAS"
}

View File

@ -588,8 +588,8 @@ export class Arena {
// creates a new tag object
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
if (newTag) {
this.tags.push(newTag);
newTag.onAdd(this, quiet);
this.tags.push(newTag);
const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {};

View File

@ -63,8 +63,9 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
import { WeatherType } from "#enums/weather-type";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
import type { Ability, AbAttr } from "#app/data/ability";
import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseClearWeatherAbAttrs } from "#app/data/ability";
import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseAbAttrs, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr } from "#app/data/ability";
import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#app/battle";
import { Mode } from "#app/ui/ui";
@ -1225,10 +1226,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/**
* Checks if the {@linkcode Pokemon} has is the specified {@linkcode Species} or is fused with it.
* @param species the pokemon {@linkcode Species} to check
* @param formKey If provided, requires the species to be in that form
* @returns `true` if the pokemon is the species or is fused with it, `false` otherwise
*/
hasSpecies(species: Species): boolean {
return this.species.speciesId === species || this.fusionSpecies?.speciesId === species;
hasSpecies(species: Species, formKey?: string): boolean {
if (Utils.isNullOrUndefined(formKey)) {
return this.species.speciesId === species || this.fusionSpecies?.speciesId === species;
}
return (this.species.speciesId === species && this.getFormKey() === formKey) || (this.fusionSpecies?.speciesId === species && this.getFusionFormKey() === formKey);
}
abstract isBoss(): boolean;
@ -1487,7 +1493,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ability New Ability
*/
public setTempAbility(ability: Ability, passive: boolean = false): void {
applyOnLoseClearWeatherAbAttrs(this, passive);
applyOnLoseAbAttrs(this, passive);
if (passive) {
this.summonData.passiveAbility = ability.id;
} else {
@ -1496,6 +1502,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyOnGainAbAttrs(this, passive);
}
/**
* Suppresses an ability and calls its onlose attributes
*/
public suppressAbility() {
[ true, false ].forEach((passive) => applyOnLoseAbAttrs(this, passive));
this.summonData.abilitySuppressed = true;
}
/**
* Checks if a pokemon has a passive either from:
* - bought with starter candy
@ -1553,17 +1567,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
return false;
}
if (this.isOnField() && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) {
const suppressed = new Utils.BooleanHolder(false);
globalScene.getField(true).filter(p => p !== this).map(p => {
if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) {
p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, false, suppressed, [ ability ]));
}
if (p.getPassiveAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility(true)) {
p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, false, suppressed, [ ability ]));
}
});
if (suppressed.value) {
const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
if (this.isOnField() && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) {
const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr);
const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false);
// Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas
// (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized)
// If the ability itself is neutralizing gas, don't suppress it (handled through arena tag)
const unsuppressable = ability.hasAttr(UnsuppressableAbilityAbAttr) || thisAbilitySuppressing || (hasSuppressingAbility && !suppressAbilitiesTag.shouldApplyToSelf());
if (!unsuppressable) {
return false;
}
}
@ -3197,6 +3209,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return maxForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && maxForms.includes(this.getFusionFormKey()!));
}
isMega(): boolean {
const megaForms = [ SpeciesFormKey.MEGA, SpeciesFormKey.MEGA_X, SpeciesFormKey.MEGA_Y, SpeciesFormKey.PRIMAL ] as string[];
return megaForms.includes(this.getFormKey()) || (!!this.getFusionFormKey() && megaForms.includes(this.getFusionFormKey()!));
}
canAddTag(tagType: BattlerTagType): boolean {
if (this.getTag(tagType)) {
return false;

View File

@ -416,14 +416,16 @@ export default class Trainer extends Phaser.GameObjects.Container {
}
}
if (!retry && this.config.specialtyTypes.length && !this.config.specialtyTypes.find(t => ret.isOfType(t))) {
// Prompts reroll of party member species if doesn't fit specialty type.
// Can be removed by adding a type parameter to getTrainerSpeciesForLevel and filtering the list of evolutions for that type.
if (!retry && this.config.hasSpecialtyType() && !ret.isOfType(this.config.specialtyType)) {
retry = true;
console.log("Attempting reroll of species evolution to fit specialty type...");
let evoAttempt = 0;
while (retry && evoAttempt++ < 10) {
ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, globalScene.currentBattle.waveIndex));
console.log(ret.name);
if (this.config.specialtyTypes.find(t => ret.isOfType(t))) {
if (ret.isOfType(this.config.specialtyType)) {
retry = false;
}
}
@ -677,6 +679,11 @@ export default class Trainer extends Phaser.GameObjects.Container {
});
}
/**
* Determines whether a Trainer should Terastallize their Pokemon
* @param pokemon {@linkcode EnemyPokemon} Trainer Pokemon in question
* @returns boolean Whether the EnemyPokemon should Terastalize this turn
*/
shouldTera(pokemon: EnemyPokemon): boolean {
if (this.config.trainerAI.teraMode === TeraAIMode.INSTANT_TERA) {
if (!pokemon.isTerastallized && this.config.trainerAI.instantTeras.includes(pokemon.initialTeamIndex)) {

View File

@ -564,6 +564,15 @@ export class InputsController {
if (!this.configs[selectedDevice]) {
this.configs[selectedDevice] = {};
}
// A proper way of handling migrating keybinds would be much better
const mappingOverrides = {
"BUTTON_CYCLE_VARIANT": "BUTTON_CYCLE_TERA",
};
for (const key in mappingConfigs.custom) {
if (mappingConfigs.custom[key] in mappingOverrides) {
mappingConfigs.custom[key] = mappingOverrides[mappingConfigs.custom[key]];
}
}
this.configs[selectedDevice].custom = mappingConfigs.custom;
}

View File

@ -101,6 +101,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("icon_lock", "ui", "icon_lock.png");
this.loadImage("icon_stop", "ui", "icon_stop.png");
this.loadImage("icon_tera", "ui");
this.loadImage("cursor_tera", "ui");
this.loadImage("type_tera", "ui");
this.loadAtlas("type_bgs", "ui");
this.loadAtlas("button_tera", "ui");
@ -250,9 +251,9 @@ export class LoadingScene extends SceneBase {
}
const availableLangs = [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ];
if (lang && availableLangs.includes(lang)) {
this.loadImage("valentines2025event-" + lang, "events");
this.loadImage("pkmnday2025event-" + lang, "events");
} else {
this.loadImage("valentines2025event-en", "events");
this.loadImage("pkmnday2025event-en", "events");
}
this.loadAtlas("statuses", "");

View File

@ -2561,7 +2561,7 @@ export function getPartyLuckValue(party: Pokemon[]): number {
return DailyLuck.value;
}
const eventSpecies = globalScene.eventManager.getEventLuckBoostedSpecies();
const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 3 : 0) : 0)
const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0)
.reduce((total: number, value: number) => total += value, 0), 0, 14);
return Math.min(globalScene.eventManager.getEventLuckBoost() + (luck ?? 0), 14);
}

View File

@ -253,7 +253,10 @@ export class EncounterPhase extends BattlePhase {
globalScene.getEnemyField().forEach(enemy => {
overrideHeldItems(enemy, false);
});
}
if (battle.battleType === BattleType.TRAINER) {
globalScene.currentBattle.trainer!.genAI(globalScene.getEnemyParty());
}
globalScene.ui.setMode(Mode.MESSAGE).then(() => {

View File

@ -1645,11 +1645,19 @@ export class GameData {
} else if (formIndex === 3) {
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(1);
}
}
const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : [];
const toCurrentFormChanges = allFormChanges.filter(f => (f.formKey === formKey));
if (toCurrentFormChanges.length > 0) {
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0);
} else if (pokemon.species.speciesId === Species.ZYGARDE) {
if (formIndex === 4) {
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(2);
} else if (formIndex === 5) {
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(3);
}
} else {
const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : [];
const toCurrentFormChanges = allFormChanges.filter(f => (f.formKey === formKey));
if (toCurrentFormChanges.length > 0) {
// Needs to do this or Castform can unlock the wrong form, etc.
dexEntry.caughtAttr |= globalScene.gameData.getFormAttr(0);
}
}
}

View File

@ -1,8 +1,29 @@
import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
import type { SessionSaveData } from "#app/system/game-data";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { globalScene } from "#app/global-scene";
import { DexAttr, type SessionSaveData, type SystemSaveData } from "#app/system/game-data";
import * as Utils from "#app/utils";
export const systemMigrators = [] as const;
export const systemMigrators = [
/**
* If a starter is caught, but the only forms registered as caught are not starterSelectable,
* unlock the default form.
* @param data {@linkcode SystemSaveData}
*/
function migrateUnselectableForms(data: SystemSaveData) {
if (data.starterData && data.dexData) {
Object.keys(data.starterData).forEach(sd => {
const caughtAttr = data.dexData[sd]?.caughtAttr;
const species = getPokemonSpecies(Number(sd));
if (caughtAttr && species.forms?.length > 1) {
const selectableForms = species.forms.filter((form, formIndex) => form.isStarterSelectable && (caughtAttr & globalScene.gameData.getFormAttr(formIndex)));
if (selectableForms.length === 0) {
data.dexData[sd].caughtAttr += DexAttr.DEFAULT_FORM;
}
}
});
}
},
] as const;
export const settingsMigrators = [] as const;

View File

@ -169,7 +169,7 @@ const timedEvents: TimedEvent[] = [
{ species: Species.WOOBAT },
{ species: Species.FRILLISH },
{ species: Species.ALOMOMOLA },
{ species: Species.FURFROU, formIndex: 1 }, // Heart trim
{ species: Species.FURFROU, formIndex: 1 }, // Heart Trim
{ species: Species.ESPURR },
{ species: Species.SPRITZEE },
{ species: Species.SWIRLIX },
@ -180,6 +180,33 @@ const timedEvents: TimedEvent[] = [
{ species: Species.ENAMORUS }
],
luckBoostedSpecies: [ Species.LUVDISC ]
},
{
name: "PKMNDAY2025",
eventType: EventType.LUCK,
startDate: new Date(Date.UTC(2025, 1, 27)),
endDate: new Date(Date.UTC(2025, 2, 4)),
classicFriendshipMultiplier: 4,
bannerKey: "pkmnday2025event-",
scale: 0.21,
availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ],
eventEncounters: [
{ species: Species.PIKACHU, formIndex: 1, blockEvolution: true }, // Partner Form
{ species: Species.EEVEE, formIndex: 1, blockEvolution: true }, // Partner Form
{ species: Species.CHIKORITA },
{ species: Species.TOTODILE },
{ species: Species.TEPIG }
],
luckBoostedSpecies: [
Species.PICHU, Species.PIKACHU, Species.RAICHU, Species.ALOLA_RAICHU,
Species.PSYDUCK, Species.GOLDUCK,
Species.EEVEE, Species.FLAREON, Species.JOLTEON, Species.VAPOREON, Species.ESPEON, Species.UMBREON, Species.LEAFEON, Species.GLACEON, Species.SYLVEON,
Species.CHIKORITA, Species.BAYLEEF, Species.MEGANIUM,
Species.TOTODILE, Species.CROCONAW, Species.FERALIGATR,
Species.TEPIG, Species.PIGNITE, Species.EMBOAR,
Species.ZYGARDE,
Species.ETERNAL_FLOETTE
]
}
];

View File

@ -109,7 +109,7 @@ export class UiInputs {
[Button.CYCLE_GENDER]: () => undefined,
[Button.CYCLE_ABILITY]: () => undefined,
[Button.CYCLE_NATURE]: () => undefined,
[Button.CYCLE_TERA]: () => undefined,
[Button.CYCLE_TERA]: () => this.buttonInfo(false),
[Button.SPEED_UP]: () => undefined,
[Button.SLOW_DOWN]: () => undefined,
};

View File

@ -147,7 +147,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
itemIcon.setScale(3 * this.scale);
this.optionSelectIcons.push(itemIcon);
this.optionSelectContainer.add(itemIcon);
this.optionSelectTextContainer.add(itemIcon);
itemIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3));
@ -156,7 +156,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
itemOverlayIcon.setScale(3 * this.scale);
this.optionSelectIcons.push(itemOverlayIcon);
this.optionSelectContainer.add(itemOverlayIcon);
this.optionSelectTextContainer.add(itemOverlayIcon);
itemOverlayIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3));

View File

@ -10,6 +10,7 @@ import { globalScene } from "#app/global-scene";
import { TerastallizeAccessModifier } from "#app/modifier/modifier";
import { Type } from "#app/enums/type";
import { getTypeRgb } from "#app/data/type";
import { Species } from "#enums/species";
export enum Command {
FIGHT = 0,
@ -180,9 +181,11 @@ export default class CommandUiHandler extends UiHandler {
canTera(): boolean {
const hasTeraMod = !!globalScene.getModifiers(TerastallizeAccessModifier).length;
const activePokemon = globalScene.getField()[this.fieldIndex];
const isBlockedForm = activePokemon.isMega() || activePokemon.isMax() || activePokemon.hasSpecies(Species.NECROZMA, "ultra");
const currentTeras = globalScene.arena.playerTerasUsed;
const plannedTera = globalScene.currentBattle.preTurnCommands[0]?.command === Command.TERA && this.fieldIndex > 0 ? 1 : 0;
return hasTeraMod && (currentTeras + plannedTera) < 1;
return hasTeraMod && !isBlockedForm && (currentTeras + plannedTera) < 1;
}
toggleTeraButton() {

View File

@ -629,6 +629,8 @@ export class DropDown extends Phaser.GameObjects.Container {
}
}
}
this.onChange();
}
}

View File

@ -226,7 +226,9 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
}
if (!this.cursorObj) {
this.cursorObj = globalScene.add.image(0, 0, "cursor");
const isTera = this.fromCommand === Command.TERA;
this.cursorObj = globalScene.add.image(0, 0, isTera ? "cursor_tera" : "cursor");
this.cursorObj.setScale(isTera ? 0.7 : 1);
ui.add(this.cursorObj);
}

View File

@ -86,6 +86,15 @@ export class FilterBar extends Phaser.GameObjects.Container {
return this.dropDowns[this.columns.indexOf(col)];
}
/**
* Get the DropDownColumn associated to a given index
* @param index the index of the column to retrieve
* @returns the associated DropDownColumn if it exists, undefined otherwise
*/
public getColumn(index: number): DropDownColumn {
return this.columns[index];
}
/**
* Highlight the labels of the FilterBar if the filters are different from their default values
*/
@ -185,6 +194,11 @@ export class FilterBar extends Phaser.GameObjects.Container {
return this.getFilter(col).getVals();
}
public resetSelection(col: DropDownColumn): void {
this.dropDowns[col].resetToDefault();
this.updateFilterLabels();
}
setValsToDefault(): void {
for (const dropDown of this.dropDowns) {
dropDown.resetToDefault();

View File

@ -422,7 +422,10 @@ export default class PartyUiHandler extends MessageUiHandler {
if (option === PartyOption.TRANSFER) {
if (this.transferCursor !== this.cursor) {
if (this.transferAll) {
getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor));
getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach((_, i, array) => {
const invertedIndex = array.length - 1 - i;
(this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, invertedIndex, this.transferQuantitiesMax[invertedIndex], this.cursor);
});
} else {
(this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor);
}
@ -1187,7 +1190,6 @@ class PartySlot extends Phaser.GameObjects.Container {
public slotHpText: Phaser.GameObjects.Text;
public slotDescriptionLabel: Phaser.GameObjects.Text; // this is used to show text instead of the HP bar i.e. for showing "Able"/"Not Able" for TMs when you try to learn them
private pokemonIcon: Phaser.GameObjects.Container;
private iconAnimHandler: PokemonIconAnimHandler;
@ -1208,6 +1210,10 @@ class PartySlot extends Phaser.GameObjects.Container {
}
setup(partyUiMode: PartyUiMode, tmMoveId: Moves) {
const currentLanguage = i18next.resolvedLanguage ?? "en";
const offsetJa = currentLanguage === "ja";
const battlerCount = globalScene.currentBattle.getBattlerCount();
const slotKey = `party_slot${this.slotIndex >= battlerCount ? "" : "_main"}`;
@ -1246,15 +1252,15 @@ class PartySlot extends Phaser.GameObjects.Container {
nameSizeTest.destroy();
this.slotName = addTextObject(0, 0, displayName, TextStyle.PARTY);
this.slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10);
this.slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, (this.slotIndex >= battlerCount ? 2 : 10) + (offsetJa ? 2 : 0));
this.slotName.setOrigin(0, 0);
const slotLevelLabel = globalScene.add.image(0, 0, "party_slot_overlay_lv");
slotLevelLabel.setPositionRelative(this.slotName, 8, 12);
slotLevelLabel.setPositionRelative(slotBg, (this.slotIndex >= battlerCount ? 21 : 24) + 8, (this.slotIndex >= battlerCount ? 2 : 10) + 12);
slotLevelLabel.setOrigin(0, 0);
const slotLevelText = addTextObject(0, 0, this.pokemon.level.toString(), this.pokemon.level < globalScene.getMaxExpLevel() ? TextStyle.PARTY : TextStyle.PARTY_RED);
slotLevelText.setPositionRelative(slotLevelLabel, 9, 0);
slotLevelText.setPositionRelative(slotLevelLabel, 9, offsetJa ? 1.5 : 0);
slotLevelText.setOrigin(0, 0.25);
slotInfoContainer.add([ this.slotName, slotLevelLabel, slotLevelText ]);
@ -1331,7 +1337,7 @@ class PartySlot extends Phaser.GameObjects.Container {
this.slotHpOverlay.setVisible(false);
this.slotHpText = addTextObject(0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY);
this.slotHpText.setPositionRelative(this.slotHpBar, this.slotHpBar.width - 3, this.slotHpBar.height - 2);
this.slotHpText.setPositionRelative(this.slotHpBar, this.slotHpBar.width - 3, this.slotHpBar.height - 2 + (offsetJa ? 2 : 0));
this.slotHpText.setOrigin(1, 0);
this.slotHpText.setVisible(false);

View File

@ -250,6 +250,8 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
private availableVariants: number;
private unlockedVariants: boolean[];
private canUseCandies: boolean;
constructor() {
super(Mode.POKEDEX_PAGE);
}
@ -556,6 +558,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
show(args: any[]): boolean {
// Allow the use of candies if we are in one of the whitelisted phases
this.canUseCandies = [ "TitlePhase", "SelectStarterPhase", "CommandPhase" ].includes(globalScene.getCurrentPhase()?.constructor.name ?? "");
if (args.length >= 1 && args[0] === "refresh") {
return false;
} else {
@ -597,6 +602,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.battleForms = [];
const species = this.species;
let formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : "";
this.isFormGender = formKey === "male" || formKey === "female";
if (this.isFormGender && ((this.savedStarterAttributes.female === true && formKey === "male") || (this.savedStarterAttributes.female === false && formKey === "female"))) {
this.formIndex = (this.formIndex + 1) % 2;
formKey = this.species.forms[this.formIndex].formKey;
}
const formIndex = this.formIndex ?? 0;
this.starterId = this.getStarterSpeciesId(this.species.speciesId);
@ -630,12 +643,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.eggMoves = speciesEggMoves[this.starterId] ?? [];
this.hasEggMoves = Array.from({ length: 4 }, (_, em) => (globalScene.gameData.starterData[this.starterId].eggMoves & (1 << em)) !== 0);
const formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : "";
this.tmMoves = speciesTmMoves[species.speciesId]?.filter(m => Array.isArray(m) ? (m[0] === formKey ? true : false ) : true)
.map(m => Array.isArray(m) ? m[1] : m).sort((a, b) => allMoves[a].name > allMoves[b].name ? 1 : -1) ?? [];
this.isFormGender = formKey === "male" || formKey === "female";
const passiveId = starterPassiveAbilities.hasOwnProperty(species.speciesId) ? species.speciesId :
starterPassiveAbilities.hasOwnProperty(this.starterId) ? this.starterId : pokemonPrevolutions[this.starterId];
const passives = starterPassiveAbilities[passiveId];
@ -779,6 +789,10 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
const formIndex = otherFormIndex !== undefined ? otherFormIndex : this.formIndex;
const caughtAttr = this.isCaught(species);
if (caughtAttr && (!species.forms.length || species.forms.length === 1)) {
return true;
}
const isFormCaught = (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n;
return isFormCaught;
}
@ -1570,15 +1584,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
starterAttributes.variant = newVariant; // store the selected variant
this.savedStarterAttributes.variant = starterAttributes.variant;
if (newVariant > props.variant) {
this.setSpeciesDetails(this.species, { variant: newVariant as Variant });
success = true;
} else {
if ((this.isCaught() & DexAttr.NON_SHINY) && (newVariant <= props.variant)) {
this.setSpeciesDetails(this.species, { shiny: false, variant: 0 });
success = true;
starterAttributes.shiny = false;
this.savedStarterAttributes.shiny = starterAttributes.shiny;
} else {
this.setSpeciesDetails(this.species, { variant: newVariant as Variant });
success = true;
}
}
}
@ -1626,7 +1639,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
}
break;
case Button.STATS:
if (!isCaught || !isFormCaught) {
if (!isCaught || !isFormCaught || !this.canUseCandies) {
error = true;
} else {
const ui = this.getUi();
@ -1888,7 +1901,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
if (this.isCaught()) {
if (isFormCaught) {
this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel);
if (this.canUseCandies) {
this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel);
}
if (this.canCycleShiny) {
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel);
}
@ -2189,7 +2204,8 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY);
const isShinyCaught = !!(caughtAttr & DexAttr.SHINY);
this.canCycleShiny = isNonShinyCaught && isShinyCaught;
const caughtVariants = [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => caughtAttr & v);
this.canCycleShiny = (isNonShinyCaught && isShinyCaught) || (isShinyCaught && caughtVariants.length > 1);
const isMaleCaught = !!(caughtAttr & DexAttr.MALE);
const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE);

View File

@ -898,16 +898,11 @@ export default class PokedexUiHandler extends MessageUiHandler {
if (this.filterMode && this.filterBar.openDropDown) {
// CANCEL with a filter menu open > close it
this.filterBar.toggleDropDown(this.filterBarCursor);
// if there are possible pokemon go the first one of the list
if (numberOfStarters > 0) {
this.setFilterMode(false);
this.scrollCursor = 0;
this.updateScroll();
this.setCursor(0);
}
success = true;
} else if (this.filterMode && !this.filterBar.getFilter(this.filterBarCursor).hasDefaultValues()) {
this.filterBar.resetSelection(this.filterBarCursor);
this.updateStarters();
success = true;
} else if (this.filterTextMode && !(this.filterText.getValue(this.filterTextCursor) === this.filterText.defaultText)) {
this.filterText.resetSelection(this.filterTextCursor);
success = true;
@ -991,7 +986,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.updateScroll();
const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1);
const targetCol = Math.min(8, proportion < 0.5 ? Math.floor(proportion * 8) : Math.ceil(proportion * 8));
this.setCursor(Math.min(targetCol, numberOfStarters));
this.setCursor(Math.min(targetCol, numberOfStarters - 1));
success = true;
}
break;
@ -1113,7 +1108,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
}
break;
case Button.DOWN:
if (currentRow < numOfRows - 1) { // not last row
if ((currentRow < numOfRows - 1) && (this.cursor + 9 < this.filteredPokemonData.length)) { // not last row
if (currentRow - this.scrollCursor === 8) { // last row of visible pokemon
this.scrollCursor++;
this.updateScroll();
@ -1582,6 +1577,37 @@ export default class PokedexUiHandler extends MessageUiHandler {
container.icon.setTint(0);
}
if (data.eggMove1) {
container.eggMove1Icon.setVisible(true);
} else {
container.eggMove1Icon.setVisible(false);
}
if (data.eggMove2) {
container.eggMove2Icon.setVisible(true);
} else {
container.eggMove2Icon.setVisible(false);
}
if (data.tmMove1) {
container.tmMove1Icon.setVisible(true);
} else {
container.tmMove1Icon.setVisible(false);
}
if (data.tmMove2) {
container.tmMove2Icon.setVisible(true);
} else {
container.tmMove2Icon.setVisible(false);
}
if (data.passive1) {
container.passive1Icon.setVisible(true);
} else {
container.passive1Icon.setVisible(false);
}
if (data.passive2) {
container.passive2Icon.setVisible(true);
} else {
container.passive2Icon.setVisible(false);
}
if (this.showDecorations) {
if (this.pokerusSpecies.includes(data.species)) {

View File

@ -8,6 +8,7 @@ import i18next from "i18next";
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import { starterColors } from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import type { Ability } from "#app/data/ability";
import { allAbilities } from "#app/data/ability";
import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate, getGrowthRateColor } from "#app/data/exp";
@ -1080,10 +1081,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
/**
* Set the selections for all filters to their default starting value
*/
resetFilters() : void {
public resetFilters(): void {
this.filterBar.setValsToDefault();
this.resetCaughtDropdown();
}
/**
* Set default value for the caught dropdown, which only shows caught mons
*/
public resetCaughtDropdown(): void {
const caughtDropDown: DropDown = this.filterBar.getFilter(DropDownColumn.CAUGHT);
this.filterBar.setValsToDefault();
caughtDropDown.resetToDefault();
// initial setting, in caught filter, select the options excluding the uncaught option
for (let i = 0; i < caughtDropDown.options.length; i++) {
@ -1323,16 +1332,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (this.filterMode && this.filterBar.openDropDown) {
// CANCEL with a filter menu open > close it
this.filterBar.toggleDropDown(this.filterBarCursor);
// if there are possible starters go the first one of the list
if (numberOfStarters > 0) {
this.setFilterMode(false);
this.scrollCursor = 0;
this.updateScroll();
this.setCursor(0);
}
success = true;
} else if (this.filterMode && !this.filterBar.getFilter(this.filterBar.getColumn(this.filterBarCursor)).hasDefaultValues()) {
if (this.filterBar.getColumn(this.filterBarCursor) === DropDownColumn.CAUGHT) {
this.resetCaughtDropdown();
} else {
this.filterBar.resetSelection(this.filterBarCursor);
}
this.updateStarters();
success = true;
} else if (this.statsMode) {
this.toggleStatsMode(false);
success = true;
@ -2060,20 +2068,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
} while (newVariant !== props.variant);
starterAttributes.variant = newVariant; // store the selected variant
// If going to a higher variant, display that
if (newVariant > props.variant) {
if ((this.speciesStarterDexEntry!.caughtAttr & DexAttr.NON_SHINY) && (newVariant <= props.variant)) {
// If we have run out of variants, go back to non shiny
this.setSpeciesDetails(this.lastSpecies, { shiny: false, variant: 0 });
this.pokemonShinyIcon.setVisible(false);
success = true;
starterAttributes.shiny = false;
} else {
// If going to a higher variant, or only shiny forms are caught, go to next variant
this.setSpeciesDetails(this.lastSpecies, { variant: newVariant as Variant });
// Cycle tint based on current sprite tint
const tint = getVariantTint(newVariant as Variant);
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant));
this.pokemonShinyIcon.setTint(tint);
success = true;
// If we have run out of variants, go back to non shiny
} else {
this.setSpeciesDetails(this.lastSpecies, { shiny: false, variant: 0 });
this.pokemonShinyIcon.setVisible(false);
success = true;
starterAttributes.shiny = false;
}
}
}
@ -3320,7 +3328,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY);
const isShinyCaught = !!(caughtAttr & DexAttr.SHINY);
this.canCycleShiny = isNonShinyCaught && isShinyCaught;
const caughtVariants = [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => caughtAttr & v);
this.canCycleShiny = (isNonShinyCaught && isShinyCaught) || (isShinyCaught && caughtVariants.length > 1);
const isMaleCaught = !!(caughtAttr & DexAttr.MALE);
const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE);
@ -3344,7 +3353,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey))
.map((_, f) => dexEntry.caughtAttr & globalScene.gameData.getFormAttr(f)).filter(f => f).length > 1;
this.canCycleNature = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1;
this.canCycleTera = globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2);
this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(species.speciesId, formIndex ?? 0).type2);
}
if (dexEntry.caughtAttr && species.malePercent !== null) {
@ -3357,7 +3366,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
if (dexEntry.caughtAttr) {
const ability = allAbilities[this.lastSpecies.getAbility(abilityIndex!)]; // TODO: is this bang correct?
let ability: Ability;
if (this.lastSpecies.forms?.length > 1) {
ability = allAbilities[this.lastSpecies.forms[formIndex ?? 0].getAbility(abilityIndex!)];
} else {
ability = allAbilities[this.lastSpecies.getAbility(abilityIndex!)]; // TODO: is this bang correct?
}
this.pokemonAbilityText.setText(ability.name);
const isHidden = abilityIndex === (this.lastSpecies.ability2 ? 2 : 1);
@ -3844,12 +3858,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.showStats();
this.statsMode = true;
this.pokemonSprite.setVisible(false);
this.teraIcon.setVisible(false);
this.canCycleTera = false;
this.updateInstructions();
} else {
this.statsMode = false;
this.statsContainer.setVisible(false);
this.pokemonSprite.setVisible(!!this.speciesStarterDexEntry?.caughtAttr);
//@ts-ignore
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!?
this.teraIcon.setVisible(globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id));
const props = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId));
const formIndex = props.formIndex;
this.canCycleTera = !this.statsMode && globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(getPokemonSpeciesForm(this.lastSpecies.speciesId, formIndex ?? 0).type2);
this.updateInstructions();
}
}

View File

@ -28,6 +28,7 @@ import { modifierSortFunc } from "#app/modifier/modifier";
import { PlayerGender } from "#enums/player-gender";
import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat";
import { Nature } from "#enums/nature";
import { achvs } from "#app/system/achv";
enum Page {
PROFILE,
@ -783,9 +784,6 @@ export default class SummaryUiHandler extends UiHandler {
if (types.length > 1) {
profileContainer.add(getTypeIcon(1, types[1]));
}
if (this.pokemon?.isTerastallized) {
profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true));
}
if (this.pokemon?.getLuck()) {
const luckLabelText = addTextObject(141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT);
@ -798,6 +796,13 @@ export default class SummaryUiHandler extends UiHandler {
profileContainer.add(luckText);
}
if (globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id) && !Utils.isNullOrUndefined(this.pokemon)) {
const teraIcon = globalScene.add.sprite(123, 26, "button_tera");
teraIcon.setName("terrastallize-icon");
teraIcon.setFrame(Type[this.pokemon.getTeraType()].toLowerCase());
profileContainer.add(teraIcon);
}
this.abilityContainer = {
labelImage: globalScene.add.image(0, 0, "summary_profile_ability"),
ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct?

View File

@ -0,0 +1,165 @@
import { BattlerIndex } from "#app/battle";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { Moves } from "#enums/moves";
import { PokeballType } from "#enums/pokeball";
import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Neutralizing Gas", () => {
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.SPLASH ])
.ability(Abilities.NEUTRALIZING_GAS)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should prevent other abilities from activating", async () => {
game.override.enemyAbility(Abilities.INTIMIDATE);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
// Intimidate is suppressed, so the attack stat should not be lowered
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(0);
});
it("should allow the user's passive to activate", async () => {
game.override.passiveAbility(Abilities.INTREPID_SWORD);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(1);
});
it.todo("should activate before other abilities", async () => {
game.override.enemySpecies(Species.ACCELGOR)
.enemyLevel(100)
.enemyAbility(Abilities.INTIMIDATE);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
// Intimidate is suppressed even when the user's speed is lower
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(0);
});
it("should activate other abilities when removed", async () => {
game.override.enemyAbility(Abilities.INTREPID_SWORD)
.enemyPassiveAbility(Abilities.DAUNTLESS_SHIELD)
.enemyMoveset(Moves.ENTRAINMENT);
await game.classicMode.startBattle([ Species.FEEBAS ]);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon?.getStatStage(Stat.DEF)).toBe(0);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
// Enemy removes user's ability, so both abilities are activated
expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(1);
expect(enemyPokemon?.getStatStage(Stat.DEF)).toBe(1);
});
it("should not activate the user's other ability when removed", async () => {
game.override.passiveAbility(Abilities.INTIMIDATE)
.enemyMoveset(Moves.ENTRAINMENT);
await game.classicMode.startBattle([ Species.FEEBAS ]);
// Neutralising gas user's passive is still active
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
// Intimidate did not reactivate after neutralizing gas was removed
expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1);
});
it("should only deactivate when all setters are off the field", async () => {
game.override.enemyMoveset([ Moves.ENTRAINMENT, Moves.SPLASH ])
.battleType("double");
await game.classicMode.startBattle([ Species.ACCELGOR, Species.ACCELGOR ]);
game.move.select(Moves.SPLASH, 0);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.SPLASH);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); // Now one neut gas user is left
game.move.select(Moves.SPLASH, 0);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER_2);
await game.forceEnemyMove(Moves.SPLASH);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); // No neut gas users are left
});
it("should deactivate when suppressed by gastro acid", async () => {
game.override.enemyMoveset(Moves.GASTRO_ACID);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined();
});
it("should deactivate when the pokemon faints", async () => {
game.override.ability(Abilities.BALL_FETCH)
.enemyAbility(Abilities.NEUTRALIZING_GAS);
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();
await game.doKillOpponents();
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined();
});
it("should deactivate upon catching a wild pokemon", async () => {
game.override
.battleType("single")
.enemyAbility(Abilities.NEUTRALIZING_GAS)
.ability(Abilities.BALL_FETCH);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();
game.scene.pokeballCounts[PokeballType.MASTER_BALL] = 1;
game.doThrowPokeball(PokeballType.MASTER_BALL);
await game.phaseInterceptor.to("TurnEndPhase");
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined();
});
});

View File

@ -140,6 +140,6 @@ describe("Moves - Focus Punch", () => {
await game.phaseInterceptor.to("MessagePhase", false);
const consoleSpy = vi.spyOn(console, "log");
await game.phaseInterceptor.to("MoveEndPhase", true);
expect(consoleSpy).nthCalledWith(1, i18next.t("moveTriggers:lostFocus"));
expect(consoleSpy).nthCalledWith(1, i18next.t("moveTriggers:lostFocus", { pokemonName: "Charizard" }));
});
});

View File

@ -46,11 +46,8 @@ describe("Moves - Shed Tail", () => {
expect(feebas).not.toBe(magikarp);
expect(feebas.hp).toBe(feebas.getMaxHp());
// Note: Shed Tail's HP cost is currently not accurate to mainline, as it
// should cost ceil(maxHP / 2) instead of max(floor(maxHp / 2), 1). The current
// implementation is consistent with Substitute's HP cost logic, but that's not
// the case in mainline for some reason :regiDespair:.
expect(magikarp.hp).toBe(Math.ceil(magikarp.getMaxHp() / 2));
// Note: Altered the test to be consistent with the correct HP cost :yipeevee_static:
expect(magikarp.hp).toBe(Math.floor(magikarp.getMaxHp() / 2));
expect(substituteTag).toBeDefined();
expect(substituteTag?.hp).toBe(Math.floor(magikarp.getMaxHp() / 4));
});

View File

@ -223,14 +223,14 @@ describe("Mystery Encounter Utils", () => {
});
it("excludes species from search", () => {
// Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian
const result = getRandomSpeciesByStarterCost(9, [ Species.KORAIDON, Species.MIRAIDON, Species.ARCEUS, Species.RAYQUAZA, Species.KYOGRE, Species.GROUDON ]);
// Only 9 tiers are: Kyogre, Groudon, Rayquaza, Arceus, Zacian, Koraidon, Miraidon, Terapagos
const result = getRandomSpeciesByStarterCost(9, [ Species.KYOGRE, Species.GROUDON, Species.RAYQUAZA, Species.ARCEUS, Species.KORAIDON, Species.MIRAIDON, Species.TERAPAGOS ]);
const pokeSpecies = getPokemonSpecies(result);
expect(pokeSpecies.speciesId).toBe(Species.ZACIAN);
});
it("gets species of specified types", () => {
// Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian
// Only 9 tiers are: Kyogre, Groudon, Rayquaza, Arceus, Zacian, Koraidon, Miraidon, Terapagos
const result = getRandomSpeciesByStarterCost(9, undefined, [ Type.GROUND ]);
const pokeSpecies = getPokemonSpecies(result);
expect(pokeSpecies.speciesId).toBe(Species.GROUDON);