[Refactor] Add type inference and support for simulated calls to `ArenaTag.apply` (#4659)
* Add simulated support for Arena Tag application * Add type inference to ArenaTag.apply * Fix screen tests * back to `any` again lol * fix missing spread syntax (maybe) * updated docs * named imports for `Utils`
This commit is contained in:
parent
d01d856898
commit
1966335627
|
@ -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;
|
||||||
|
|
||||||
arena.scene.queueMessage(i18next.t("arenaTag:mistApply"));
|
if (!simulated) {
|
||||||
|
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,38 +257,34 @@ 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
|
&& this.protectConditionFunc(arena, moveId)) {
|
||||||
&& target instanceof Pokemon
|
if (!isProtected.value) {
|
||||||
&& typeof moveId === "number"
|
isProtected.value = true;
|
||||||
&& ignoresBypass instanceof Utils.BooleanHolder) {
|
if (!simulated) {
|
||||||
|
attacker.stopMultiHit(defender);
|
||||||
|
|
||||||
if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer()
|
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene);
|
||||||
&& this.protectConditionFunc(arena, moveId)) {
|
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) }));
|
||||||
if (!cancelled.value) {
|
|
||||||
cancelled.value = true;
|
|
||||||
user.stopMultiHit(target);
|
|
||||||
|
|
||||||
new CommonBattleAnim(CommonAnim.PROTECT, target).play(arena.scene);
|
|
||||||
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoresBypass.value = ignoresBypass.value || this.ignoresBypass;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -141,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? */
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue