pokerogue/src/phases/pokemon-anim-phase.ts
innerthunder 70295280da
[Move] Implement Substitute (#2559)
* Implement Substitute

Squashed commit from working branch

* Fix integration test imports

* Use Override Helper utils + Fix Baton Pass test

* Update src/test/moves/substitute.test.ts

Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com>

* Fix test imports + nits

* Document RemoveAllSubstitutesAttr

* Fix some strict-null issues

* more strict-null fixes

* Fix baton pass test

* Reorganized Substitute translation keys

* Added checks for substitute in contact logic

* Clean up Unseen Fist contact logic

* Remove misleading comment in Download attr

* RIP phases.ts

* Fix imports post-phase migration

* Rewrite `move.canIgnoreSubstitute` to `move.hitsSubstitute`

* Also fixed interactions with Shell Trap and Beak Blast

* Removed some leftover `canIgnoreSubstitute`s

* fix issues after beta merge

* Status move effectiveness now accounts for substitute

* More edge case tests (Counter test failing)

* Fix Counter + Trap edge cases + add Fail messagesd

* Fix leftover nit

* Resolve leftover test issues

* Fix Sub offset carrying over to Trainer fights

* Hide substitute sprite during catch attempts

* Make substitutes baton-passable again

* Remove placeholder locale keys and SPLASH_ONLY

* Fix imports and other nits

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* ESLint

* Fix imports

* Fix incorrect `resetSprite` timing

* Fix substitute disappearing on hit (maybe?)

* More animation fixes (mostly for Roar)

---------

Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com>
Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2024-09-13 12:46:22 -04:00

238 lines
6.2 KiB
TypeScript

import BattleScene from "#app/battle-scene";
import { SubstituteTag } from "#app/data/battler-tags";
import { PokemonAnimType } from "#enums/pokemon-anim-type";
import Pokemon from "#app/field/pokemon";
import { BattlePhase } from "#app/phases/battle-phase";
export class PokemonAnimPhase extends BattlePhase {
/** The type of animation to play in this phase */
private key: PokemonAnimType;
/** The Pokemon to which this animation applies */
private pokemon: Pokemon;
/** Any other field sprites affected by this animation */
private fieldAssets: Phaser.GameObjects.Sprite[];
constructor(scene: BattleScene, key: PokemonAnimType, pokemon: Pokemon, fieldAssets?: Phaser.GameObjects.Sprite[]) {
super(scene);
this.key = key;
this.pokemon = pokemon;
this.fieldAssets = fieldAssets ?? [];
}
start(): void {
super.start();
switch (this.key) {
case PokemonAnimType.SUBSTITUTE_ADD:
this.doSubstituteAddAnim();
break;
case PokemonAnimType.SUBSTITUTE_PRE_MOVE:
this.doSubstitutePreMoveAnim();
break;
case PokemonAnimType.SUBSTITUTE_POST_MOVE:
this.doSubstitutePostMoveAnim();
break;
case PokemonAnimType.SUBSTITUTE_REMOVE:
this.doSubstituteRemoveAnim();
break;
default:
this.end();
}
}
doSubstituteAddAnim(): void {
const substitute = this.pokemon.getTag(SubstituteTag);
if (substitute === null) {
return this.end();
}
const getSprite = () => {
const sprite = this.scene.addFieldSprite(
this.pokemon.x + this.pokemon.getSprite().x,
this.pokemon.y + this.pokemon.getSprite().y,
`pkmn${this.pokemon.isPlayer() ? "__back": ""}__sub`
);
sprite.setOrigin(0.5, 1);
this.scene.field.add(sprite);
return sprite;
};
const [ subSprite, subTintSprite ] = [ getSprite(), getSprite() ];
const subScale = this.pokemon.getSpriteScale() * (this.pokemon.isPlayer() ? 0.5 : 1);
subSprite.setVisible(false);
subSprite.setScale(subScale);
subTintSprite.setTintFill(0xFFFFFF);
subTintSprite.setScale(0.01);
if (this.pokemon.isPlayer()) {
this.scene.field.bringToTop(this.pokemon);
}
this.scene.playSound("PRSFX- Transform");
this.scene.tweens.add({
targets: this.pokemon,
duration: 500,
x: this.pokemon.x + this.pokemon.getSubstituteOffset()[0],
y: this.pokemon.y + this.pokemon.getSubstituteOffset()[1],
alpha: 0.5,
ease: "Sine.easeIn"
});
this.scene.tweens.add({
targets: subTintSprite,
delay: 250,
scale: subScale,
ease: "Cubic.easeInOut",
duration: 500,
onComplete: () => {
subSprite.setVisible(true);
this.pokemon.scene.tweens.add({
targets: subTintSprite,
delay: 250,
alpha: 0,
ease: "Cubic.easeOut",
duration: 1000,
onComplete: () => {
subTintSprite.destroy();
substitute.sprite = subSprite;
this.end();
}
});
}
});
}
doSubstitutePreMoveAnim(): void {
if (this.fieldAssets.length !== 1) {
return this.end();
}
const subSprite = this.fieldAssets[0];
if (subSprite === undefined) {
return this.end();
}
this.scene.tweens.add({
targets: subSprite,
alpha: 0,
ease: "Sine.easeInOut",
duration: 500
});
this.scene.tweens.add({
targets: this.pokemon,
x: subSprite.x,
y: subSprite.y,
alpha: 1,
ease: "Sine.easeInOut",
delay: 250,
duration: 500,
onComplete: () => this.end()
});
}
doSubstitutePostMoveAnim(): void {
if (this.fieldAssets.length !== 1) {
return this.end();
}
const subSprite = this.fieldAssets[0];
if (subSprite === undefined) {
return this.end();
}
this.scene.tweens.add({
targets: this.pokemon,
x: subSprite.x + this.pokemon.getSubstituteOffset()[0],
y: subSprite.y + this.pokemon.getSubstituteOffset()[1],
alpha: 0.5,
ease: "Sine.easeInOut",
duration: 500
});
this.scene.tweens.add({
targets: subSprite,
alpha: 1,
ease: "Sine.easeInOut",
delay: 250,
duration: 500,
onComplete: () => this.end()
});
}
doSubstituteRemoveAnim(): void {
if (this.fieldAssets.length !== 1) {
return this.end();
}
const subSprite = this.fieldAssets[0];
if (subSprite === undefined) {
return this.end();
}
const getSprite = () => {
const sprite = this.scene.addFieldSprite(
subSprite.x,
subSprite.y,
`pkmn${this.pokemon.isPlayer() ? "__back": ""}__sub`
);
sprite.setOrigin(0.5, 1);
this.scene.field.add(sprite);
return sprite;
};
const subTintSprite = getSprite();
const subScale = this.pokemon.getSpriteScale() * (this.pokemon.isPlayer() ? 0.5 : 1);
subTintSprite.setAlpha(0);
subTintSprite.setTintFill(0xFFFFFF);
subTintSprite.setScale(subScale);
this.scene.tweens.add({
targets: subTintSprite,
alpha: 1,
ease: "Sine.easeInOut",
duration: 500,
onComplete: () => {
subSprite.destroy();
const flashTimer = this.scene.time.addEvent({
delay: 100,
repeat: 7,
startAt: 200,
callback: () => {
this.scene.playSound("PRSFX- Substitute2.wav");
subTintSprite.setVisible(flashTimer.repeatCount % 2 === 0);
if (!flashTimer.repeatCount) {
this.scene.tweens.add({
targets: subTintSprite,
scale: 0.01,
ease: "Sine.cubicEaseIn",
duration: 500
});
this.scene.tweens.add({
targets: this.pokemon,
x: this.pokemon.x - this.pokemon.getSubstituteOffset()[0],
y: this.pokemon.y - this.pokemon.getSubstituteOffset()[1],
alpha: 1,
ease: "Sine.easeInOut",
delay: 250,
duration: 500,
onComplete: () => {
subTintSprite.destroy();
this.end();
}
});
}
}
});
}
});
}
}