Hotfix 1.11.3 to Beta

Hotfix 1.11.3 to beta
This commit is contained in:
damocleas 2025-11-08 10:31:28 -05:00 committed by GitHub
commit e438536dc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 212 additions and 267 deletions

2
assets

@ -1 +1 @@
Subproject commit d2d9309cd1acfcebeefbf4c1c63e1104a1294ed8
Subproject commit 9d391bd666f339c31db3d48a9907139950c14d1e

@ -1 +1 @@
Subproject commit ddf9509e1c6abe8fc93b455d79bfaa0202e05ede
Subproject commit b5b0d94eee7cbcf0e055f8074ca1ebedb920e59e

View File

@ -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",

View File

@ -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<AugmentMoveInteractionAbAttrParams, "opponent"> {
/** 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<AugmentMoveInteraction
* Used by {@linkcode MoveId.PARENTAL_BOND | Parental Bond}.
*/
export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
/** The damage multiplier for the second strike, relative to the first */
private readonly damageMultiplier: number;
/**
* @param damageMultiplier - The damage multiplier for the second strike, relative to the first
*/
constructor(damageMultiplier: number) {
super(false);
this.damageMultiplier = damageMultiplier;
override canApply({ pokemon, opponent, move }: AddSecondStrikeAbAttrParams): boolean {
return move.canBeMultiStrikeEnhanced(pokemon, true, opponent);
}
/**
* Return whether the move can be multi-strike enhanced.
*/
override canApply({ pokemon, move }: AddSecondStrikeAbAttrParams): boolean {
return move.canBeMultiStrikeEnhanced(pokemon, true);
}
/**
* Add one to the move's hit count, and, if the pokemon has only one hit left, sets the damage multiplier
* to the damage multiplier of this ability.
*/
override apply({ hitCount, multiplier, pokemon }: AddSecondStrikeAbAttrParams): void {
if (hitCount?.value) {
hitCount.value += 1;
}
if (multiplier?.value && pokemon.turnData.hitsLeft === 1) {
multiplier.value = this.damageMultiplier;
}
override apply({ hitCount }: AddSecondStrikeAbAttrParams): void {
hitCount.value += 1;
}
}
@ -1895,10 +1872,12 @@ export interface PreAttackModifyDamageAbAttrParams extends AugmentMoveInteractio
* @param damageMultiplier the amount to multiply the damage by
* @param condition the condition for this ability to be applied
*/
export class DamageBoostAbAttr extends PreAttackAbAttr {
export class MoveDamageBoostAbAttr extends PreAttackAbAttr {
private readonly damageMultiplier: number;
private readonly condition: PokemonAttackCondition;
// TODO: This should not take a `PokemonAttackCondition` (with nullish parameters)
// as it's effectively offloading nullishness checks to its child attributes
constructor(damageMultiplier: number, condition: PokemonAttackCondition) {
super(false);
this.damageMultiplier = damageMultiplier;
@ -6657,7 +6636,7 @@ const AbilityAttrs = Object.freeze({
MoveTypeChangeAbAttr,
PokemonTypeChangeAbAttr,
AddSecondStrikeAbAttr,
DamageBoostAbAttr,
MoveDamageBoostAbAttr,
MovePowerBoostAbAttr,
MoveTypePowerBoostAbAttr,
LowHpMoveTypePowerBoostAbAttr,
@ -7298,7 +7277,7 @@ export function initAbilities() {
.ignorable()
.build(),
new AbBuilder(AbilityId.TINTED_LENS, 4)
.attr(DamageBoostAbAttr, 2, (user, target, move) => (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) }))

View File

@ -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"),

View File

@ -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)

View File

@ -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 = {

View File

@ -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,

View File

@ -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;
}),

View File

@ -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,

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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"],

View File

@ -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);
});
});

View File

@ -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 () => {

View File

@ -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);
});
});