diff --git a/public/images/ui/party_exp_bar.png b/public/images/ui/party_exp_bar.png new file mode 100644 index 00000000000..0a514b43393 Binary files /dev/null and b/public/images/ui/party_exp_bar.png differ diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 22585a5aa13..06b0fd77710 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -1893,7 +1893,7 @@ export class VictoryPhase extends PokemonPhase { if (exp) { const partyMemberIndex = party.indexOf(expPartyMembers[pm]); - this.scene.unshiftPhase(new ExpPhase(this.scene, partyMemberIndex, exp)); + this.scene.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(this.scene, partyMemberIndex, exp) : new ShowPartyExpBarPhase(this.scene, partyMemberIndex, exp)); } } @@ -2025,8 +2025,53 @@ export class ExpPhase extends PartyMemberPokemonPhase { pokemon.updateInfo().then(() => this.end()); }, null, true); } +} +export class ShowPartyExpBarPhase extends PartyMemberPokemonPhase { + private expValue: number; + constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { + super(scene, partyMemberIndex); + + this.expValue = expValue; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + let exp = new Utils.NumberHolder(this.expValue); + this.scene.applyModifiers(ExpBoosterModifier, true, exp); + exp.value = Math.floor(exp.value); + + const lastLevel = pokemon.level; + let newLevel: integer; + pokemon.addExp(exp.value); + newLevel = pokemon.level; + if (newLevel > lastLevel) + this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); + this.scene.unshiftPhase(new HidePartyExpBarPhase(this.scene)); + pokemon.updateInfo(); + + this.scene.partyExpBar.showPokemonExp(pokemon, exp.value).then(() => { + if (newLevel > lastLevel) + this.end(); + else + setTimeout(() => this.end(), 500); + }); + } +} + +export class HidePartyExpBarPhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.partyExpBar.hide().then(() => this.end()); + } } export class LevelUpPhase extends PartyMemberPokemonPhase { diff --git a/src/battle-scene.ts b/src/battle-scene.ts index faeaeb7a2e3..a84202edd6e 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -22,6 +22,7 @@ import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, applyAbAttrs, initAbili import Battle from './battle'; import { GameMode } from './game-mode'; import SpritePipeline from './pipelines/sprite'; +import PartyExpBar from './ui/party-exp-bar'; const enableAuto = true; const quickStart = false; @@ -65,6 +66,7 @@ export default class BattleScene extends Phaser.Scene { public field: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container; public abilityBar: AbilityBar; + public partyExpBar: PartyExpBar; public arenaBg: Phaser.GameObjects.Sprite; public arenaBgTransition: Phaser.GameObjects.Sprite; public arenaPlayer: ArenaBase; @@ -166,6 +168,7 @@ export default class BattleScene extends Phaser.Scene { this.loadImage('overlay_exp', 'ui'); this.loadImage('icon_owned', 'ui'); this.loadImage('ability_bar', 'ui'); + this.loadImage('party_exp_bar', 'ui'); this.loadImage('shiny_star', 'ui', 'shiny.png'); this.loadImage('party_bg', 'ui'); @@ -333,9 +336,13 @@ export default class BattleScene extends Phaser.Scene { this.abilityBar.setup(); this.fieldUI.add(this.abilityBar); + this.partyExpBar = new PartyExpBar(this); + this.partyExpBar.setup(); + this.fieldUI.add(this.partyExpBar); + this.waveCountText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO); this.waveCountText.setOrigin(1, 0); - this.updateWaveCountPosition(); + this.updateUIPositions(); this.fieldUI.add(this.waveCountText); this.party = []; @@ -601,8 +608,9 @@ export default class BattleScene extends Phaser.Scene { this.waveCountText.setVisible(true); } - updateWaveCountPosition(): void { + updateUIPositions(): void { this.waveCountText.setY(-(this.game.canvas.height / 6) + (this.enemyModifiers.length ? 15 : 0)); + this.partyExpBar.setY(this.waveCountText.y + 15); } getMaxExpLevel(): integer { @@ -901,7 +909,7 @@ export default class BattleScene extends Phaser.Scene { clearEnemyModifiers(): void { this.enemyModifiers.splice(0, this.enemyModifiers.length); - this.updateModifiers(false).then(() => this.updateWaveCountPosition()); + this.updateModifiers(false).then(() => this.updateUIPositions()); } updateModifiers(player?: boolean): Promise { @@ -928,7 +936,7 @@ export default class BattleScene extends Phaser.Scene { this.updatePartyForModifiers(player ? this.getParty() : this.getEnemyField().filter(p => p.isActive())).then(() => { (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); if (!player) - this.updateWaveCountPosition(); + this.updateUIPositions(); resolve(); }); }); diff --git a/src/data/battler-tag.ts b/src/data/battler-tag.ts index 37d46199bf5..e0b0517c8be 100644 --- a/src/data/battler-tag.ts +++ b/src/data/battler-tag.ts @@ -203,7 +203,7 @@ export class ConfusedTag extends BattlerTag { if (Utils.randInt(2)) { const atk = pokemon.getBattleStat(Stat.ATK); const def = pokemon.getBattleStat(Stat.DEF); - const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * ((Utils.randInt(15) + 85) / 100)); + const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (Utils.randInt(15, 85) / 100)); pokemon.scene.queueMessage('It hurt itself in its\nconfusion!'); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex())); pokemon.damage(damage); diff --git a/src/data/move.ts b/src/data/move.ts index 0842ad5da56..c0eb7dc4e90 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -956,7 +956,7 @@ export class RandomLevelDamageAttr extends FixedDamageAttr { } getDamage(user: Pokemon, target: Pokemon, move: Move): number { - return user.level * (Utils.randInt(100, 50) * 0.01); + return user.level * (Utils.randIntRange(50, 150) * 0.01); } } @@ -1672,7 +1672,7 @@ export class FrenzyAttr extends MoveEffectAttr { if (!user.getMoveQueue().length) { if (!user.getTag(BattlerTagType.FRENZY)) { - const turnCount = Utils.randInt(2) + 1; + const turnCount = Utils.randIntRange(3, 4); new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true })); user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id); } else { @@ -1801,13 +1801,13 @@ export class FlinchAttr extends AddBattlerTagAttr { export class ConfuseAttr extends AddBattlerTagAttr { constructor(selfTarget?: boolean) { - super(BattlerTagType.CONFUSED, selfTarget, Utils.randInt(4, 1)); + super(BattlerTagType.CONFUSED, selfTarget, Utils.randIntRange(1, 4)); } } export class TrapAttr extends AddBattlerTagAttr { constructor(tagType: BattlerTagType) { - super(tagType, false, Utils.randInt(2, 5)); + super(tagType, false, Utils.randIntRange(2, 5) + 1); } } diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index 6b9ec823317..cd9fa514a00 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -21,7 +21,7 @@ export class Status { this.turnCount = turnCount === undefined ? 0 : turnCount; if (cureTurn === undefined) { if (effect === StatusEffect.SLEEP) - this.cureTurn = Utils.randInt(3, 1); + this.cureTurn = Utils.randInt(3, 2); } else this.cureTurn = cureTurn; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index a09b19e99a6..2cbc4692c8c 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -683,6 +683,7 @@ const modifierPool = { ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), [ModifierTier.GREAT]: [ new WeightedModifierType(modifierTypes.GREAT_BALL, 6), + new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, 1), new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status).length, 3); return statusEffectPartyMemberCount * 6; @@ -722,7 +723,6 @@ const modifierPool = { ].map(m => { m.setTier(ModifierTier.GREAT); return m; }), [ModifierTier.ULTRA]: [ new WeightedModifierType(modifierTypes.ULTRA_BALL, 8), - new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, 12), new WeightedModifierType(modifierTypes.MAX_LURE, 4), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 4), new WeightedModifierType(modifierTypes.TM_ULTRA, 5), diff --git a/src/pokemon.ts b/src/pokemon.ts index 41fb5610e11..53b84883d6b 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -769,22 +769,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getTag(tagType: BattlerTagType | { new(...args: any[]): BattlerTag }): BattlerTag { + if (!this.summonData) + return null; return typeof(tagType) === 'number' ? this.summonData.tags.find(t => t.tagType === tagType) : this.summonData.tags.find(t => t instanceof tagType); } findTag(tagFilter: ((tag: BattlerTag) => boolean)) { + if (!this.summonData) + return null; return this.summonData.tags.find(t => tagFilter(t)); } getTags(tagType: BattlerTagType | { new(...args: any[]): BattlerTag }): BattlerTag[] { + if (!this.summonData) + return []; return typeof(tagType) === 'number' ? this.summonData.tags.filter(t => t.tagType === tagType) : this.summonData.tags.filter(t => t instanceof tagType); } - findTags(tagFilter: ((tag: BattlerTag) => boolean)) { + findTags(tagFilter: ((tag: BattlerTag) => boolean)): BattlerTag[] { + if (!this.summonData) + return []; return this.summonData.tags.filter(t => tagFilter(t)); } @@ -935,6 +943,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (cancelled.value) return false; + if (effect === StatusEffect.SLEEP) + this.setFrameRate(4); + this.status = new Status(effect); return true; } @@ -943,6 +954,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const lastStatus = this.status.effect; this.status = undefined; if (lastStatus === StatusEffect.SLEEP) { + this.setFrameRate(12); if (this.getTag(BattlerTagType.NIGHTMARE)) this.lapseTag(BattlerTagType.NIGHTMARE); } @@ -968,6 +980,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return (this.getSpeciesForm().baseExp * this.level) / 5 + 1; } + setFrameRate(frameRate: integer) { + this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate; + this.getSprite().play(this.getBattleSpriteKey()); + this.getTintSprite().play(this.getBattleSpriteKey()); + } + tint(color: number, alpha?: number, duration?: integer, ease?: string) { const tintSprite = this.getTintSprite(); tintSprite.setTintFill(color); diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 84cfbe7b127..88e96a442f7 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -271,7 +271,6 @@ export default class BattleInfo extends Phaser.GameObjects.Container { ratio = 0; instant = true; } - console.log(ratio); let duration = this.visible && !instant ? ((levelExp - this.lastLevelExp) / relLevelExp) * 1650 : 0; if (duration) this.scene.sound.play('exp'); diff --git a/src/ui/party-exp-bar.ts b/src/ui/party-exp-bar.ts new file mode 100644 index 00000000000..6eaca216ac9 --- /dev/null +++ b/src/ui/party-exp-bar.ts @@ -0,0 +1,91 @@ +import BattleScene from "../battle-scene"; +import Pokemon from "../pokemon"; +import { TextStyle, addTextObject } from "./text"; + +export default class PartyExpBar extends Phaser.GameObjects.Container { + private bg: Phaser.GameObjects.NineSlice; + private pokemonIcon: Phaser.GameObjects.Sprite; + private expText: Phaser.GameObjects.Text; + + private tween: Phaser.Tweens.Tween; + + public shown: boolean; + + constructor(scene: BattleScene) { + super(scene, (scene.game.canvas.width / 6), -((scene.game.canvas.height) / 6) + 15); + } + + setup(): void { + this.bg = this.scene.add.nineslice(0, 0, 'party_exp_bar', null, 8, 18, 21, 5, 6, 4); + this.bg.setOrigin(0, 0); + + this.add(this.bg); + + this.pokemonIcon = this.scene.add.sprite(3, 7, 'pokemon_icons_0'); + this.pokemonIcon.setOrigin(0, 0.5); + this.pokemonIcon.setScale(0.5); + this.add(this.pokemonIcon); + + this.expText = addTextObject(this.scene, 22, 4, '', TextStyle.BATTLE_INFO); + this.expText.setOrigin(0, 0); + this.add(this.expText); + + this.setVisible(false); + this.shown = false; + } + + showPokemonExp(pokemon: Pokemon, expValue: integer): Promise { + return new Promise(resolve => { + if (this.shown) + return resolve(); + + this.pokemonIcon.setTexture(pokemon.species.getIconAtlasKey()); + this.pokemonIcon.play(pokemon.getIconKey()).stop(); + this.expText.setText(`+${expValue.toString()}`); + + this.bg.width = this.expText.displayWidth + 28; + + (this.scene as BattleScene).fieldUI.bringToTop(this); + + if (this.tween) + this.tween.stop(); + + this.tween = this.scene.tweens.add({ + targets: this, + x: (this.scene.game.canvas.width / 6) - (this.bg.width - 5), + duration: 500, + ease: 'Sine.easeOut', + onComplete: () => { + this.tween = null; + resolve(); + } + }); + + this.setVisible(true); + this.shown = true; + }); + } + + hide(): Promise { + return new Promise(resolve => { + if (!this.shown) + return resolve(); + + if (this.tween) + this.tween.stop(); + + this.tween = this.scene.tweens.add({ + targets: this, + x: (this.scene.game.canvas.width / 6), + duration: 500, + ease: 'Sine.easeIn', + onComplete: () => { + this.tween = null; + this.shown = false; + this.setVisible(false); + resolve(); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 555159f201d..826194a850f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,6 +30,10 @@ export function randInt(range: integer, min?: integer): integer { return Math.floor(Math.random() * range) + min; } +export function randIntRange(min: integer, max: integer): integer { + return randInt(max - min, min); +} + export function getFrameMs(frameCount: integer): integer { return Math.floor((1 / 60) * 1000 * frameCount); }