mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-24 13:08:39 +00:00
Merge branch 'beta' into pr-illusion
This commit is contained in:
commit
95443499f0
BIN
public/audio/cry/718-10-complete.m4a
Normal file
BIN
public/audio/cry/718-10-complete.m4a
Normal file
Binary file not shown.
@ -4,7 +4,7 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import * as Utils from "#app/utils";
|
||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
import { PokeballType } from "#app/data/pokeball";
|
||||
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
|
||||
import { Phase } from "#app/phase";
|
||||
@ -1389,7 +1389,7 @@ export default class BattleScene extends SceneBase {
|
||||
case Species.GRENINJA:
|
||||
return Utils.randSeedInt(2);
|
||||
case Species.ZYGARDE:
|
||||
return Utils.randSeedInt(3);
|
||||
return Utils.randSeedInt(4);
|
||||
case Species.MINIOR:
|
||||
return Utils.randSeedInt(6);
|
||||
case Species.ALCREMIE:
|
||||
@ -2425,7 +2425,7 @@ export default class BattleScene extends SceneBase {
|
||||
return Math.floor(moneyValue / 10) * 10;
|
||||
}
|
||||
|
||||
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise<boolean> {
|
||||
addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): Promise<boolean> {
|
||||
if (!modifier) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
@ -2482,6 +2482,8 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
} else if (modifier instanceof FusePokemonModifier) {
|
||||
args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon);
|
||||
} else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) {
|
||||
args.push(cost);
|
||||
}
|
||||
|
||||
if (modifier.shouldApply(pokemon, ...args)) {
|
||||
|
@ -5654,16 +5654,18 @@ export function initAbilities() {
|
||||
.attr(UnsuppressableAbilityAbAttr)
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
.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
|
||||
.attr(PostBattleInitFormChangeAbAttr, () => 2)
|
||||
.attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
||||
.attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
||||
new Ability(Abilities.POWER_CONSTRUCT, 7)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2)
|
||||
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3)
|
||||
.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(UnswappableAbilityAbAttr)
|
||||
.attr(UnsuppressableAbilityAbAttr)
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
.bypassFaint()
|
||||
.partial(),
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.CORROSION, 7)
|
||||
.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)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Arena } from "#app/field/arena";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
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 { getPokemonNameWithAffix } from "#app/messages";
|
||||
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
|
||||
@ -36,7 +36,7 @@ export abstract class ArenaTag {
|
||||
public side: ArenaTagSide = ArenaTagSide.BOTH
|
||||
) {}
|
||||
|
||||
apply(arena: Arena, args: any[]): boolean {
|
||||
apply(arena: Arena, simulated: boolean, ...args: unknown[]): boolean {
|
||||
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;
|
||||
}
|
||||
@ -157,17 +167,15 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
||||
/**
|
||||
* Applies the weakening effect to the move.
|
||||
*
|
||||
* @param arena - The arena where the move is applied.
|
||||
* @param args - The arguments for the move application.
|
||||
* @param args[0] - The category of the move.
|
||||
* @param args[1] - A boolean indicating whether it is a double battle.
|
||||
* @param args[2] - An object of type `Utils.NumberHolder` that holds the damage multiplier
|
||||
*
|
||||
* @returns True if the move was weakened, otherwise false.
|
||||
* @param arena the {@linkcode Arena} where the move is applied.
|
||||
* @param simulated n/a
|
||||
* @param moveCategory the attacking move's {@linkcode MoveCategory}.
|
||||
* @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier
|
||||
* @returns `true` if the attacking move was weakened; `false` otherwise.
|
||||
*/
|
||||
apply(arena: Arena, args: any[]): boolean {
|
||||
if (this.weakenedCategories.includes((args[0] as MoveCategory))) {
|
||||
(args[2] as Utils.NumberHolder).value = (args[1] as boolean) ? 2732 / 4096 : 0.5;
|
||||
override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean {
|
||||
if (this.weakenedCategories.includes(moveCategory)) {
|
||||
damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -249,38 +257,34 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
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
|
||||
* @param arena The arena containing this tag
|
||||
* @param args\[0\] (Utils.BooleanHolder) Signals if the move is cancelled
|
||||
* @param args\[1\] (Pokemon) The Pokemon using the move
|
||||
* @param args\[2\] (Pokemon) The intended target of the move
|
||||
* @param args\[3\] (Moves) The parameters to the condition function
|
||||
* @param args\[4\] (Utils.BooleanHolder) Signals if the applied protection supercedes protection-ignoring effects
|
||||
* @returns
|
||||
* @param arena the {@linkcode Arena} containing this tag
|
||||
* @param simulated `true` if the tag is applied quietly; `false` otherwise.
|
||||
* @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against
|
||||
* @param attacker the attacking {@linkcode Pokemon}
|
||||
* @param defender the defending {@linkcode Pokemon}
|
||||
* @param moveId the {@linkcode Moves | identifier} for the move being used
|
||||
* @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 {
|
||||
const [ cancelled, user, target, moveId, ignoresBypass ] = args;
|
||||
override apply(arena: Arena, simulated: boolean, isProtected: BooleanHolder, attacker: Pokemon, defender: Pokemon,
|
||||
moveId: Moves, ignoresProtectBypass: BooleanHolder): boolean {
|
||||
|
||||
if (cancelled instanceof Utils.BooleanHolder
|
||||
&& user instanceof Pokemon
|
||||
&& target instanceof Pokemon
|
||||
&& typeof moveId === "number"
|
||||
&& ignoresBypass instanceof Utils.BooleanHolder) {
|
||||
if ((this.side === ArenaTagSide.PLAYER) === defender.isPlayer()
|
||||
&& this.protectConditionFunc(arena, moveId)) {
|
||||
if (!isProtected.value) {
|
||||
isProtected.value = true;
|
||||
if (!simulated) {
|
||||
attacker.stopMultiHit(defender);
|
||||
|
||||
if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer()
|
||||
&& this.protectConditionFunc(arena, moveId)) {
|
||||
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) }));
|
||||
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene);
|
||||
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) }));
|
||||
}
|
||||
|
||||
ignoresBypass.value = ignoresBypass.value || this.ignoresBypass;
|
||||
return true;
|
||||
}
|
||||
|
||||
ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -296,7 +300,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
*/
|
||||
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||
const move = allMoves[moveId];
|
||||
const priority = new Utils.NumberHolder(move.priority);
|
||||
const priority = new NumberHolder(move.priority);
|
||||
const effectPhase = arena.scene.getCurrentPhase();
|
||||
|
||||
if (effectPhase instanceof MoveEffectPhase) {
|
||||
@ -460,7 +464,7 @@ class WishTag extends ArenaTag {
|
||||
if (user) {
|
||||
this.battlerIndex = user.getBattlerIndex();
|
||||
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
|
||||
this.healHp = Utils.toDmgValue(user.getMaxHp() / 2);
|
||||
this.healHp = toDmgValue(user.getMaxHp() / 2);
|
||||
} else {
|
||||
console.warn("Failed to get source for WishTag onAdd");
|
||||
}
|
||||
@ -497,12 +501,19 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
||||
this.weakenedType = type;
|
||||
}
|
||||
|
||||
apply(arena: Arena, args: any[]): boolean {
|
||||
if ((args[0] as Type) === this.weakenedType) {
|
||||
(args[1] as Utils.NumberHolder).value *= 0.33;
|
||||
/**
|
||||
* Reduces an attack's power by 0.33x if it matches this tag's weakened type.
|
||||
* @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 false;
|
||||
}
|
||||
}
|
||||
@ -563,13 +574,12 @@ export class IonDelugeTag extends ArenaTag {
|
||||
/**
|
||||
* Converts Normal-type moves to Electric type
|
||||
* @param arena n/a
|
||||
* @param args
|
||||
* - `[0]` {@linkcode Utils.NumberHolder} A container with a move's {@linkcode Type}
|
||||
* @param simulated n/a
|
||||
* @param moveType a {@linkcode NumberHolder} containing a move's {@linkcode Type}
|
||||
* @returns `true` if the given move type changed; `false` otherwise.
|
||||
*/
|
||||
apply(arena: Arena, args: any[]): boolean {
|
||||
const moveType = args[0];
|
||||
if (moveType instanceof Utils.NumberHolder && moveType.value === Type.NORMAL) {
|
||||
override apply(arena: Arena, simulated: boolean, moveType: NumberHolder): boolean {
|
||||
if (moveType.value === Type.NORMAL) {
|
||||
moveType.value = Type.ELECTRIC;
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.activateTrap(pokemon);
|
||||
return this.activateTrap(pokemon, simulated);
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -651,14 +667,18 @@ class SpikesTag extends ArenaTrapTag {
|
||||
}
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
if (pokemon.isGrounded()) {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
|
||||
if (simulated) {
|
||||
return !cancelled.value;
|
||||
}
|
||||
|
||||
if (!cancelled.value) {
|
||||
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.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 (simulated) {
|
||||
return true;
|
||||
}
|
||||
if (pokemon.isOfType(Type.POISON)) {
|
||||
this.neutralized = true;
|
||||
if (pokemon.scene.arena.removeTag(this.tagType)) {
|
||||
@ -807,8 +830,8 @@ class StealthRockTag extends ArenaTrapTag {
|
||||
return damageHpRatio;
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
|
||||
if (cancelled.value) {
|
||||
@ -818,12 +841,16 @@ class StealthRockTag extends ArenaTrapTag {
|
||||
const damageHpRatio = this.getDamageHpRatio(pokemon);
|
||||
|
||||
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.damageAndUpdate(damage, HitResult.OTHER);
|
||||
if (pokemon.turnData) {
|
||||
pokemon.turnData.damageTaken += damage;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -853,14 +880,20 @@ class StickyWebTag extends ArenaTrapTag {
|
||||
}
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
if (pokemon.isGrounded()) {
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
||||
|
||||
if (simulated) {
|
||||
return !cancelled.value;
|
||||
}
|
||||
|
||||
if (!cancelled.value) {
|
||||
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));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -879,8 +912,15 @@ export class TrickRoomTag extends ArenaTag {
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
@ -1087,7 +1127,7 @@ class FireGrassPledgeTag extends ArenaTag {
|
||||
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
// TODO: Replace this with a proper animation
|
||||
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);
|
||||
@ -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" : ""}`));
|
||||
}
|
||||
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
@ -808,7 +808,7 @@ export default class Move implements Localizable {
|
||||
source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1026,7 +1026,7 @@ export class MoveEffectAttr extends MoveAttr {
|
||||
|
||||
if (!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) {
|
||||
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) {
|
||||
|
@ -799,8 +799,8 @@ export const pokemonFormChanges: PokemonFormChanges = {
|
||||
[Species.ZYGARDE]: [
|
||||
new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", 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, "complete", "10-pc", new SpeciesFormChangeManualTrigger(), true)
|
||||
new SpeciesFormChange(Species.ZYGARDE, "10-pc", "10-complete", new SpeciesFormChangeManualTrigger(), true),
|
||||
new SpeciesFormChange(Species.ZYGARDE, "10-complete", "10-pc", new SpeciesFormChangeManualTrigger(), true)
|
||||
],
|
||||
[Species.DIANCIE]: [
|
||||
new SpeciesFormChange(Species.DIANCIE, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE))
|
||||
|
@ -425,6 +425,7 @@ export abstract class PokemonSpeciesForm {
|
||||
case "hero":
|
||||
case "roaming":
|
||||
case "complete":
|
||||
case "10-complete":
|
||||
case "10":
|
||||
case "10-pc":
|
||||
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("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("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 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),
|
||||
|
@ -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
|
||||
* @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 simulated if `true`, this applies arena tags without changing game state
|
||||
* @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"
|
||||
? this.tags.filter(t => t.tagType === tagType)
|
||||
: this.tags.filter(t => t instanceof tagType);
|
||||
if (side !== ArenaTagSide.BOTH) {
|
||||
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)
|
||||
* by calling {@linkcode applyTagsForSide()}
|
||||
* @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
|
||||
*/
|
||||
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, ...args: unknown[]): void {
|
||||
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args);
|
||||
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, simulated: boolean, ...args: unknown[]): void {
|
||||
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1730,7 +1730,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, 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)) {
|
||||
moveTypeHolder.value = Type.ELECTRIC;
|
||||
}
|
||||
@ -2800,7 +2800,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) */
|
||||
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:
|
||||
|
@ -2227,7 +2227,8 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base
|
||||
],
|
||||
[
|
||||
new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8),
|
||||
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75)
|
||||
new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75),
|
||||
new ModifierTypeOption(modifierTypes.MEMORY_MUSHROOM(), 0, baseCost * 4)
|
||||
],
|
||||
[
|
||||
new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5),
|
||||
|
@ -11,7 +11,7 @@ import Pokemon, { type PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import Overrides from "#app/overrides";
|
||||
import { EvolutionPhase } from "#app/phases/evolution-phase";
|
||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
|
||||
import { LevelUpPhase } from "#app/phases/level-up-phase";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||
import { achvs } from "#app/system/achv";
|
||||
@ -2235,7 +2235,7 @@ export class TmModifier extends ConsumablePokemonModifier {
|
||||
*/
|
||||
override apply(playerPokemon: PlayerPokemon): boolean {
|
||||
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, true));
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2255,8 +2255,9 @@ export class RememberMoveModifier extends ConsumablePokemonModifier {
|
||||
* @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move
|
||||
* @returns always `true`
|
||||
*/
|
||||
override apply(playerPokemon: PlayerPokemon): boolean {
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex]));
|
||||
override apply(playerPokemon: PlayerPokemon, cost?: number): boolean {
|
||||
|
||||
playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2,24 +2,37 @@ import BattleScene from "#app/battle-scene";
|
||||
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
|
||||
import Move, { allMoves } from "#app/data/move";
|
||||
import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import Overrides from "#app/overrides";
|
||||
import EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
|
||||
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import i18next from "i18next";
|
||||
import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase";
|
||||
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
|
||||
import Pokemon from "#app/field/pokemon";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
|
||||
export enum LearnMoveType {
|
||||
/** For learning a move via level-up, evolution, or other non-item-based event */
|
||||
LEARN_MOVE,
|
||||
/** For learning a move via Memory Mushroom */
|
||||
MEMORY,
|
||||
/** For learning a move via TM */
|
||||
TM
|
||||
}
|
||||
|
||||
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
||||
private moveId: Moves;
|
||||
private messageMode: Mode;
|
||||
private fromTM: boolean;
|
||||
private learnMoveType;
|
||||
private cost: number;
|
||||
|
||||
constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, fromTM?: boolean) {
|
||||
constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) {
|
||||
super(scene, partyMemberIndex);
|
||||
this.moveId = moveId;
|
||||
this.fromTM = fromTM ?? false;
|
||||
this.learnMoveType = learnMoveType;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -136,11 +149,23 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
||||
* @param Pokemon The Pokemon learning the move
|
||||
*/
|
||||
async learnMove(index: number, move: Move, pokemon: Pokemon, textMessage?: string) {
|
||||
if (this.fromTM) {
|
||||
if (this.learnMoveType === LearnMoveType.TM) {
|
||||
if (!pokemon.usedTMs) {
|
||||
pokemon.usedTMs = [];
|
||||
}
|
||||
pokemon.usedTMs.push(this.moveId);
|
||||
this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase);
|
||||
} else if (this.learnMoveType === LearnMoveType.MEMORY) {
|
||||
if (this.cost !== -1) {
|
||||
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
this.scene.money -= this.cost;
|
||||
this.scene.updateMoneyText();
|
||||
this.scene.animateMoneyChanged(false);
|
||||
}
|
||||
this.scene.playSound("se/buy");
|
||||
} else {
|
||||
this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase);
|
||||
}
|
||||
}
|
||||
pokemon.setMove(index, this.moveId);
|
||||
initMoveAnim(this.scene, this.moveId).then(() => {
|
||||
|
@ -99,8 +99,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
|
||||
const hasActiveTargets = targets.some(t => t.isActive(true));
|
||||
|
||||
/** Check if the target is immune via ability to the attacking move */
|
||||
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(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))
|
||||
&& !targets[0].getTag(SemiInvulnerableTag);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */
|
||||
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? */
|
||||
@ -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)))
|
||||
|| (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? */
|
||||
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
|
||||
/** 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))
|
||||
&& !target.getTag(SemiInvulnerableTag);
|
||||
|
||||
/**
|
||||
* If the move missed a target, stop all future hits against that target
|
||||
|
@ -147,8 +147,9 @@ export class MovePhase extends BattlePhase {
|
||||
const moveQueue = this.pokemon.getMoveQueue();
|
||||
|
||||
if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) {
|
||||
this.showMoveText();
|
||||
this.showFailedText();
|
||||
this.cancelled = true;
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ export class PostSummonPhase extends PokemonPhase {
|
||||
if (pokemon.status?.effect === StatusEffect.TOXIC) {
|
||||
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.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) {
|
||||
|
@ -16,26 +16,32 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
private rerollCount: integer;
|
||||
private modifierTiers?: ModifierTier[];
|
||||
private customModifierSettings?: CustomModifierSettings;
|
||||
private isCopy: boolean;
|
||||
|
||||
constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings) {
|
||||
private typeOptions: ModifierTypeOption[];
|
||||
|
||||
constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) {
|
||||
super(scene);
|
||||
|
||||
this.rerollCount = rerollCount;
|
||||
this.modifierTiers = modifierTiers;
|
||||
this.customModifierSettings = customModifierSettings;
|
||||
this.isCopy = isCopy;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
if (!this.rerollCount) {
|
||||
if (!this.rerollCount && !this.isCopy) {
|
||||
this.updateSeed();
|
||||
} else {
|
||||
} else if (this.rerollCount) {
|
||||
this.scene.reroll = false;
|
||||
}
|
||||
|
||||
const party = this.scene.getParty();
|
||||
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
|
||||
if (!this.isCopy) {
|
||||
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
|
||||
}
|
||||
const modifierCount = new Utils.IntegerHolder(3);
|
||||
if (this.isPlayer()) {
|
||||
this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount);
|
||||
@ -54,7 +60,7 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
}
|
||||
}
|
||||
|
||||
const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value);
|
||||
this.typeOptions = this.getModifierTypeOptions(modifierCount.value);
|
||||
|
||||
const modifierSelectCallback = (rowCursor: integer, cursor: integer) => {
|
||||
if (rowCursor < 0 || cursor < 0) {
|
||||
@ -63,13 +69,13 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
this.scene.ui.revertMode();
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
super.end();
|
||||
}, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)));
|
||||
}, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
let modifierType: ModifierType;
|
||||
let cost: integer;
|
||||
const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers);
|
||||
const rerollCost = this.getRerollCost(this.scene.lockModifierTiers);
|
||||
switch (rowCursor) {
|
||||
case 0:
|
||||
switch (cursor) {
|
||||
@ -79,7 +85,7 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
return false;
|
||||
} else {
|
||||
this.scene.reroll = true;
|
||||
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[]));
|
||||
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[]));
|
||||
this.scene.ui.clearText();
|
||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
|
||||
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
@ -98,13 +104,13 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
const itemModifier = itemModifiers[itemIndex];
|
||||
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
|
||||
} else {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
}, PartyUiHandler.FilterItemMaxStacks);
|
||||
break;
|
||||
case 2:
|
||||
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
@ -115,21 +121,21 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
}
|
||||
this.scene.lockModifierTiers = !this.scene.lockModifierTiers;
|
||||
const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
|
||||
uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers));
|
||||
uiHandler.updateLockRaritiesText();
|
||||
uiHandler.updateRerollCostText();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case 1:
|
||||
if (typeOptions.length === 0) {
|
||||
if (this.typeOptions.length === 0) {
|
||||
this.scene.ui.clearText();
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
super.end();
|
||||
return true;
|
||||
}
|
||||
if (typeOptions[cursor].type) {
|
||||
modifierType = typeOptions[cursor].type;
|
||||
if (this.typeOptions[cursor].type) {
|
||||
modifierType = this.typeOptions[cursor].type;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -151,8 +157,16 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
}
|
||||
|
||||
const applyModifier = (modifier: Modifier, playSound: boolean = false) => {
|
||||
const result = this.scene.addModifier(modifier, false, playSound);
|
||||
if (cost) {
|
||||
const result = this.scene.addModifier(modifier, false, playSound, undefined, undefined, cost);
|
||||
// Queue a copy of this phase when applying a TM or Memory Mushroom.
|
||||
// If the player selects either of these, then escapes out of consuming them,
|
||||
// they are returned to a shop in the same state.
|
||||
if (modifier.type instanceof RememberMoveModifierType ||
|
||||
modifier.type instanceof TmModifierType) {
|
||||
this.scene.unshiftPhase(this.copy());
|
||||
}
|
||||
|
||||
if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
|
||||
result.then(success => {
|
||||
if (success) {
|
||||
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
@ -189,7 +203,7 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
applyModifier(modifier, true);
|
||||
});
|
||||
} else {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
}, modifierType.selectFilter);
|
||||
} else {
|
||||
@ -216,7 +230,7 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
applyModifier(modifier!, true); // TODO: is the bang correct?
|
||||
});
|
||||
} else {
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
}, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier);
|
||||
}
|
||||
@ -226,7 +240,7 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
|
||||
return !cost!;// TODO: is the bang correct?
|
||||
};
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
|
||||
this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers));
|
||||
}
|
||||
|
||||
updateSeed(): void {
|
||||
@ -237,13 +251,13 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
return true;
|
||||
}
|
||||
|
||||
getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): number {
|
||||
getRerollCost(lockRarities: boolean): number {
|
||||
let baseValue = 0;
|
||||
if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
|
||||
return baseValue;
|
||||
} else if (lockRarities) {
|
||||
const tierValues = [ 50, 125, 300, 750, 2000 ];
|
||||
for (const opt of typeOptions) {
|
||||
for (const opt of this.typeOptions) {
|
||||
baseValue += tierValues[opt.type.tier ?? 0];
|
||||
}
|
||||
} else {
|
||||
@ -271,6 +285,16 @@ export class SelectModifierPhase extends BattlePhase {
|
||||
return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings);
|
||||
}
|
||||
|
||||
copy(): SelectModifierPhase {
|
||||
return new SelectModifierPhase(
|
||||
this.scene,
|
||||
this.rerollCount,
|
||||
this.modifierTiers,
|
||||
{ guaranteedModifierTypeOptions: this.typeOptions, rerollMultiplier: this.customModifierSettings?.rerollMultiplier, allowLuckUpgrades: false },
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
addModifier(modifier: Modifier): Promise<boolean> {
|
||||
return this.scene.addModifier(modifier, false, true);
|
||||
}
|
||||
|
@ -64,8 +64,7 @@ export class StatStageChangePhase extends PokemonPhase {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
|
||||
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, cancelled);
|
||||
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled);
|
||||
}
|
||||
|
||||
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.
|
||||
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.
|
||||
orderedTargets.sort((a: Pokemon, b: Pokemon) => {
|
||||
|
@ -32,7 +32,7 @@ describe("Abilities - POWER CONSTRUCT", () => {
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const baseForm = 2,
|
||||
completeForm = 4;
|
||||
@ -41,7 +41,37 @@ describe("Abilities - POWER CONSTRUCT", () => {
|
||||
[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);
|
||||
expect(zygarde).not.toBe(undefined);
|
||||
|
@ -71,4 +71,23 @@ describe("Abilities - Volt Absorb", () => {
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
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());
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phase from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
@ -32,15 +33,16 @@ describe("Items - Lock Capsule", () => {
|
||||
});
|
||||
|
||||
it("doesn't set the cost of common tier items to 0", async () => {
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
game.scene.overridePhase(new SelectModifierPhase(game.scene, 0, undefined, { guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON ], fillRemaining: false }));
|
||||
|
||||
game.move.select(Moves.SURF);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
|
||||
const selectModifierPhase = game.scene.getCurrentPhase() as SelectModifierPhase;
|
||||
const rerollCost = selectModifierPhase.getRerollCost(true);
|
||||
expect(rerollCost).toBe(150);
|
||||
});
|
||||
|
||||
const rewards = game.scene.getCurrentPhase() as SelectModifierPhase;
|
||||
const potion = new ModifierTypeOption(modifierTypes.POTION(), 0, 40); // Common tier item
|
||||
const rerollCost = rewards.getRerollCost([ potion, potion, potion ], true);
|
||||
|
||||
expect(rerollCost).toBe(150);
|
||||
game.doSelectModifier();
|
||||
await game.phaseInterceptor.to("SelectModifierPhase");
|
||||
}, 20000);
|
||||
});
|
||||
|
@ -111,7 +111,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
|
||||
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;
|
||||
|
@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
|
||||
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;
|
||||
|
@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
|
||||
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
|
||||
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;
|
||||
|
@ -63,11 +63,11 @@ describe("SelectModifierPhase", () => {
|
||||
new ModifierTypeOption(modifierTypes.REVIVE(), 0, 1000)
|
||||
];
|
||||
|
||||
const selectModifierPhase1 = new SelectModifierPhase(scene);
|
||||
const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { rerollMultiplier: 2 });
|
||||
const selectModifierPhase1 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options });
|
||||
const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options, rerollMultiplier: 2 });
|
||||
|
||||
const cost1 = selectModifierPhase1.getRerollCost(options, false);
|
||||
const cost2 = selectModifierPhase2.getRerollCost(options, false);
|
||||
const cost1 = selectModifierPhase1.getRerollCost(false);
|
||||
const cost2 = selectModifierPhase2.getRerollCost(false);
|
||||
expect(cost2).toEqual(cost1 * 2);
|
||||
});
|
||||
|
||||
|
@ -16,7 +16,7 @@ import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
||||
import { IntegerHolder } from "./../utils";
|
||||
import Phaser from "phaser";
|
||||
|
||||
export const SHOP_OPTIONS_ROW_LIMIT = 6;
|
||||
export const SHOP_OPTIONS_ROW_LIMIT = 7;
|
||||
|
||||
export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
||||
private modifierContainer: Phaser.GameObjects.Container;
|
||||
@ -211,7 +211,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
|
||||
const row = m < SHOP_OPTIONS_ROW_LIMIT ? 0 : 1;
|
||||
const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT;
|
||||
const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT);
|
||||
const sliceWidth = (this.scene.game.canvas.width / SHOP_OPTIONS_ROW_LIMIT) / (rowOptions.length + 2);
|
||||
const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2);
|
||||
const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (40 - (28 * row - 1))), shopTypeOptions[m]);
|
||||
option.setScale(0.375);
|
||||
this.scene.add.existing(option);
|
||||
|
@ -587,13 +587,13 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]";
|
||||
rules.push(typeText);
|
||||
break;
|
||||
case Challenges.FRESH_START:
|
||||
rules.push(i18next.t("challenges:freshStart.name"));
|
||||
break;
|
||||
case Challenges.INVERSE_BATTLE:
|
||||
//
|
||||
rules.push(i18next.t("challenges:inverseBattle.shortName"));
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user