mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-17 01:37:36 +00:00
fully implemented spectral thief
This commit is contained in:
parent
360a897ed2
commit
781845ae8f
@ -4312,6 +4312,53 @@ export class CueNextRoundAttr extends MoveEffectAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Steals the postitive Stat stages of the target before damage calculation so stat changes
|
||||
* apply to damage calculation (e.g. {@linkcode Moves.SPECTRAL_THIEF})
|
||||
* {@link https://bulbapedia.bulbagarden.net/wiki/Spectral_Thief_(move) | Spectral Thief}
|
||||
*/
|
||||
export class SpectralThiefAttr extends MoveAttr {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* steals max amount of positive stats of the target while not exceeding the limit of max 6 stat stages
|
||||
*
|
||||
* @param user {@linkcode Pokemon} that called {@linkcode move}
|
||||
* @param target {@linkcode Pokemon} that is the target of {@linkcode move}
|
||||
* @param move {@linkcode Move} called by {@linkcode user}
|
||||
* @param args N/A
|
||||
* @returns true if stat stages where correctly stolen
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.apply(user, target, move, args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy all positive stat stages to user and reduce copied stat stages on target
|
||||
for (const s of BATTLE_STATS) {
|
||||
const statStageValueTarget = target.getStatStage(s);
|
||||
const statStageValueUser = user.getStatStage(s);
|
||||
|
||||
if (statStageValueTarget > 0) {
|
||||
// Only value of up to 6 can be stolen (stat stages don't exceed 6)
|
||||
const availableToSteal = Math.min(statStageValueTarget, 6 - statStageValueUser);
|
||||
|
||||
user.scene.unshiftPhase(new StatStageChangePhase(user.scene, user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal));
|
||||
target.setStatStage(s, statStageValueTarget - availableToSteal);
|
||||
}
|
||||
}
|
||||
|
||||
target.updateInfo();
|
||||
user.updateInfo();
|
||||
target.scene.queueMessage(i18next.t("moveTriggers:stolePositiveStatChanges", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class VariableAtkAttr extends MoveAttr {
|
||||
constructor() {
|
||||
super();
|
||||
@ -9872,6 +9919,7 @@ export function initMoves() {
|
||||
new AttackMove(Moves.PRISMATIC_LASER, Type.PSYCHIC, MoveCategory.SPECIAL, 160, 100, 10, -1, 0, 7)
|
||||
.attr(RechargeAttr),
|
||||
new AttackMove(Moves.SPECTRAL_THIEF, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7)
|
||||
.attr(SpectralThiefAttr)
|
||||
.ignoresSubstitute()
|
||||
.partial(), // Does not steal stats
|
||||
new AttackMove(Moves.SUNSTEEL_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7)
|
||||
|
@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "#app/battle-scene";
|
||||
import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
|
||||
import { variantData } from "#app/data/variant";
|
||||
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
|
||||
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr, VariableMoveTypeChartAttr } from "#app/data/move";
|
||||
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr, VariableMoveTypeChartAttr, SpectralThiefAttr } from "#app/data/move";
|
||||
import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
||||
@ -2831,6 +2831,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
isCritical = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Steals positive stat stages from {@linkcode this} and gives it to {@linkcode source}
|
||||
* before damage calculation
|
||||
*/
|
||||
applyMoveAttrs(SpectralThiefAttr, source, this, move);
|
||||
|
||||
const { cancelled, result, damage: dmg } = this.getAttackDamage(source, move, false, false, isCritical, false);
|
||||
|
||||
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === source.getMoveType(move)) as TypeBoostTag;
|
||||
|
125
src/test/moves/spectral_thief.test.ts
Normal file
125
src/test/moves/spectral_thief.test.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Moves - Spectral Thief", () => {
|
||||
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
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.enemyLevel(100)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.moveset([ Moves.SPECTRAL_THIEF, Moves.SPLASH ])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.disableCrits;
|
||||
});
|
||||
|
||||
it("should steal max possible positive stat changes and ignore negative ones.", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
enemy.setStatStage(Stat.ATK, 6);
|
||||
enemy.setStatStage(Stat.DEF, -6);
|
||||
enemy.setStatStage(Stat.SPATK, 6);
|
||||
enemy.setStatStage(Stat.SPDEF, -6);
|
||||
enemy.setStatStage(Stat.SPD, 3);
|
||||
|
||||
player.setStatStage(Stat.ATK, 4);
|
||||
player.setStatStage(Stat.DEF, 1);
|
||||
player.setStatStage(Stat.SPATK, 0);
|
||||
player.setStatStage(Stat.SPDEF, 0);
|
||||
player.setStatStage(Stat.SPD, -2);
|
||||
|
||||
game.move.select(Moves.SPECTRAL_THIEF);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
/**
|
||||
* enemy has +6 ATK and player +4 => player only steals +2
|
||||
* enemy has -6 DEF and player 1 => player should not steal
|
||||
* enemy has +6 SPATK and player 0 => player only steals +6
|
||||
* enemy has -6 SPDEF and player 0 => player should not steal
|
||||
* enemy has +3 SPD and player -2 => player only steals +3
|
||||
*/
|
||||
expect(player.getStatStages()).toEqual([ 6, 1, 6, 0, 1, 0, 0 ]);
|
||||
expect(enemy.getStatStages()).toEqual([ 4, -6, 0, -6, 0, 0, 0 ]);
|
||||
});
|
||||
|
||||
it("should steal stat stages before dmg calculation", async () => {
|
||||
game.override
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyLevel(50);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
const moveToCheck = allMoves[Moves.SPECTRAL_THIEF];
|
||||
const dmgBefore = enemy.getAttackDamage(player, moveToCheck, false, false, false, false).damage;
|
||||
|
||||
enemy.setStatStage(Stat.ATK, 6);
|
||||
|
||||
player.setStatStage(Stat.ATK, 0);
|
||||
|
||||
game.move.select(Moves.SPECTRAL_THIEF);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(dmgBefore).toBeLessThan(enemy.getAttackDamage(player, moveToCheck, false, false, false, false).damage);
|
||||
});
|
||||
|
||||
it("should steal stat stages as a negative value with Contrary.", async () => {
|
||||
game.override
|
||||
.ability(Abilities.CONTRARY);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
enemy.setStatStage(Stat.ATK, 6);
|
||||
|
||||
player.setStatStage(Stat.ATK, 0);
|
||||
|
||||
game.move.select(Moves.SPECTRAL_THIEF);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(player.getStatStage(Stat.ATK)).toEqual(-6);
|
||||
expect(enemy.getStatStage(Stat.ATK)).toEqual(0);
|
||||
});
|
||||
|
||||
it("should steal double the stat stages with Simple.", async () => {
|
||||
game.override
|
||||
.ability(Abilities.SIMPLE);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
enemy.setStatStage(Stat.ATK, 3);
|
||||
|
||||
player.setStatStage(Stat.ATK, 0);
|
||||
|
||||
game.move.select(Moves.SPECTRAL_THIEF);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(player.getStatStage(Stat.ATK)).toEqual(6);
|
||||
expect(enemy.getStatStage(Stat.ATK)).toEqual(0);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user