mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-19 23:02:19 +02:00
Fully implement Flower Gift and Victory Star
This commit is contained in:
parent
aa3f924633
commit
4a1033ce5a
@ -1669,8 +1669,56 @@ export class StatMultiplierAbAttr extends AbAttr {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies a Stat from an ally pokemon's ability.
|
||||
* @see {@link applyAllyStatMultiplierAbAttrs}
|
||||
* @see {@link applyAllyStat}
|
||||
*/
|
||||
export class AllyStatMultiplierAbAttr extends AbAttr {
|
||||
private stat: BattleStat;
|
||||
private multiplier: number;
|
||||
private ignorable: boolean;
|
||||
|
||||
/**
|
||||
* @param stat - The stat being modified
|
||||
* @param multipler - The multiplier to apply to the stat
|
||||
* @param ignorable - Whether the multiplier can be ignored by mold breaker-like moves and abilities
|
||||
*/
|
||||
constructor(stat: BattleStat, multiplier: number, ignorable: boolean = true) {
|
||||
super(false);
|
||||
|
||||
this.stat = stat;
|
||||
this.multiplier = multiplier;
|
||||
this.ignorable = ignorable;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* applyAllyStat: Multiply a Pokemon's Stat due to an Ally's ability.
|
||||
* @param pokemon {@linkcode Pokemon} the ally Pokemon with the ability (unused)
|
||||
* @param passive {@linkcode boolean} unused
|
||||
* @param simulated {@linkcode boolean} whether the ability is being simulated (unused)
|
||||
* @param stat {@linkcode Stat} the type of the checked stat
|
||||
* @param statValue {@linkcode Utils.NumberHolder} the value of the checked stat
|
||||
* @param checkedPokemon {@linkcode Pokemon} the Pokemon this ability is targeting (unused)
|
||||
* @param ignore {@linkcode boolean} Whether the ability should be ignored if possible
|
||||
* @param args {any[]} unused
|
||||
* @returns true if this changed the checked stat, false otherwise.
|
||||
*/
|
||||
applyAllyStat(pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, ignore: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
if (stat === this.stat && !(ignore && this.ignorable)) {
|
||||
statValue.value *= this.multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class PostAttackAbAttr extends AbAttr {
|
||||
private attackCondition: PokemonAttackCondition;
|
||||
|
||||
@ -5559,6 +5607,23 @@ export function applyStatMultiplierAbAttrs(
|
||||
args,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an ally's Stat multiplier attribute
|
||||
* @param attrType {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being
|
||||
* @param pokemon {@linkcode Pokemon} the pokemon with the ability
|
||||
* @param stat {@linkcode Stat} the type of the checked stat
|
||||
* @param statValue {@linkcode Utils.NumberHolder} the value of the checked stat
|
||||
* @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat
|
||||
* @param ignore {@linkcode boolean} Whether or not the ability should be ignored by the pokemon or its move.
|
||||
*
|
||||
* @param args unused
|
||||
*/
|
||||
export function applyAllyStatMultiplierAbAttrs(attrType: Constructor<AllyStatMultiplierAbAttr>,
|
||||
pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, checkedPokemon, ignore, ...args: any[]): void {
|
||||
return applyAbAttrsInternal<AllyStatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyAllyStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, ignore, args), args);
|
||||
}
|
||||
|
||||
export function applyPostSetStatusAbAttrs(
|
||||
attrType: Constructor<PostSetStatusAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
@ -6389,11 +6454,12 @@ export function initAbilities() {
|
||||
new Ability(Abilities.FLOWER_GIFT, 4)
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 1.5)
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.SPDEF, 1.5)
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.ATK, 1.5)
|
||||
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.SPDEF, 1.5)
|
||||
.attr(UncopiableAbilityAbAttr)
|
||||
.attr(NoFusionAbilityAbAttr)
|
||||
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FLOWER_GIFT)
|
||||
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
|
||||
.partial() // Should also boosts stats of ally
|
||||
.ignorable(),
|
||||
new Ability(Abilities.BAD_DREAMS, 4)
|
||||
.attr(PostTurnHurtIfSleepingAbAttr),
|
||||
@ -6529,7 +6595,7 @@ export function initAbilities() {
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.VICTORY_STAR, 5)
|
||||
.attr(StatMultiplierAbAttr, Stat.ACC, 1.1)
|
||||
.partial(), // Does not boost ally's accuracy
|
||||
.attr(AllyStatMultiplierAbAttr, Stat.ACC, 1.1, false),
|
||||
new Ability(Abilities.TURBOBLAZE, 5)
|
||||
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
||||
.attr(MoveAbilityBypassAbAttr),
|
||||
|
@ -4881,8 +4881,8 @@ export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const category = (args[0] as Utils.NumberHolder);
|
||||
|
||||
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true);
|
||||
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true);
|
||||
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true, true, true);
|
||||
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true, true, true);
|
||||
|
||||
if (predictedPhysDmg > predictedSpecDmg) {
|
||||
category.value = MoveCategory.PHYSICAL;
|
||||
|
@ -38,7 +38,7 @@ import {
|
||||
RespectAttackTypeImmunityAttr,
|
||||
MoveTarget,
|
||||
CombinedPledgeStabBoostAttr,
|
||||
VariableMoveTypeChartAttr,
|
||||
VariableMoveTypeChartAttr, MoveFlags,
|
||||
HpSplitAttr
|
||||
} from "#app/data/move";
|
||||
import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
@ -65,7 +65,62 @@ import { WeatherType } from "#enums/weather-type";
|
||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||
import type { Ability, AbAttr } from "#app/data/ability";
|
||||
import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseAbAttrs, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr } from "#app/data/ability";
|
||||
import {
|
||||
StatMultiplierAbAttr,
|
||||
BlockCritAbAttr,
|
||||
BonusCritAbAttr,
|
||||
BypassBurnDamageReductionAbAttr,
|
||||
FieldPriorityMoveImmunityAbAttr,
|
||||
IgnoreOpponentStatStagesAbAttr,
|
||||
MoveImmunityAbAttr,
|
||||
PreDefendFullHpEndureAbAttr,
|
||||
ReceivedMoveDamageMultiplierAbAttr,
|
||||
StabBoostAbAttr,
|
||||
StatusEffectImmunityAbAttr,
|
||||
TypeImmunityAbAttr,
|
||||
WeightMultiplierAbAttr,
|
||||
allAbilities,
|
||||
applyAbAttrs,
|
||||
applyStatMultiplierAbAttrs,
|
||||
applyPreApplyBattlerTagAbAttrs,
|
||||
applyPreAttackAbAttrs,
|
||||
applyPreDefendAbAttrs,
|
||||
applyPreSetStatusAbAttrs,
|
||||
UnsuppressableAbilityAbAttr,
|
||||
NoFusionAbilityAbAttr,
|
||||
MultCritAbAttr,
|
||||
IgnoreTypeImmunityAbAttr,
|
||||
DamageBoostAbAttr,
|
||||
IgnoreTypeStatusEffectImmunityAbAttr,
|
||||
ConditionalCritAbAttr,
|
||||
applyFieldStatMultiplierAbAttrs,
|
||||
FieldMultiplyStatAbAttr,
|
||||
AddSecondStrikeAbAttr,
|
||||
UserFieldStatusEffectImmunityAbAttr,
|
||||
UserFieldBattlerTagImmunityAbAttr,
|
||||
BattlerTagImmunityAbAttr,
|
||||
MoveTypeChangeAbAttr,
|
||||
FullHpResistTypeAbAttr,
|
||||
applyCheckTrappedAbAttrs,
|
||||
CheckTrappedAbAttr,
|
||||
PostSetStatusAbAttr,
|
||||
applyPostSetStatusAbAttrs,
|
||||
InfiltratorAbAttr,
|
||||
AlliedFieldDamageReductionAbAttr,
|
||||
PostDamageAbAttr,
|
||||
applyPostDamageAbAttrs,
|
||||
CommanderAbAttr,
|
||||
applyPostItemLostAbAttrs,
|
||||
PostItemLostAbAttr,
|
||||
AllyStatMultiplierAbAttr,
|
||||
applyAllyStatMultiplierAbAttrs,
|
||||
MoveAbilityBypassAbAttr,
|
||||
applyOnGainAbAttrs,
|
||||
PreLeaveFieldAbAttr,
|
||||
applyPreLeaveFieldAbAttrs,
|
||||
applyOnLoseAbAttrs,
|
||||
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
|
||||
} from "#app/data/ability";
|
||||
import type PokemonData from "#app/system/pokemon-data";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
@ -993,12 +1048,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param move the {@linkcode Move} being used
|
||||
* @param ignoreAbility determines whether this Pokemon's abilities should be ignored during the stat calculation
|
||||
* @param ignoreOppAbility during an attack, determines whether the opposing Pokemon's abilities should be ignored during the stat calculation.
|
||||
* @param ignoreAllyAbility during an attack, determines whether the ally Pokemon's abilities should be ignored during the stat calculation.
|
||||
* @param isCritical determines whether a critical hit has occurred or not (`false` by default)
|
||||
* @param simulated if `true`, nullifies any effects that produce any changes to game state from triggering
|
||||
* @param ignoreHeldItems determines whether this Pokemon's held items should be ignored during the stat calculation, default `false`
|
||||
* @returns the final in-battle value of a stat
|
||||
*/
|
||||
getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreAbility: boolean = false, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true, ignoreHeldItems: boolean = false): number {
|
||||
getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreAbility: boolean = false, ignoreOppAbility: boolean = false, ignoreAllyAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true, ignoreHeldItems: boolean = false): number {
|
||||
const statValue = new Utils.NumberHolder(this.getStat(stat, false));
|
||||
if (!ignoreHeldItems) {
|
||||
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue);
|
||||
@ -1015,6 +1071,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (!ignoreAbility) {
|
||||
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue, simulated);
|
||||
}
|
||||
const ally = this.getAlly();
|
||||
if (!!ally) {
|
||||
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, stat, statValue, simulated, this, move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility);
|
||||
}
|
||||
|
||||
let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
|
||||
|
||||
@ -2669,9 +2729,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, false, sourceMove);
|
||||
|
||||
|
||||
const evasionMultiplier = new Utils.NumberHolder(1);
|
||||
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, target, Stat.EVA, evasionMultiplier);
|
||||
|
||||
const ally = this.getAlly();
|
||||
if (!!ally) {
|
||||
const ignore = this.hasAbilityWithAttr(MoveAbilityBypassAbAttr) || sourceMove.ignoresAbilities;
|
||||
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.ACC, accuracyMultiplier, false, this, ignore);
|
||||
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.EVA, evasionMultiplier, false, this, ignore);
|
||||
}
|
||||
|
||||
return accuracyMultiplier.value / evasionMultiplier.value;
|
||||
}
|
||||
|
||||
@ -2683,11 +2751,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param moveCategory the move's {@linkcode MoveCategory} after variable-category effects are applied.
|
||||
* @param ignoreAbility if `true`, ignores this Pokemon's defensive ability effects (defaults to `false`).
|
||||
* @param ignoreSourceAbility if `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`).
|
||||
* @param ignoreAllyAbility if `true`, ignores the ally Pokemon's ability effects (defaults to `false`).
|
||||
* @param ignoreSourceAllyAbility if `true`, ignores the attacking Pokemon's ally's ability effects (defaults to `false`).
|
||||
* @param isCritical if `true`, calculates effective stats as if the hit were critical (defaults to `false`).
|
||||
* @param simulated if `true`, suppresses changes to game state during calculation (defaults to `true`).
|
||||
* @returns The move's base damage against this Pokemon when used by the source Pokemon.
|
||||
*/
|
||||
getBaseDamage(source: Pokemon, move: Move, moveCategory: MoveCategory, ignoreAbility: boolean = false, ignoreSourceAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number {
|
||||
getBaseDamage(source: Pokemon, move: Move, moveCategory: MoveCategory, ignoreAbility: boolean = false, ignoreSourceAbility: boolean = false, ignoreAllyAbility: boolean = false, ignoreSourceAllyAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number {
|
||||
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
|
||||
|
||||
/** A base damage multiplier based on the source's level */
|
||||
@ -2700,14 +2770,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* The attacker's offensive stat for the given move's category.
|
||||
* Critical hits cause negative stat stages to be ignored.
|
||||
*/
|
||||
const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, isCritical, simulated));
|
||||
const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, ignoreAllyAbility, isCritical, simulated));
|
||||
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk);
|
||||
|
||||
/**
|
||||
* This Pokemon's defensive stat for the given move's category.
|
||||
* Critical hits cause positive stat stages to be ignored.
|
||||
*/
|
||||
const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, isCritical, simulated));
|
||||
const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, ignoreSourceAllyAbility, isCritical, simulated));
|
||||
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
|
||||
|
||||
/**
|
||||
@ -2730,6 +2800,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param move {@linkcode Pokemon} the move used in the attack
|
||||
* @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects
|
||||
* @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects
|
||||
* @param ignoreAllyAbility If `true`, ignores the ally Pokemon's ability effects
|
||||
* @param ignoreSourceAllyAbility If `true`, ignores the ability effects of the attacking pokemon's ally
|
||||
* @param isCritical If `true`, calculates damage for a critical hit.
|
||||
* @param simulated If `true`, suppresses changes to game state during the calculation.
|
||||
* @returns a {@linkcode DamageCalculationResult} object with three fields:
|
||||
@ -2737,7 +2809,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* - `result`: {@linkcode HitResult} indicates the attack's type effectiveness.
|
||||
* - `damage`: `number` the attack's final damage output.
|
||||
*/
|
||||
getAttackDamage(source: Pokemon, move: Move, ignoreAbility: boolean = false, ignoreSourceAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): DamageCalculationResult {
|
||||
getAttackDamage(source: Pokemon, move: Move, ignoreAbility: boolean = false, ignoreSourceAbility: boolean = false, ignoreAllyAbility: boolean = false, ignoreSourceAllyAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): DamageCalculationResult {
|
||||
const damage = new Utils.NumberHolder(0);
|
||||
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
|
||||
@ -2806,7 +2878,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* The attack's base damage, as determined by the source's level, move power
|
||||
* and Attack stat as well as this Pokemon's Defense stat
|
||||
*/
|
||||
const baseDamage = this.getBaseDamage(source, move, moveCategory, ignoreAbility, ignoreSourceAbility, isCritical, simulated);
|
||||
const baseDamage = this.getBaseDamage(source, move, moveCategory, ignoreAbility, ignoreSourceAbility, ignoreAllyAbility, isCritical, simulated);
|
||||
|
||||
/** 25% damage debuff on moves hitting more than one non-fainted target (regardless of immunities) */
|
||||
const { targets, multiple } = getMoveTargets(source, move.id);
|
||||
@ -4985,7 +5057,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
return move.category !== MoveCategory.STATUS
|
||||
&& moveTargets.some(p => {
|
||||
const doesNotFail = move.applyConditions(this, p, move) || [ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id);
|
||||
return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp;
|
||||
return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, !p.getAlly()?.battleData.abilityRevealed, isCritical).damage >= p.hp;
|
||||
});
|
||||
}, this);
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { allAbilities } from "#app/data/ability";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { WeatherType } from "#app/enums/weather-type";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Abilities - Flower Gift", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -28,6 +30,59 @@ describe("Abilities - Flower Gift", () => {
|
||||
expect(game.scene.getPlayerPokemon()?.formIndex).toBe(OVERCAST_FORM);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests damage dealt by a move used against a target before and after Flower Gift is activated.
|
||||
* @param game The game manager instance
|
||||
* @param move The move that should be used
|
||||
* @param allyAttacker True if the ally is attacking the enemy, false if the enemy is attacking the ally
|
||||
* @param ability The ability that the ally pokemon should have
|
||||
* @param enemyAbility The ability that the enemy pokemon should have
|
||||
*
|
||||
* @returns Two numbers, the first being the damage done to the target without flower gift active, the second being the damage done with flower gift active
|
||||
*/
|
||||
const testDamageDealt = async (game: GameManager, move: Moves, allyAttacker: boolean, allyAbility = Abilities.BALL_FETCH, enemyAbility = Abilities.BALL_FETCH): Promise<[number, number]> => {
|
||||
game.override.battleType("double");
|
||||
game.override.moveset([ Moves.SPLASH, Moves.SUNNY_DAY, move, Moves.HEAL_PULSE ]);
|
||||
game.override.enemyMoveset([ Moves.SPLASH, Moves.HEAL_PULSE ]);
|
||||
const target_index = allyAttacker ? BattlerIndex.ENEMY : BattlerIndex.PLAYER_2;
|
||||
const attacker_index = allyAttacker ? BattlerIndex.PLAYER_2 : BattlerIndex.ENEMY;
|
||||
const ally_move = allyAttacker ? move : Moves.SPLASH;
|
||||
const enemy_move = allyAttacker ? Moves.SPLASH : move;
|
||||
const ally_target = allyAttacker ? BattlerIndex.ENEMY : null;
|
||||
|
||||
await game.classicMode.startBattle([ Species.CHERRIM, Species.MAGIKARP ]);
|
||||
const target = allyAttacker ? game.scene.getEnemyField()[0] : game.scene.getPlayerField()[1];
|
||||
const initialHp = target.getMaxHp();
|
||||
|
||||
// Override the ability for the target and attacker only
|
||||
vi.spyOn(game.scene.getPlayerField()[1], "getAbility").mockReturnValue(allAbilities[allyAbility]);
|
||||
vi.spyOn(game.scene.getEnemyField()[0], "getAbility").mockReturnValue(allAbilities[enemyAbility]);
|
||||
|
||||
// turn 1
|
||||
game.move.select(Moves.SUNNY_DAY, 0);
|
||||
game.move.select(ally_move, 1, ally_target);
|
||||
await game.forceEnemyMove(enemy_move, BattlerIndex.PLAYER_2);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
// Ensure sunny day is used last.
|
||||
await game.setTurnOrder([ attacker_index, target_index, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER ]);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
const damageWithoutGift = initialHp - target.hp;
|
||||
|
||||
target.hp = initialHp;
|
||||
|
||||
// turn 2. Make target use recover to reset hp calculation.
|
||||
game.move.select(Moves.SPLASH, 0, target_index);
|
||||
game.move.select(ally_move, 1, ally_target);
|
||||
await game.forceEnemyMove(enemy_move, BattlerIndex.PLAYER_2);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, target_index, attacker_index ]);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
const damageWithGift = initialHp - target.hp;
|
||||
|
||||
return [ damageWithoutGift, damageWithGift ];
|
||||
};
|
||||
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
@ -41,23 +96,24 @@ describe("Abilities - Flower Gift", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.SPLASH, Moves.RAIN_DANCE, Moves.SUNNY_DAY, Moves.SKILL_SWAP ])
|
||||
.moveset([ Moves.SPLASH, Moves.SUNSTEEL_STRIKE, Moves.SUNNY_DAY, Moves.MUD_SLAP ])
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyAbility(Abilities.BALL_FETCH);
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyLevel(100)
|
||||
.startingLevel(100);
|
||||
});
|
||||
|
||||
// TODO: Uncomment expect statements when the ability is implemented - currently does not increase stats of allies
|
||||
it("increases the ATK and SPDEF stat stages of the Pokémon with this Ability and its allies by 1.5× during Harsh Sunlight", async () => {
|
||||
game.override.battleType("double");
|
||||
await game.classicMode.startBattle([ Species.CHERRIM, Species.MAGIKARP ]);
|
||||
|
||||
const [ cherrim ] = game.scene.getPlayerField();
|
||||
const [ cherrim, magikarp ] = game.scene.getPlayerField();
|
||||
const cherrimAtkStat = cherrim.getEffectiveStat(Stat.ATK);
|
||||
const cherrimSpDefStat = cherrim.getEffectiveStat(Stat.SPDEF);
|
||||
|
||||
// const magikarpAtkStat = magikarp.getEffectiveStat(Stat.ATK);;
|
||||
// const magikarpSpDefStat = magikarp.getEffectiveStat(Stat.SPDEF);
|
||||
const magikarpAtkStat = magikarp.getEffectiveStat(Stat.ATK);
|
||||
const magikarpSpDefStat = magikarp.getEffectiveStat(Stat.SPDEF);
|
||||
|
||||
game.move.select(Moves.SUNNY_DAY, 0);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
@ -68,8 +124,28 @@ describe("Abilities - Flower Gift", () => {
|
||||
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
|
||||
expect(cherrim.getEffectiveStat(Stat.ATK)).toBe(Math.floor(cherrimAtkStat * 1.5));
|
||||
expect(cherrim.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(cherrimSpDefStat * 1.5));
|
||||
// expect(magikarp.getEffectiveStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 1.5));
|
||||
// expect(magikarp.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5));
|
||||
expect(magikarp.getEffectiveStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 1.5));
|
||||
expect(magikarp.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5));
|
||||
});
|
||||
|
||||
it("should not increase the damage of an ally using an ability ignoring move", async () => {
|
||||
const [ damageWithGift, damageWithoutGift ] = await testDamageDealt(game, Moves.SUNSTEEL_STRIKE, true);
|
||||
expect(damageWithGift).toBe(damageWithoutGift);
|
||||
});
|
||||
|
||||
it("should not increase the damage of a mold breaker ally", async () => {
|
||||
const [ damageWithGift, damageWithoutGift ] = await testDamageDealt(game, Moves.TACKLE, true, Abilities.MOLD_BREAKER);
|
||||
expect(damageWithGift).toBe(damageWithoutGift);
|
||||
});
|
||||
|
||||
it("should decrease the damage an ally takes from a special attack", async () => {
|
||||
const [ damageWithoutGift, damageWithGift ] = await testDamageDealt(game, Moves.MUD_SLAP, false);
|
||||
expect(damageWithGift).toBeLessThan(damageWithoutGift);
|
||||
});
|
||||
|
||||
it("should not decrease the damage an ally takes from a mold breaker enemy using a special attack", async () => {
|
||||
const [ damageWithoutGift, damageWithGift ] = await testDamageDealt(game, Moves.MUD_SLAP, false, Abilities.BALL_FETCH, Abilities.MOLD_BREAKER);
|
||||
expect(damageWithGift).toBe(damageWithoutGift);
|
||||
});
|
||||
|
||||
it("changes the Pokemon's form during Harsh Sunlight", async () => {
|
||||
@ -92,6 +168,7 @@ describe("Abilities - Flower Gift", () => {
|
||||
|
||||
it("reverts to Overcast Form when the Pokémon loses Flower Gift, changes form under Harsh Sunlight/Sunny when it regains it", async () => {
|
||||
game.override.enemyMoveset([ Moves.SKILL_SWAP ]).weather(WeatherType.HARSH_SUN);
|
||||
game.override.moveset([ Moves.SKILL_SWAP ]);
|
||||
|
||||
await game.classicMode.startBattle([ Species.CHERRIM ]);
|
||||
|
||||
|
60
test/abilities/victory_star.test.ts
Normal file
60
test/abilities/victory_star.test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Abilities - Victory Star", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([ Moves.TACKLE, Moves.SPLASH ])
|
||||
.battleType("double")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should increase the accuracy of its user", async () => {
|
||||
await game.classicMode.startBattle([ Species.VICTINI, Species.MAGIKARP ]);
|
||||
|
||||
const user = game.scene.getPlayerField()[0];
|
||||
|
||||
vi.spyOn(user, "getAccuracyMultiplier");
|
||||
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(user.getAccuracyMultiplier).toHaveReturnedWith(1.1);
|
||||
});
|
||||
|
||||
it("should increase the accuracy of its user's ally", async () => {
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP, Species.VICTINI ]);
|
||||
|
||||
const ally = game.scene.getPlayerField()[0];
|
||||
vi.spyOn(ally, "getAccuracyMultiplier");
|
||||
|
||||
game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(ally.getAccuracyMultiplier).toHaveReturnedWith(1.1);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user