diff --git a/assets b/assets index d2d9309cd1a..9d391bd666f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit d2d9309cd1acfcebeefbf4c1c63e1104a1294ed8 +Subproject commit 9d391bd666f339c31db3d48a9907139950c14d1e diff --git a/locales b/locales index ddf9509e1c6..b5b0d94eee7 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit ddf9509e1c6abe8fc93b455d79bfaa0202e05ede +Subproject commit b5b0d94eee7cbcf0e055f8074ca1ebedb920e59e diff --git a/package.json b/package.json index 0730cc5197b..59d0a5f877a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.11.2", + "version": "1.11.3", "type": "module", "scripts": { "start:prod": "vite --mode production", diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index cd18bbcfb9c..21a00e53aed 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1833,13 +1833,13 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { } /** - * Parameters for abilities that modify the hit count and damage of a move + * Parameters for abilities that modify the hit count of a move. */ export interface AddSecondStrikeAbAttrParams extends Omit { - /** Holder for the number of hits. May be modified by ability application */ - hitCount?: NumberHolder; - /** Holder for the damage multiplier _of the current hit_ */ - multiplier?: NumberHolder; + /** Holder for the number of hits. Modified by ability application */ + hitCount: NumberHolder; + /** The Pokemon on the other side of this interaction */ + opponent: Pokemon | undefined; } /** @@ -1847,35 +1847,12 @@ export interface AddSecondStrikeAbAttrParams extends Omit (target?.getMoveEffectiveness(user!, move) ?? 1) <= 0.5) + .attr(MoveDamageBoostAbAttr, 2, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) <= 0.5) .build(), new AbBuilder(AbilityId.FILTER, 4) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75) @@ -7636,7 +7615,15 @@ export function initAbilities() { .attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL) .build(), new AbBuilder(AbilityId.PARENTAL_BOND, 6) - .attr(AddSecondStrikeAbAttr, 0.25) + .attr(AddSecondStrikeAbAttr) + // Only multiply damage on the last strike of multi-strike moves + .attr(MoveDamageBoostAbAttr, 0.25, (user, target, move) => ( + !!user + && user.turnData.hitCount > 1 // move was originally multi hit + && user.turnData.hitsLeft === 1 // move is on its final strike + && move.canBeMultiStrikeEnhanced(user, true, target) + ) + ) .build(), new AbBuilder(AbilityId.DARK_AURA, 6) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonDarkAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index f8829ee4344..763ac3621b9 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -319,7 +319,11 @@ export class MistTag extends SerializableArenaTag { cancelled.value = true; if (!simulated) { - globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mistApply")); + globalScene.phaseManager.queueMessage( + i18next.t("arenaTag:mistApply", { + pokemonNameWithAffix: getPokemonNameWithAffix(this.getSourcePokemon()), + }), + ); } return true; @@ -1532,6 +1536,11 @@ export class SuppressAbilitiesTag extends SerializableArenaTag { const setter = globalScene .getField(true) .filter(p => p.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0]; + // Setter may not exist if both NG Pokemon faint simultaneously + if (setter == null) { + return; + } + applyOnGainAbAttrs({ pokemon: setter, passive: setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"), diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 32800b873f1..cbdfe780382 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -100,7 +100,6 @@ import i18next from "i18next"; import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; import { inSpeedOrder } from "#utils/speed-order-generator"; import { canSpeciesTera, willTerastallize } from "#utils/pokemon-utils"; -import type { ReadonlyGenericUint8Array } from "#types/typed-arrays"; import { MovePriorityInBracket } from "#enums/move-priority-in-bracket"; /** @@ -1117,20 +1116,34 @@ export abstract class Move implements Localizable { } /** - * Returns `true` if this move can be given additional strikes - * by enhancing effects. + * Check whether this Move can be given additional strikes from enhancing effects. * Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond} - * and {@linkcode PokemonMultiHitModifier | Multi-Lens}. - * @param user The {@linkcode Pokemon} using the move - * @param restrictSpread `true` if the enhancing effect - * should not affect multi-target moves (default `false`) + * and {@linkcode PokemonMultiHitModifier | Multi Lens}. + * @param user - The {@linkcode Pokemon} using the move + * @param restrictSpread - Whether the enhancing effect should ignore multi-target moves; default `false` + * @returns Whether this Move can be given additional strikes. */ - canBeMultiStrikeEnhanced(user: Pokemon, restrictSpread: boolean = false): boolean { + // TODO: Remove target parameter used solely to circumvent Pollen Puff shenanigans - the entire move needs to be fixed anyhow + public canBeMultiStrikeEnhanced(user: Pokemon, restrictSpread: boolean = false, target?: Pokemon | null): boolean { // Multi-strike enhancers... - // ...cannot enhance moves that hit multiple targets + // ...cannot enhance charging or 2-turn moves + if (this.isChargingMove()) { + return false; + } + + // ...cannot enhance moves hitting multiple targets unless specified const { targets, multiple } = getMoveTargets(user, this.id); - const isMultiTarget = multiple && targets.length > 1; + if (restrictSpread && multiple && targets.length > 1) { + return false; + }; + + // ...cannot enhance status moves, including ally-targeting Pollen Puff + if ( + this.category === MoveCategory.STATUS + || (target != null && user.getMoveCategory(target, this) === MoveCategory.STATUS)) { + return false; + } // ...cannot enhance multi-hit or sacrificial moves const exceptAttrs: MoveAttrString[] = [ @@ -1138,6 +1151,9 @@ export abstract class Move implements Localizable { "SacrificialAttr", "SacrificialAttrOnHit" ]; + if (exceptAttrs.some(attr => this.hasAttr(attr))) { + return false; + } // ...and cannot enhance these specific moves const exceptMoves: MoveId[] = [ @@ -1147,17 +1163,11 @@ export abstract class Move implements Localizable { MoveId.ICE_BALL, MoveId.ENDEAVOR ]; + if (exceptMoves.includes(this.id)) { + return false; + } - // ...and cannot enhance Pollen Puff when targeting an ally. - const ally = user.getAlly(); - const exceptPollenPuffAlly: boolean = this.id === MoveId.POLLEN_PUFF && ally != null && targets.includes(ally.getBattlerIndex()) - - return (!restrictSpread || !isMultiTarget) - && !this.isChargingMove() - && !exceptAttrs.some(attr => this.hasAttr(attr)) - && !exceptMoves.some(id => this.id === id) - && !exceptPollenPuffAlly - && this.category !== MoveCategory.STATUS; + return true; } } @@ -9033,7 +9043,7 @@ export function initMoves() { new AttackMove(MoveId.STRUGGLE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1) .attr(RecoilAttr, true, 0.25, true) .attr(TypelessAttr) - .attr(PreMoveMessageAttr, (user: Pokemon) => i18next.t("moveTriggers:struggleMessage", { pokemonName: getPokemonNameWithAffix(user) })) + .attr(PreMoveMessageAttr, (user: Pokemon) => i18next.t("moveTriggers:struggle", { pokemonName: getPokemonNameWithAffix(user) })) .target(MoveTarget.RANDOM_NEAR_ENEMY), new StatusMove(MoveId.SKETCH, PokemonType.NORMAL, -1, 1, -1, 0, 2) .ignoresSubstitute() @@ -10700,7 +10710,7 @@ export function initMoves() { .attr(HealOnAllyAttr, 0.5, true, false) .ballBombMove() // Fail if used against an ally that is affected by heal block, during the second failure check - .condition((user, target) => target.isOpponent(user) || !!target.getTag(BattlerTagType.HEAL_BLOCK), 2), + .condition((user, target) => target == null || target.isOpponent(user) || !target.getTag(BattlerTagType.HEAL_BLOCK), 2), new AttackMove(MoveId.ANCHOR_SHOT, PokemonType.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true), new StatusMove(MoveId.PSYCHIC_TERRAIN, PokemonType.PSYCHIC, -1, 10, -1, 0, 7) diff --git a/src/data/trainers/evil-admin-trainer-pools.ts b/src/data/trainers/evil-admin-trainer-pools.ts index ae7ff106f8a..27b9a70dcce 100644 --- a/src/data/trainers/evil-admin-trainer-pools.ts +++ b/src/data/trainers/evil-admin-trainer-pools.ts @@ -223,57 +223,47 @@ const PLASMA_COLRESS: TrainerTierPools = { const FLARE: TrainerTierPools = { [TrainerPoolTier.COMMON]: [ - SpeciesId.FOONGUS, SpeciesId.SCRAGGY, - SpeciesId.DRUDDIGON, + SpeciesId.FOONGUS, SpeciesId.BUNNELBY, SpeciesId.FLETCHLING, SpeciesId.PANCHAM, SpeciesId.ESPURR, - SpeciesId.PUMPKABOO, - SpeciesId.PHANTUMP, + SpeciesId.SKRELP, SpeciesId.CLAUNCHER, - SpeciesId.HELIOPTILE, SpeciesId.KLEFKI, + SpeciesId.PHANTUMP, + SpeciesId.PUMPKABOO, ], [TrainerPoolTier.UNCOMMON]: [ SpeciesId.LITWICK, SpeciesId.HEATMOR, SpeciesId.BINACLE, - SpeciesId.SKRELP, - SpeciesId.BERGMITE, + SpeciesId.HELIOPTILE, + SpeciesId.AVALUGG, SpeciesId.CAPSAKID, ], - [TrainerPoolTier.RARE]: [SpeciesId.GOODRA, SpeciesId.HONEDGE], + [TrainerPoolTier.RARE]: [SpeciesId.AERODACTYL, SpeciesId.HONEDGE, SpeciesId.GOOMY], }; const FLARE_XEROSIC: TrainerTierPools = { [TrainerPoolTier.COMMON]: [ + SpeciesId.EKANS, + SpeciesId.LITWICK, SpeciesId.PANCHAM, SpeciesId.BINACLE, + [SpeciesId.SKRELP, SpeciesId.CLAUNCHER], SpeciesId.HELIOPTILE, - SpeciesId.CLAUNCHER, - SpeciesId.BUNNELBY, - SpeciesId.FLETCHLING, - SpeciesId.LITLEO, - SpeciesId.PANGORO, - SpeciesId.ESPURR, - SpeciesId.INKAY, - SpeciesId.CLAUNCHER, - SpeciesId.HELIOPTILE, + SpeciesId.KLEFKI, ], [TrainerPoolTier.UNCOMMON]: [ - [SpeciesId.AMAURA, SpeciesId.TYRUNT], - SpeciesId.SNEASEL, - SpeciesId.LITWICK, - SpeciesId.LITLEO, - SpeciesId.BINACLE, - SpeciesId.SKRELP, + SpeciesId.ROTOM, // Always Rotom-Heat, Xerosic has their specialty type set to fire + [SpeciesId.TYRUNT, SpeciesId.AMAURA], SpeciesId.NOIBAT, - SpeciesId.PHANTUMP, - SpeciesId.PUMPKABOO, + SpeciesId.SIZZLIPEDE, + SpeciesId.CAPSAKID, ], - [TrainerPoolTier.RARE]: [SpeciesId.HISUI_GOODRA, SpeciesId.HONEDGE], + [TrainerPoolTier.RARE]: [SpeciesId.BELDUM, SpeciesId.HISUI_SLIGGOO, SpeciesId.HISUI_AVALUGG], }; const AETHER: TrainerTierPools = { diff --git a/src/data/trainers/rival-party-config.ts b/src/data/trainers/rival-party-config.ts index 67b3f50d379..0a014764bbc 100644 --- a/src/data/trainers/rival-party-config.ts +++ b/src/data/trainers/rival-party-config.ts @@ -420,7 +420,7 @@ const SLOT_4_FIGHT_3 = [ SpeciesId.GOLISOPOD, SpeciesId.MIMIKYU, SpeciesId.DHELMISE, - SpeciesId.POLTEAGEIST, + [SpeciesId.POLTEAGEIST, SpeciesId.SINISTCHA], SpeciesId.COPPERAJAH, SpeciesId.KLEAVOR, SpeciesId.BASCULIN, @@ -431,7 +431,6 @@ const SLOT_4_FIGHT_3 = [ SpeciesId.DONDOZO, SpeciesId.DUDUNSPARCE, SpeciesId.GHOLDENGO, - SpeciesId.POLTCHAGEIST, [SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWKING], SpeciesId.HISUI_ARCANINE, SpeciesId.PALDEA_TAUROS, @@ -485,7 +484,7 @@ const SLOT_4_FINAL = [ SpeciesId.GOLISOPOD, SpeciesId.MIMIKYU, SpeciesId.DHELMISE, - SpeciesId.POLTEAGEIST, + [SpeciesId.POLTEAGEIST, SpeciesId.SINISTCHA], SpeciesId.COPPERAJAH, SpeciesId.KLEAVOR, SpeciesId.BASCULEGION, // Ensure gender does not change @@ -496,7 +495,6 @@ const SLOT_4_FINAL = [ SpeciesId.DONDOZO, SpeciesId.DUDUNSPARCE, SpeciesId.GHOLDENGO, - SpeciesId.POLTCHAGEIST, [SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWKING], SpeciesId.HISUI_ARCANINE, SpeciesId.PALDEA_TAUROS, diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 8d95bad2767..44ff3b8092b 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -592,15 +592,9 @@ export class TrainerConfig { * @param poolName - The evil team the admin belongs to. * @param signatureSpecies - The signature species for the evil team leader. * @param specialtyType - The specialty Type of the admin, if they have one - * @param starAdminInstantTeraSlot - (default `4`); If the admin is a Star Admin, the slot that should instantly Tera in {@linkcode ClassicFixedBossWaves.EVIL_ADMIN_3} * @returns The updated TrainerConfig instance. */ - initForEvilTeamAdmin( - title: string, - poolName: EvilTeam, - specialtyType?: PokemonType, - starAdminInstantTeraSlot = 4, - ): TrainerConfig { + initForEvilTeamAdmin(title: string, poolName: EvilTeam, specialtyType?: PokemonType): TrainerConfig { if (!getIsInitialized()) { initI18n(); } @@ -609,13 +603,6 @@ export class TrainerConfig { this.setSpecialtyType(specialtyType); } - if (title === "star_admin") { - this.setInstantTera( - starAdminInstantTeraSlot, - () => globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3, - ); - } - this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); // Set the species pools for the evil team admin. @@ -2070,7 +2057,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.DRATINI, SpeciesId.LARVITAR], }), [TrainerType.ARCHER]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("rocket_admin", "rocket_archer") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2091,7 +2077,6 @@ export const trainerConfigs: TrainerConfigs = { }), ), [TrainerType.ARIANA]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("rocket_admin_female", "rocket_ariana") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2103,16 +2088,12 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.ARBOK], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 0; // Intimidate p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), ), [TrainerType.PROTON]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("rocket_admin", "rocket_proton") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2124,15 +2105,11 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.CROBAT], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), ), [TrainerType.PETREL]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("rocket_admin", "rocket_petrel") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2188,7 +2165,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.RHYHORN, SpeciesId.ARON], }), [TrainerType.TABITHA]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("magma_admin", "magma") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2200,16 +2176,12 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.CAMERUPT], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 1; // Solid Rock p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), ), [TrainerType.COURTNEY]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("magma_admin_female", "magma") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2221,9 +2193,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.CAMERUPT], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 1; // Solid Rock p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; @@ -2273,7 +2242,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.FEEBAS, SpeciesId.DONDOZO], }), [TrainerType.MATT]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("aqua_admin", "aqua") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2285,15 +2253,11 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.SHARPEDO], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), ), [TrainerType.SHELLY]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("aqua_admin_female", "aqua") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2305,9 +2269,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.SHARPEDO], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), @@ -2354,7 +2315,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.SPIRITOMB, SpeciesId.ROTOM], }), [TrainerType.JUPITER]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("galactic_commander_female", "galactic_jupiter") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2366,15 +2326,11 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.SKUNTANK], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), ), [TrainerType.MARS]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("galactic_commander_female", "galactic_mars") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2386,16 +2342,12 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.PURUGLY], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 0; // Thick Fat p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), ), [TrainerType.SATURN]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("galactic_commander", "galactic_saturn") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2407,9 +2359,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.TOXICROAK], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 1; // Dry Skin p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; @@ -2460,7 +2409,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.AXEW, SpeciesId.DRUDDIGON, SpeciesId.DEINO, SpeciesId.HISUI_ZORUA], }), [TrainerType.ZINZOLIN]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("plasma_sage", "plasma_zinzolin") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2477,7 +2425,6 @@ export const trainerConfigs: TrainerConfigs = { }), ), [TrainerType.COLRESS]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("plasma_boss", "plasma_colress") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_colress") @@ -2489,9 +2436,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.KLINKLANG], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 2; // Clear Body p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; @@ -2537,7 +2481,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.GOOMY, SpeciesId.HONEDGE], }), [TrainerType.BRYONY]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("flare_admin_female", "flare") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2553,7 +2496,6 @@ export const trainerConfigs: TrainerConfigs = { }), ), [TrainerType.XEROSIC]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("flare_admin", "flare_xerosic", PokemonType.FIRE) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2564,9 +2506,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.MALAMAR], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 0; // Contrary p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; @@ -2624,7 +2563,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.PORYGON, SpeciesId.JANGMO_O], }), [TrainerType.FABA]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("aether_admin", "aether") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2636,9 +2574,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.HYPNO], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 1; // FOREWARN p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; @@ -2691,7 +2626,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.PAWNIARD, SpeciesId.GRUBBIN], }), [TrainerType.PLUMERIA]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("skull_admin", "skull") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2703,9 +2637,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.SALAZZLE], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; }), @@ -2756,7 +2687,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.DURALUDON, SpeciesId.DREEPY], }), [TrainerType.OLEANA]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("macro_admin", "macro_cosmos") .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2768,9 +2698,6 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc( 5, getRandomPartyMemberFunc([SpeciesId.GARBODOR], TrainerSlot.TRAINER, true, p => { - if (globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_ADMIN_3) { - p.setBoss(true, 2); - } p.abilityIndex = 1; // Weak Armor p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; @@ -2836,7 +2763,6 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.SUPER_RARE]: [SpeciesId.DONDOZO, SpeciesId.GIMMIGHOUL], }), [TrainerType.GIACOMO]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("star_admin", "star_dark", PokemonType.DARK) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2853,6 +2779,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); } else { p.formIndex = 1; // Segin Starmobile + p.gender = Gender.GENDERLESS; p.moveset = [ new PokemonMove(MoveId.WICKED_TORQUE), new PokemonMove(MoveId.SPIN_OUT), @@ -2863,7 +2790,6 @@ export const trainerConfigs: TrainerConfigs = { }), ), [TrainerType.MELA]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("star_admin", "star_fire", PokemonType.FIRE) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2880,6 +2806,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); } else { p.formIndex = 2; // Schedar Starmobile + p.gender = Gender.GENDERLESS; p.moveset = [ new PokemonMove(MoveId.BLAZING_TORQUE), new PokemonMove(MoveId.SPIN_OUT), @@ -2890,7 +2817,6 @@ export const trainerConfigs: TrainerConfigs = { }), ), [TrainerType.ATTICUS]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("star_admin", "star_poison", PokemonType.POISON) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2907,6 +2833,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); } else { p.formIndex = 3; // Navi Starmobile + p.gender = Gender.GENDERLESS; p.moveset = [ new PokemonMove(MoveId.NOXIOUS_TORQUE), new PokemonMove(MoveId.SPIN_OUT), @@ -2917,7 +2844,6 @@ export const trainerConfigs: TrainerConfigs = { }), ), [TrainerType.ORTEGA]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("star_admin", "star_fairy", PokemonType.FAIRY) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2934,6 +2860,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); } else { p.formIndex = 4; // Ruchbah Starmobile + p.gender = Gender.GENDERLESS; p.moveset = [ new PokemonMove(MoveId.MAGICAL_TORQUE), new PokemonMove(MoveId.SPIN_OUT), @@ -2944,7 +2871,6 @@ export const trainerConfigs: TrainerConfigs = { }), ), [TrainerType.ERI]: new TrainerConfig(++t) - .setMoneyMultiplier(1.5) .initForEvilTeamAdmin("star_admin", "star_fighting", PokemonType.FIGHTING) .setEncounterBgm(TrainerType.PLASMA_GRUNT) .setBattleBgm("battle_plasma_grunt") @@ -2961,6 +2887,7 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); } else { p.formIndex = 5; // Caph Starmobile + p.gender = Gender.GENDERLESS; p.moveset = [ new PokemonMove(MoveId.COMBAT_TORQUE), new PokemonMove(MoveId.SPIN_OUT), @@ -4385,6 +4312,8 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.TOGEKISS], TrainerSlot.TRAINER, true, p => { p.abilityIndex = 1; // Serene Grace p.generateAndPopulateMoveset(); + p.moveset[0] = new PokemonMove(MoveId.DAZZLING_GLEAM); + p.moveset[1] = new PokemonMove(MoveId.AIR_SLASH); p.teraType = p.species.type1; }), ) @@ -4401,9 +4330,13 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.GARCHOMP], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.formIndex = 1; // Mega Garchomp - p.generateAndPopulateMoveset(); p.generateName(); p.gender = Gender.FEMALE; + p.generateAndPopulateMoveset(); + if (!p.moveset.some(move => move != null && move.moveId === MoveId.SANDSTORM)) { + // Check if Sandstorm is in the moveset, if not, replace the fourth move with Sandstorm. + p.moveset[3] = new PokemonMove(MoveId.SANDSTORM); + } }), ) .setInstantTera(2), // Tera Fairy Togekiss @@ -4559,16 +4492,10 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 2, - getRandomPartyMemberFunc( - [SpeciesId.TORNADUS, SpeciesId.THUNDURUS, SpeciesId.LANDORUS], - TrainerSlot.TRAINER, - true, - p => { - p.formIndex = 1; // Therian Formes - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ROGUE_BALL; - }, - ), + getRandomPartyMemberFunc([SpeciesId.SNORLAX], TrainerSlot.TRAINER, true, p => { + p.formIndex = 1; // G-Max + p.generateAndPopulateMoveset(); + }), ) .setPartyMemberFunc( 3, @@ -4580,9 +4507,9 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.SNORLAX], TrainerSlot.TRAINER, true, p => { - p.formIndex = 1; // G-Max Snorlax + getRandomPartyMemberFunc([SpeciesId.LUNALA], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.MASTER_BALL; }), ) .setPartyMemberFunc( @@ -5522,9 +5449,12 @@ export const trainerConfigs: TrainerConfigs = { 5, getRandomPartyMemberFunc([SpeciesId.NECROZMA], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); - p.formIndex = 2; // Dawn Wings - p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; + p.generateAndPopulateMoveset(); + if (!p.moveset.some(move => move != null && move.moveId === MoveId.PHOTON_GEYSER)) { + // Check if Photon Geyser is in the moveset, if not, replace the first move with Photon Geyser. + p.moveset[0] = new PokemonMove(MoveId.PHOTON_GEYSER); + } }), ), [TrainerType.GUZMA]: new TrainerConfig(++t) @@ -5543,18 +5473,15 @@ export const trainerConfigs: TrainerConfigs = { p.generateAndPopulateMoveset(); }), ) - .setPartyMemberFunc(1, getRandomPartyMemberFunc([SpeciesId.HERACROSS])) .setPartyMemberFunc( - 2, - getRandomPartyMemberFunc([SpeciesId.SCIZOR, SpeciesId.KLEAVOR], TrainerSlot.TRAINER, true, p => { - if (p.species.speciesId === SpeciesId.SCIZOR) { - p.abilityIndex = 1; // Technician - } else if (p.species.speciesId === SpeciesId.KLEAVOR) { - p.abilityIndex = 2; // Sharpness - } + 1, + getRandomPartyMemberFunc([SpeciesId.SCIZOR], TrainerSlot.TRAINER, true, p => { + p.abilityIndex = 1; // Technician p.generateAndPopulateMoveset(); + p.gender = Gender.MALE; }), ) + .setPartyMemberFunc(2, getRandomPartyMemberFunc([SpeciesId.HERACROSS])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([SpeciesId.GALVANTULA, SpeciesId.VIKAVOLT])) .setPartyMemberFunc( 4, @@ -5562,6 +5489,7 @@ export const trainerConfigs: TrainerConfigs = { p.formIndex = 1; // Mega Pinsir p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ULTRA_BALL; + p.gender = Gender.MALE; p.generateName(); }), ) @@ -5569,11 +5497,11 @@ export const trainerConfigs: TrainerConfigs = { 5, getRandomPartyMemberFunc([SpeciesId.GOLISOPOD], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); + p.gender = Gender.MALE; p.generateAndPopulateMoveset(); if (!p.moveset.some(move => move != null && move.moveId === MoveId.FIRST_IMPRESSION)) { // Check if First Impression is in the moveset, if not, replace the third move with First Impression. p.moveset[2] = new PokemonMove(MoveId.FIRST_IMPRESSION); - p.gender = Gender.MALE; } }), ), @@ -5586,27 +5514,36 @@ export const trainerConfigs: TrainerConfigs = { 0, getRandomPartyMemberFunc([SpeciesId.GOLISOPOD], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); + p.abilityIndex = 2; // Anticipation + p.gender = Gender.MALE; p.generateAndPopulateMoveset(); if (!p.moveset.some(move => move != null && move.moveId === MoveId.FIRST_IMPRESSION)) { // Check if First Impression is in the moveset, if not, replace the third move with First Impression. p.moveset[2] = new PokemonMove(MoveId.FIRST_IMPRESSION); - p.abilityIndex = 2; // Anticipation - p.gender = Gender.MALE; } }), ) .setPartyMemberFunc( 1, - getRandomPartyMemberFunc([SpeciesId.BUZZWOLE], TrainerSlot.TRAINER, true, p => { + getRandomPartyMemberFunc([SpeciesId.SCIZOR], TrainerSlot.TRAINER, true, p => { p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ROGUE_BALL; + p.abilityIndex = 1; // Technician + p.moveset[0] = new PokemonMove(MoveId.BUG_BITE); + p.moveset[1] = new PokemonMove(MoveId.BULLET_PUNCH); + p.gender = Gender.MALE; + p.pokeball = PokeballType.ULTRA_BALL; }), ) .setPartyMemberFunc( 2, getRandomPartyMemberFunc([SpeciesId.CRAWDAUNT, SpeciesId.HISUI_SAMUROTT], TrainerSlot.TRAINER, true, p => { - p.abilityIndex = 2; // Sharpness Hisuian Samurott, Adaptability Crawdaunt + p.abilityIndex = 2; // Adaptability Crawdaunt, Sharpness Samurott + p.pokeball = PokeballType.ULTRA_BALL; p.generateAndPopulateMoveset(); + if (!p.moveset.some(move => move != null && move.moveId === MoveId.AQUA_JET)) { + // Check if Aqua Jet is in the moveset, if not, replace the third move with Aqua Jet. + p.moveset[2] = new PokemonMove(MoveId.AQUA_JET); + } }), ) .setPartyMemberFunc( @@ -5618,26 +5555,22 @@ export const trainerConfigs: TrainerConfigs = { ) .setPartyMemberFunc( 4, - getRandomPartyMemberFunc([SpeciesId.GENESECT], TrainerSlot.TRAINER, true, p => { - p.setBoss(true, 2); - p.generateAndPopulateMoveset(); - p.pokeball = PokeballType.ROGUE_BALL; - p.formIndex = randSeedInt(4, 1); // Shock, Burn, Chill, or Douse Drive - if (!p.moveset.some(move => move != null && move.moveId === MoveId.TECHNO_BLAST)) { - // Check if Techno Blast is in the moveset, if not, replace the third move with Techno Blast. - p.moveset[2] = new PokemonMove(MoveId.TECHNO_BLAST); - } - }), - ) - .setPartyMemberFunc( - 5, getRandomPartyMemberFunc([SpeciesId.PINSIR], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.formIndex = 1; // Mega Pinsir p.generateAndPopulateMoveset(); p.generateName(); + p.gender = Gender.MALE; p.pokeball = PokeballType.ULTRA_BALL; }), + ) + .setPartyMemberFunc( + 5, + getRandomPartyMemberFunc([SpeciesId.BUZZWOLE], TrainerSlot.TRAINER, true, p => { + p.setBoss(true, 2); + p.generateAndPopulateMoveset(); + p.pokeball = PokeballType.ROGUE_BALL; + }), ), [TrainerType.ROSE]: new TrainerConfig(++t) .setName("Rose") @@ -5825,6 +5758,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([SpeciesId.REVAVROOM], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 2); p.formIndex = randSeedInt(5, 1); // Random Starmobile form + p.gender = Gender.GENDERLESS; p.generateAndPopulateMoveset(); p.pokeball = PokeballType.ROGUE_BALL; }), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 2801fef0ffa..101db9307f3 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3675,15 +3675,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { multiStrikeEnhancementMultiplier, ); - if (!ignoreSourceAbility) { - applyAbAttrs("AddSecondStrikeAbAttr", { - pokemon: source, - move, - simulated, - multiplier: multiStrikeEnhancementMultiplier, - }); - } - /** Doubles damage if this Pokemon's last move was Glaive Rush */ const glaiveRushMultiplier = new NumberHolder(1); if (this.getTag(BattlerTagType.RECEIVE_DOUBLE_DAMAGE)) { @@ -3772,9 +3763,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * mistyTerrainMultiplier, ); - /** Doubles damage if the attacker has Tinted Lens and is using a resisted move */ if (!ignoreSourceAbility) { - applyAbAttrs("DamageBoostAbAttr", { + applyAbAttrs("MoveDamageBoostAbAttr", { pokemon: source, opponent: this, move, diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 476f0de4a36..3209298a265 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -276,7 +276,7 @@ export class MoveEffectPhase extends PokemonPhase { // Assume single target for multi hit applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount); // If Parental Bond is applicable, add another hit - applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount }); + applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount, opponent: this.getFirstTarget() }); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); // Set the user's relevant turnData fields to reflect the final hit count diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 5115e3da595..48fc5f6f960 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -205,7 +205,7 @@ export class MovePhase extends PokemonPhase { user.cureStatus( StatusEffect.FREEZE, i18next.t("statusEffect:freeze.healByMove", { - pokemonName: getPokemonNameWithAffix(user), + pokemonNameWithAffix: getPokemonNameWithAffix(user), moveName: this.move.getMove().name, }), ); @@ -509,6 +509,9 @@ export class MovePhase extends PokemonPhase { ) { this.showFailedText(); this.fail(); + // clear out 2 turn moves + // TODO: Make a helper for this atp + this.pokemon.getMoveQueue().shift(); this.pokemon.pushMoveHistory(this.moveHistoryEntry); return true; } diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index a18be85374f..ceac7ed1958 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -268,7 +268,13 @@ export class TitlePhase extends Phase { globalScene.addModifier(m, true, false, false, true); } for (const m of timedEventManager.getEventDailyStartingItems()) { - globalScene.addModifier(modifierTypes[m]().newModifier(), true, false, false, true); + globalScene.addModifier( + modifierTypes[m]().withIdFromFunc(modifierTypes[m]).newModifier(), + true, + false, + false, + true, + ); } globalScene.updateModifiers(true, true); diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 32a49f0a704..efc05cd78d3 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -396,7 +396,7 @@ const timedEvents: readonly TimedEvent[] = [ name: "Halloween 25", eventType: EventType.SHINY, startDate: new Date(Date.UTC(2025, 9, 30)), - endDate: new Date(Date.UTC(2025, 10, 10)), + endDate: new Date(Date.UTC(2025, 10, 12)), bannerKey: "halloween2025", scale: 0.19, availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-419", "pt-BR", "zh-Hans", "zh-Hant", "da", "ru"], diff --git a/test/abilities/parental-bond.test.ts b/test/abilities/parental-bond.test.ts index a72fc82260f..95f0e8d4159 100644 --- a/test/abilities/parental-bond.test.ts +++ b/test/abilities/parental-bond.test.ts @@ -384,4 +384,24 @@ describe("Abilities - Parental Bond", () => { // TODO: Update hit count to 1 once Future Sight is fixed to not activate abilities if user is off the field expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2); }); + + it("should not reduce damage against the remaining target if the first one faints", async () => { + game.override.battleStyle("double").enemySpecies(SpeciesId.MAGIKARP); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const [karp1, karp2] = game.scene.getEnemyField(); + + // Mock base damage for both mons for consistent results + vi.spyOn(karp1, "getBaseDamage").mockReturnValue(100); + vi.spyOn(karp2, "getBaseDamage").mockReturnValue(100); + karp1.hp = 1; + + game.move.use(MoveId.HYPER_VOICE); + await game.toEndOfTurn(); + + expect(karp1).toHaveFainted(); + expect(feebas).not.toHaveAbilityApplied(AbilityId.PARENTAL_BOND); + expect(karp2).toHaveTakenDamage(100); + }); }); diff --git a/test/items/multi-lens.test.ts b/test/items/multi-lens.test.ts index 3686aff0fcf..bdf93a4ae12 100644 --- a/test/items/multi-lens.test.ts +++ b/test/items/multi-lens.test.ts @@ -26,6 +26,7 @@ describe("Items - Multi Lens", () => { game.override .moveset([MoveId.TACKLE, MoveId.TRAILBLAZE, MoveId.TACHYON_CUTTER, MoveId.FUTURE_SIGHT]) .ability(AbilityId.BALL_FETCH) + .passiveAbility(AbilityId.NO_GUARD) .startingHeldItems([{ name: "MULTI_LENS" }]) .battleStyle("single") .criticalHits(false) @@ -135,61 +136,36 @@ describe("Items - Multi Lens", () => { expect(damageResults[1]).toBe(Math.floor(playerPokemon.level * 0.25)); }); - it("should result in correct damage for hp% attacks with 1 lens", async () => { + it.each([1, 2])("should result in original damage for HP-cutting attacks with %d lenses", async lensCount => { game.override - .startingHeldItems([{ name: "MULTI_LENS", count: 1 }]) - .moveset(MoveId.SUPER_FANG) - .ability(AbilityId.COMPOUND_EYES) + .startingHeldItems([{ name: "MULTI_LENS", count: lensCount }]) .enemyLevel(1000) .enemySpecies(SpeciesId.BLISSEY); // allows for unrealistically high levels of accuracy + await game.classicMode.startBattle([SpeciesId.FEEBAS]); - await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + const blissey = game.field.getEnemyPokemon(); - const enemyPokemon = game.field.getEnemyPokemon(); + game.move.use(MoveId.SUPER_FANG); + await game.toEndOfTurn(); - game.move.select(MoveId.SUPER_FANG); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase"); - expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); + expect(blissey.getHpRatio()).toBeCloseTo(0.5, 5); }); - it("should result in correct damage for hp% attacks with 2 lenses", async () => { + it("should result in original damage for HP-cutting attacks with 2 lenses + Parental Bond", async () => { game.override .startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) - .moveset(MoveId.SUPER_FANG) - .ability(AbilityId.COMPOUND_EYES) - .enemyMoveset(MoveId.SPLASH) - .enemyLevel(1000) - .enemySpecies(SpeciesId.BLISSEY); // allows for unrealistically high levels of accuracy - - await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - - const enemyPokemon = game.field.getEnemyPokemon(); - - game.move.select(MoveId.SUPER_FANG); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase"); - expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); - }); - - it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => { - game.override - .startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) - .moveset(MoveId.SUPER_FANG) .ability(AbilityId.PARENTAL_BOND) - .passiveAbility(AbilityId.COMPOUND_EYES) - .enemyMoveset(MoveId.SPLASH) .enemyLevel(1000) .enemySpecies(SpeciesId.BLISSEY); // allows for unrealistically high levels of accuracy - await game.classicMode.startBattle([SpeciesId.MAGIKARP]); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); - const enemyPokemon = game.field.getEnemyPokemon(); + const blissey = game.field.getEnemyPokemon(); - game.move.select(MoveId.SUPER_FANG); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase"); - expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 5); + game.move.use(MoveId.SUPER_FANG); + await game.toEndOfTurn(); + + expect(blissey.getHpRatio()).toBeCloseTo(0.25, 5); }); it("should not allow Future Sight to hit infinitely many times if the user switches out", async () => { diff --git a/test/moves/dig.test.ts b/test/moves/dig.test.ts index 28cbf2882a6..4e87fb3204e 100644 --- a/test/moves/dig.test.ts +++ b/test/moves/dig.test.ts @@ -131,4 +131,26 @@ describe("Moves - Dig", () => { expect(postDigEarthquakeDmg).toBeGreaterThanOrEqual(2 * preDigEarthquakeDmg); expect(postDigEarthquakeDmg).toBeLessThan(2 * (preDigEarthquakeDmg + 1)); }); + + it("should not softlock when used against a dying enemy 2 in Doubles", async () => { + game.override.battleStyle("double"); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const enemy2 = game.scene.getEnemyField()[1]; + + // use dig and make the targeted enemy faint post charge + game.move.use(MoveId.DIG, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2); + await game.toEndOfTurn(); + await game.killPokemon(enemy2); + await game.phaseInterceptor.to("CommandPhase"); + + expect(feebas.getMoveQueue()[0]?.targets).toEqual([BattlerIndex.ENEMY_2]); + expect(enemy2).toHaveFainted(); + + await game.toEndOfTurn(); + + // TODO: Does this redirect to the other enemy? + expect(feebas.getMoveQueue()).toHaveLength(0); + }); });