pokerogue/src/ui/battle-info.ts

461 lines
17 KiB
TypeScript
Raw Normal View History

2024-03-01 01:08:50 +00:00
import { EnemyPokemon, default as Pokemon } from '../field/pokemon';
2023-04-20 20:46:05 +01:00
import { getLevelTotalExp, getLevelRelExp } from '../data/exp';
import * as Utils from '../utils';
2023-04-02 01:06:44 +01:00
import { addTextObject, TextStyle } from './text';
import { getGenderSymbol, getGenderColor, Gender } from '../data/gender';
2023-04-20 20:46:05 +01:00
import { StatusEffect } from '../data/status-effect';
import BattleScene from '../battle-scene';
import { Type, getTypeRgb } from '../data/type';
2023-03-28 19:54:52 +01:00
export default class BattleInfo extends Phaser.GameObjects.Container {
private player: boolean;
private mini: boolean;
2024-01-08 04:17:24 +00:00
private boss: boolean;
private bossSegments: integer;
private offset: boolean;
2023-04-10 18:54:06 +01:00
private lastName: string;
private lastTeraType: Type;
2023-04-12 00:08:03 +01:00
private lastStatus: StatusEffect;
2023-03-28 19:54:52 +01:00
private lastHp: integer;
private lastMaxHp: integer;
private lastHpFrame: string;
private lastExp: integer;
private lastLevelExp: integer;
private lastLevel: integer;
private lastLevelCapped: boolean;
2023-03-28 19:54:52 +01:00
private box: Phaser.GameObjects.Sprite;
2023-03-28 19:54:52 +01:00
private nameText: Phaser.GameObjects.Text;
2023-04-02 01:06:44 +01:00
private genderText: Phaser.GameObjects.Text;
private ownedIcon: Phaser.GameObjects.Sprite;
private teraIcon: Phaser.GameObjects.Sprite;
private splicedIcon: Phaser.GameObjects.Sprite;
2023-12-03 04:31:45 +00:00
private shinyIcon: Phaser.GameObjects.Sprite;
2023-04-12 00:08:03 +01:00
private statusIndicator: Phaser.GameObjects.Sprite;
2023-03-28 19:54:52 +01:00
private levelContainer: Phaser.GameObjects.Container;
private hpBar: Phaser.GameObjects.Image;
2024-01-08 04:17:24 +00:00
private hpBarSegmentDividers: Phaser.GameObjects.Rectangle[];
2023-03-28 19:54:52 +01:00
private levelNumbersContainer: Phaser.GameObjects.Container;
private hpNumbersContainer: Phaser.GameObjects.Container;
private expBar: Phaser.GameObjects.Image;
constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
super(scene, x, y);
this.player = player;
this.mini = !player;
2024-01-08 04:17:24 +00:00
this.boss = false;
this.offset = false;
2023-04-12 00:08:03 +01:00
this.lastName = null;
this.lastTeraType = Type.UNKNOWN;
2023-04-12 00:08:03 +01:00
this.lastStatus = StatusEffect.NONE;
2023-03-28 19:54:52 +01:00
this.lastHp = -1;
this.lastMaxHp = -1;
this.lastHpFrame = null;
this.lastExp = -1;
this.lastLevelExp = -1;
this.lastLevel = -1;
// Initially invisible and shown via Pokemon.showInfo
this.setVisible(false);
this.box = this.scene.add.sprite(0, 0, this.getTextureName());
this.box.setOrigin(1, 0.5);
this.add(this.box);
2023-03-28 19:54:52 +01:00
2023-04-02 01:06:44 +01:00
this.nameText = addTextObject(this.scene, player ? -115 : -124, player ? -15.2 : -11.2, '', TextStyle.BATTLE_INFO);
this.nameText.setOrigin(0, 0);
this.add(this.nameText);
2023-03-28 19:54:52 +01:00
2023-04-02 01:06:44 +01:00
this.genderText = addTextObject(this.scene, 0, 0, '', TextStyle.BATTLE_INFO);
this.genderText.setOrigin(0, 0);
this.genderText.setPositionRelative(this.nameText, 0, 2);
this.add(this.genderText);
2023-03-28 19:54:52 +01:00
2023-04-18 06:32:26 +01:00
if (!this.player) {
this.ownedIcon = this.scene.add.sprite(0, 0, 'icon_owned');
2023-04-18 06:32:26 +01:00
this.ownedIcon.setVisible(false);
this.ownedIcon.setOrigin(0, 0);
this.ownedIcon.setPositionRelative(this.nameText, 0, 11.5);
this.add(this.ownedIcon);
}
this.teraIcon = this.scene.add.sprite(0, 0, 'icon_tera');
this.teraIcon.setVisible(false);
this.teraIcon.setOrigin(0, 0);
this.teraIcon.setScale(0.5);
this.teraIcon.setPositionRelative(this.nameText, 0, 2);
this.teraIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains);
this.add(this.teraIcon);
this.splicedIcon = this.scene.add.sprite(0, 0, 'icon_spliced');
this.splicedIcon.setVisible(false);
this.splicedIcon.setOrigin(0, 0);
this.splicedIcon.setPositionRelative(this.nameText, 0, 2);
this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 5, 8), Phaser.Geom.Rectangle.Contains);
this.add(this.splicedIcon);
2023-04-12 00:08:03 +01:00
this.statusIndicator = this.scene.add.sprite(0, 0, 'statuses');
this.statusIndicator.setVisible(false);
this.statusIndicator.setOrigin(0, 0);
this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5);
this.add(this.statusIndicator);
2023-04-02 01:06:44 +01:00
this.levelContainer = this.scene.add.container(player ? -41 : -50, player ? -10 : -5);
this.add(this.levelContainer);
2023-03-28 19:54:52 +01:00
const levelOverlay = this.scene.add.image(0, 0, 'overlay_lv');
2023-04-02 01:06:44 +01:00
this.levelContainer.add(levelOverlay);
2023-03-28 19:54:52 +01:00
2023-12-03 04:31:45 +00:00
this.shinyIcon = this.scene.add.sprite(0, 0, 'shiny_star');
this.shinyIcon.setVisible(false);
this.shinyIcon.setOrigin(0, 0);
this.shinyIcon.setPositionRelative(this.levelContainer, -12, -5);
this.add(this.shinyIcon);
2023-04-02 01:06:44 +01:00
this.hpBar = this.scene.add.image(player ? -61 : -71, player ? -1 : 4.5, 'overlay_hp');
this.hpBar.setOrigin(0);
this.add(this.hpBar);
2023-03-28 19:54:52 +01:00
2024-01-08 04:17:24 +00:00
this.hpBarSegmentDividers = [];
2023-04-02 01:06:44 +01:00
this.levelNumbersContainer = this.scene.add.container(9.5, 0);
this.levelContainer.add(this.levelNumbersContainer);
2023-03-28 19:54:52 +01:00
if (this.player) {
2023-04-02 01:06:44 +01:00
this.hpNumbersContainer = this.scene.add.container(-15, 10);
this.add(this.hpNumbersContainer);
2023-03-28 19:54:52 +01:00
const expBar = this.scene.add.image(-98, 18, 'overlay_exp');
expBar.setOrigin(0);
expBar.setScale(0, 1);
this.add(expBar);
this.expBar = expBar;
}
}
2023-03-29 17:23:52 +01:00
initInfo(pokemon: Pokemon) {
this.updateNameText(pokemon);
const nameTextWidth = this.nameText.displayWidth;
2023-04-02 01:06:44 +01:00
this.genderText.setText(getGenderSymbol(pokemon.gender));
this.genderText.setColor(getGenderColor(pokemon.gender));
this.genderText.setPositionRelative(this.nameText, nameTextWidth, 0);
2023-03-28 19:54:52 +01:00
this.lastTeraType = pokemon.getTeraType();
this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2);
this.teraIcon.setVisible(this.lastTeraType !== Type.UNKNOWN);
this.teraIcon.on('pointerover', () => {
if (this.lastTeraType !== Type.UNKNOWN)
(this.scene as BattleScene).ui.showTooltip(null, `${Utils.toReadableString(Type[this.lastTeraType])} Terastallized`);
});
this.teraIcon.on('pointerout', () => (this.scene as BattleScene).ui.hideTooltip());
this.splicedIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), 1);
this.splicedIcon.setVisible(!!pokemon.fusionSpecies);
if (this.splicedIcon.visible) {
2023-12-07 22:43:56 +00:00
this.splicedIcon.on('pointerover', () => (this.scene as BattleScene).ui.showTooltip(null, `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies.getName(pokemon.fusionFormIndex)}`));
this.splicedIcon.on('pointerout', () => (this.scene as BattleScene).ui.hideTooltip());
}
2023-04-18 06:32:26 +01:00
if (!this.player) {
2023-11-13 04:47:04 +00:00
const dexEntry = pokemon.scene.gameData.dexData[pokemon.species.speciesId];
this.ownedIcon.setVisible(!!dexEntry.caughtAttr);
const dexAttr = pokemon.getDexAttr();
if ((dexEntry.caughtAttr & dexAttr) < dexAttr)
2023-04-18 06:32:26 +01:00
this.ownedIcon.setTint(0x808080);
2024-01-08 04:17:24 +00:00
if (this.boss)
2024-02-20 05:02:44 +00:00
this.updateBossSegmentDividers(pokemon as EnemyPokemon);
2023-04-18 06:32:26 +01:00
}
2023-03-29 17:23:52 +01:00
this.hpBar.setScale(pokemon.getHpRatio(), 1);
this.lastHpFrame = this.hpBar.scaleX > 0.5 ? 'high' : this.hpBar.scaleX > 0.25 ? 'medium' : 'low';
this.hpBar.setFrame(this.lastHpFrame);
2023-03-28 19:54:52 +01:00
if (this.player)
2023-03-29 17:23:52 +01:00
this.setHpNumbers(pokemon.hp, pokemon.getMaxHp());
this.lastHp = pokemon.hp;
this.lastMaxHp = pokemon.getMaxHp();
2023-03-28 19:54:52 +01:00
2023-03-29 17:23:52 +01:00
this.setLevel(pokemon.level);
this.lastLevel = pokemon.level;
2023-03-28 19:54:52 +01:00
this.shinyIcon.setVisible(pokemon.isShiny());
2023-03-28 19:54:52 +01:00
if (this.player) {
2023-03-29 17:23:52 +01:00
this.expBar.setScale(pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate), 1);
this.lastExp = pokemon.exp;
this.lastLevelExp = pokemon.levelExp;
2023-03-28 19:54:52 +01:00
}
}
getTextureName(): string {
2024-01-08 04:17:24 +00:00
return `pbinfo_${this.player ? 'player' : 'enemy'}${!this.player && this.boss ? '_boss' : this.mini ? '_mini' : ''}`;
}
setMini(mini: boolean): void {
if (this.mini === mini)
return;
this.mini = mini;
this.box.setTexture(this.getTextureName());
2024-02-20 05:02:44 +00:00
if (this.player)
this.y -= 12 * (mini ? 1 : -1);
2024-02-28 01:48:58 +00:00
const offsetElements = [ this.nameText, this.genderText, this.teraIcon, this.splicedIcon, this.statusIndicator, this.levelContainer ];
offsetElements.forEach(el => el.y += 1.5 * (mini ? -1 : 1));
2023-12-03 05:08:13 +00:00
this.shinyIcon.setPositionRelative(this.levelContainer, -12, -5);
const toggledElements = [ this.hpNumbersContainer, this.expBar ];
toggledElements.forEach(el => el.setVisible(!mini));
}
2024-01-08 04:17:24 +00:00
updateBossSegments(pokemon: EnemyPokemon): void {
const boss = !!pokemon.bossSegments;
if (boss !== this.boss) {
this.boss = boss;
[ this.nameText, this.genderText, this.teraIcon, this.splicedIcon, this.ownedIcon, this.statusIndicator, this.levelContainer ].map(e => e.x += 48 * (boss ? -1 : 1));
2024-01-08 04:17:24 +00:00
this.hpBar.x += 38 * (boss ? -1 : 1);
this.hpBar.y += 2 * (this.boss ? -1 : 1);
this.hpBar.setTexture(`overlay_hp${boss ? '_boss' : ''}`);
this.box.setTexture(this.getTextureName());
}
this.bossSegments = boss ? pokemon.bossSegments : 0;
2024-02-20 05:02:44 +00:00
this.updateBossSegmentDividers(pokemon);
2024-01-08 04:17:24 +00:00
}
2024-02-20 05:02:44 +00:00
updateBossSegmentDividers(pokemon: EnemyPokemon): void {
2024-01-08 04:17:24 +00:00
while (this.hpBarSegmentDividers.length)
this.hpBarSegmentDividers.pop().destroy();
if (this.boss && this.bossSegments > 1) {
const maxHp = pokemon.getMaxHp();
2024-01-08 04:17:24 +00:00
for (let s = 1; s < this.bossSegments; s++) {
const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width;
2024-02-20 05:02:44 +00:00
const divider = this.scene.add.rectangle(0, 0, 1, this.hpBar.height, pokemon.bossSegmentIndex >= s ? 0xFFFFFF : 0x404040);
2024-01-08 04:17:24 +00:00
divider.setOrigin(0.5, 0);
this.add(divider);
divider.setPositionRelative(this.hpBar, dividerX, 0);
this.hpBarSegmentDividers.push(divider);
}
}
}
setOffset(offset: boolean): void {
if (this.offset === offset)
return;
this.offset = offset;
this.x += 10 * (offset === this.player ? 1 : -1);
this.y += 27 * (offset ? 1 : -1);
}
2023-04-10 12:59:00 +01:00
updateInfo(pokemon: Pokemon, instant?: boolean): Promise<void> {
2023-04-10 00:15:21 +01:00
return new Promise(resolve => {
if (!this.scene)
return resolve();
2023-04-10 00:15:21 +01:00
const nameUpdated = this.lastName !== pokemon.name;
if (nameUpdated) {
this.updateNameText(pokemon);
this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0);
2023-04-10 18:54:06 +01:00
}
const teraType = pokemon.getTeraType();
const teraTypeUpdated = this.lastTeraType !== teraType;
if (teraTypeUpdated) {
this.teraIcon.setVisible(teraType !== Type.UNKNOWN);
this.teraIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2);
this.teraIcon.setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(teraType)));
this.lastTeraType = teraType;
}
if (nameUpdated || teraTypeUpdated)
this.splicedIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), 1);
2023-04-10 18:54:06 +01:00
2023-04-12 00:08:03 +01:00
if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) {
this.lastStatus = pokemon.status?.effect || StatusEffect.NONE;
if (this.lastStatus !== StatusEffect.NONE)
this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase());
this.statusIndicator.setVisible(!!this.lastStatus);
2023-04-18 06:32:26 +01:00
if (!this.player && this.ownedIcon.visible)
this.ownedIcon.setAlpha(this.statusIndicator.visible ? 0 : 1);
2023-04-12 00:08:03 +01:00
}
const updateHpFrame = () => {
const hpFrame = this.hpBar.scaleX > 0.5 ? 'high' : this.hpBar.scaleX > 0.25 ? 'medium' : 'low';
if (hpFrame !== this.lastHpFrame) {
this.hpBar.setFrame(hpFrame);
this.lastHpFrame = hpFrame;
};
};
2023-04-10 00:15:21 +01:00
const updatePokemonHp = () => {
2023-04-10 12:59:00 +01:00
const duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0;
2023-04-10 00:15:21 +01:00
this.scene.tweens.add({
targets: this.hpBar,
ease: 'Sine.easeOut',
scaleX: pokemon.getHpRatio(),
duration: duration,
onUpdate: () => {
if (this.player && this.lastHp !== pokemon.hp) {
const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp());
this.setHpNumbers(tweenHp, pokemon.getMaxHp())
this.lastHp = tweenHp;
}
updateHpFrame();
2023-04-10 00:15:21 +01:00
},
onComplete: () => {
updateHpFrame();
2023-04-10 00:15:21 +01:00
resolve();
}
});
if (!this.player)
this.lastHp = pokemon.hp;
this.lastMaxHp = pokemon.getMaxHp();
};
if (this.player) {
const isLevelCapped = pokemon.level >= (this.scene as BattleScene).getMaxExpLevel();
if ((this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level)) {
const originalResolve = resolve;
let durationMultipler = Math.max(Phaser.Tweens.Builders.GetEaseFunction('Cubic.easeIn')(1 - (Math.min(pokemon.level - this.lastLevel, 10) / 10)), 0.1);
resolve = () => this.updatePokemonExp(pokemon, false, durationMultipler).then(() => originalResolve());
} else if (isLevelCapped !== this.lastLevelCapped)
this.setLevel(pokemon.level);
this.lastLevelCapped = isLevelCapped;
2023-04-10 00:15:21 +01:00
}
if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp())
return updatePokemonHp();
else if (!this.player && this.lastLevel !== pokemon.level) {
2023-04-10 00:15:21 +01:00
this.setLevel(pokemon.level);
this.lastLevel = pokemon.level;
}
2023-12-03 04:31:45 +00:00
this.shinyIcon.setVisible(pokemon.isShiny());
2023-04-10 00:15:21 +01:00
resolve();
});
}
2023-03-28 19:54:52 +01:00
updateNameText(pokemon: Pokemon): void {
let displayName = pokemon.name;
let nameTextWidth: number;
let nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.BATTLE_INFO);
nameTextWidth = nameSizeTest.displayWidth;
2024-02-15 04:25:12 +00:00
while (nameTextWidth > (this.player || !this.boss ? 60 : 98) - ((pokemon.gender !== Gender.GENDERLESS ? 6 : 0) + (pokemon.fusionSpecies ? 6 : 0) + (pokemon.isShiny() ? 8 : 0) + (Math.min(pokemon.level.toString().length, 3) - 3) * 8)) {
displayName = `${displayName.slice(0, displayName.endsWith('.') ? -2 : -1).trimEnd()}.`;
nameSizeTest.setText(displayName);
nameTextWidth = nameSizeTest.displayWidth;
}
nameSizeTest.destroy();
this.nameText.setText(displayName);
this.lastName = pokemon.name;
}
updatePokemonExp(pokemon: Pokemon, instant?: boolean, levelDurationMultiplier: number = 1): Promise<void> {
2023-04-10 00:15:21 +01:00
return new Promise(resolve => {
const levelUp = this.lastLevel < pokemon.level;
const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate);
const levelExp = levelUp ? relLevelExp : pokemon.levelExp;
let ratio = relLevelExp ? levelExp / relLevelExp : 0;
if (this.lastLevel >= (this.scene as BattleScene).getMaxExpLevel(true)) {
if (levelUp)
ratio = 1;
else
ratio = 0;
instant = true;
}
2023-11-04 04:32:12 +00:00
const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction('Sine.easeIn')(1 - (Math.max(this.lastLevel - 100, 0) / 150));
let duration = this.visible && !instant ? (((levelExp - this.lastLevelExp) / relLevelExp) * 1650) * durationMultiplier * levelDurationMultiplier : 0;
2023-04-10 00:15:21 +01:00
if (duration)
2023-10-21 13:58:39 +01:00
(this.scene as BattleScene).playSound('exp');
2023-03-28 19:54:52 +01:00
this.scene.tweens.add({
2023-04-10 00:15:21 +01:00
targets: this.expBar,
ease: 'Sine.easeIn',
scaleX: ratio,
2023-03-28 19:54:52 +01:00
duration: duration,
onComplete: () => {
if (!this.scene)
return resolve();
2023-04-10 00:15:21 +01:00
if (duration)
this.scene.sound.stopByKey('exp');
if (ratio === 1) {
this.lastLevelExp = 0;
this.lastLevel++;
2023-10-21 13:58:39 +01:00
(this.scene as BattleScene).playSound('level_up');
2023-04-10 00:15:21 +01:00
this.setLevel(this.lastLevel);
this.scene.time.delayedCall(500 * levelDurationMultiplier, () => {
2023-04-10 00:15:21 +01:00
this.expBar.setScale(0, 1);
this.updateInfo(pokemon, instant).then(() => resolve());
2023-04-10 00:15:21 +01:00
});
return;
} else {
this.lastExp = pokemon.exp;
this.lastLevelExp = pokemon.levelExp;
2023-03-28 19:54:52 +01:00
}
2023-04-10 00:15:21 +01:00
resolve();
2023-03-28 19:54:52 +01:00
}
});
});
}
setLevel(level: integer) {
const isCapped = level >= (this.scene as BattleScene).getMaxExpLevel();
2023-04-10 18:54:06 +01:00
this.levelNumbersContainer.removeAll(true);
2023-03-28 19:54:52 +01:00
const levelStr = level.toString();
for (let i = 0; i < levelStr.length; i++)
this.levelNumbersContainer.add(this.scene.add.image(i * 8, 0, `numbers${isCapped && this.player ? '_red' : ''}`, levelStr[i]));
2023-03-28 19:54:52 +01:00
this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0));
2023-12-03 04:31:45 +00:00
this.shinyIcon.setPositionRelative(this.levelContainer, -12, -5);
2023-03-28 19:54:52 +01:00
}
setHpNumbers(hp: integer, maxHp: integer) {
2023-04-20 06:04:36 +01:00
if (!this.player || !this.scene)
2023-03-28 19:54:52 +01:00
return;
2023-04-10 18:54:06 +01:00
this.hpNumbersContainer.removeAll(true);
2023-03-28 19:54:52 +01:00
const hpStr = hp.toString();
const maxHpStr = maxHp.toString();
let offset = 0;
for (let i = maxHpStr.length - 1; i >= 0; i--)
this.hpNumbersContainer.add(this.scene.add.image(offset++ * -8, 0, 'numbers', maxHpStr[i]));
this.hpNumbersContainer.add(this.scene.add.image(offset++ * -8, 0, 'numbers', '/'));
for (let i = hpStr.length - 1; i >= 0; i--)
this.hpNumbersContainer.add(this.scene.add.image(offset++ * -8, 0, 'numbers', hpStr[i]));
}
}
export class PlayerBattleInfo extends BattleInfo {
constructor(scene: Phaser.Scene) {
super(scene, Math.floor(scene.game.canvas.width / 6) - 10, -72, true);
}
}
export class EnemyBattleInfo extends BattleInfo {
constructor(scene: Phaser.Scene) {
super(scene, 140, -141, false);
}
setMini(mini: boolean): void { } // Always mini
2023-03-28 19:54:52 +01:00
}