Skip to content

Commit

Permalink
[Move] Implement Power Trick (#2658)
Browse files Browse the repository at this point in the history
* add `PowerTrickTag`

* modify getStat() with PowerTrickTag

* implement `PowerTrickAttr`

* add unit test

* enhance docs and tag apply logic

---------

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Enoch <enoch.jwsong@gmail.com>
Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com>
Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com>
Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
  • Loading branch information
9 people authored Oct 14, 2024
1 parent 676322e commit e7a4d40
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 2 deletions.
40 changes: 40 additions & 0 deletions src/data/battler-tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2724,6 +2724,44 @@ export class TelekinesisTag extends BattlerTag {
}
}

/**
* Tag that swaps the user's base ATK stat with its base DEF stat.
* @extends BattlerTag
*/
export class PowerTrickTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: number) {
super(BattlerTagType.POWER_TRICK, BattlerTagLapseType.CUSTOM, 0, sourceMove, sourceId, true);
}

onAdd(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}

onRemove(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}

/**
* Removes the Power Trick tag and reverts any stat changes if the tag is already applied.
* @param {Pokemon} pokemon The {@linkcode Pokemon} that already has the Power Trick tag.
*/
onOverlap(pokemon: Pokemon): void {
pokemon.removeTag(this.tagType);
}

/**
* Swaps the user's base ATK stat with its base DEF stat.
* @param {Pokemon} pokemon The {@linkcode Pokemon} whose stats will be swapped.
*/
swapStat(pokemon: Pokemon): void {
const temp = pokemon.getStat(Stat.ATK, false);
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.DEF, false), false);
pokemon.setStat(Stat.DEF, temp, false);
}
}

/**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
* @param sourceId - The ID of the pokemon adding the tag
Expand Down Expand Up @@ -2899,6 +2937,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new SyrupBombTag(sourceId);
case BattlerTagType.TELEKINESIS:
return new TelekinesisTag(sourceMove);
case BattlerTagType.POWER_TRICK:
return new PowerTrickTag(sourceMove, sourceId);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
Expand Down
5 changes: 4 additions & 1 deletion src/data/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6430,6 +6430,9 @@ export class TransformAttr extends MoveEffectAttr {
user.summonData.gender = target.getGender();
user.summonData.fusionGender = target.getFusionGender();

// Power Trick's effect will not preserved after using Transform
user.removeTag(BattlerTagType.POWER_TRICK);

// Copy all stats (except HP)
for (const s of EFFECTIVE_STATS) {
user.setStat(s, target.getStat(s, false), false);
Expand Down Expand Up @@ -8153,7 +8156,7 @@ export function initMoves() {
.attr(OpponentHighHpPowerAttr, 120)
.makesContact(),
new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4)
.unimplemented(),
.attr(AddBattlerTagAttr, BattlerTagType.POWER_TRICK, true),
new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4)
.attr(SuppressAbilitiesAttr),
new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4)
Expand Down
1 change: 1 addition & 0 deletions src/enums/battler-tag-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export enum BattlerTagType {
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
AUTOTOMIZED = "AUTOTOMIZED",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
POWER_TRICK = "POWER_TRICK",
HEAL_BLOCK = "HEAL_BLOCK",
TORMENT = "TORMENT",
TAUNT = "TAUNT",
Expand Down
6 changes: 5 additions & 1 deletion src/field/pokemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
import { WeatherType } from "#app/data/weather";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability";
Expand Down Expand Up @@ -3048,6 +3048,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
continue;
}

if (tag instanceof PowerTrickTag) {
tag.swapStat(this);
}

this.summonData.tags.push(tag);
}

Expand Down
113 changes: 113 additions & 0 deletions src/test/moves/power_trick.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import { Moves } from "#enums/moves";
import { Stat } from "#enums/stat";
import { Species } from "#enums/species";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";

describe("Moves - Power Trick", () => {
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
.battleType("single")
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH)
.enemySpecies(Species.MEW)
.enemyLevel(200)
.moveset([ Moves.POWER_TRICK ])
.ability(Abilities.BALL_FETCH);
});

it("swaps the user's ATK and DEF stats", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);

const player = game.scene.getPlayerPokemon()!;
const baseATK = player.getStat(Stat.ATK, false);
const baseDEF = player.getStat(Stat.DEF, false);

game.move.select(Moves.POWER_TRICK);

await game.phaseInterceptor.to(TurnEndPhase);

expect(player.getStat(Stat.ATK, false)).toBe(baseDEF);
expect(player.getStat(Stat.DEF, false)).toBe(baseATK);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeDefined();
});

it("resets initial ATK and DEF stat swap when used consecutively", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);

const player = game.scene.getPlayerPokemon()!;
const baseATK = player.getStat(Stat.ATK, false);
const baseDEF = player.getStat(Stat.DEF, false);

game.move.select(Moves.POWER_TRICK);

await game.phaseInterceptor.to(TurnEndPhase);

game.move.select(Moves.POWER_TRICK);

await game.phaseInterceptor.to(TurnEndPhase);

expect(player.getStat(Stat.ATK, false)).toBe(baseATK);
expect(player.getStat(Stat.DEF, false)).toBe(baseDEF);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined();
});

it("should pass effect when using BATON_PASS", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
await game.override.moveset([ Moves.POWER_TRICK, Moves.BATON_PASS ]);

const player = game.scene.getPlayerPokemon()!;
player.addTag(BattlerTagType.POWER_TRICK);

game.move.select(Moves.BATON_PASS);
game.doSelectPartyPokemon(1);

await game.phaseInterceptor.to(TurnEndPhase);

const switchedPlayer = game.scene.getPlayerPokemon()!;
const baseATK = switchedPlayer.getStat(Stat.ATK);
const baseDEF = switchedPlayer.getStat(Stat.DEF);

expect(switchedPlayer.getStat(Stat.ATK, false)).toBe(baseDEF);
expect(switchedPlayer.getStat(Stat.DEF, false)).toBe(baseATK);
expect(switchedPlayer.getTag(BattlerTagType.POWER_TRICK)).toBeDefined();
});

it("should remove effect after using Transform", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
await game.override.moveset([ Moves.POWER_TRICK, Moves.TRANSFORM ]);

const player = game.scene.getPlayerPokemon()!;
player.addTag(BattlerTagType.POWER_TRICK);

game.move.select(Moves.TRANSFORM);

await game.phaseInterceptor.to(TurnEndPhase);

const enemy = game.scene.getEnemyPokemon()!;
const baseATK = enemy.getStat(Stat.ATK);
const baseDEF = enemy.getStat(Stat.DEF);

expect(player.getStat(Stat.ATK, false)).toBe(baseATK);
expect(player.getStat(Stat.DEF, false)).toBe(baseDEF);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined();
});
});

0 comments on commit e7a4d40

Please sign in to comment.