mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-21 14:59:26 +02:00
Optional parameter interfaces for MoveEffectAttr
and StatStageChangeAttr
This commit is contained in:
parent
5e7f2042fc
commit
af9f58610f
130
src/data/move.ts
130
src/data/move.ts
@ -967,6 +967,17 @@ export enum MoveEffectTrigger {
|
|||||||
POST_TARGET,
|
POST_TARGET,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MoveEffectOptions {
|
||||||
|
/** Should this effect only apply on the first hit? */
|
||||||
|
firstHitOnly?: boolean;
|
||||||
|
/** Should this effect only apply on the last hit? */
|
||||||
|
lastHitOnly?: boolean;
|
||||||
|
/** Should this effect only apply on the first target hit? */
|
||||||
|
firstTargetOnly?: boolean;
|
||||||
|
/** Overrides the secondary effect chance for this attr if set. */
|
||||||
|
effectChanceOverride?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** Base class defining all Move Effect Attributes
|
/** Base class defining all Move Effect Attributes
|
||||||
* @extends MoveAttr
|
* @extends MoveAttr
|
||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
@ -976,22 +987,28 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
* @see {@linkcode phases.MoveEffectPhase.start}
|
* @see {@linkcode phases.MoveEffectPhase.start}
|
||||||
*/
|
*/
|
||||||
public trigger: MoveEffectTrigger;
|
public trigger: MoveEffectTrigger;
|
||||||
/** Should this effect only apply on the first hit? */
|
protected options?: MoveEffectOptions;
|
||||||
public firstHitOnly: boolean;
|
|
||||||
/** Should this effect only apply on the last hit? */
|
|
||||||
public lastHitOnly: boolean;
|
|
||||||
/** Should this effect only apply on the first target hit? */
|
|
||||||
public firstTargetOnly: boolean;
|
|
||||||
/** Overrides the secondary effect chance for this attr if set. */
|
|
||||||
public effectChanceOverride?: number;
|
|
||||||
|
|
||||||
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false, effectChanceOverride?: number) {
|
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, options?: MoveEffectOptions) {
|
||||||
super(selfTarget);
|
super(selfTarget);
|
||||||
this.trigger = trigger ?? MoveEffectTrigger.POST_APPLY;
|
this.trigger = trigger ?? MoveEffectTrigger.POST_APPLY;
|
||||||
this.firstHitOnly = firstHitOnly;
|
this.options = options;
|
||||||
this.lastHitOnly = lastHitOnly;
|
}
|
||||||
this.firstTargetOnly = firstTargetOnly;
|
|
||||||
this.effectChanceOverride = effectChanceOverride;
|
public get firstHitOnly () {
|
||||||
|
return this.options?.firstHitOnly ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get lastHitOnly () {
|
||||||
|
return this.options?.lastHitOnly ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get firstTargetOnly () {
|
||||||
|
return this.options?.firstTargetOnly ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get effectChanceOverride () {
|
||||||
|
return this.options?.effectChanceOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1316,7 +1333,7 @@ export class RecoilAttr extends MoveEffectAttr {
|
|||||||
private unblockable: boolean;
|
private unblockable: boolean;
|
||||||
|
|
||||||
constructor(useHp: boolean = false, damageRatio: number = 0.25, unblockable: boolean = false) {
|
constructor(useHp: boolean = false, damageRatio: number = 0.25, unblockable: boolean = false) {
|
||||||
super(true, MoveEffectTrigger.POST_APPLY, false, true);
|
super(true, MoveEffectTrigger.POST_APPLY, { lastHitOnly: true });
|
||||||
|
|
||||||
this.useHp = useHp;
|
this.useHp = useHp;
|
||||||
this.damageRatio = damageRatio;
|
this.damageRatio = damageRatio;
|
||||||
@ -2396,7 +2413,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
|
|||||||
* @param ...effects - List of status effects to cure
|
* @param ...effects - List of status effects to cure
|
||||||
*/
|
*/
|
||||||
constructor(selfTarget: boolean, ...effects: StatusEffect[]) {
|
constructor(selfTarget: boolean, ...effects: StatusEffect[]) {
|
||||||
super(selfTarget, MoveEffectTrigger.POST_APPLY, false, true);
|
super(selfTarget, MoveEffectTrigger.POST_APPLY, { lastHitOnly: true });
|
||||||
|
|
||||||
this.effects = effects;
|
this.effects = effects;
|
||||||
}
|
}
|
||||||
@ -2774,6 +2791,15 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface StatStageChangeAttrOptions {
|
||||||
|
condition?: MoveConditionFunc,
|
||||||
|
showMessage?: boolean,
|
||||||
|
firstHitOnly?: boolean,
|
||||||
|
lastHitOnly?: boolean,
|
||||||
|
firstTargetOnly?: boolean,
|
||||||
|
effectChanceOverride?: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute used for moves that change stat stages
|
* Attribute used for moves that change stat stages
|
||||||
*
|
*
|
||||||
@ -2794,15 +2820,21 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
|
|||||||
export class StatStageChangeAttr extends MoveEffectAttr {
|
export class StatStageChangeAttr extends MoveEffectAttr {
|
||||||
public stats: BattleStat[];
|
public stats: BattleStat[];
|
||||||
public stages: integer;
|
public stages: integer;
|
||||||
private condition?: MoveConditionFunc | null;
|
protected override options?: StatStageChangeAttrOptions;
|
||||||
private showMessage: boolean;
|
|
||||||
|
|
||||||
constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false, effectChanceOverride?: number) {
|
constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, options?: StatStageChangeAttrOptions) {
|
||||||
super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly, effectChanceOverride);
|
super(selfTarget, moveEffectTrigger, options);
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
this.stages = stages;
|
this.stages = stages;
|
||||||
this.condition = condition;
|
this.options = options;
|
||||||
this.showMessage = showMessage;
|
}
|
||||||
|
|
||||||
|
private get condition () {
|
||||||
|
return this.options?.condition ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get showMessage () {
|
||||||
|
return this.options?.showMessage ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2887,20 +2919,6 @@ export class SecretPowerAttr extends MoveEffectAttr {
|
|||||||
super(false);
|
super(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to determine if the move should apply a secondary effect based on Secret Power's 30% chance
|
|
||||||
* @returns `true` if the move's secondary effect should apply
|
|
||||||
*/
|
|
||||||
override canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
|
||||||
this.effectChanceOverride = move.chance;
|
|
||||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget);
|
|
||||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to apply the secondary effect to the target Pokemon
|
* Used to apply the secondary effect to the target Pokemon
|
||||||
* @returns `true` if a secondary effect is successfully applied
|
* @returns `true` if a secondary effect is successfully applied
|
||||||
@ -2917,8 +2935,6 @@ export class SecretPowerAttr extends MoveEffectAttr {
|
|||||||
const biome = user.scene.arena.biomeType;
|
const biome = user.scene.arena.biomeType;
|
||||||
secondaryEffect = this.determineBiomeEffect(biome);
|
secondaryEffect = this.determineBiomeEffect(biome);
|
||||||
}
|
}
|
||||||
// effectChanceOverride used in the application of the actual secondary effect
|
|
||||||
secondaryEffect.effectChanceOverride = 100;
|
|
||||||
return secondaryEffect.apply(user, target, move, []);
|
return secondaryEffect.apply(user, target, move, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3094,7 +3110,7 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
|
|||||||
private messageCallback: ((user: Pokemon) => void) | undefined;
|
private messageCallback: ((user: Pokemon) => void) | undefined;
|
||||||
|
|
||||||
constructor(stat: BattleStat[], levels: integer, cutRatio: integer, messageCallback?: ((user: Pokemon) => void) | undefined) {
|
constructor(stat: BattleStat[], levels: integer, cutRatio: integer, messageCallback?: ((user: Pokemon) => void) | undefined) {
|
||||||
super(stat, levels, true, null, true);
|
super(stat, levels, true);
|
||||||
|
|
||||||
this.cutRatio = cutRatio;
|
this.cutRatio = cutRatio;
|
||||||
this.messageCallback = messageCallback;
|
this.messageCallback = messageCallback;
|
||||||
@ -4844,7 +4860,7 @@ export class BypassRedirectAttr extends MoveAttr {
|
|||||||
|
|
||||||
export class FrenzyAttr extends MoveEffectAttr {
|
export class FrenzyAttr extends MoveEffectAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(true, MoveEffectTrigger.HIT, false, true);
|
super(true, MoveEffectTrigger.HIT, { lastHitOnly: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
||||||
@ -4886,7 +4902,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||||||
private failOnOverlap: boolean;
|
private failOnOverlap: boolean;
|
||||||
|
|
||||||
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer, lastHitOnly: boolean = false, cancelOnFail: boolean = false) {
|
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer, lastHitOnly: boolean = false, cancelOnFail: boolean = false) {
|
||||||
super(selfTarget, MoveEffectTrigger.POST_APPLY, false, lastHitOnly);
|
super(selfTarget, MoveEffectTrigger.POST_APPLY, { lastHitOnly: lastHitOnly });
|
||||||
|
|
||||||
this.tagType = tagType;
|
this.tagType = tagType;
|
||||||
this.turnCountMin = turnCountMin;
|
this.turnCountMin = turnCountMin;
|
||||||
@ -5625,7 +5641,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
private selfSwitch: boolean = false,
|
private selfSwitch: boolean = false,
|
||||||
private switchType: SwitchType = SwitchType.SWITCH
|
private switchType: SwitchType = SwitchType.SWITCH
|
||||||
) {
|
) {
|
||||||
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
super(false, MoveEffectTrigger.POST_APPLY, { lastHitOnly: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
isBatonPass() {
|
isBatonPass() {
|
||||||
@ -6777,7 +6793,7 @@ export class DiscourageFrequentUseAttr extends MoveAttr {
|
|||||||
|
|
||||||
export class MoneyAttr extends MoveEffectAttr {
|
export class MoneyAttr extends MoveEffectAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(true, MoveEffectTrigger.HIT, true);
|
super(true, MoveEffectTrigger.HIT, { firstHitOnly: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||||
@ -8871,7 +8887,7 @@ export function initMoves() {
|
|||||||
// If any fielded pokémon is grass-type and grounded.
|
// If any fielded pokémon is grass-type and grounded.
|
||||||
return [ ...user.scene.getEnemyParty(), ...user.scene.getParty() ].some((poke) => poke.isOfType(Type.GRASS) && poke.isGrounded());
|
return [ ...user.scene.getEnemyParty(), ...user.scene.getParty() ].some((poke) => poke.isOfType(Type.GRASS) && poke.isGrounded());
|
||||||
})
|
})
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, (user, target, move) => target.isOfType(Type.GRASS) && target.isGrounded()),
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, undefined, { condition: (user, target, move) => target.isOfType(Type.GRASS) && target.isGrounded() }),
|
||||||
new StatusMove(Moves.STICKY_WEB, Type.BUG, -1, 20, -1, 0, 6)
|
new StatusMove(Moves.STICKY_WEB, Type.BUG, -1, 20, -1, 0, 6)
|
||||||
.attr(AddArenaTrapTagAttr, ArenaTagType.STICKY_WEB)
|
.attr(AddArenaTrapTagAttr, ArenaTagType.STICKY_WEB)
|
||||||
.target(MoveTarget.ENEMY_SIDE),
|
.target(MoveTarget.ENEMY_SIDE),
|
||||||
@ -8909,7 +8925,7 @@ export function initMoves() {
|
|||||||
.soundBased()
|
.soundBased()
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
|
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, false, MoveEffectTrigger.PRE_APPLY)
|
||||||
.attr(ForceSwitchOutAttr, true)
|
.attr(ForceSwitchOutAttr, true)
|
||||||
.soundBased(),
|
.soundBased(),
|
||||||
new StatusMove(Moves.TOPSY_TURVY, Type.DARK, -1, 20, -1, 0, 6)
|
new StatusMove(Moves.TOPSY_TURVY, Type.DARK, -1, 20, -1, 0, 6)
|
||||||
@ -8924,7 +8940,7 @@ export function initMoves() {
|
|||||||
.condition(failIfLastCondition),
|
.condition(failIfLastCondition),
|
||||||
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
||||||
.target(MoveTarget.ALL)
|
.target(MoveTarget.ALL)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, undefined, { condition: (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag) }),
|
||||||
new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6)
|
new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6)
|
||||||
.attr(TerrainChangeAttr, TerrainType.GRASSY)
|
.attr(TerrainChangeAttr, TerrainType.GRASSY)
|
||||||
.target(MoveTarget.BOTH_SIDES),
|
.target(MoveTarget.BOTH_SIDES),
|
||||||
@ -8956,7 +8972,7 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
|
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
|
||||||
.soundBased(),
|
.soundBased(),
|
||||||
new AttackMove(Moves.DIAMOND_STORM, Type.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6)
|
new AttackMove(Moves.DIAMOND_STORM, Type.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true, undefined, undefined, undefined, undefined, true)
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true, undefined, { lastHitOnly: true })
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new AttackMove(Moves.STEAM_ERUPTION, Type.WATER, MoveCategory.SPECIAL, 110, 95, 5, 30, 0, 6)
|
new AttackMove(Moves.STEAM_ERUPTION, Type.WATER, MoveCategory.SPECIAL, 110, 95, 5, 30, 0, 6)
|
||||||
@ -8982,7 +8998,7 @@ export function initMoves() {
|
|||||||
new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6)
|
new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -2),
|
.attr(StatStageChangeAttr, [ Stat.SPATK ], -2),
|
||||||
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, -1, 0, 6)
|
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], -1, false, (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], -1, false, undefined, { condition: (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC })
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
@ -8993,7 +9009,7 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
|
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new StatusMove(Moves.MAGNETIC_FLUX, Type.ELECTRIC, -1, 20, -1, 0, 6)
|
new StatusMove(Moves.MAGNETIC_FLUX, Type.ELECTRIC, -1, 20, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)))
|
.attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, false, undefined, { condition: (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)) })
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.target(MoveTarget.USER_AND_ALLIES)
|
.target(MoveTarget.USER_AND_ALLIES)
|
||||||
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p.hasAbility(a, false)))),
|
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p.hasAbility(a, false)))),
|
||||||
@ -9212,7 +9228,7 @@ export function initMoves() {
|
|||||||
new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7)
|
new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false),
|
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false),
|
||||||
new StatusMove(Moves.GEAR_UP, Type.STEEL, -1, 20, -1, 0, 7)
|
new StatusMove(Moves.GEAR_UP, Type.STEEL, -1, 20, -1, 0, 7)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)))
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, undefined, { condition: (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)) })
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.target(MoveTarget.USER_AND_ALLIES)
|
.target(MoveTarget.USER_AND_ALLIES)
|
||||||
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p.hasAbility(a, false)))),
|
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS ].find(a => p.hasAbility(a, false)))),
|
||||||
@ -9269,7 +9285,7 @@ export function initMoves() {
|
|||||||
.ballBombMove()
|
.ballBombMove()
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7)
|
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, null, true, false, MoveEffectTrigger.HIT, true)
|
.attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, MoveEffectTrigger.HIT, { firstTargetOnly: true })
|
||||||
.soundBased()
|
.soundBased()
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new AttackMove(Moves.DRAGON_HAMMER, Type.DRAGON, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 7),
|
new AttackMove(Moves.DRAGON_HAMMER, Type.DRAGON, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 7),
|
||||||
@ -9383,7 +9399,7 @@ export function initMoves() {
|
|||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new AttackMove(Moves.CLANGOROUS_SOULBLAZE, Type.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
|
new AttackMove(Moves.CLANGOROUS_SOULBLAZE, Type.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, undefined, undefined, undefined, undefined, true)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, undefined, { firstTargetOnly: true })
|
||||||
.soundBased()
|
.soundBased()
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||||
.edgeCase() // I assume it needs clanging scales and Kommo-O
|
.edgeCase() // I assume it needs clanging scales and Kommo-O
|
||||||
@ -9621,8 +9637,8 @@ export function initMoves() {
|
|||||||
.attr(ClearTerrainAttr)
|
.attr(ClearTerrainAttr)
|
||||||
.condition((user, target, move) => !!user.scene.arena.terrain),
|
.condition((user, target, move) => !!user.scene.arena.terrain),
|
||||||
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8)
|
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true, null, true, false, MoveEffectTrigger.HIT, false, true)
|
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true, MoveEffectTrigger.HIT, { lastHitOnly: true })
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, null, true, false, MoveEffectTrigger.HIT, false, true)
|
.attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, MoveEffectTrigger.HIT, { lastHitOnly: true })
|
||||||
.attr(MultiHitAttr)
|
.attr(MultiHitAttr)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, 100, 0, 8)
|
new AttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, 100, 0, 8)
|
||||||
@ -9756,7 +9772,7 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.TRIPLE_ARROWS, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
|
new AttackMove(Moves.TRIPLE_ARROWS, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.attr(HighCritAttr)
|
.attr(HighCritAttr)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], -1, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 50)
|
.attr(StatStageChangeAttr, [ Stat.DEF ], -1, false, undefined, { effectChanceOverride: 50 })
|
||||||
.attr(FlinchAttr),
|
.attr(FlinchAttr),
|
||||||
new AttackMove(Moves.INFERNAL_PARADE, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8)
|
new AttackMove(Moves.INFERNAL_PARADE, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8)
|
||||||
.attr(StatusEffectAttr, StatusEffect.BURN)
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
||||||
@ -9892,7 +9908,7 @@ export function initMoves() {
|
|||||||
.attr(TeraMoveCategoryAttr)
|
.attr(TeraMoveCategoryAttr)
|
||||||
.attr(TeraBlastTypeAttr)
|
.attr(TeraBlastTypeAttr)
|
||||||
.attr(TeraBlastPowerAttr)
|
.attr(TeraBlastPowerAttr)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR))
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, undefined, { condition: (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR) })
|
||||||
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
|
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
|
||||||
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
|
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
|
||||||
.attr(ProtectAttr, BattlerTagType.SILK_TRAP)
|
.attr(ProtectAttr, BattlerTagType.SILK_TRAP)
|
||||||
@ -9975,7 +9991,7 @@ export function initMoves() {
|
|||||||
.attr(RemoveScreensAttr),
|
.attr(RemoveScreensAttr),
|
||||||
new AttackMove(Moves.MAKE_IT_RAIN, Type.STEEL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
new AttackMove(Moves.MAKE_IT_RAIN, Type.STEEL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
||||||
.attr(MoneyAttr)
|
.attr(MoneyAttr)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1, true, null, true, false, MoveEffectTrigger.HIT, true)
|
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1, true, MoveEffectTrigger.HIT, { firstTargetOnly: true })
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new AttackMove(Moves.PSYBLADE, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
|
new AttackMove(Moves.PSYBLADE, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
|
||||||
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1)
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1)
|
||||||
@ -9997,7 +10013,7 @@ export function initMoves() {
|
|||||||
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) }))
|
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||||
.attr(ChillyReceptionAttr, true),
|
.attr(ChillyReceptionAttr, true),
|
||||||
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true)
|
||||||
.attr(RemoveArenaTrapAttr, true)
|
.attr(RemoveArenaTrapAttr, true)
|
||||||
.attr(RemoveAllSubstitutesAttr),
|
.attr(RemoveAllSubstitutesAttr),
|
||||||
new StatusMove(Moves.SNOWSCAPE, Type.ICE, -1, 10, -1, 0, 9)
|
new StatusMove(Moves.SNOWSCAPE, Type.ICE, -1, 10, -1, 0, 9)
|
||||||
|
@ -2,7 +2,7 @@ import { Abilities } from "#enums/abilities";
|
|||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { allMoves, SecretPowerAttr } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
@ -11,6 +11,7 @@ import { StatusEffect } from "#enums/status-effect";
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
|
import { allAbilities, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability";
|
||||||
|
|
||||||
describe("Moves - Secret Power", () => {
|
describe("Moves - Secret Power", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -60,30 +61,38 @@ describe("Moves - Secret Power", () => {
|
|||||||
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1);
|
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("the 'rainbow' effect of fire+water pledge does not double the chance of secret power's secondary effect",
|
it("Secret Power's effect chance is doubled by Serene Grace, but not by the 'rainbow' effect from Fire/Water Pledge",
|
||||||
async () => {
|
async () => {
|
||||||
game.override
|
game.override
|
||||||
.moveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE, Moves.SECRET_POWER, Moves.SPLASH ])
|
.moveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE, Moves.SECRET_POWER, Moves.SPLASH ])
|
||||||
|
.ability(Abilities.SERENE_GRACE)
|
||||||
.enemyMoveset([ Moves.SPLASH ])
|
.enemyMoveset([ Moves.SPLASH ])
|
||||||
.battleType("double");
|
.battleType("double");
|
||||||
await game.classicMode.startBattle([ Species.BLASTOISE, Species.CHARIZARD ]);
|
await game.classicMode.startBattle([ Species.BLASTOISE, Species.CHARIZARD ]);
|
||||||
|
|
||||||
const secretPowerAttr = allMoves[Moves.SECRET_POWER].getAttrs(SecretPowerAttr)[0];
|
const sereneGraceAttr = allAbilities[Abilities.SERENE_GRACE].getAttrs(MoveEffectChanceMultiplierAbAttr)[0];
|
||||||
vi.spyOn(secretPowerAttr, "getMoveChance");
|
vi.spyOn(sereneGraceAttr, "apply");
|
||||||
|
|
||||||
game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY);
|
game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY);
|
||||||
game.move.select(Moves.FIRE_PLEDGE, 1, BattlerIndex.ENEMY_2);
|
game.move.select(Moves.FIRE_PLEDGE, 1, BattlerIndex.ENEMY_2);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.WATER_FIRE_PLEDGE, ArenaTagSide.PLAYER)).toBeDefined();
|
let rainbowEffect = game.scene.arena.getTagOnSide(ArenaTagType.WATER_FIRE_PLEDGE, ArenaTagSide.PLAYER);
|
||||||
|
expect(rainbowEffect).toBeDefined();
|
||||||
|
|
||||||
|
rainbowEffect = rainbowEffect!;
|
||||||
|
vi.spyOn(rainbowEffect, "apply");
|
||||||
|
|
||||||
game.move.select(Moves.SECRET_POWER, 0, BattlerIndex.ENEMY);
|
game.move.select(Moves.SECRET_POWER, 0, BattlerIndex.ENEMY);
|
||||||
game.move.select(Moves.SPLASH, 1);
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(secretPowerAttr.getMoveChance).toHaveLastReturnedWith(30);
|
expect(sereneGraceAttr.apply).toHaveBeenCalledOnce();
|
||||||
|
expect(sereneGraceAttr.apply).toHaveLastReturnedWith(true);
|
||||||
|
|
||||||
|
expect(rainbowEffect.apply).toHaveBeenCalledTimes(0);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user