diff --git a/public/images/ui/ability_bar.png b/public/images/ui/ability_bar.png new file mode 100644 index 000000000..428aa3676 Binary files /dev/null and b/public/images/ui/ability_bar.png differ diff --git a/src/battle-phases.ts b/src/battle-phases.ts index ac17d779e..d3c064239 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -1283,6 +1283,13 @@ export class MessagePhase extends BattlePhase { this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt); } + + end() { + if (this.scene.abilityBar.shown) + this.scene.abilityBar.hide(); + + super.end(); + } } export class DamagePhase extends PokemonPhase { diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 75725fdae..e2d775097 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -19,6 +19,7 @@ import StarterSelectUiHandler from './ui/starter-select-ui-handler'; import { TextStyle, addTextObject } from './ui/text'; import { Moves } from './data/move'; import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; +import AbilityBar from './ui/ability-bar'; const enableAuto = true; const quickStart = false; @@ -60,6 +61,7 @@ export default class BattleScene extends Phaser.Scene { private currentPhase: BattlePhase; public field: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container; + public abilityBar: AbilityBar; public arenaBg: Phaser.GameObjects.Image; public arenaBgTransition: Phaser.GameObjects.Image; public arenaPlayer: Phaser.GameObjects.Image; @@ -153,6 +155,7 @@ export default class BattleScene extends Phaser.Scene { this.loadImage('overlay_exp', 'ui'); this.loadImage('icon_owned', 'ui'); this.loadImage('level_up_stats', 'ui'); + this.loadImage('ability_bar', 'ui'); this.loadImage('ball_window', 'ui'); this.loadImage('boolean_window', 'ui'); @@ -311,6 +314,10 @@ export default class BattleScene extends Phaser.Scene { this.add.existing(this.enemyModifierBar); uiContainer.add(this.enemyModifierBar); + this.abilityBar = new AbilityBar(this); + this.abilityBar.setup(); + this.fieldUI.add(this.abilityBar); + this.waveCountText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO); this.waveCountText.setOrigin(1, 0); this.updateWaveCountPosition(); diff --git a/src/data/ability.ts b/src/data/ability.ts index 9a3e1cfb6..2e26b3984 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1,8 +1,9 @@ import Pokemon, { PokemonMove } from "../pokemon"; import { Type } from "./type"; import * as Utils from "../utils"; -import { BattleStat } from "./battle-stat"; +import { BattleStat, getBattleStatName } from "./battle-stat"; import { StatChangePhase } from "../battle-phases"; +import { getPokemonMessage } from "../messages"; export class Ability { public id: Abilities; @@ -13,7 +14,7 @@ export class Ability { constructor(id: Abilities, name: string, description: string, generation: integer) { this.id = id; - this.name = name; + this.name = name.toUpperCase(); this.description = description; this.generation = generation; this.attrs = []; @@ -31,7 +32,11 @@ export class Ability { } } -export abstract class AbilityAttr { } +export abstract class AbilityAttr { + getTriggerMessage(pokemon: Pokemon, ...args: any[]) { + return null; + } +} export class PreDefendAbilityAttr extends AbilityAttr { applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { @@ -88,22 +93,26 @@ export class PreStatChangeAbilityAttr extends AbilityAttr { } export class ProtectStatAttr extends PreStatChangeAbilityAttr { - private protectedStats: BattleStat[]; + private protectedStat: BattleStat; - constructor(...stats: BattleStat[]) { + constructor(protectedStat?: BattleStat) { super(); - this.protectedStats = stats; + this.protectedStat = protectedStat; } applyPreStatChange(pokemon: Pokemon, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.protectedStats.length || this.protectedStats.indexOf(stat) > -1) { + if (this.protectedStat === undefined || stat === this.protectedStat) { cancelled.value = true; return true; } return false; } + + getTriggerMessage(pokemon: Pokemon, ...args: any[]) { + return getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nprevents lowering its ${this.protectedStat !== undefined ? getBattleStatName(this.protectedStat) : 'stats'}!`); + } } export function applyPreDefendAbilityAttrs(attrType: { new(...args: any[]): PreDefendAbilityAttr }, @@ -111,8 +120,12 @@ export function applyPreDefendAbilityAttrs(attrType: { new(...args: any[]): PreD const ability = pokemon.getAbility(); const attrs = ability.getAttrs(attrType) as PreDefendAbilityAttr[]; for (let attr of attrs) { - if (attr.applyPreDefend(pokemon, attacker, move, cancelled, args)) - console.log('Applied', ability.name, attr); + if (attr.applyPreDefend(pokemon, attacker, move, cancelled, args)) { + pokemon.scene.abilityBar.showAbility(pokemon); + const message = attr.getTriggerMessage(pokemon, attacker, move); + if (message) + pokemon.scene.queueMessage(message); + } } } @@ -121,8 +134,12 @@ export function applyPreStatChangeAbilityAttrs(attrType: { new(...args: any[]): const ability = pokemon.getAbility(); const attrs = ability.getAttrs(attrType) as PreStatChangeAbilityAttr[]; for (let attr of attrs) { - if (attr.applyPreStatChange(pokemon, stat, cancelled, args)) - console.log('Applied', ability.name, attr); + if (attr.applyPreStatChange(pokemon, stat, cancelled, args)) { + pokemon.scene.abilityBar.showAbility(pokemon); + const message = attr.getTriggerMessage(pokemon, stat); + if (message) + pokemon.scene.queueMessage(message); + } } } diff --git a/src/ui/ability-bar.ts b/src/ui/ability-bar.ts new file mode 100644 index 000000000..78fedde78 --- /dev/null +++ b/src/ui/ability-bar.ts @@ -0,0 +1,72 @@ +import BattleScene from "../battle-scene"; +import Pokemon from "../pokemon"; +import { TextStyle, addTextObject } from "./text"; + +export default class AbilityBar extends Phaser.GameObjects.Container { + private bg: Phaser.GameObjects.Image; + private pokemonNameText: Phaser.GameObjects.Text; + private abilityNameText: Phaser.GameObjects.Text; + + private tween: Phaser.Tweens.Tween; + + public shown: boolean; + + constructor(scene: BattleScene) { + super(scene, -91, (-scene.game.canvas.height / 6) + 64); + } + + setup(): void { + this.bg = this.scene.add.image(0, 0, 'ability_bar'); + this.bg.setOrigin(0, 0); + + this.add(this.bg); + + this.pokemonNameText = addTextObject(this.scene, 5, 3, 'Pokemon', TextStyle.MESSAGE, { fontSize: '72px' }); + this.pokemonNameText.setOrigin(0, 0); + this.add(this.pokemonNameText); + + this.abilityNameText = addTextObject(this.scene, 87, 16, 'Chlorophyll', TextStyle.WINDOW, { fontSize: '72px' }); + this.abilityNameText.setOrigin(1, 0); + this.add(this.abilityNameText); + + this.setVisible(false); + this.shown = false; + } + + showAbility(pokemon: Pokemon) { + this.pokemonNameText.setText(`${pokemon.name}'s`); + this.abilityNameText.setText(pokemon.getAbility().name); + + if (this.tween) + this.tween.stop(); + + this.tween = this.scene.tweens.add({ + targets: this, + x: 10, + duration: 500, + ease: 'Sine.easeOut', + onComplete: () => this.tween = null + }); + + this.setVisible(true); + this.shown = true; + } + + hide() { + if (this.tween) + this.tween.stop(); + + this.tween = this.scene.tweens.add({ + targets: this, + x: -91, + duration: 500, + ease: 'Sine.easeIn', + onComplete: () => { + this.tween = null; + this.setVisible(false); + } + }); + + this.shown = false; + } +} \ No newline at end of file diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 825418a00..3c984a09a 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -576,7 +576,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (defaultDexEntry) { const ability = this.lastSpecies.getAbility(abilityIndex); - this.pokemonAbilityText.setText(abilities[ability].name.toUpperCase()); + this.pokemonAbilityText.setText(abilities[ability].name); const isHidden = ability === this.lastSpecies.abilityHidden; this.pokemonAbilityText.setColor(getTextColor(!isHidden ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD)); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 561321bbc..949c7dbe5 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -437,7 +437,7 @@ export default class SummaryUiHandler extends UiHandler { const ability = abilities[this.pokemon.species.getAbility(this.pokemon.abilityIndex)]; - const abilityNameText = addTextObject(this.scene, 7, 66, ability.name.toUpperCase(), TextStyle.SUMMARY); + const abilityNameText = addTextObject(this.scene, 7, 66, ability.name, TextStyle.SUMMARY); abilityNameText.setOrigin(0, 1); profileContainer.add(abilityNameText);