mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-03-18 15:55:10 +00:00
[Refactor] Align ability display with mainline (#5267)
* Stop ShowAbilityPhase from ending until the bar has popped out * Remove ability bar hiding from messagePhase * Remove abilityBar reference from base Phase class * Add HideAbilityPhase to hide ability bar after effects * Add willSucceed to ability attrs * Update AbAttrs and PostInitAbAttrs * Update PreDefendAbAttrs * Update postDefend, postMoveUsed, StatStage, postSetStatus, and PostDamage * Update preAttack and fieldStat * Partially implement postAttack * Finish PostAttack * Update PostSummon * Update PreSwitchOut * Update preStatStageChange * Update PostStatStageChange, PreSetStatus, PreApplyBattlerTag * Update postTurn and preWeatherEffect * Update postWeatherChange * Update postWeatherChange * Update PostTerrainChange * Update CheckTrapped and PostBattle * Update postFaint * Update PostItemLost * Bug fixes from test cases * Fix intimidate display * Stop trace from displaying itself * Rename to canApply * Fix ability displays using getTriggerMessage * Ensure abilities which are mistakenly shown are still hidden * Fix ability bar showing the wrong ability with imposter * Add canApply for imposter * Update abilities using promises and `trySet...` functions * Committing overrides changes is bad * Document apply and canApply * Update PreLeaveFieldAbAttr * Remove boolean return type apply functions * Remove redundant assignment * Remove ability display from abilities that shouldn't have it * Move queueAbilityDisplay to battlescene * Remove unused shown variable * Minor changes * Fix using id instead of battlerindex in queueAbilityDisplay * Fix PostBattleInitFormChangeAbAttr displaying * Prevent crashes in case an ability for a pokemon not on the field is shown * Stop more abilities from displaying * Move enemy ability bar to the right side * Automatically reload bar if shown while already out, fix specific abilities * Remove duplicate call to clearPhaseQueueSplice * Remove ShowAbilityPhase import from ability.ts * Update PostDefendTypeChangeAbAttr to use PokemonType * Update PostSummonAddArenaTagAbAttr * Minor changes
This commit is contained in:
parent
7aa5649aa8
commit
1d7f916240
@ -168,6 +168,8 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { initGlobalScene } from "#app/global-scene";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
|
||||
|
||||
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
|
||||
|
||||
@ -2904,6 +2906,21 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues an ability bar flyout phase
|
||||
* @param pokemon The pokemon who has the ability
|
||||
* @param passive Whether the ability is a passive
|
||||
* @param show Whether to show or hide the bar
|
||||
*/
|
||||
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
|
||||
this.unshiftPhase(
|
||||
show
|
||||
? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive)
|
||||
: new HideAbilityPhase(pokemon.getBattlerIndex(), passive),
|
||||
);
|
||||
this.clearPhaseQueueSplice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order)
|
||||
*/
|
||||
@ -3133,6 +3150,41 @@ export default class BattleScene extends SceneBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
canTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, transferQuantity = 1): boolean {
|
||||
const mod = itemModifier.clone() as PokemonHeldItemModifier;
|
||||
const source = mod.pokemonId ? mod.getPokemon() : null;
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
|
||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled);
|
||||
}
|
||||
|
||||
if (cancelled.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matchingModifier = this.findModifier(
|
||||
m => m instanceof PokemonHeldItemModifier && m.matchType(mod) && m.pokemonId === target.id,
|
||||
target.isPlayer(),
|
||||
) as PokemonHeldItemModifier;
|
||||
|
||||
if (matchingModifier) {
|
||||
const maxStackCount = matchingModifier.getMaxStackCount();
|
||||
if (matchingModifier.stackCount >= maxStackCount) {
|
||||
return false;
|
||||
}
|
||||
const countTaken = Math.min(transferQuantity, mod.stackCount, maxStackCount - matchingModifier.stackCount);
|
||||
mod.stackCount -= countTaken;
|
||||
} else {
|
||||
const countTaken = Math.min(transferQuantity, mod.stackCount);
|
||||
mod.stackCount -= countTaken;
|
||||
}
|
||||
|
||||
const removeOld = mod.stackCount === 0;
|
||||
|
||||
return !removeOld || !source || this.hasModifier(itemModifier, !source.isPlayer());
|
||||
}
|
||||
|
||||
removePartyMemberModifiers(partyMemberIndex: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const pokemonId = this.getPlayerParty()[partyMemberIndex].id;
|
||||
@ -3290,6 +3342,11 @@ export default class BattleScene extends SceneBase {
|
||||
});
|
||||
}
|
||||
|
||||
hasModifier(modifier: PersistentModifier, enemy = false): boolean {
|
||||
const modifiers = !enemy ? this.modifiers : this.enemyModifiers;
|
||||
return modifiers.indexOf(modifier) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a currently owned item. If the item is stacked, the entire item stack
|
||||
* gets removed. This function does NOT apply in-battle effects, such as Unburden.
|
||||
|
2996
src/data/ability.ts
2996
src/data/ability.ts
File diff suppressed because it is too large
Load Diff
@ -303,6 +303,11 @@ export class Arena {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns weather or not the weather can be changed to {@linkcode weather} */
|
||||
canSetWeather(weather: WeatherType): boolean {
|
||||
return !(this.weather?.weatherType === (weather || undefined));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to set a new weather to the battle
|
||||
* @param weather {@linkcode WeatherType} new {@linkcode WeatherType} to set
|
||||
@ -314,7 +319,7 @@ export class Arena {
|
||||
return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE);
|
||||
}
|
||||
|
||||
if (this.weather?.weatherType === (weather || undefined)) {
|
||||
if (!this.canSetWeather(weather)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -388,8 +393,13 @@ export class Arena {
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns whether or not the terrain can be set to {@linkcode terrain} */
|
||||
canSetTerrain(terrain: TerrainType): boolean {
|
||||
return !(this.terrain?.terrainType === (terrain || undefined));
|
||||
}
|
||||
|
||||
trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim = false): boolean {
|
||||
if (this.terrain?.terrainType === (terrain || undefined)) {
|
||||
if (!this.canSetTerrain(terrain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,7 @@ export class LoadingScene extends SceneBase {
|
||||
this.loadImage("icon_owned", "ui");
|
||||
this.loadImage("icon_egg_move", "ui");
|
||||
this.loadImage("ability_bar_left", "ui");
|
||||
this.loadImage("ability_bar_right", "ui");
|
||||
this.loadImage("bgm_bar", "ui");
|
||||
this.loadImage("party_exp_bar", "ui");
|
||||
this.loadImage("achv_bar", "ui");
|
||||
|
@ -1,11 +1,7 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export class Phase {
|
||||
start() {
|
||||
if (globalScene.abilityBar.shown) {
|
||||
globalScene.abilityBar.resetAutoHideTimer();
|
||||
}
|
||||
}
|
||||
start() {}
|
||||
|
||||
end() {
|
||||
globalScene.shiftPhase();
|
||||
|
27
src/phases/hide-ability-phase.ts
Normal file
27
src/phases/hide-ability-phase.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
|
||||
export class HideAbilityPhase extends PokemonPhase {
|
||||
private passive: boolean;
|
||||
|
||||
constructor(battlerIndex: BattlerIndex, passive = false) {
|
||||
super(battlerIndex);
|
||||
|
||||
this.passive = passive;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
|
||||
if (pokemon) {
|
||||
globalScene.abilityBar.hide().then(() => {
|
||||
this.end();
|
||||
});
|
||||
} else {
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
}
|
@ -61,12 +61,4 @@ export class MessagePhase extends Phase {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
end() {
|
||||
if (globalScene.abilityBar.shown) {
|
||||
globalScene.abilityBar.hide();
|
||||
}
|
||||
|
||||
super.end();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import i18next from "i18next";
|
||||
|
||||
/**
|
||||
* Transforms a Pokemon into another Pokemon on the field.
|
||||
@ -62,6 +64,13 @@ export class PokemonTransformPhase extends PokemonPhase {
|
||||
globalScene.playSound("battle_anims/PRSFX- Transform");
|
||||
}
|
||||
|
||||
globalScene.queueMessage(
|
||||
i18next.t("abilityTriggers:postSummonTransform", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
||||
targetName: target.name,
|
||||
}),
|
||||
);
|
||||
|
||||
promises.push(
|
||||
user.loadAssets(false).then(() => {
|
||||
user.playAnim();
|
||||
|
@ -1,36 +1,60 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
|
||||
|
||||
export class ShowAbilityPhase extends PokemonPhase {
|
||||
private passive: boolean;
|
||||
private pokemonName: string;
|
||||
private abilityName: string;
|
||||
private pokemonOnField: boolean;
|
||||
|
||||
constructor(battlerIndex: BattlerIndex, passive = false) {
|
||||
super(battlerIndex);
|
||||
|
||||
this.passive = passive;
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
if (pokemon) {
|
||||
// Set these now as the pokemon object may change before the queued phase is run
|
||||
this.pokemonName = getPokemonNameWithAffix(pokemon);
|
||||
this.abilityName = (passive ? this.getPokemon().getPassiveAbility() : this.getPokemon().getAbility()).name;
|
||||
this.pokemonOnField = true;
|
||||
} else {
|
||||
this.pokemonOnField = false;
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
if (!this.pokemonOnField || !this.getPokemon()) {
|
||||
return this.end();
|
||||
}
|
||||
|
||||
// If the bar is already out, hide it before showing the new one
|
||||
if (globalScene.abilityBar.isVisible()) {
|
||||
globalScene.unshiftPhase(new HideAbilityPhase(this.battlerIndex, this.passive));
|
||||
globalScene.unshiftPhase(new ShowAbilityPhase(this.battlerIndex, this.passive));
|
||||
return this.end();
|
||||
}
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
|
||||
if (pokemon) {
|
||||
if (!pokemon.isPlayer()) {
|
||||
/** If its an enemy pokemon, list it as last enemy to use ability or move */
|
||||
globalScene.currentBattle.lastEnemyInvolved = pokemon.getBattlerIndex() % 2;
|
||||
} else {
|
||||
globalScene.currentBattle.lastPlayerInvolved = pokemon.getBattlerIndex() % 2;
|
||||
}
|
||||
|
||||
globalScene.abilityBar.showAbility(pokemon, this.passive);
|
||||
if (!pokemon.isPlayer()) {
|
||||
/** If its an enemy pokemon, list it as last enemy to use ability or move */
|
||||
globalScene.currentBattle.lastEnemyInvolved = pokemon.getBattlerIndex() % 2;
|
||||
} else {
|
||||
globalScene.currentBattle.lastPlayerInvolved = pokemon.getBattlerIndex() % 2;
|
||||
}
|
||||
|
||||
globalScene.abilityBar.showAbility(this.pokemonName, this.abilityName, this.passive, this.player).then(() => {
|
||||
if (pokemon?.battleData) {
|
||||
pokemon.battleData.abilityRevealed = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.end();
|
||||
this.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,33 @@
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type Pokemon from "../field/pokemon";
|
||||
import { TextStyle, addTextObject } from "./text";
|
||||
import i18next from "i18next";
|
||||
|
||||
const hiddenX = -118;
|
||||
const shownX = 0;
|
||||
const barWidth = 118;
|
||||
const screenLeft = 0;
|
||||
const baseY = -116;
|
||||
|
||||
export default class AbilityBar extends Phaser.GameObjects.Container {
|
||||
private bg: Phaser.GameObjects.Image;
|
||||
private abilityBars: Phaser.GameObjects.Image[];
|
||||
private abilityBarText: Phaser.GameObjects.Text;
|
||||
|
||||
private tween: Phaser.Tweens.Tween | null;
|
||||
private autoHideTimer: NodeJS.Timeout | null;
|
||||
|
||||
public shown: boolean;
|
||||
private player: boolean;
|
||||
private screenRight: number; // hold screenRight in case size changes between show and hide
|
||||
private shown: boolean;
|
||||
|
||||
constructor() {
|
||||
super(globalScene, hiddenX, baseY);
|
||||
super(globalScene, barWidth, baseY);
|
||||
this.abilityBars = [];
|
||||
this.player = true;
|
||||
this.shown = false;
|
||||
}
|
||||
|
||||
setup(): void {
|
||||
this.bg = globalScene.add.image(0, 0, "ability_bar_left");
|
||||
this.bg.setOrigin(0, 0);
|
||||
|
||||
this.add(this.bg);
|
||||
for (const key of ["ability_bar_right", "ability_bar_left"]) {
|
||||
const bar = globalScene.add.image(0, 0, key);
|
||||
bar.setOrigin(0, 0);
|
||||
bar.setVisible(false);
|
||||
this.add(bar);
|
||||
this.abilityBars.push(bar);
|
||||
}
|
||||
|
||||
this.abilityBarText = addTextObject(15, 3, "", TextStyle.MESSAGE, {
|
||||
fontSize: "72px",
|
||||
@ -33,72 +35,80 @@ export default class AbilityBar extends Phaser.GameObjects.Container {
|
||||
this.abilityBarText.setOrigin(0, 0);
|
||||
this.abilityBarText.setWordWrapWidth(600, true);
|
||||
this.add(this.abilityBarText);
|
||||
this.bringToTop(this.abilityBarText);
|
||||
|
||||
this.setVisible(false);
|
||||
this.shown = false;
|
||||
this.setX(-barWidth); // start hidden (right edge of bar at x=0)
|
||||
}
|
||||
|
||||
showAbility(pokemon: Pokemon, passive = false): void {
|
||||
this.abilityBarText.setText(
|
||||
`${i18next.t("fightUiHandler:abilityFlyInText", { pokemonName: getPokemonNameWithAffix(pokemon), passive: passive ? i18next.t("fightUiHandler:passive") : "", abilityName: !passive ? pokemon.getAbility().name : pokemon.getPassiveAbility().name })}`,
|
||||
);
|
||||
public override setVisible(value: boolean): this {
|
||||
this.abilityBars[+this.player].setVisible(value);
|
||||
this.shown = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.shown) {
|
||||
return;
|
||||
public async startTween(config: any, text?: string): Promise<void> {
|
||||
this.setVisible(true);
|
||||
if (text) {
|
||||
this.abilityBarText.setText(text);
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
globalScene.tweens.add({
|
||||
...config,
|
||||
onComplete: () => {
|
||||
if (config.onComplete) {
|
||||
config.onComplete();
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async showAbility(pokemonName: string, abilityName: string, passive = false, player = true): Promise<void> {
|
||||
const text = `${i18next.t("fightUiHandler:abilityFlyInText", { pokemonName: pokemonName, passive: passive ? i18next.t("fightUiHandler:passive") : "", abilityName: abilityName })}`;
|
||||
this.screenRight = globalScene.scaledCanvas.width;
|
||||
if (player !== this.player) {
|
||||
// Move the bar if it has changed from the player to enemy side (or vice versa)
|
||||
this.setX(player ? -barWidth : this.screenRight);
|
||||
this.player = player;
|
||||
}
|
||||
globalScene.fieldUI.bringToTop(this);
|
||||
|
||||
this.y = baseY + (globalScene.currentBattle.double ? 14 : 0);
|
||||
this.tween = globalScene.tweens.add({
|
||||
targets: this,
|
||||
x: shownX,
|
||||
duration: 500,
|
||||
ease: "Sine.easeOut",
|
||||
onComplete: () => {
|
||||
this.tween = null;
|
||||
this.resetAutoHideTimer();
|
||||
},
|
||||
});
|
||||
let y = baseY;
|
||||
if (this.player) {
|
||||
y += globalScene.currentBattle.double ? 14 : 0;
|
||||
} else {
|
||||
y -= globalScene.currentBattle.double ? 28 : 14;
|
||||
}
|
||||
|
||||
this.setVisible(true);
|
||||
this.shown = true;
|
||||
this.setY(y);
|
||||
|
||||
return this.startTween(
|
||||
{
|
||||
targets: this,
|
||||
x: this.player ? screenLeft : this.screenRight - barWidth,
|
||||
duration: 500,
|
||||
ease: "Sine.easeOut",
|
||||
hold: 1000,
|
||||
},
|
||||
text,
|
||||
);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
if (!this.shown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.autoHideTimer) {
|
||||
clearInterval(this.autoHideTimer);
|
||||
}
|
||||
|
||||
if (this.tween) {
|
||||
this.tween.stop();
|
||||
}
|
||||
|
||||
this.tween = globalScene.tweens.add({
|
||||
public async hide(): Promise<void> {
|
||||
return this.startTween({
|
||||
targets: this,
|
||||
x: -91,
|
||||
duration: 500,
|
||||
x: this.player ? -barWidth : this.screenRight,
|
||||
duration: 200,
|
||||
ease: "Sine.easeIn",
|
||||
onComplete: () => {
|
||||
this.tween = null;
|
||||
this.setVisible(false);
|
||||
},
|
||||
});
|
||||
|
||||
this.shown = false;
|
||||
}
|
||||
|
||||
resetAutoHideTimer(): void {
|
||||
if (this.autoHideTimer) {
|
||||
clearInterval(this.autoHideTimer);
|
||||
}
|
||||
this.autoHideTimer = setTimeout(() => {
|
||||
this.hide();
|
||||
this.autoHideTimer = null;
|
||||
}, 2500);
|
||||
public isVisible(): boolean {
|
||||
return this.shown;
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ describe("Moves - Secret Power", () => {
|
||||
await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]);
|
||||
|
||||
const sereneGraceAttr = allAbilities[Abilities.SERENE_GRACE].getAttrs(MoveEffectChanceMultiplierAbAttr)[0];
|
||||
vi.spyOn(sereneGraceAttr, "apply");
|
||||
vi.spyOn(sereneGraceAttr, "canApply");
|
||||
|
||||
game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.FIRE_PLEDGE, 1, BattlerIndex.ENEMY_2);
|
||||
@ -86,8 +86,8 @@ describe("Moves - Secret Power", () => {
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(sereneGraceAttr.apply).toHaveBeenCalledOnce();
|
||||
expect(sereneGraceAttr.apply).toHaveLastReturnedWith(true);
|
||||
expect(sereneGraceAttr.canApply).toHaveBeenCalledOnce();
|
||||
expect(sereneGraceAttr.canApply).toHaveLastReturnedWith(true);
|
||||
|
||||
expect(rainbowEffect.apply).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user