Merge and attempt to fix conflicts

This commit is contained in:
Christopher Schmidt 2024-10-15 18:15:25 -04:00
commit c205f07255
29 changed files with 452 additions and 141 deletions

Binary file not shown.

View File

@ -1389,7 +1389,7 @@ export default class BattleScene extends SceneBase {
case Species.GRENINJA: case Species.GRENINJA:
return Utils.randSeedInt(2); return Utils.randSeedInt(2);
case Species.ZYGARDE: case Species.ZYGARDE:
return Utils.randSeedInt(3); return Utils.randSeedInt(4);
case Species.MINIOR: case Species.MINIOR:
return Utils.randSeedInt(6); return Utils.randSeedInt(6);
case Species.ALCREMIE: case Species.ALCREMIE:

View File

@ -5541,16 +5541,18 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.bypassFaint(), .bypassFaint(),
new Ability(Abilities.POWER_CONSTRUCT, 7) // TODO: 10% Power Construct Zygarde isn't accounted for yet. If changed, update Zygarde's getSpeciesFormIndex entry accordingly new Ability(Abilities.POWER_CONSTRUCT, 7)
.attr(PostBattleInitFormChangeAbAttr, () => 2) .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2)
.attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) .conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3)
.attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.bypassFaint() .bypassFaint(),
.partial(),
new Ability(Abilities.CORROSION, 7) new Ability(Abilities.CORROSION, 7)
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ]) .attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ])
.edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet) .edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet)

View File

@ -1,7 +1,7 @@
import { Arena } from "#app/field/arena"; import { Arena } from "#app/field/arena";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import * as Utils from "#app/utils"; import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils";
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
@ -36,7 +36,7 @@ export abstract class ArenaTag {
public side: ArenaTagSide = ArenaTagSide.BOTH public side: ArenaTagSide = ArenaTagSide.BOTH
) {} ) {}
apply(arena: Arena, args: any[]): boolean { apply(arena: Arena, simulated: boolean, ...args: unknown[]): boolean {
return true; return true;
} }
@ -122,10 +122,20 @@ export class MistTag extends ArenaTag {
} }
} }
apply(arena: Arena, args: any[]): boolean { /**
(args[0] as Utils.BooleanHolder).value = true; * Cancels the lowering of stats
* @param arena the {@linkcode Arena} containing this effect
* @param simulated `true` if the effect should be applied quietly
* @param cancelled a {@linkcode BooleanHolder} whose value is set to `true`
* to flag the stat reduction as cancelled
* @returns `true` if a stat reduction was cancelled; `false` otherwise
*/
override apply(arena: Arena, simulated: boolean, cancelled: BooleanHolder): boolean {
cancelled.value = true;
if (!simulated) {
arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); arena.scene.queueMessage(i18next.t("arenaTag:mistApply"));
}
return true; return true;
} }
@ -157,17 +167,15 @@ export class WeakenMoveScreenTag extends ArenaTag {
/** /**
* Applies the weakening effect to the move. * Applies the weakening effect to the move.
* *
* @param arena - The arena where the move is applied. * @param arena the {@linkcode Arena} where the move is applied.
* @param args - The arguments for the move application. * @param simulated n/a
* @param args[0] - The category of the move. * @param moveCategory the attacking move's {@linkcode MoveCategory}.
* @param args[1] - A boolean indicating whether it is a double battle. * @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier
* @param args[2] - An object of type `Utils.NumberHolder` that holds the damage multiplier * @returns `true` if the attacking move was weakened; `false` otherwise.
*
* @returns True if the move was weakened, otherwise false.
*/ */
apply(arena: Arena, args: any[]): boolean { override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean {
if (this.weakenedCategories.includes((args[0] as MoveCategory))) { if (this.weakenedCategories.includes(moveCategory)) {
(args[2] as Utils.NumberHolder).value = (args[1] as boolean) ? 2732 / 4096 : 0.5; damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5;
return true; return true;
} }
return false; return false;
@ -249,39 +257,35 @@ export class ConditionalProtectTag extends ArenaTag {
onRemove(arena: Arena): void { } onRemove(arena: Arena): void { }
/** /**
* apply(): Checks incoming moves against the condition function * Checks incoming moves against the condition function
* and protects the target if conditions are met * and protects the target if conditions are met
* @param arena The arena containing this tag * @param arena the {@linkcode Arena} containing this tag
* @param args\[0\] (Utils.BooleanHolder) Signals if the move is cancelled * @param simulated `true` if the tag is applied quietly; `false` otherwise.
* @param args\[1\] (Pokemon) The Pokemon using the move * @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against
* @param args\[2\] (Pokemon) The intended target of the move * @param attacker the attacking {@linkcode Pokemon}
* @param args\[3\] (Moves) The parameters to the condition function * @param defender the defending {@linkcode Pokemon}
* @param args\[4\] (Utils.BooleanHolder) Signals if the applied protection supercedes protection-ignoring effects * @param moveId the {@linkcode Moves | identifier} for the move being used
* @returns * @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection
* @returns `true` if this tag protected against the attack; `false` otherwise
*/ */
apply(arena: Arena, args: any[]): boolean { override apply(arena: Arena, simulated: boolean, isProtected: BooleanHolder, attacker: Pokemon, defender: Pokemon,
const [ cancelled, user, target, moveId, ignoresBypass ] = args; moveId: Moves, ignoresProtectBypass: BooleanHolder): boolean {
if (cancelled instanceof Utils.BooleanHolder if ((this.side === ArenaTagSide.PLAYER) === defender.isPlayer()
&& user instanceof Pokemon
&& target instanceof Pokemon
&& typeof moveId === "number"
&& ignoresBypass instanceof Utils.BooleanHolder) {
if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer()
&& this.protectConditionFunc(arena, moveId)) { && this.protectConditionFunc(arena, moveId)) {
if (!cancelled.value) { if (!isProtected.value) {
cancelled.value = true; isProtected.value = true;
user.stopMultiHit(target); if (!simulated) {
attacker.stopMultiHit(defender);
new CommonBattleAnim(CommonAnim.PROTECT, target).play(arena.scene); new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene);
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(target) })); arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) }));
}
} }
ignoresBypass.value = ignoresBypass.value || this.ignoresBypass; ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass;
return true; return true;
} }
}
return false; return false;
} }
} }
@ -296,7 +300,7 @@ export class ConditionalProtectTag extends ArenaTag {
*/ */
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
const move = allMoves[moveId]; const move = allMoves[moveId];
const priority = new Utils.NumberHolder(move.priority); const priority = new NumberHolder(move.priority);
const effectPhase = arena.scene.getCurrentPhase(); const effectPhase = arena.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) { if (effectPhase instanceof MoveEffectPhase) {
@ -460,7 +464,7 @@ class WishTag extends ArenaTag {
if (user) { if (user) {
this.battlerIndex = user.getBattlerIndex(); this.battlerIndex = user.getBattlerIndex();
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) }); this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
this.healHp = Utils.toDmgValue(user.getMaxHp() / 2); this.healHp = toDmgValue(user.getMaxHp() / 2);
} else { } else {
console.warn("Failed to get source for WishTag onAdd"); console.warn("Failed to get source for WishTag onAdd");
} }
@ -497,12 +501,19 @@ export class WeakenMoveTypeTag extends ArenaTag {
this.weakenedType = type; this.weakenedType = type;
} }
apply(arena: Arena, args: any[]): boolean { /**
if ((args[0] as Type) === this.weakenedType) { * Reduces an attack's power by 0.33x if it matches this tag's weakened type.
(args[1] as Utils.NumberHolder).value *= 0.33; * @param arena n/a
* @param simulated n/a
* @param type the attack's {@linkcode Type}
* @param power a {@linkcode NumberHolder} containing the attack's power
* @returns `true` if the attack's power was reduced; `false` otherwise.
*/
override apply(arena: Arena, simulated: boolean, type: Type, power: NumberHolder): boolean {
if (type === this.weakenedType) {
power.value *= 0.33;
return true; return true;
} }
return false; return false;
} }
} }
@ -563,13 +574,12 @@ export class IonDelugeTag extends ArenaTag {
/** /**
* Converts Normal-type moves to Electric type * Converts Normal-type moves to Electric type
* @param arena n/a * @param arena n/a
* @param args * @param simulated n/a
* - `[0]` {@linkcode Utils.NumberHolder} A container with a move's {@linkcode Type} * @param moveType a {@linkcode NumberHolder} containing a move's {@linkcode Type}
* @returns `true` if the given move type changed; `false` otherwise. * @returns `true` if the given move type changed; `false` otherwise.
*/ */
apply(arena: Arena, args: any[]): boolean { override apply(arena: Arena, simulated: boolean, moveType: NumberHolder): boolean {
const moveType = args[0]; if (moveType.value === Type.NORMAL) {
if (moveType instanceof Utils.NumberHolder && moveType.value === Type.NORMAL) {
moveType.value = Type.ELECTRIC; moveType.value = Type.ELECTRIC;
return true; return true;
} }
@ -608,16 +618,22 @@ export class ArenaTrapTag extends ArenaTag {
} }
} }
apply(arena: Arena, args: any[]): boolean { /**
const pokemon = args[0] as Pokemon; * Activates the hazard effect onto a Pokemon when it enters the field
* @param arena the {@linkcode Arena} containing this tag
* @param simulated if `true`, only checks if the hazard would activate.
* @param pokemon the {@linkcode Pokemon} triggering this hazard
* @returns `true` if this hazard affects the given Pokemon; `false` otherwise.
*/
override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean {
if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) { if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) {
return false; return false;
} }
return this.activateTrap(pokemon); return this.activateTrap(pokemon, simulated);
} }
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
return false; return false;
} }
@ -651,14 +667,18 @@ class SpikesTag extends ArenaTrapTag {
} }
} }
activateTrap(pokemon: Pokemon): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (simulated) {
return !cancelled.value;
}
if (!cancelled.value) { if (!cancelled.value) {
const damageHpRatio = 1 / (10 - 2 * this.layers); const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER); pokemon.damageAndUpdate(damage, HitResult.OTHER);
@ -702,8 +722,11 @@ class ToxicSpikesTag extends ArenaTrapTag {
} }
} }
activateTrap(pokemon: Pokemon): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
if (simulated) {
return true;
}
if (pokemon.isOfType(Type.POISON)) { if (pokemon.isOfType(Type.POISON)) {
this.neutralized = true; this.neutralized = true;
if (pokemon.scene.arena.removeTag(this.tagType)) { if (pokemon.scene.arena.removeTag(this.tagType)) {
@ -807,8 +830,8 @@ class StealthRockTag extends ArenaTrapTag {
return damageHpRatio; return damageHpRatio;
} }
activateTrap(pokemon: Pokemon): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (cancelled.value) { if (cancelled.value) {
@ -818,12 +841,16 @@ class StealthRockTag extends ArenaTrapTag {
const damageHpRatio = this.getDamageHpRatio(pokemon); const damageHpRatio = this.getDamageHpRatio(pokemon);
if (damageHpRatio) { if (damageHpRatio) {
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio); if (simulated) {
return true;
}
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER); pokemon.damageAndUpdate(damage, HitResult.OTHER);
if (pokemon.turnData) { if (pokemon.turnData) {
pokemon.turnData.damageTaken += damage; pokemon.turnData.damageTaken += damage;
} }
return true;
} }
return false; return false;
@ -853,14 +880,20 @@ class StickyWebTag extends ArenaTrapTag {
} }
} }
activateTrap(pokemon: Pokemon): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
const cancelled = new Utils.BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (simulated) {
return !cancelled.value;
}
if (!cancelled.value) { if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
const stages = new Utils.NumberHolder(-1); const stages = new NumberHolder(-1);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
return true;
} }
} }
@ -879,8 +912,15 @@ export class TrickRoomTag extends ArenaTag {
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId); super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
} }
apply(arena: Arena, args: any[]): boolean { /**
const speedReversed = args[0] as Utils.BooleanHolder; * Reverses Speed-based turn order for all Pokemon on the field
* @param arena n/a
* @param simulated n/a
* @param speedReversed a {@linkcode BooleanHolder} used to flag if Speed-based
* turn order should be reversed.
* @returns `true` if turn order is successfully reversed; `false` otherwise
*/
override apply(arena: Arena, simulated: boolean, speedReversed: BooleanHolder): boolean {
speedReversed.value = !speedReversed.value; speedReversed.value = !speedReversed.value;
return true; return true;
} }
@ -1087,7 +1127,7 @@ class FireGrassPledgeTag extends ArenaTag {
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
// TODO: Replace this with a proper animation // TODO: Replace this with a proper animation
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8)); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
}); });
return super.lapse(arena); return super.lapse(arena);
@ -1111,8 +1151,15 @@ class WaterFirePledgeTag extends ArenaTag {
arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
} }
override apply(arena: Arena, args: any[]): boolean { /**
const moveChance = args[0] as Utils.NumberHolder; * Doubles the chance for the given move's secondary effect(s) to trigger
* @param arena the {@linkcode Arena} containing this tag
* @param simulated n/a
* @param moveChance a {@linkcode NumberHolder} containing
* the move's current effect chance
* @returns `true` if the move's effect chance was doubled (currently always `true`)
*/
override apply(arena: Arena, simulated: boolean, moveChance: NumberHolder): boolean {
moveChance.value *= 2; moveChance.value *= 2;
return true; return true;
} }

View File

@ -2724,6 +2724,44 @@ export class TelekinesisTag extends BattlerTag {
} }
} }
/**
* Tag that swaps the user's base ATK stat with its base DEF stat.
* @extends BattlerTag
*/
export class PowerTrickTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: number) {
super(BattlerTagType.POWER_TRICK, BattlerTagLapseType.CUSTOM, 0, sourceMove, sourceId, true);
}
onAdd(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onRemove(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
/**
* Removes the Power Trick tag and reverts any stat changes if the tag is already applied.
* @param {Pokemon} pokemon The {@linkcode Pokemon} that already has the Power Trick tag.
*/
onOverlap(pokemon: Pokemon): void {
pokemon.removeTag(this.tagType);
}
/**
* Swaps the user's base ATK stat with its base DEF stat.
* @param {Pokemon} pokemon The {@linkcode Pokemon} whose stats will be swapped.
*/
swapStat(pokemon: Pokemon): void {
const temp = pokemon.getStat(Stat.ATK, false);
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.DEF, false), false);
pokemon.setStat(Stat.DEF, temp, false);
}
}
/** /**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
* @param sourceId - The ID of the pokemon adding the tag * @param sourceId - The ID of the pokemon adding the tag
@ -2899,6 +2937,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new SyrupBombTag(sourceId); return new SyrupBombTag(sourceId);
case BattlerTagType.TELEKINESIS: case BattlerTagType.TELEKINESIS:
return new TelekinesisTag(sourceMove); return new TelekinesisTag(sourceMove);
case BattlerTagType.POWER_TRICK:
return new PowerTrickTag(sourceMove, sourceId);
case BattlerTagType.NONE: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -808,7 +808,7 @@ export default class Move implements Localizable {
source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
if (!this.hasAttr(TypelessAttr)) { if (!this.hasAttr(TypelessAttr)) {
source.scene.arena.applyTags(WeakenMoveTypeTag, this.type, power); source.scene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power);
source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power);
} }
@ -1026,7 +1026,7 @@ export class MoveEffectAttr extends MoveAttr {
if (!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) { if (!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, moveChance); user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance);
} }
if (!selfEffect) { if (!selfEffect) {
@ -6430,6 +6430,9 @@ export class TransformAttr extends MoveEffectAttr {
user.summonData.gender = target.getGender(); user.summonData.gender = target.getGender();
user.summonData.fusionGender = target.getFusionGender(); user.summonData.fusionGender = target.getFusionGender();
// Power Trick's effect will not preserved after using Transform
user.removeTag(BattlerTagType.POWER_TRICK);
// Copy all stats (except HP) // Copy all stats (except HP)
for (const s of EFFECTIVE_STATS) { for (const s of EFFECTIVE_STATS) {
user.setStat(s, target.getStat(s, false), false); user.setStat(s, target.getStat(s, false), false);
@ -6441,7 +6444,7 @@ export class TransformAttr extends MoveEffectAttr {
} }
user.summonData.moveset = target.getMoveset().map(m => { user.summonData.moveset = target.getMoveset().map(m => {
const pp = m?.getMove().pp!; const pp = m?.getMove().pp ?? 0;
// if PP value is less than 5, do nothing. If greater, we need to reduce the value to 5 using a negative ppUp value. // if PP value is less than 5, do nothing. If greater, we need to reduce the value to 5 using a negative ppUp value.
const ppUp = pp <= 5 ? 0 : (5 - pp) / Math.max(Math.floor(pp / 5), 1); const ppUp = pp <= 5 ? 0 : (5 - pp) / Math.max(Math.floor(pp / 5), 1);
return new PokemonMove(m?.moveId!, 0, ppUp); return new PokemonMove(m?.moveId!, 0, ppUp);
@ -8160,7 +8163,7 @@ export function initMoves() {
.attr(OpponentHighHpPowerAttr, 120) .attr(OpponentHighHpPowerAttr, 120)
.makesContact(), .makesContact(),
new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4) new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4)
.unimplemented(), .attr(AddBattlerTagAttr, BattlerTagType.POWER_TRICK, true),
new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4) new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4)
.attr(SuppressAbilitiesAttr), .attr(SuppressAbilitiesAttr),
new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4) new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4)

View File

@ -799,8 +799,8 @@ export const pokemonFormChanges: PokemonFormChanges = {
[Species.ZYGARDE]: [ [Species.ZYGARDE]: [
new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.ZYGARDE, "complete", "50-pc", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.ZYGARDE, "complete", "50-pc", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.ZYGARDE, "10-pc", "complete", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.ZYGARDE, "10-pc", "10-complete", new SpeciesFormChangeManualTrigger(), true),
new SpeciesFormChange(Species.ZYGARDE, "complete", "10-pc", new SpeciesFormChangeManualTrigger(), true) new SpeciesFormChange(Species.ZYGARDE, "10-complete", "10-pc", new SpeciesFormChangeManualTrigger(), true)
], ],
[Species.DIANCIE]: [ [Species.DIANCIE]: [
new SpeciesFormChange(Species.DIANCIE, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE)) new SpeciesFormChange(Species.DIANCIE, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE))

View File

@ -425,6 +425,7 @@ export abstract class PokemonSpeciesForm {
case "hero": case "hero":
case "roaming": case "roaming":
case "complete": case "complete":
case "10-complete":
case "10": case "10":
case "10-pc": case "10-pc":
case "super": case "super":
@ -2135,7 +2136,8 @@ export function initSpecies() {
new PokemonForm("10% Forme", "10", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.AURA_BREAK, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, null, true), new PokemonForm("10% Forme", "10", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.AURA_BREAK, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, null, true),
new PokemonForm("50% Forme Power Construct", "50-pc", Type.DRAGON, Type.GROUND, 5, 305, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), new PokemonForm("50% Forme Power Construct", "50-pc", Type.DRAGON, Type.GROUND, 5, 305, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true),
new PokemonForm("10% Forme Power Construct", "10-pc", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, "10", true), new PokemonForm("10% Forme Power Construct", "10-pc", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, "10", true),
new PokemonForm("Complete Forme", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300), new PokemonForm("Complete Forme (50% PC)", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300),
new PokemonForm("Complete Forme (10% PC)", "10-complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300, false, "complete"),
), ),
new PokemonSpecies(Species.DIANCIE, 6, false, false, true, "Jewel Pokémon", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, GrowthRate.SLOW, null, false, true, new PokemonSpecies(Species.DIANCIE, 6, false, false, true, "Jewel Pokémon", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, GrowthRate.SLOW, null, false, true,
new PokemonForm("Normal", "", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, false, null, true), new PokemonForm("Normal", "", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, false, null, true),

View File

@ -574,13 +574,13 @@ export class TrainerConfig {
case "magma": { case "magma": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ], [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ],
[TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.BARBOACH ], [TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ],
[TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ] [TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
}; };
} }
case "aqua": { case "aqua": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID ], [TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID, Species.BARBOACH ],
[TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ], [TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ],
[TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ] [TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ]
}; };
@ -601,9 +601,9 @@ export class TrainerConfig {
} }
case "flare": { case "flare": {
return { return {
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ], [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.FOONGUS, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ],
[TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ], [TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ],
[TrainerPoolTier.RARE]: [ Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] [TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ]
}; };
} }
case "aether": { case "aether": {
@ -1504,7 +1504,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools({ .setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.GROWLITHE, Species.BALTOY ], [TrainerPoolTier.COMMON]: [ Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.GROWLITHE, Species.BALTOY ],
[TrainerPoolTier.UNCOMMON]: [ Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.ROLYCOLY, Species.GLIGAR, Species.RHYHORN, Species.HEATMOR ], [TrainerPoolTier.UNCOMMON]: [ Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.ROLYCOLY, Species.GLIGAR, Species.RHYHORN, Species.HEATMOR ],
[TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON ], [TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ],
[TrainerPoolTier.SUPER_RARE]: [ Species.CAPSAKID, Species.CHARCADET ] [TrainerPoolTier.SUPER_RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
}), }),
[TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1540,9 +1540,9 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) [TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({ .setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR ], [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR ],
[TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP ], [TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP, Species.FOONGUS ],
[TrainerPoolTier.RARE]: [ Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO ], [TrainerPoolTier.RARE]: [ Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO ],
[TrainerPoolTier.SUPER_RARE]: [ Species.NOIVERN, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ] [TrainerPoolTier.SUPER_RARE]: [ Species.NOIBAT, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ]
}), }),
[TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [ Species.LIEPARD ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [ Species.LIEPARD ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [ Species.MALAMAR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), [TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [ Species.MALAMAR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1893,7 +1893,10 @@ export const trainerConfigs: TrainerConfigs = {
}), }),
[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") [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, Species.ALOLA_PERSIAN ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.DUGTRIO, Species.ALOLA_DUGTRIO ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.DUGTRIO, Species.ALOLA_DUGTRIO ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HONCHKROW ])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HONCHKROW ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.NIDOKING, Species.NIDOQUEEN ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.NIDOKING, Species.NIDOQUEEN ]))
@ -1945,6 +1948,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Camerupt p.formIndex = 1; // Mega Camerupt
p.generateName(); p.generateName();
p.gender = Gender.MALE;
})), })),
[TrainerType.MAXIE_2]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma") [TrainerType.MAXIE_2]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SOLROCK, Species.TYPHLOSION ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SOLROCK, Species.TYPHLOSION ], TrainerSlot.TRAINER, true, p => {
@ -1967,6 +1971,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Camerupt p.formIndex = 1; // Mega Camerupt
p.generateName(); p.generateName();
p.gender = Gender.MALE;
})) }))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GROUDON ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GROUDON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -1985,6 +1990,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Sharpedo p.formIndex = 1; // Mega Sharpedo
p.generateName(); p.generateName();
p.gender = Gender.MALE;
})), })),
[TrainerType.ARCHIE_2]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma") [TrainerType.ARCHIE_2]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.EMPOLEON, Species.LUDICOLO ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.EMPOLEON, Species.LUDICOLO ], TrainerSlot.TRAINER, true, p => {
@ -2010,6 +2016,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Sharpedo p.formIndex = 1; // Mega Sharpedo
p.generateName(); p.generateName();
p.gender = Gender.MALE;
})) }))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYOGRE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYOGRE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2031,6 +2038,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
})), })),
[TrainerType.CYRUS_2]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss", [], true).setMixedBattleBgm("battle_galactic_boss").setVictoryBgm("victory_team_plasma") [TrainerType.CYRUS_2]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss", [], true).setMixedBattleBgm("battle_galactic_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AZELF, Species.UXIE, Species.MESPRIT ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AZELF, Species.UXIE, Species.MESPRIT ], TrainerSlot.TRAINER, true, p => {
@ -2049,6 +2057,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
})) }))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DARKRAI ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DARKRAI ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2065,6 +2074,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
})), })),
[TrainerType.GHETSIS_2]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss", [], true).setMixedBattleBgm("battle_plasma_boss").setVictoryBgm("victory_team_plasma") [TrainerType.GHETSIS_2]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss", [], true).setMixedBattleBgm("battle_plasma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GENESECT ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GENESECT ], TrainerSlot.TRAINER, true, p => {
@ -2084,6 +2094,11 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
if (p.species.speciesId === Species.HYDREIGON) {
p.gender = Gender.MALE;
} else if (p.species.speciesId === Species.IRON_JUGULIS) {
p.gender = Gender.GENDERLESS;
}
})) }))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYUREM ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYUREM ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2105,6 +2120,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Gyarados p.formIndex = 1; // Mega Gyarados
p.generateName(); p.generateName();
p.gender = Gender.MALE;
})), })),
[TrainerType.LYSANDRE_2]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss", [], true).setMixedBattleBgm("battle_flare_boss").setVictoryBgm("victory_team_plasma") [TrainerType.LYSANDRE_2]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss", [], true).setMixedBattleBgm("battle_flare_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SCREAM_TAIL, Species.FLUTTER_MANE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SCREAM_TAIL, Species.FLUTTER_MANE ], TrainerSlot.TRAINER, true, p => {
@ -2124,6 +2140,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Gyardos p.formIndex = 1; // Mega Gyardos
p.generateName(); p.generateName();
p.gender = Gender.MALE;
})) }))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.YVELTAL ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.YVELTAL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2131,7 +2148,10 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
})), })),
[TrainerType.LUSAMINE]: new TrainerConfig(++t).setName("Lusamine").initForEvilTeamLeader("Aether Boss", []).setMixedBattleBgm("battle_aether_boss").setVictoryBgm("victory_team_plasma") [TrainerType.LUSAMINE]: new TrainerConfig(++t).setName("Lusamine").initForEvilTeamLeader("Aether Boss", []).setMixedBattleBgm("battle_aether_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.LILLIGANT, Species.HISUI_LILLIGANT ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.LILLIGANT, Species.HISUI_LILLIGANT ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ])) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ]))
@ -2148,7 +2168,10 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ROGUE_BALL; p.pokeball = PokeballType.ROGUE_BALL;
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.STAKATAKA, Species.CELESTEELA, Species.GUZZLORD ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.STAKATAKA, Species.CELESTEELA, Species.GUZZLORD ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ROGUE_BALL; p.pokeball = PokeballType.ROGUE_BALL;
@ -2191,6 +2214,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GOLISOPOD ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GOLISOPOD ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})), })),
[TrainerType.GUZMA_2]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", [], true).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma") [TrainerType.GUZMA_2]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", [], true).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma")
@ -2198,6 +2222,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2); p.setBoss(true, 2);
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.abilityIndex = 2; //Anticipation p.abilityIndex = 2; //Anticipation
p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => {
@ -2239,6 +2264,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // G-Max Copperajah p.formIndex = 1; // G-Max Copperajah
p.generateName(); p.generateName();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
})), })),
[TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma") [TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => {
@ -2262,6 +2288,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // G-Max Copperajah p.formIndex = 1; // G-Max Copperajah
p.generateName(); p.generateName();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
})), })),
[TrainerType.PENNY]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", []).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") [TrainerType.PENNY]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", []).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VAPOREON, Species.JOLTEON, Species.FLAREON ])) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VAPOREON, Species.JOLTEON, Species.FLAREON ]))
@ -2275,8 +2302,9 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = Utils.randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow p.formIndex = Utils.randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Pixilate p.abilityIndex = 2; // Pixilate
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
})) }))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2290,20 +2318,21 @@ export const trainerConfigs: TrainerConfigs = {
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct? return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}), }),
[TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") [TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form p.abilityIndex = 2; // Pixilate
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.gender = Gender.FEMALE;
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ENTEI, Species.RAIKOU, Species.SUICUNE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ENTEI, Species.RAIKOU, Species.SUICUNE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
})) }))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.WALKING_WAKE, Species.GOUGING_FIRE, Species.RAGING_BOLT ])) .setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.WALKING_WAKE, Species.GOUGING_FIRE, Species.RAGING_BOLT ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Pixilate p.pokeball = PokeballType.ROGUE_BALL;
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2318,7 +2347,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
})) }))
.setGenModifiersFunc(party => { .setGenModifiersFunc(party => {
const teraPokemon = party[3]; const teraPokemon = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct? return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}), }),
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true) [TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true)

View File

@ -80,6 +80,7 @@ export enum BattlerTagType {
DOUBLE_SHOCKED = "DOUBLE_SHOCKED", DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
AUTOTOMIZED = "AUTOTOMIZED", AUTOTOMIZED = "AUTOTOMIZED",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
POWER_TRICK = "POWER_TRICK",
HEAL_BLOCK = "HEAL_BLOCK", HEAL_BLOCK = "HEAL_BLOCK",
TORMENT = "TORMENT", TORMENT = "TORMENT",
TAUNT = "TAUNT", TAUNT = "TAUNT",

View File

@ -579,26 +579,28 @@ export class Arena {
* Applies each `ArenaTag` in this Arena, based on which side (self, enemy, or both) is passed in as a parameter * Applies each `ArenaTag` in this Arena, based on which side (self, enemy, or both) is passed in as a parameter
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply * @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param side {@linkcode ArenaTagSide} which side's arena tags to apply * @param side {@linkcode ArenaTagSide} which side's arena tags to apply
* @param simulated if `true`, this applies arena tags without changing game state
* @param args array of parameters that the called upon tags may need * @param args array of parameters that the called upon tags may need
*/ */
applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, ...args: unknown[]): void { applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, simulated: boolean, ...args: unknown[]): void {
let tags = typeof tagType === "string" let tags = typeof tagType === "string"
? this.tags.filter(t => t.tagType === tagType) ? this.tags.filter(t => t.tagType === tagType)
: this.tags.filter(t => t instanceof tagType); : this.tags.filter(t => t instanceof tagType);
if (side !== ArenaTagSide.BOTH) { if (side !== ArenaTagSide.BOTH) {
tags = tags.filter(t => t.side === side); tags = tags.filter(t => t.side === side);
} }
tags.forEach(t => t.apply(this, args)); tags.forEach(t => t.apply(this, simulated, ...args));
} }
/** /**
* Applies the specified tag to both sides (ie: both user and trainer's tag that match the Tag specified) * Applies the specified tag to both sides (ie: both user and trainer's tag that match the Tag specified)
* by calling {@linkcode applyTagsForSide()} * by calling {@linkcode applyTagsForSide()}
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply * @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param simulated if `true`, this applies arena tags without changing game state
* @param args array of parameters that the called upon tags may need * @param args array of parameters that the called upon tags may need
*/ */
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, ...args: unknown[]): void { applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, simulated: boolean, ...args: unknown[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args);
} }
/** /**

View File

@ -19,7 +19,7 @@ import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect"; import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags"; 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 "#app/data/weather"; import { WeatherType } from "#app/data/weather";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, 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 } from "#app/data/ability"; import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, 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 } from "#app/data/ability";
@ -1538,7 +1538,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, moveTypeHolder); this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder);
if (this.getTag(BattlerTagType.ELECTRIFIED)) { if (this.getTag(BattlerTagType.ELECTRIFIED)) {
moveTypeHolder.value = Type.ELECTRIC; moveTypeHolder.value = Type.ELECTRIC;
} }
@ -2605,7 +2605,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */
const screenMultiplier = new Utils.NumberHolder(1); const screenMultiplier = new Utils.NumberHolder(1);
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier); this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, moveCategory, screenMultiplier);
/** /**
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
@ -3048,6 +3048,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
continue; continue;
} }
if (tag instanceof PowerTrickTag) {
tag.swapStat(this);
}
this.summonData.tags.push(tag); this.summonData.tags.push(tag);
} }

View File

@ -33,7 +33,7 @@ export class EggLapsePhase extends Phase {
if (eggsToHatchCount > 0) { if (eggsToHatchCount > 0) {
if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) { if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) {
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => { this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
// show prompt for skip // show prompt for skip, blocking inputs for 1 second
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0); this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0);
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
this.hatchEggsSkipped(eggsToHatch); this.hatchEggsSkipped(eggsToHatch);
@ -41,7 +41,8 @@ export class EggLapsePhase extends Phase {
}, () => { }, () => {
this.hatchEggsRegular(eggsToHatch); this.hatchEggsRegular(eggsToHatch);
this.end(); this.end();
} },
null, null, null, 1000, true
); );
}, 100, true); }, 100, true);
} else if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 2) { } else if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 2) {

View File

@ -1,7 +1,6 @@
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler";
import { EggHatchData } from "#app/data/egg-hatch-data"; import { EggHatchData } from "#app/data/egg-hatch-data";
/** /**
@ -11,7 +10,6 @@ import { EggHatchData } from "#app/data/egg-hatch-data";
*/ */
export class EggSummaryPhase extends Phase { export class EggSummaryPhase extends Phase {
private eggHatchData: EggHatchData[]; private eggHatchData: EggHatchData[];
private eggHatchHandler: EggHatchSceneHandler;
constructor(scene: BattleScene, eggHatchData: EggHatchData[]) { constructor(scene: BattleScene, eggHatchData: EggHatchData[]) {
super(scene); super(scene);
@ -26,7 +24,6 @@ export class EggSummaryPhase extends Phase {
if (i >= this.eggHatchData.length) { if (i >= this.eggHatchData.length) {
this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => { this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => {
this.scene.fadeOutBgm(undefined, false); this.scene.fadeOutBgm(undefined, false);
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
}); });
} else { } else {

View File

@ -99,8 +99,9 @@ export class MoveEffectPhase extends PokemonPhase {
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const hasActiveTargets = targets.some(t => t.isActive(true)); const hasActiveTargets = targets.some(t => t.isActive(true));
/** Check if the target is immune via ability to the attacking move */ /** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)); const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
&& !targets[0].getTag(SemiInvulnerableTag);
/** /**
* If no targets are left for the move to hit (FAIL), or the invoked move is single-target * If no targets are left for the move to hit (FAIL), or the invoked move is single-target
@ -140,7 +141,7 @@ export class MoveEffectPhase extends PokemonPhase {
const bypassIgnoreProtect = new Utils.BooleanHolder(false); const bypassIgnoreProtect = new Utils.BooleanHolder(false);
/** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */ /** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */
if (!this.move.getMove().isAllyTarget()) { if (!this.move.getMove().isAllyTarget()) {
this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, false, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect);
} }
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
@ -148,8 +149,9 @@ export class MoveEffectPhase extends PokemonPhase {
&& (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))) && (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)))
|| (this.move.getMove().category !== MoveCategory.STATUS && target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType)))); || (this.move.getMove().category !== MoveCategory.STATUS && target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType))));
/** Is the pokemon immune due to an ablility? */ /** Is the pokemon immune due to an ablility, and also not in a semi invulnerable state? */
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)); const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
&& !target.getTag(SemiInvulnerableTag);
/** /**
* If the move missed a target, stop all future hits against that target * If the move missed a target, stop all future hits against that target

View File

@ -128,7 +128,9 @@ export class MovePhase extends BattlePhase {
this.lapsePreMoveAndMoveTags(); this.lapsePreMoveAndMoveTags();
if (!(this.failed || this.cancelled)) {
this.resolveFinalPreMoveCancellationChecks(); this.resolveFinalPreMoveCancellationChecks();
}
if (this.cancelled || this.failed) { if (this.cancelled || this.failed) {
this.handlePreMoveFailures(); this.handlePreMoveFailures();
@ -145,8 +147,9 @@ export class MovePhase extends BattlePhase {
const moveQueue = this.pokemon.getMoveQueue(); const moveQueue = this.pokemon.getMoveQueue();
if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) { if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) {
this.showMoveText();
this.showFailedText(); this.showFailedText();
this.cancelled = true; this.cancel();
} }
} }

View File

@ -20,7 +20,7 @@ export class PostSummonPhase extends PokemonPhase {
if (pokemon.status?.effect === StatusEffect.TOXIC) { if (pokemon.status?.effect === StatusEffect.TOXIC) {
pokemon.status.turnCount = 0; pokemon.status.turnCount = 0;
} }
this.scene.arena.applyTags(ArenaTrapTag, pokemon); this.scene.arena.applyTags(ArenaTrapTag, false, pokemon);
// If this is mystery encounter and has post summon phase tag, apply post summon effects // If this is mystery encounter and has post summon phase tag, apply post summon effects
if (this.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) { if (this.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) {

View File

@ -64,8 +64,7 @@ export class StatStageChangePhase extends PokemonPhase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!this.selfTarget && stages.value < 0) { if (!this.selfTarget && stages.value < 0) {
// TODO: Include simulate boolean when tag applications can be simulated this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled);
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
} }
if (!cancelled.value && !this.selfTarget && stages.value < 0) { if (!cancelled.value && !this.selfTarget && stages.value < 0) {

View File

@ -45,7 +45,7 @@ export class TurnStartPhase extends FieldPhase {
// Next, a check for Trick Room is applied to determine sort order. // Next, a check for Trick Room is applied to determine sort order.
const speedReversed = new Utils.BooleanHolder(false); const speedReversed = new Utils.BooleanHolder(false);
this.scene.arena.applyTags(TrickRoomTag, speedReversed); this.scene.arena.applyTags(TrickRoomTag, false, speedReversed);
// Adjust the sort function based on whether Trick Room is active. // Adjust the sort function based on whether Trick Room is active.
orderedTargets.sort((a: Pokemon, b: Pokemon) => { orderedTargets.sort((a: Pokemon, b: Pokemon) => {

View File

@ -32,7 +32,7 @@ describe("Abilities - POWER CONSTRUCT", () => {
}); });
test( test(
"check if fainted pokemon switches to base form on arena reset", "check if fainted 50% Power Construct Pokemon switches to base form on arena reset",
async () => { async () => {
const baseForm = 2, const baseForm = 2,
completeForm = 4; completeForm = 4;
@ -41,7 +41,37 @@ describe("Abilities - POWER CONSTRUCT", () => {
[Species.ZYGARDE]: completeForm, [Species.ZYGARDE]: completeForm,
}); });
await game.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]); await game.classicMode.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]);
const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE);
expect(zygarde).not.toBe(undefined);
expect(zygarde!.formIndex).toBe(completeForm);
zygarde!.hp = 0;
zygarde!.status = new Status(StatusEffect.FAINT);
expect(zygarde!.isFainted()).toBe(true);
game.move.select(Moves.SPLASH);
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(zygarde!.formIndex).toBe(baseForm);
},
);
test(
"check if fainted 10% Power Construct Pokemon switches to base form on arena reset",
async () => {
const baseForm = 3,
completeForm = 5;
game.override.startingWave(4);
game.override.starterForms({
[Species.ZYGARDE]: completeForm,
});
await game.classicMode.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]);
const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE); const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE);
expect(zygarde).not.toBe(undefined); expect(zygarde).not.toBe(undefined);

View File

@ -71,4 +71,23 @@ describe("Abilities - Volt Absorb", () => {
await game.phaseInterceptor.to("BerryPhase", false); await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
}); });
it("regardless of accuracy should not trigger on pokemon in semi invulnerable state", async () => {
game.override.moveset(Moves.THUNDERBOLT);
game.override.enemyMoveset(Moves.DIVE);
game.override.enemySpecies(Species.MAGIKARP);
game.override.enemyAbility(Abilities.VOLT_ABSORB);
await game.classicMode.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.THUNDERBOLT);
enemyPokemon.hp = enemyPokemon.hp - 1;
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("MoveEffectPhase");
await game.move.forceMiss();
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
});
}); });

View File

@ -111,7 +111,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) { if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, move.category, defender.scene.currentBattle.double, multiplierHolder); defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, move.category, multiplierHolder);
} }
return move.power * multiplierHolder.value; return move.power * multiplierHolder.value;

View File

@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, move.category, defender.scene.currentBattle.double, multiplierHolder); defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, move.category, multiplierHolder);
} }
return move.power * multiplierHolder.value; return move.power * multiplierHolder.value;

View File

@ -0,0 +1,113 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import { Moves } from "#enums/moves";
import { Stat } from "#enums/stat";
import { Species } from "#enums/species";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
describe("Moves - Power Trick", () => {
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
.battleType("single")
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH)
.enemySpecies(Species.MEW)
.enemyLevel(200)
.moveset([ Moves.POWER_TRICK ])
.ability(Abilities.BALL_FETCH);
});
it("swaps the user's ATK and DEF stats", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);
const player = game.scene.getPlayerPokemon()!;
const baseATK = player.getStat(Stat.ATK, false);
const baseDEF = player.getStat(Stat.DEF, false);
game.move.select(Moves.POWER_TRICK);
await game.phaseInterceptor.to(TurnEndPhase);
expect(player.getStat(Stat.ATK, false)).toBe(baseDEF);
expect(player.getStat(Stat.DEF, false)).toBe(baseATK);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeDefined();
});
it("resets initial ATK and DEF stat swap when used consecutively", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);
const player = game.scene.getPlayerPokemon()!;
const baseATK = player.getStat(Stat.ATK, false);
const baseDEF = player.getStat(Stat.DEF, false);
game.move.select(Moves.POWER_TRICK);
await game.phaseInterceptor.to(TurnEndPhase);
game.move.select(Moves.POWER_TRICK);
await game.phaseInterceptor.to(TurnEndPhase);
expect(player.getStat(Stat.ATK, false)).toBe(baseATK);
expect(player.getStat(Stat.DEF, false)).toBe(baseDEF);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined();
});
it("should pass effect when using BATON_PASS", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
await game.override.moveset([ Moves.POWER_TRICK, Moves.BATON_PASS ]);
const player = game.scene.getPlayerPokemon()!;
player.addTag(BattlerTagType.POWER_TRICK);
game.move.select(Moves.BATON_PASS);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to(TurnEndPhase);
const switchedPlayer = game.scene.getPlayerPokemon()!;
const baseATK = switchedPlayer.getStat(Stat.ATK);
const baseDEF = switchedPlayer.getStat(Stat.DEF);
expect(switchedPlayer.getStat(Stat.ATK, false)).toBe(baseDEF);
expect(switchedPlayer.getStat(Stat.DEF, false)).toBe(baseATK);
expect(switchedPlayer.getTag(BattlerTagType.POWER_TRICK)).toBeDefined();
});
it("should remove effect after using Transform", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
await game.override.moveset([ Moves.POWER_TRICK, Moves.TRANSFORM ]);
const player = game.scene.getPlayerPokemon()!;
player.addTag(BattlerTagType.POWER_TRICK);
game.move.select(Moves.TRANSFORM);
await game.phaseInterceptor.to(TurnEndPhase);
const enemy = game.scene.getEnemyPokemon()!;
const baseATK = enemy.getStat(Stat.ATK);
const baseDEF = enemy.getStat(Stat.DEF);
expect(player.getStat(Stat.ATK, false)).toBe(baseATK);
expect(player.getStat(Stat.DEF, false)).toBe(baseDEF);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined();
});
});

View File

@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, move.category, defender.scene.currentBattle.double, multiplierHolder); defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, move.category, multiplierHolder);
} }
return move.power * multiplierHolder.value; return move.power * multiplierHolder.value;

View File

@ -165,6 +165,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
if (this.config.delay) { if (this.config.delay) {
this.blockInput = true; this.blockInput = true;
this.optionSelectText.setAlpha(0.5); this.optionSelectText.setAlpha(0.5);
this.cursorObj?.setAlpha(0.8);
this.scene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput()); this.scene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput());
} }
@ -256,6 +257,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
this.blockInput = false; this.blockInput = false;
this.optionSelectText.setAlpha(1); this.optionSelectText.setAlpha(1);
this.cursorObj?.setAlpha(1);
} }
getOptionsWithScroll(): OptionSelectItem[] { getOptionsWithScroll(): OptionSelectItem[] {

View File

@ -76,7 +76,8 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
} }
} }
], ],
delay: args.length >= 6 && args[5] !== null ? args[5] as integer : 0 delay: args.length >= 6 && args[5] !== null ? args[5] as number : 0,
noCancel: args.length >= 7 && args[6] !== null ? args[6] as boolean : false,
}; };
super.show([ config ]); super.show([ config ]);
@ -96,7 +97,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
} }
processInput(button: Button): boolean { processInput(button: Button): boolean {
if (button === Button.CANCEL && this.blockInput) { if (button === Button.CANCEL && this.blockInput && !this.config?.noCancel) {
this.unblockInput(); this.unblockInput();
} }

View File

@ -42,6 +42,9 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
private scrollGridHandler : ScrollableGridUiHandler; private scrollGridHandler : ScrollableGridUiHandler;
private cursorObj: Phaser.GameObjects.Image; private cursorObj: Phaser.GameObjects.Image;
/** used to add a delay before which it is not possible to exit the summary */
private blockExit: boolean;
/** /**
* Allows subscribers to listen for events * Allows subscribers to listen for events
* *
@ -168,6 +171,13 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
this.setCursor(0); this.setCursor(0);
this.scene.playSoundWithoutBgm("evolution_fanfare"); this.scene.playSoundWithoutBgm("evolution_fanfare");
// Prevent exiting the egg summary for 2 seconds if the egg hatching
// was skipped automatically and for 1 second otherwise
const exitBlockingDuration = (this.scene.eggSkipPreference === 2) ? 2000 : 1000;
this.blockExit = true;
this.scene.time.delayedCall(exitBlockingDuration, () => this.blockExit = false);
return true; return true;
} }
@ -203,13 +213,17 @@ export default class EggSummaryUiHandler extends MessageUiHandler {
const ui = this.getUi(); const ui = this.getUi();
let success = false; let success = false;
const error = false; let error = false;
if (button === Button.CANCEL) { if (button === Button.CANCEL) {
if (!this.blockExit) {
const phase = this.scene.getCurrentPhase(); const phase = this.scene.getCurrentPhase();
if (phase instanceof EggSummaryPhase) { if (phase instanceof EggSummaryPhase) {
phase.end(); phase.end();
} }
success = true; success = true;
} else {
error = true;
}
} else { } else {
this.scrollGridHandler.processInput(button); this.scrollGridHandler.processInput(button);
} }

View File

@ -587,13 +587,13 @@ export default class RunInfoUiHandler extends UiHandler {
const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]";
rules.push(typeText); rules.push(typeText);
break; break;
case Challenges.FRESH_START:
rules.push(i18next.t("challenges:freshStart.name"));
break;
case Challenges.INVERSE_BATTLE: case Challenges.INVERSE_BATTLE:
//
rules.push(i18next.t("challenges:inverseBattle.shortName")); rules.push(i18next.t("challenges:inverseBattle.shortName"));
break; break;
default:
const localisationKey = Challenges[this.runInfo.challenges[i].id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("");
rules.push(i18next.t(`challenges:${localisationKey}.name`));
break;
} }
} }
} }